mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Merge remote-tracking branch 'origin/main' into add-labels-to-install-upgrade
This commit is contained in:
commit
6853c3eab5
94 changed files with 1804 additions and 844 deletions
|
|
@ -5,7 +5,7 @@ jobs:
|
|||
build:
|
||||
working_directory: ~/helm.sh/helm
|
||||
docker:
|
||||
- image: circleci/golang:1.17
|
||||
- image: cimg/go:1.18
|
||||
|
||||
auth:
|
||||
username: $DOCKER_USER
|
||||
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
environment:
|
||||
GOCACHE: "/tmp/go/cache"
|
||||
GOLANGCI_LINT_VERSION: "1.43.0"
|
||||
GOLANGCI_LINT_VERSION: "1.46.2"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ else
|
|||
fi
|
||||
|
||||
echo "Installing Azure CLI"
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ stretch main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add
|
||||
sudo apt install apt-transport-https
|
||||
sudo apt update
|
||||
|
|
|
|||
6
.github/workflows/build-pr.yml
vendored
6
.github/workflows/build-pr.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
go-version: '1.18'
|
||||
- name: Install golangci-lint
|
||||
run: |
|
||||
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
|
||||
|
|
@ -21,8 +21,8 @@ jobs:
|
|||
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
|
||||
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
|
||||
env:
|
||||
GOLANGCI_LINT_VERSION: '1.43.0'
|
||||
GOLANGCI_LINT_SHA256: 'f3515cebec926257da703ba0a2b169e4a322c11dc31a8b4656b50a43e48877f4'
|
||||
GOLANGCI_LINT_VERSION: '1.46.2'
|
||||
GOLANGCI_LINT_SHA256: '242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b'
|
||||
- name: Test style
|
||||
run: make test-style
|
||||
- name: Run unit tests
|
||||
|
|
|
|||
2
.github/workflows/stale-issue-bot.yaml
vendored
2
.github/workflows/stale-issue-bot.yaml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'
|
||||
exempt-issue-labels: 'keep open,v4.x'
|
||||
exempt-issue-labels: 'keep open,v4.x,in progress'
|
||||
days-before-stale: 90
|
||||
days-before-close: 30
|
||||
operations-per-run: 100
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ run:
|
|||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- dupl
|
||||
- gofmt
|
||||
- goimports
|
||||
|
|
@ -14,9 +13,7 @@ linters:
|
|||
- misspell
|
||||
- nakedret
|
||||
- revive
|
||||
- structcheck
|
||||
- unused
|
||||
- varcheck
|
||||
- staticcheck
|
||||
|
||||
linters-settings:
|
||||
|
|
|
|||
25
Makefile
25
Makefile
|
|
@ -18,12 +18,13 @@ ACCEPTANCE_DIR:=../acceptance-testing
|
|||
ACCEPTANCE_RUN_TESTS=.
|
||||
|
||||
# go option
|
||||
PKG := ./...
|
||||
TAGS :=
|
||||
TESTS := .
|
||||
TESTFLAGS :=
|
||||
LDFLAGS := -w -s
|
||||
GOFLAGS :=
|
||||
PKG := ./...
|
||||
TAGS :=
|
||||
TESTS := .
|
||||
TESTFLAGS :=
|
||||
LDFLAGS := -w -s
|
||||
GOFLAGS :=
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
# Rebuild the binary if any of these files change
|
||||
SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum
|
||||
|
|
@ -77,7 +78,7 @@ all: build
|
|||
build: $(BINDIR)/$(BINNAME)
|
||||
|
||||
$(BINDIR)/$(BINNAME): $(SRC)
|
||||
GO111MODULE=on CGO_ENABLED=0 go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
|
||||
GO111MODULE=on CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# install
|
||||
|
|
@ -149,15 +150,15 @@ gen-test-golden: test-unit
|
|||
# ------------------------------------------------------------------------------
|
||||
# dependencies
|
||||
|
||||
# If go get is run from inside the project directory it will add the dependencies
|
||||
# to the go.mod file. To avoid that we change to a directory without a go.mod file
|
||||
# when downloading the following dependencies
|
||||
# If go install is run from inside the project directory it will add the
|
||||
# dependencies to the go.mod file. To avoid that we change to a directory
|
||||
# without a go.mod file when downloading the following dependencies
|
||||
|
||||
$(GOX):
|
||||
(cd /; GO111MODULE=on go get -u github.com/mitchellh/gox)
|
||||
(cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest)
|
||||
|
||||
$(GOIMPORTS):
|
||||
(cd /; GO111MODULE=on go get -u golang.org/x/tools/cmd/goimports)
|
||||
(cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# release
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
)
|
||||
|
|
@ -84,7 +86,7 @@ func (o *docsOptions) run(out io.Writer) error {
|
|||
hdrFunc := func(filename string) string {
|
||||
base := filepath.Base(filename)
|
||||
name := strings.TrimSuffix(base, path.Ext(base))
|
||||
title := strings.Title(strings.Replace(name, "_", " ", -1))
|
||||
title := cases.Title(language.Und, cases.NoLower).String(strings.Replace(name, "_", " ", -1))
|
||||
return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
|
|||
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
|
||||
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)")
|
||||
}
|
||||
|
||||
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
return tpl(template, data, out)
|
||||
}
|
||||
|
||||
return output.Table.Write(out, &statusPrinter{res, true, false})
|
||||
return output.Table.Write(out, &statusPrinter{res, true, false, false})
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ To override values in a chart, use either the '--values' flag and pass in a file
|
|||
or use the '--set' flag and pass configuration from the command line, to force
|
||||
a string value use '--set-string'. You can use '--set-file' to set individual
|
||||
values from a file when the value itself is too long for the command line
|
||||
or is dynamically generated.
|
||||
or is dynamically generated. You can also use '--set-json' to set json values
|
||||
(scalars/objects/arrays) from the command line.
|
||||
|
||||
$ helm install -f myvalues.yaml myredis ./redis
|
||||
|
||||
|
|
@ -67,6 +68,11 @@ or
|
|||
|
||||
$ helm install --set-file my_script=dothings.sh myredis ./redis
|
||||
|
||||
or
|
||||
|
||||
$ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis
|
||||
|
||||
|
||||
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
|
||||
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
|
||||
contained a key called 'Test', the value set in override.yaml would take precedence:
|
||||
|
|
@ -79,6 +85,13 @@ set for a key called 'foo', the 'newbar' value would take precedence:
|
|||
|
||||
$ helm install --set foo=bar --set foo=newbar myredis ./redis
|
||||
|
||||
Similarly, in the following example 'foo' is set to '["four"]':
|
||||
|
||||
$ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis
|
||||
|
||||
And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}':
|
||||
|
||||
$ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis
|
||||
|
||||
To check the generated manifests of a release without installing the chart,
|
||||
the '--debug' and '--dry-run' flags can be combined.
|
||||
|
|
@ -86,13 +99,14 @@ the '--debug' and '--dry-run' flags can be combined.
|
|||
If --verify is set, the chart MUST have a provenance file, and the provenance
|
||||
file MUST pass all verification steps.
|
||||
|
||||
There are five different ways you can express the chart you want to install:
|
||||
There are six different ways you can express the chart you want to install:
|
||||
|
||||
1. By chart reference: helm install mymaria example/mariadb
|
||||
2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz
|
||||
3. By path to an unpacked chart directory: helm install mynginx ./nginx
|
||||
4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
|
||||
5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx
|
||||
6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx
|
||||
|
||||
CHART REFERENCES
|
||||
|
||||
|
|
@ -127,7 +141,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
return errors.Wrap(err, "INSTALLATION FAILED")
|
||||
}
|
||||
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +155,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
|
||||
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
|
||||
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
|
||||
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
|
||||
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
|
||||
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
|
|
@ -156,6 +171,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
|
|||
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
|
||||
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
|
||||
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.")
|
||||
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
|
||||
addValueOptionsFlags(f, valueOpts)
|
||||
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
||||
|
||||
|
|
|
|||
|
|
@ -83,8 +83,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
}
|
||||
|
||||
if client.Short {
|
||||
|
||||
names := make([]string, 0)
|
||||
names := make([]string, 0, len(results))
|
||||
for _, res := range results {
|
||||
names = append(names, res.Name)
|
||||
}
|
||||
|
|
@ -103,17 +102,16 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
fmt.Fprintln(out, res.Name)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
|
||||
}
|
||||
}
|
||||
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
|
||||
f.BoolVarP(&client.NoHeaders, "no-headers", "", false, "don't print headers when using the default output format")
|
||||
f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`)
|
||||
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
|
||||
f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
|
||||
|
|
@ -145,10 +143,11 @@ type releaseElement struct {
|
|||
}
|
||||
|
||||
type releaseListWriter struct {
|
||||
releases []releaseElement
|
||||
releases []releaseElement
|
||||
noHeaders bool
|
||||
}
|
||||
|
||||
func newReleaseListWriter(releases []*release.Release, timeFormat string) *releaseListWriter {
|
||||
func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool) *releaseListWriter {
|
||||
// Initialize the array so no results returns an empty array instead of null
|
||||
elements := make([]releaseElement, 0, len(releases))
|
||||
for _, r := range releases {
|
||||
|
|
@ -173,12 +172,14 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string) *relea
|
|||
|
||||
elements = append(elements, element)
|
||||
}
|
||||
return &releaseListWriter{elements}
|
||||
return &releaseListWriter{elements, noHeaders}
|
||||
}
|
||||
|
||||
func (r *releaseListWriter) WriteTable(out io.Writer) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
|
||||
if !r.noHeaders {
|
||||
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
|
||||
}
|
||||
for _, r := range r.releases {
|
||||
table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,11 @@ func TestListCmd(t *testing.T) {
|
|||
cmd: "list",
|
||||
golden: "output/list.txt",
|
||||
rels: releaseFixture,
|
||||
}, {
|
||||
name: "list without headers",
|
||||
cmd: "list --no-headers",
|
||||
golden: "output/list-no-headers.txt",
|
||||
rels: releaseFixture,
|
||||
}, {
|
||||
name: "list all releases",
|
||||
cmd: "list --all",
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
|
|||
func manuallyProcessArgs(args []string) ([]string, []string) {
|
||||
known := []string{}
|
||||
unknown := []string{}
|
||||
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"}
|
||||
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"}
|
||||
knownArg := func(a string) bool {
|
||||
for _, pre := range kvargs {
|
||||
if strings.HasPrefix(a, pre+"=") {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/term" //nolint
|
||||
"github.com/moby/term"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
|
|||
return runErr
|
||||
}
|
||||
|
||||
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}); err != nil {
|
||||
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ Environment variables:
|
|||
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
|
||||
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
|
||||
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
|
||||
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
|
||||
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
|
||||
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
|
||||
|
||||
Helm stores cache, configuration, and data based on the following configuration order:
|
||||
|
|
@ -152,7 +154,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
|||
|
||||
registryClient, err := registry.NewClient(
|
||||
registry.ClientOptDebug(settings.Debug),
|
||||
registry.ClientOptWriter(out),
|
||||
registry.ClientOptEnableCache(true),
|
||||
registry.ClientOptWriter(os.Stderr),
|
||||
registry.ClientOptCredentialsFile(settings.RegistryConfig),
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ func TestShowPreReleaseChart(t *testing.T) {
|
|||
name: "show pre-release chart",
|
||||
args: "test/pre-release-chart",
|
||||
fail: true,
|
||||
expectedErr: "failed to download \"test/pre-release-chart\"",
|
||||
expectedErr: "chart \"pre-release-chart\" matching not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-",
|
||||
},
|
||||
{
|
||||
name: "show pre-release chart",
|
||||
args: "test/pre-release-chart",
|
||||
fail: true,
|
||||
flags: "--version 1.0.0",
|
||||
expectedErr: "failed to download \"test/pre-release-chart\" at version \"1.0.0\"",
|
||||
expectedErr: "chart \"pre-release-chart\" matching 1.0.0 not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-1.0.0",
|
||||
},
|
||||
{
|
||||
name: "show pre-release chart with 'devel' flag",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
|
@ -25,6 +26,8 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubectl/pkg/cmd/get"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
|
|
@ -41,7 +44,7 @@ The status consists of:
|
|||
- state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback)
|
||||
- revision of the release
|
||||
- description of the release (can be completion message or error message, need to enable --show-desc)
|
||||
- list of resources that this release consists of, sorted by kind
|
||||
- list of resources that this release consists of (need to enable --show-resources)
|
||||
- details on last test suite run, if applicable
|
||||
- additional notes provided by the chart
|
||||
`
|
||||
|
|
@ -62,6 +65,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
return compListReleases(toComplete, args, cfg)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// When the output format is a table the resources should be fetched
|
||||
// and displayed as a table. When YAML or JSON the resources will be
|
||||
// returned. This mirrors the handling in kubectl.
|
||||
if outfmt == output.Table {
|
||||
client.ShowResourcesTable = true
|
||||
}
|
||||
rel, err := client.Run(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -70,7 +80,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
// strip chart metadata from the output
|
||||
rel.Chart = nil
|
||||
|
||||
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription})
|
||||
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources})
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +102,8 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
bindOutputFlag(cmd, &outfmt)
|
||||
f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release")
|
||||
|
||||
f.BoolVar(&client.ShowResources, "show-resources", false, "if set, display the resources of the named release")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +111,7 @@ type statusPrinter struct {
|
|||
release *release.Release
|
||||
debug bool
|
||||
showDescription bool
|
||||
showResources bool
|
||||
}
|
||||
|
||||
func (s statusPrinter) WriteJSON(out io.Writer) error {
|
||||
|
|
@ -124,6 +137,33 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
|
|||
fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
|
||||
}
|
||||
|
||||
if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 {
|
||||
buf := new(bytes.Buffer)
|
||||
printFlags := get.NewHumanPrintFlags()
|
||||
typePrinter, _ := printFlags.ToPrinter("")
|
||||
printer := &get.TablePrinter{Delegate: typePrinter}
|
||||
|
||||
var keys []string
|
||||
for key := range s.release.Info.Resources {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
for _, t := range keys {
|
||||
fmt.Fprintf(buf, "==> %s\n", t)
|
||||
|
||||
vk := s.release.Info.Resources[t]
|
||||
for _, resource := range vk {
|
||||
if err := printer.PrintObj(resource, buf); err != nil {
|
||||
fmt.Fprintf(buf, "failed to print object type %s: %v\n", t, err)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "RESOURCES:\n%s\n", buf.String())
|
||||
}
|
||||
|
||||
executions := executionsByHookEvent(s.release)
|
||||
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
|
||||
fmt.Fprintln(out, "TEST SUITE: None")
|
||||
|
|
|
|||
|
|
@ -68,6 +68,24 @@ func TestStatusCmd(t *testing.T) {
|
|||
Status: release.StatusDeployed,
|
||||
Notes: "release notes",
|
||||
}),
|
||||
}, {
|
||||
name: "get status of a deployed release with resources",
|
||||
cmd: "status --show-resources flummoxed-chickadee",
|
||||
golden: "output/status-with-resources.txt",
|
||||
rels: releasesMockWithStatus(
|
||||
&release.Info{
|
||||
Status: release.StatusDeployed,
|
||||
},
|
||||
),
|
||||
}, {
|
||||
name: "get status of a deployed release with resources in json",
|
||||
cmd: "status --show-resources flummoxed-chickadee -o json",
|
||||
golden: "output/status-with-resources.json",
|
||||
rels: releasesMockWithStatus(
|
||||
&release.Info{
|
||||
Status: release.StatusDeployed,
|
||||
},
|
||||
),
|
||||
}, {
|
||||
name: "get status of a deployed release with test suite",
|
||||
cmd: "status flummoxed-chickadee",
|
||||
|
|
|
|||
|
|
@ -106,11 +106,15 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
if client.UseReleaseName {
|
||||
newDir = filepath.Join(client.OutputDir, client.ReleaseName)
|
||||
}
|
||||
_, err := os.Stat(filepath.Join(newDir, m.Path))
|
||||
if err == nil {
|
||||
fileWritten[m.Path] = true
|
||||
}
|
||||
|
||||
err = writeToFile(newDir, m.Path, m.Manifest, fileWritten[m.Path])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileWritten[m.Path] = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -181,7 +185,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
|
||||
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
|
||||
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
|
||||
f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
|
||||
f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
|
||||
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
|
||||
bindPostRenderFlag(cmd, &client.PostRenderer)
|
||||
|
||||
|
|
|
|||
2
cmd/helm/testdata/output/env-comp.txt
vendored
2
cmd/helm/testdata/output/env-comp.txt
vendored
|
|
@ -9,6 +9,8 @@ HELM_KUBEASGROUPS
|
|||
HELM_KUBEASUSER
|
||||
HELM_KUBECAFILE
|
||||
HELM_KUBECONTEXT
|
||||
HELM_KUBEINSECURE_SKIP_TLS_VERIFY
|
||||
HELM_KUBETLS_SERVER_NAME
|
||||
HELM_KUBETOKEN
|
||||
HELM_MAX_HISTORY
|
||||
HELM_NAMESPACE
|
||||
|
|
|
|||
4
cmd/helm/testdata/output/list-no-headers.txt
vendored
Normal file
4
cmd/helm/testdata/output/list-no-headers.txt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
hummingbird default 1 2016-01-16 00:00:03 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
iguana default 2 2016-01-16 00:00:04 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
rocket default 1 2016-01-16 00:00:02 +0000 UTC failed chickadee-1.0.0 0.0.1
|
||||
starlord default 2 2016-01-16 00:00:01 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
1
cmd/helm/testdata/output/status-with-resources.json
vendored
Normal file
1
cmd/helm/testdata/output/status-with-resources.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"name":"flummoxed-chickadee","info":{"first_deployed":"","last_deployed":"2016-01-16T00:00:00Z","deleted":"","status":"deployed"},"namespace":"default"}
|
||||
6
cmd/helm/testdata/output/status-with-resources.txt
vendored
Normal file
6
cmd/helm/testdata/output/status-with-resources.txt
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
NAME: flummoxed-chickadee
|
||||
LAST DEPLOYED: Sat Jan 16 00:00:00 2016
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 0
|
||||
TEST SUITE: None
|
||||
|
|
@ -1 +1 @@
|
|||
version.BuildInfo{Version:"v3.9", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
|
|
|
|||
2
cmd/helm/testdata/output/version-client.txt
vendored
2
cmd/helm/testdata/output/version-client.txt
vendored
|
|
@ -1 +1 @@
|
|||
version.BuildInfo{Version:"v3.9", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
|
|
|
|||
2
cmd/helm/testdata/output/version-short.txt
vendored
2
cmd/helm/testdata/output/version-short.txt
vendored
|
|
@ -1 +1 @@
|
|||
v3.9
|
||||
v3.11
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Version: v3.9
|
||||
Version: v3.11
|
||||
2
cmd/helm/testdata/output/version.txt
vendored
2
cmd/helm/testdata/output/version.txt
vendored
|
|
@ -1 +1 @@
|
|||
version.BuildInfo{Version:"v3.9", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
version.BuildInfo{Version:"v3.11", GitCommit:"", GitTreeState:"", GoVersion:""}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ To override values in a chart, use either the '--values' flag and pass in a file
|
|||
or use the '--set' flag and pass configuration from the command line, to force string
|
||||
values, use '--set-string'. You can use '--set-file' to set individual
|
||||
values from a file when the value itself is too long for the command line
|
||||
or is dynamically generated.
|
||||
or is dynamically generated. You can also use '--set-json' to set json values
|
||||
(scalars/objects/arrays) from the command line.
|
||||
|
||||
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
|
||||
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
|
||||
|
|
@ -103,6 +104,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
instClient := action.NewInstall(cfg)
|
||||
instClient.CreateNamespace = createNamespace
|
||||
instClient.ChartPathOptions = client.ChartPathOptions
|
||||
instClient.Force = client.Force
|
||||
instClient.DryRun = client.DryRun
|
||||
instClient.DisableHooks = client.DisableHooks
|
||||
instClient.SkipCRDs = client.SkipCRDs
|
||||
|
|
@ -118,12 +120,13 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
instClient.Description = client.Description
|
||||
instClient.DependencyUpdate = client.DependencyUpdate
|
||||
instClient.Labels = client.Labels
|
||||
instClient.EnableDNS = client.EnableDNS
|
||||
|
||||
rel, err := runInstall(args, instClient, valueOpts, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -205,7 +208,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
|
||||
}
|
||||
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
|
||||
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +235,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be separated by comma. Original release labels will be merged with upgrade labels. You can unset label using null.")
|
||||
f.StringVar(&client.Description, "description", "", "add a custom description")
|
||||
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
|
||||
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
|
||||
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
||||
addValueOptionsFlags(f, valueOpts)
|
||||
bindOutputFlag(cmd, &outfmt)
|
||||
|
|
|
|||
143
go.mod
143
go.mod
|
|
@ -3,61 +3,53 @@ module helm.sh/helm/v3
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.1.0
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/Masterminds/vcs v1.13.3
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||
github.com/containerd/containerd v1.6.4
|
||||
github.com/containerd/containerd v1.6.15
|
||||
github.com/cyphar/filepath-securejoin v0.2.3
|
||||
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684
|
||||
github.com/docker/docker v20.10.16+incompatible
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/flock v0.8.1
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lib/pq v1.10.5
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rubenv/sql-migrate v1.1.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/rubenv/sql-migrate v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
k8s.io/api v0.24.1
|
||||
k8s.io/apiextensions-apiserver v0.24.1
|
||||
k8s.io/apimachinery v0.24.1
|
||||
k8s.io/apiserver v0.24.1
|
||||
k8s.io/cli-runtime v0.24.1
|
||||
k8s.io/client-go v0.24.1
|
||||
k8s.io/klog/v2 v2.60.1
|
||||
k8s.io/kubectl v0.24.1
|
||||
oras.land/oras-go v1.1.1
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/text v0.6.0
|
||||
k8s.io/api v0.26.0
|
||||
k8s.io/apiextensions-apiserver v0.26.0
|
||||
k8s.io/apimachinery v0.26.0
|
||||
k8s.io/apiserver v0.26.0
|
||||
k8s.io/cli-runtime v0.26.0
|
||||
k8s.io/client-go v0.26.0
|
||||
k8s.io/klog/v2 v2.80.1
|
||||
k8s.io/kubectl v0.26.0
|
||||
oras.land/oras-go v1.2.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.99.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
|
||||
|
|
@ -65,102 +57,99 @@ require (
|
|||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.11+incompatible // indirect
|
||||
github.com/docker/cli v20.10.21+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/docker v20.10.21+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gomodule/redigo v1.8.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/compress v1.11.13 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.15.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/russross/blackfriday v1.5.2 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/component-base v0.24.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.11.4 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/component-base v0.26.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func AssertGoldenString(t TestingT, actual, filename string) {
|
|||
t.Helper()
|
||||
|
||||
if err := compare([]byte(actual), path(filename)); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ func compare(actual []byte, filename string) error {
|
|||
}
|
||||
expected = normalize(expected)
|
||||
if !bytes.Equal(expected, actual) {
|
||||
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'\n", filename, expected, actual)
|
||||
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ var (
|
|||
//
|
||||
// Increment major number for new feature additions and behavioral changes.
|
||||
// Increment minor number for bug fixes and performance enhancements.
|
||||
version = "v3.9"
|
||||
version = "v3.11"
|
||||
|
||||
// metadata is extra build time data
|
||||
metadata = ""
|
||||
|
|
|
|||
|
|
@ -101,8 +101,9 @@ type Configuration struct {
|
|||
//
|
||||
// TODO: This function is badly in need of a refactor.
|
||||
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
|
||||
// This code has to do with writing files to disk.
|
||||
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) {
|
||||
//
|
||||
// This code has to do with writing files to disk.
|
||||
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun, enableDNS bool) ([]*release.Hook, *bytes.Buffer, string, error) {
|
||||
hs := []*release.Hook{}
|
||||
b := bytes.NewBuffer(nil)
|
||||
|
||||
|
|
@ -130,9 +131,13 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
|
|||
if err != nil {
|
||||
return hs, b, "", err
|
||||
}
|
||||
files, err2 = engine.RenderWithClient(ch, values, restConfig)
|
||||
e := engine.New(restConfig)
|
||||
e.EnableDNS = enableDNS
|
||||
files, err2 = e.Render(ch, values)
|
||||
} else {
|
||||
files, err2 = engine.Render(ch, values)
|
||||
var e engine.Engine
|
||||
e.EnableDNS = enableDNS
|
||||
files, err2 = e.Render(ch, values)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ type Install struct {
|
|||
ChartPathOptions
|
||||
|
||||
ClientOnly bool
|
||||
Force bool
|
||||
CreateNamespace bool
|
||||
DryRun bool
|
||||
DisableHooks bool
|
||||
|
|
@ -97,6 +98,8 @@ type Install struct {
|
|||
APIVersions chartutil.VersionSet
|
||||
// Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false
|
||||
IsUpgrade bool
|
||||
// Enable DNS lookups when rendering templates
|
||||
EnableDNS bool
|
||||
// Used by helm template to add the release as part of OutputDir path
|
||||
// OutputDir/<ReleaseName>
|
||||
UseReleaseName bool
|
||||
|
|
@ -257,7 +260,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
|
|||
rel := i.createRelease(chrt, vals, i.Labels)
|
||||
|
||||
var manifestDoc *bytes.Buffer
|
||||
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun)
|
||||
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun, i.EnableDNS)
|
||||
// Even for errors, attach this if available
|
||||
if manifestDoc != nil {
|
||||
rel.Manifest = manifestDoc.String()
|
||||
|
|
@ -373,7 +376,7 @@ func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, t
|
|||
return
|
||||
}
|
||||
} else if len(resources) > 0 {
|
||||
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil {
|
||||
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force); err != nil {
|
||||
i.reportToRun(c, rel, err)
|
||||
return
|
||||
}
|
||||
|
|
@ -457,10 +460,10 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release
|
|||
//
|
||||
// Roughly, this will return an error if name is
|
||||
//
|
||||
// - empty
|
||||
// - too long
|
||||
// - already in use, and not deleted
|
||||
// - used by a deleted release, and i.Replace is false
|
||||
// - empty
|
||||
// - too long
|
||||
// - already in use, and not deleted
|
||||
// - used by a deleted release, and i.Replace is false
|
||||
func (i *Install) availableName() error {
|
||||
start := i.ReleaseName
|
||||
|
||||
|
|
@ -757,20 +760,13 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
|
|||
}
|
||||
|
||||
filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
|
||||
if err == nil {
|
||||
lname, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return filename, err
|
||||
}
|
||||
return lname, nil
|
||||
} else if settings.Debug {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lname, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
atVersion := ""
|
||||
if version != "" {
|
||||
atVersion = fmt.Sprintf(" at version %q", version)
|
||||
}
|
||||
|
||||
return filename, errors.Errorf("failed to download %q%s", name, atVersion)
|
||||
return lname, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
|
|||
return result
|
||||
}
|
||||
|
||||
// HasWaringsOrErrors checks is LintResult has any warnings or errors
|
||||
// HasWarningsOrErrors checks is LintResult has any warnings or errors
|
||||
func HasWarningsOrErrors(result *LintResult) bool {
|
||||
for _, msg := range result.Messages {
|
||||
if msg.Severity > support.InfoSev {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ type List struct {
|
|||
// Filter is a filter that is applied to the results
|
||||
Filter string
|
||||
Short bool
|
||||
NoHeaders bool
|
||||
TimeFormat string
|
||||
Uninstalled bool
|
||||
Superseded bool
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ limitations under the License.
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
|
|
@ -32,6 +36,14 @@ type Status struct {
|
|||
// only affect print type table.
|
||||
// TODO Helm 4: Remove this flag and output the description by default.
|
||||
ShowDescription bool
|
||||
|
||||
// ShowResources sets if the resources should be retrieved with the status.
|
||||
// TODO Helm 4: Remove this flag and output the resources by default.
|
||||
ShowResources bool
|
||||
|
||||
// ShowResourcesTable is used with ShowResources. When true this will cause
|
||||
// the resulting objects to be retrieved as a kind=table.
|
||||
ShowResourcesTable bool
|
||||
}
|
||||
|
||||
// NewStatus creates a new Status object with the given configuration.
|
||||
|
|
@ -47,5 +59,37 @@ func (s *Status) Run(name string) (*release.Release, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return s.cfg.releaseContent(name, s.Version)
|
||||
if !s.ShowResources {
|
||||
return s.cfg.releaseContent(name, s.Version)
|
||||
}
|
||||
|
||||
rel, err := s.cfg.releaseContent(name, s.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kubeClient, ok := s.cfg.KubeClient.(kube.InterfaceResources); ok {
|
||||
var resources kube.ResourceList
|
||||
if s.ShowResourcesTable {
|
||||
resources, err = kubeClient.BuildTable(bytes.NewBufferString(rel.Manifest), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
resources, err = s.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := kubeClient.Get(resources, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rel.Info.Resources = resp
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
return nil, errors.New("unable to get kubeClient with interface InterfaceResources")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ type Upgrade struct {
|
|||
DependencyUpdate bool
|
||||
// Lock to control raceconditions when the process receives a SIGTERM
|
||||
Lock sync.Mutex
|
||||
// Enable DNS lookups when rendering templates
|
||||
EnableDNS bool
|
||||
}
|
||||
|
||||
type resultMessage struct {
|
||||
|
|
@ -232,7 +234,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun)
|
||||
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun, u.EnableDNS)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -393,6 +395,9 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
|
|||
}
|
||||
|
||||
if u.Wait {
|
||||
u.cfg.Log(
|
||||
"waiting for release %s resources (created: %d updated: %d deleted: %d)",
|
||||
upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted))
|
||||
if u.WaitForJobs {
|
||||
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
|
||||
u.cfg.recordRelease(originalRelease)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ type Dependency struct {
|
|||
// the chart. This check must be done at load time before the dependency's charts are
|
||||
// loaded.
|
||||
func (d *Dependency) Validate() error {
|
||||
if d == nil {
|
||||
return ValidationError("dependencies must not contain empty or null nodes")
|
||||
}
|
||||
d.Name = sanitizeString(d.Name)
|
||||
d.Version = sanitizeString(d.Version)
|
||||
d.Repository = sanitizeString(d.Repository)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ type Maintainer struct {
|
|||
|
||||
// Validate checks valid data and sanitizes string characters.
|
||||
func (m *Maintainer) Validate() error {
|
||||
if m == nil {
|
||||
return ValidationError("maintainers must not contain empty or null nodes")
|
||||
}
|
||||
m.Name = sanitizeString(m.Name)
|
||||
m.Email = sanitizeString(m.Email)
|
||||
m.URL = sanitizeString(m.URL)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,30 @@ func TestValidate(t *testing.T) {
|
|||
},
|
||||
ValidationError("dependency \"bad\" has disallowed characters in the alias"),
|
||||
},
|
||||
{
|
||||
&Metadata{
|
||||
Name: "test",
|
||||
APIVersion: "v2",
|
||||
Version: "1.0",
|
||||
Type: "application",
|
||||
Dependencies: []*Dependency{
|
||||
nil,
|
||||
},
|
||||
},
|
||||
ValidationError("dependencies must not contain empty or null nodes"),
|
||||
},
|
||||
{
|
||||
&Metadata{
|
||||
Name: "test",
|
||||
APIVersion: "v2",
|
||||
Version: "1.0",
|
||||
Type: "application",
|
||||
Maintainers: []*Maintainer{
|
||||
nil,
|
||||
},
|
||||
},
|
||||
ValidationError("maintainers must not contain empty or null nodes"),
|
||||
},
|
||||
{
|
||||
&Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"},
|
||||
ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"),
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) {
|
|||
func TestDefaultCapabilitiesHelmVersion(t *testing.T) {
|
||||
hv := DefaultCapabilities.HelmVersion
|
||||
|
||||
if hv.Version != "v3.9" {
|
||||
t.Errorf("Expected default HelmVersion to be v3.9, got %q", hv.Version)
|
||||
if hv.Version != "v3.11" {
|
||||
t.Errorf("Expected default HelmVersion to be v3.11, got %q", hv.Version)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ spec:
|
|||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
containerPort: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
|
|
|||
|
|
@ -55,7 +55,13 @@ func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) err
|
|||
}
|
||||
|
||||
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
|
||||
func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) error {
|
||||
func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
reterr = fmt.Errorf("unable to validate schema: %s", r)
|
||||
}
|
||||
}()
|
||||
|
||||
valuesData, err := yaml.Marshal(values)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -38,6 +38,30 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
|
||||
values, err := ReadValuesFile("./testdata/test-values.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading YAML file: %s", err)
|
||||
}
|
||||
schema, err := ioutil.ReadFile("./testdata/test-values-invalid.schema.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading YAML file: %s", err)
|
||||
}
|
||||
|
||||
var errString string
|
||||
if err := ValidateAgainstSingleSchema(values, schema); err == nil {
|
||||
t.Fatalf("Expected an error, but got nil")
|
||||
} else {
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
expectedErrString := "unable to validate schema: runtime error: invalid " +
|
||||
"memory address or nil pointer dereference"
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
|
||||
values, err := ReadValuesFile("./testdata/test-values-negative.yaml")
|
||||
if err != nil {
|
||||
|
|
|
|||
1
pkg/chartutil/testdata/test-values-invalid.schema.json
vendored
Normal file
1
pkg/chartutil/testdata/test-values-invalid.schema.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1E1111111
|
||||
|
|
@ -68,7 +68,7 @@ func (v Values) Table(name string) (Values, error) {
|
|||
//
|
||||
// It protects against nil map panics.
|
||||
func (v Values) AsMap() map[string]interface{} {
|
||||
if v == nil || len(v) == 0 {
|
||||
if len(v) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return v
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*Package cli describes the operating environment for the Helm CLI.
|
||||
/*
|
||||
Package cli describes the operating environment for the Helm CLI.
|
||||
|
||||
Helm's environment encapsulates all of the service dependencies Helm has.
|
||||
These dependencies are expressed as interfaces so that alternate implementations
|
||||
|
|
@ -24,6 +25,7 @@ package cli
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -32,6 +34,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"helm.sh/helm/v3/internal/version"
|
||||
"helm.sh/helm/v3/pkg/helmpath"
|
||||
)
|
||||
|
||||
|
|
@ -60,6 +63,12 @@ type EnvSettings struct {
|
|||
KubeAPIServer string
|
||||
// Custom certificate authority file.
|
||||
KubeCaFile string
|
||||
// KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity.
|
||||
// This makes the HTTPS connections insecure
|
||||
KubeInsecureSkipTLSVerify bool
|
||||
// KubeTLSServerName overrides the name to use for server certificate validation.
|
||||
// If it is not provided, the hostname used to contact the server is used
|
||||
KubeTLSServerName string
|
||||
// Debug indicates whether or not Helm is running in Debug mode.
|
||||
Debug bool
|
||||
// RegistryConfig is the path to the registry config file.
|
||||
|
|
@ -78,19 +87,21 @@ type EnvSettings struct {
|
|||
|
||||
func New() *EnvSettings {
|
||||
env := &EnvSettings{
|
||||
namespace: os.Getenv("HELM_NAMESPACE"),
|
||||
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
|
||||
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
|
||||
KubeToken: os.Getenv("HELM_KUBETOKEN"),
|
||||
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
|
||||
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
|
||||
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
|
||||
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
|
||||
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
|
||||
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
|
||||
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
|
||||
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
|
||||
BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit),
|
||||
namespace: os.Getenv("HELM_NAMESPACE"),
|
||||
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
|
||||
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
|
||||
KubeToken: os.Getenv("HELM_KUBETOKEN"),
|
||||
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
|
||||
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
|
||||
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
|
||||
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
|
||||
KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"),
|
||||
KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
|
||||
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
|
||||
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
|
||||
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
|
||||
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
|
||||
BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit),
|
||||
}
|
||||
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))
|
||||
|
||||
|
|
@ -103,9 +114,15 @@ func New() *EnvSettings {
|
|||
CAFile: &env.KubeCaFile,
|
||||
KubeConfig: &env.KubeConfig,
|
||||
Impersonate: &env.KubeAsUser,
|
||||
Insecure: &env.KubeInsecureSkipTLSVerify,
|
||||
TLSServerName: &env.KubeTLSServerName,
|
||||
ImpersonateGroup: &env.KubeAsGroups,
|
||||
WrapConfigFn: func(config *rest.Config) *rest.Config {
|
||||
config.Burst = env.BurstLimit
|
||||
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return &retryingRoundTripper{wrapped: rt}
|
||||
})
|
||||
config.UserAgent = version.GetUserAgent()
|
||||
return config
|
||||
},
|
||||
}
|
||||
|
|
@ -122,6 +139,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
|
|||
fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
|
||||
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
|
||||
fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection")
|
||||
fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used")
|
||||
fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
|
||||
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
|
||||
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
|
||||
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
|
||||
|
|
@ -136,6 +155,18 @@ func envOr(name, def string) string {
|
|||
return def
|
||||
}
|
||||
|
||||
func envBoolOr(name string, def bool) bool {
|
||||
if name == "" {
|
||||
return def
|
||||
}
|
||||
envVal := envOr(name, strconv.FormatBool(def))
|
||||
ret, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func envIntOr(name string, def int) int {
|
||||
if name == "" {
|
||||
return def
|
||||
|
|
@ -172,12 +203,14 @@ func (s *EnvSettings) EnvVars() map[string]string {
|
|||
"HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit),
|
||||
|
||||
// broken, these are populated from helm flags and not kubeconfig.
|
||||
"HELM_KUBECONTEXT": s.KubeContext,
|
||||
"HELM_KUBETOKEN": s.KubeToken,
|
||||
"HELM_KUBEASUSER": s.KubeAsUser,
|
||||
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
|
||||
"HELM_KUBEAPISERVER": s.KubeAPIServer,
|
||||
"HELM_KUBECAFILE": s.KubeCaFile,
|
||||
"HELM_KUBECONTEXT": s.KubeContext,
|
||||
"HELM_KUBETOKEN": s.KubeToken,
|
||||
"HELM_KUBEASUSER": s.KubeAsUser,
|
||||
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
|
||||
"HELM_KUBEAPISERVER": s.KubeAPIServer,
|
||||
"HELM_KUBECAFILE": s.KubeCaFile,
|
||||
"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify),
|
||||
"HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName,
|
||||
}
|
||||
if s.KubeConfig != "" {
|
||||
envvars["KUBECONFIG"] = s.KubeConfig
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"helm.sh/helm/v3/internal/version"
|
||||
)
|
||||
|
||||
func TestSetNamespace(t *testing.T) {
|
||||
|
|
@ -48,13 +50,15 @@ func TestEnvSettings(t *testing.T) {
|
|||
envvars map[string]string
|
||||
|
||||
// expected values
|
||||
ns, kcontext string
|
||||
debug bool
|
||||
maxhistory int
|
||||
kubeAsUser string
|
||||
kubeAsGroups []string
|
||||
kubeCaFile string
|
||||
burstLimit int
|
||||
ns, kcontext string
|
||||
debug bool
|
||||
maxhistory int
|
||||
kubeAsUser string
|
||||
kubeAsGroups []string
|
||||
kubeCaFile string
|
||||
kubeInsecure bool
|
||||
kubeTLSServer string
|
||||
burstLimit int
|
||||
}{
|
||||
{
|
||||
name: "defaults",
|
||||
|
|
@ -63,38 +67,44 @@ func TestEnvSettings(t *testing.T) {
|
|||
burstLimit: defaultBurstLimit,
|
||||
},
|
||||
{
|
||||
name: "with flags set",
|
||||
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100",
|
||||
ns: "myns",
|
||||
debug: true,
|
||||
maxhistory: defaultMaxHistory,
|
||||
burstLimit: 100,
|
||||
kubeAsUser: "poro",
|
||||
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
|
||||
kubeCaFile: "/tmp/ca.crt",
|
||||
name: "with flags set",
|
||||
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
|
||||
ns: "myns",
|
||||
debug: true,
|
||||
maxhistory: defaultMaxHistory,
|
||||
burstLimit: 100,
|
||||
kubeAsUser: "poro",
|
||||
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
|
||||
kubeCaFile: "/tmp/ca.crt",
|
||||
kubeTLSServer: "example.org",
|
||||
kubeInsecure: true,
|
||||
},
|
||||
{
|
||||
name: "with envvars set",
|
||||
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150"},
|
||||
ns: "yourns",
|
||||
maxhistory: 5,
|
||||
burstLimit: 150,
|
||||
debug: true,
|
||||
kubeAsUser: "pikachu",
|
||||
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
|
||||
kubeCaFile: "/tmp/ca.crt",
|
||||
name: "with envvars set",
|
||||
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
|
||||
ns: "yourns",
|
||||
maxhistory: 5,
|
||||
burstLimit: 150,
|
||||
debug: true,
|
||||
kubeAsUser: "pikachu",
|
||||
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
|
||||
kubeCaFile: "/tmp/ca.crt",
|
||||
kubeTLSServer: "example.org",
|
||||
kubeInsecure: true,
|
||||
},
|
||||
{
|
||||
name: "with flags and envvars set",
|
||||
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175",
|
||||
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200"},
|
||||
ns: "myns",
|
||||
debug: true,
|
||||
maxhistory: 5,
|
||||
burstLimit: 175,
|
||||
kubeAsUser: "poro",
|
||||
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
|
||||
kubeCaFile: "/my/ca.crt",
|
||||
name: "with flags and envvars set",
|
||||
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
|
||||
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
|
||||
ns: "myns",
|
||||
debug: true,
|
||||
maxhistory: 5,
|
||||
burstLimit: 175,
|
||||
kubeAsUser: "poro",
|
||||
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
|
||||
kubeCaFile: "/my/ca.crt",
|
||||
kubeTLSServer: "example.org",
|
||||
kubeInsecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -136,10 +146,108 @@ func TestEnvSettings(t *testing.T) {
|
|||
if tt.burstLimit != settings.BurstLimit {
|
||||
t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit)
|
||||
}
|
||||
if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify {
|
||||
t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify)
|
||||
}
|
||||
if tt.kubeTLSServer != settings.KubeTLSServerName {
|
||||
t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvOrBool(t *testing.T) {
|
||||
const envName = "TEST_ENV_OR_BOOL"
|
||||
tests := []struct {
|
||||
name string
|
||||
env string
|
||||
val string
|
||||
def bool
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "unset with default false",
|
||||
def: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "unset with default true",
|
||||
def: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "blank env with default false",
|
||||
env: envName,
|
||||
def: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "blank env with default true",
|
||||
env: envName,
|
||||
def: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "env true with default false",
|
||||
env: envName,
|
||||
val: "true",
|
||||
def: false,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "env false with default true",
|
||||
env: envName,
|
||||
val: "false",
|
||||
def: true,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "env fails parsing with default true",
|
||||
env: envName,
|
||||
val: "NOT_A_BOOL",
|
||||
def: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "env fails parsing with default false",
|
||||
env: envName,
|
||||
val: "NOT_A_BOOL",
|
||||
def: false,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != "" {
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv(tt.env)
|
||||
})
|
||||
os.Setenv(tt.env, tt.val)
|
||||
}
|
||||
actual := envBoolOr(tt.env, tt.def)
|
||||
if actual != tt.expected {
|
||||
t.Errorf("expected result %t, got %t", tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgentHeaderInK8sRESTClientConfig(t *testing.T) {
|
||||
defer resetEnv()()
|
||||
|
||||
settings := New()
|
||||
restConfig, err := settings.RESTClientGetter().ToRESTConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedUserAgent := version.GetUserAgent()
|
||||
if restConfig.UserAgent != expectedUserAgent {
|
||||
t.Errorf("expected User-Agent header %q in K8s REST client config, got %q", expectedUserAgent, restConfig.UserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
func resetEnv() func() {
|
||||
origEnv := os.Environ()
|
||||
|
||||
|
|
|
|||
80
pkg/cli/roundtripper.go
Normal file
80
pkg/cli/roundtripper.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type retryingRoundTripper struct {
|
||||
wrapped http.RoundTripper
|
||||
}
|
||||
|
||||
func (rt *retryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return rt.roundTrip(req, 1, nil)
|
||||
}
|
||||
|
||||
func (rt *retryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) {
|
||||
if retry < 0 {
|
||||
return prevResp, nil
|
||||
}
|
||||
resp, rtErr := rt.wrapped.RoundTrip(req)
|
||||
if rtErr != nil {
|
||||
return resp, rtErr
|
||||
}
|
||||
if resp.StatusCode < 500 {
|
||||
return resp, rtErr
|
||||
}
|
||||
if resp.Header.Get("content-type") != "application/json" {
|
||||
return resp, rtErr
|
||||
}
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return resp, rtErr
|
||||
}
|
||||
|
||||
var ke kubernetesError
|
||||
r := bytes.NewReader(b)
|
||||
err = json.NewDecoder(r).Decode(&ke)
|
||||
r.Seek(0, io.SeekStart)
|
||||
resp.Body = io.NopCloser(r)
|
||||
if err != nil {
|
||||
return resp, rtErr
|
||||
}
|
||||
if ke.Code < 500 {
|
||||
return resp, rtErr
|
||||
}
|
||||
// Matches messages like "etcdserver: leader changed"
|
||||
if strings.HasSuffix(ke.Message, "etcdserver: leader changed") {
|
||||
return rt.roundTrip(req, retry-1, resp)
|
||||
}
|
||||
// Matches messages like "rpc error: code = Unknown desc = raft proposal dropped"
|
||||
if strings.HasSuffix(ke.Message, "raft proposal dropped") {
|
||||
return rt.roundTrip(req, retry-1, resp)
|
||||
}
|
||||
return resp, rtErr
|
||||
}
|
||||
|
||||
type kubernetesError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
|
@ -29,15 +29,17 @@ import (
|
|||
"helm.sh/helm/v3/pkg/strvals"
|
||||
)
|
||||
|
||||
// Options captures the different ways to specify values
|
||||
type Options struct {
|
||||
ValueFiles []string
|
||||
StringValues []string
|
||||
Values []string
|
||||
FileValues []string
|
||||
ValueFiles []string // -f/--values
|
||||
StringValues []string // --set-string
|
||||
Values []string // --set
|
||||
FileValues []string // --set-file
|
||||
JSONValues []string // --set-json
|
||||
}
|
||||
|
||||
// MergeValues merges values from files specified via -f/--values and directly
|
||||
// via --set, --set-string, or --set-file, marshaling them to YAML
|
||||
// via --set-json, --set, --set-string, or --set-file, marshaling them to YAML
|
||||
func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
|
||||
base := map[string]interface{}{}
|
||||
|
||||
|
|
@ -57,6 +59,13 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
|
|||
base = mergeMaps(base, currentMap)
|
||||
}
|
||||
|
||||
// User specified a value via --set-json
|
||||
for _, value := range opts.JSONValues {
|
||||
if err := strvals.ParseJSON(value, base); err != nil {
|
||||
return nil, errors.Errorf("failed parsing --set-json data %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
// User specified a value via --set
|
||||
for _, value := range opts.Values {
|
||||
if err := strvals.ParseInto(value, base); err != nil {
|
||||
|
|
@ -112,7 +121,10 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) {
|
|||
if strings.TrimSpace(filePath) == "-" {
|
||||
return ioutil.ReadAll(os.Stdin)
|
||||
}
|
||||
u, _ := url.Parse(filePath)
|
||||
u, err := url.Parse(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: maybe someone handle other protocols like ftp.
|
||||
g, err := p.ByScheme(u.Scheme)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package values
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
)
|
||||
|
||||
func TestMergeValues(t *testing.T) {
|
||||
|
|
@ -75,3 +77,12 @@ func TestMergeValues(t *testing.T) {
|
|||
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
var p getter.Providers
|
||||
filePath := "%a.txt"
|
||||
_, err := readFile(filePath, p)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when has special strings")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,31 +294,13 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
|
|||
}
|
||||
|
||||
// TODO: Seems that picking first URL is not fully correct
|
||||
u, err = url.Parse(cv.URLs[0])
|
||||
resolvedURL, err := repo.ResolveReferenceURL(rc.URL, cv.URLs[0])
|
||||
|
||||
if err != nil {
|
||||
return u, errors.Errorf("invalid chart URL format: %s", ref)
|
||||
}
|
||||
|
||||
// If the URL is relative (no scheme), prepend the chart repo's base URL
|
||||
if !u.IsAbs() {
|
||||
repoURL, err := url.Parse(rc.URL)
|
||||
if err != nil {
|
||||
return repoURL, err
|
||||
}
|
||||
q := repoURL.Query()
|
||||
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
|
||||
repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/"
|
||||
u = repoURL.ResolveReference(u)
|
||||
u.RawQuery = q.Encode()
|
||||
// TODO add user-agent
|
||||
if _, err := getter.NewHTTPGetter(getter.WithURL(rc.URL)); err != nil {
|
||||
return repoURL, err
|
||||
}
|
||||
return u, err
|
||||
}
|
||||
|
||||
// TODO add user-agent
|
||||
return u, nil
|
||||
return url.Parse(resolvedURL)
|
||||
}
|
||||
|
||||
// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart.
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ func TestResolveChartRef(t *testing.T) {
|
|||
{name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
|
||||
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
|
||||
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
|
||||
{name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"},
|
||||
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true},
|
||||
{name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true},
|
||||
{name: "invalid", ref: "invalid-1.2.3", fail: true},
|
||||
|
|
|
|||
2
pkg/downloader/testdata/repositories.yaml
vendored
2
pkg/downloader/testdata/repositories.yaml
vendored
|
|
@ -24,3 +24,5 @@ repositories:
|
|||
- name: testing-https-insecureskip-tls-verify
|
||||
url: "https://example-https-insecureskiptlsverify.com"
|
||||
insecure_skip_tls_verify: true
|
||||
- name: encoded-url
|
||||
url: "http://example.com/with%2Fslash"
|
||||
|
|
|
|||
15
pkg/downloader/testdata/repository/encoded-url-index.yaml
vendored
Normal file
15
pkg/downloader/testdata/repository/encoded-url-index.yaml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
entries:
|
||||
foobar:
|
||||
- name: foobar
|
||||
description: Foo Chart With Encoded URL
|
||||
home: https://helm.sh/helm
|
||||
keywords: []
|
||||
maintainers: []
|
||||
sources:
|
||||
- https://github.com/helm/charts
|
||||
urls:
|
||||
- charts/foobar-4.2.1.tgz
|
||||
version: 4.2.1
|
||||
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
|
||||
apiVersion: v2
|
||||
|
|
@ -42,6 +42,15 @@ type Engine struct {
|
|||
LintMode bool
|
||||
// the rest config to connect to the kubernetes api
|
||||
config *rest.Config
|
||||
// EnableDNS tells the engine to allow DNS lookups when rendering templates
|
||||
EnableDNS bool
|
||||
}
|
||||
|
||||
// New creates a new instance of Engine using the passed in rest config.
|
||||
func New(config *rest.Config) Engine {
|
||||
return Engine{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates.
|
||||
|
|
@ -189,6 +198,14 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
|
|||
funcMap["lookup"] = NewLookupFunction(e.config)
|
||||
}
|
||||
|
||||
// When DNS lookups are not enabled override the sprig function and return
|
||||
// an empty string.
|
||||
if !e.EnableDNS {
|
||||
funcMap["getHostByName"] = func(name string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
t.Funcs(funcMap)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package engine
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -89,6 +90,7 @@ func TestRender(t *testing.T) {
|
|||
{Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")},
|
||||
{Name: "templates/test3", Data: []byte("{{.noValue}}")},
|
||||
{Name: "templates/test4", Data: []byte("{{toJson .Values}}")},
|
||||
{Name: "templates/test5", Data: []byte("{{getHostByName \"helm.sh\"}}")},
|
||||
},
|
||||
Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"},
|
||||
}
|
||||
|
|
@ -117,6 +119,7 @@ func TestRender(t *testing.T) {
|
|||
"moby/templates/test2": "ishmael",
|
||||
"moby/templates/test3": "",
|
||||
"moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`,
|
||||
"moby/templates/test5": "",
|
||||
}
|
||||
|
||||
for name, data := range expect {
|
||||
|
|
@ -200,6 +203,42 @@ func TestRenderInternals(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRenderWIthDNS(t *testing.T) {
|
||||
c := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "moby",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
Templates: []*chart.File{
|
||||
{Name: "templates/test1", Data: []byte("{{getHostByName \"helm.sh\"}}")},
|
||||
},
|
||||
Values: map[string]interface{}{},
|
||||
}
|
||||
|
||||
vals := map[string]interface{}{
|
||||
"Values": map[string]interface{}{},
|
||||
}
|
||||
|
||||
v, err := chartutil.CoalesceValues(c, vals)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to coalesce values: %s", err)
|
||||
}
|
||||
|
||||
var e Engine
|
||||
e.EnableDNS = true
|
||||
out, err := e.Render(c, v)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to render templates: %s", err)
|
||||
}
|
||||
|
||||
for _, val := range c.Templates {
|
||||
fp := path.Join("moby", val.Name)
|
||||
if out[fp] == "" {
|
||||
t.Errorf("Expected IP address, got %q", out[fp])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelRenderInternals(t *testing.T) {
|
||||
// Make sure that we can use one Engine to run parallel template renders.
|
||||
e := new(Engine)
|
||||
|
|
|
|||
|
|
@ -117,9 +117,9 @@ func TestFuncs(t *testing.T) {
|
|||
// version of mergo (even accidentally) that causes a breaking change. See
|
||||
// sprig changelog and notes for more details.
|
||||
// Note, Go modules assume semver is never broken. So, there is no way to tell
|
||||
// the tooling to not update to a minor or patch version. `go get -u` could be
|
||||
// used to accidentally update mergo. This test and message should catch the
|
||||
// problem and explain why it's happening.
|
||||
// the tooling to not update to a minor or patch version. `go install` could
|
||||
// be used to accidentally update mergo. This test and message should catch
|
||||
// the problem and explain why it's happening.
|
||||
func TestMerge(t *testing.T) {
|
||||
dict := map[string]interface{}{
|
||||
"src2": map[string]interface{}{
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func NewLookupFunction(config *rest.Config) lookupFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// getDynamicClientOnUnstructured returns a dynamic client on an Unstructured type. This client can be further namespaced.
|
||||
// getDynamicClientOnKind returns a dynamic client on an Unstructured type. This client can be further namespaced.
|
||||
func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) {
|
||||
gvk := schema.FromAPIVersionAndKind(apiversion, kind)
|
||||
apiRes, err := getAPIResourceForGVK(gvk, config)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't create TLS config for client")
|
||||
}
|
||||
tlsConf.BuildNameToCertificate()
|
||||
|
||||
sni, err := urlutil.ExtractHostname(g.opts.url)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -155,9 +155,8 @@ func TestHTTPGetter(t *testing.T) {
|
|||
|
||||
func TestDownload(t *testing.T) {
|
||||
expect := "Call me Ishmael"
|
||||
expectedUserAgent := "I am Groot"
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v")
|
||||
defaultUserAgent := version.GetUserAgent()
|
||||
if r.UserAgent() != defaultUserAgent {
|
||||
t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent())
|
||||
}
|
||||
|
|
@ -179,6 +178,7 @@ func TestDownload(t *testing.T) {
|
|||
}
|
||||
|
||||
// test with http server
|
||||
const expectedUserAgent = "I am Groot"
|
||||
basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok || username != "username" || password != "password" {
|
||||
|
|
@ -291,7 +291,6 @@ func TestDownloadTLS(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
|
||||
}
|
||||
tlsConf.BuildNameToCertificate()
|
||||
tlsConf.ServerName = "helm.sh"
|
||||
tlsSrv.TLS = tlsConf
|
||||
tlsSrv.StartTLS()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
|
|||
|
||||
// NewOCIGetter constructs a valid http/https client as a Getter
|
||||
func NewOCIGetter(ops ...Option) (Getter, error) {
|
||||
registryClient, err := registry.NewClient()
|
||||
registryClient, err := registry.NewClient(
|
||||
registry.ClientOptEnableCache(true),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ limitations under the License.
|
|||
package kube // import "helm.sh/helm/v3/pkg/kube"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -38,7 +40,9 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
|
|
@ -47,6 +51,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
cachetools "k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
|
@ -132,6 +137,141 @@ func (c *Client) Create(resources ResourceList) (*Result, error) {
|
|||
return &Result{Created: resources}, nil
|
||||
}
|
||||
|
||||
func transformRequests(req *rest.Request) {
|
||||
tableParam := strings.Join([]string{
|
||||
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
|
||||
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
|
||||
"application/json",
|
||||
}, ",")
|
||||
req.SetHeader("Accept", tableParam)
|
||||
|
||||
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
|
||||
req.Param("includeObject", "Object")
|
||||
}
|
||||
|
||||
// Get retrieves the resource objects supplied. If related is set to true the
|
||||
// related pods are fetched as well. If the passed in resources are a table kind
|
||||
// the related resources will also be fetched as kind=table.
|
||||
func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime.Object, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
objs := make(map[string][]runtime.Object)
|
||||
|
||||
podSelectors := []map[string]string{}
|
||||
err := resources.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvk := info.ResourceMapping().GroupVersionKind
|
||||
vk := gvk.Version + "/" + gvk.Kind
|
||||
obj, err := getResource(info)
|
||||
if err != nil {
|
||||
fmt.Fprintf(buf, "Get resource %s failed, err:%v\n", info.Name, err)
|
||||
} else {
|
||||
objs[vk] = append(objs[vk], obj)
|
||||
|
||||
// Only fetch related pods if they are requested
|
||||
if related {
|
||||
// Discover if the existing object is a table. If it is, request
|
||||
// the pods as Tables. Otherwise request them normally.
|
||||
objGVK := obj.GetObjectKind().GroupVersionKind()
|
||||
var isTable bool
|
||||
if objGVK.Kind == "Table" {
|
||||
isTable = true
|
||||
}
|
||||
|
||||
objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors)
|
||||
if err != nil {
|
||||
c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]runtime.Object, table bool, podSelectors *[]map[string]string) (map[string][]runtime.Object, error) {
|
||||
if info == nil {
|
||||
return objs, nil
|
||||
}
|
||||
c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
|
||||
selector, ok, _ := getSelectorFromObject(info.Object)
|
||||
if !ok {
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
for index := range *podSelectors {
|
||||
if reflect.DeepEqual((*podSelectors)[index], selector) {
|
||||
// check if pods for selectors are already added. This avoids duplicate printing of pods
|
||||
return objs, nil
|
||||
}
|
||||
}
|
||||
|
||||
*podSelectors = append(*podSelectors, selector)
|
||||
|
||||
var infos []*resource.Info
|
||||
var err error
|
||||
if table {
|
||||
infos, err = c.Factory.NewBuilder().
|
||||
Unstructured().
|
||||
ContinueOnError().
|
||||
NamespaceParam(info.Namespace).
|
||||
DefaultNamespace().
|
||||
ResourceTypes("pods").
|
||||
LabelSelector(labels.Set(selector).AsSelector().String()).
|
||||
TransformRequests(transformRequests).
|
||||
Do().Infos()
|
||||
if err != nil {
|
||||
return objs, err
|
||||
}
|
||||
} else {
|
||||
infos, err = c.Factory.NewBuilder().
|
||||
Unstructured().
|
||||
ContinueOnError().
|
||||
NamespaceParam(info.Namespace).
|
||||
DefaultNamespace().
|
||||
ResourceTypes("pods").
|
||||
LabelSelector(labels.Set(selector).AsSelector().String()).
|
||||
Do().Infos()
|
||||
if err != nil {
|
||||
return objs, err
|
||||
}
|
||||
}
|
||||
vk := "v1/Pod(related)"
|
||||
|
||||
for _, info := range infos {
|
||||
objs[vk] = append(objs[vk], info.Object)
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error) {
|
||||
typed := obj.(*unstructured.Unstructured)
|
||||
kind := typed.Object["kind"]
|
||||
switch kind {
|
||||
case "ReplicaSet", "Deployment", "StatefulSet", "DaemonSet", "Job":
|
||||
return unstructured.NestedStringMap(typed.Object, "spec", "selector", "matchLabels")
|
||||
case "ReplicationController":
|
||||
return unstructured.NestedStringMap(typed.Object, "spec", "selector")
|
||||
default:
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getResource(info *resource.Info) (runtime.Object, error) {
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Wait waits up to the given timeout for the specified resources to be ready.
|
||||
func (c *Client) Wait(resources ResourceList, timeout time.Duration) error {
|
||||
cs, err := c.getKubeClient()
|
||||
|
|
@ -215,6 +355,33 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
|
|||
return result, scrubValidationError(err)
|
||||
}
|
||||
|
||||
// BuildTable validates for Kubernetes objects and returns unstructured infos.
|
||||
// The returned kind is a Table.
|
||||
func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, error) {
|
||||
validationDirective := metav1.FieldValidationIgnore
|
||||
if validate {
|
||||
validationDirective = metav1.FieldValidationStrict
|
||||
}
|
||||
|
||||
dynamicClient, err := c.Factory.DynamicClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verifier := resource.NewQueryParamVerifier(dynamicClient, c.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
|
||||
schema, err := c.Factory.Validator(validationDirective, verifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := c.newBuilder().
|
||||
Unstructured().
|
||||
Schema(schema).
|
||||
Stream(reader, "").
|
||||
TransformRequests(transformRequests).
|
||||
Do().Infos()
|
||||
return result, scrubValidationError(err)
|
||||
}
|
||||
|
||||
// Update takes the current list of objects and target list of objects and
|
||||
// creates resources that don't already exist, updates resources that have been
|
||||
// modified in the target configuration, and deletes resources from the current
|
||||
|
|
@ -308,16 +475,20 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) {
|
|||
mtx := sync.Mutex{}
|
||||
err := perform(resources, func(info *resource.Info) error {
|
||||
c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
|
||||
if err := c.skipIfNotFound(deleteResource(info)); err != nil {
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
// Collect the error and continue on
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
err := deleteResource(info)
|
||||
if err == nil || apierrors.IsNotFound(err) {
|
||||
if err != nil {
|
||||
c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err)
|
||||
}
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
res.Deleted = append(res.Deleted, info)
|
||||
return nil
|
||||
}
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
// Collect the error and continue on
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -334,14 +505,6 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) skipIfNotFound(err error) error {
|
||||
if apierrors.IsNotFound(err) {
|
||||
c.Log("%v", err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error {
|
||||
return func(info *resource.Info) error {
|
||||
return c.watchUntilReady(t, info)
|
||||
|
|
@ -356,10 +519,10 @@ func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error {
|
|||
// For most kinds, it checks to see if the resource is marked as Added or Modified
|
||||
// by the Kubernetes event stream. For some kinds, it does more:
|
||||
//
|
||||
// - Jobs: A job is marked "Ready" when it has successfully completed. This is
|
||||
// ascertained by watching the Status fields in a job's output.
|
||||
// - Pods: A pod is marked "Ready" when it has successfully completed. This is
|
||||
// ascertained by watching the status.phase field in a pod's output.
|
||||
// - Jobs: A job is marked "Ready" when it has successfully completed. This is
|
||||
// ascertained by watching the Status fields in a job's output.
|
||||
// - Pods: A pod is marked "Ready" when it has successfully completed. This is
|
||||
// ascertained by watching the status.phase field in a pod's output.
|
||||
//
|
||||
// Handling for other kinds will be added as necessary.
|
||||
func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error {
|
||||
|
|
|
|||
|
|
@ -253,6 +253,45 @@ func TestBuild(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
reader io.Reader
|
||||
count int
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "Valid input",
|
||||
namespace: "test",
|
||||
reader: strings.NewReader(guestbookManifest),
|
||||
count: 6,
|
||||
}, {
|
||||
name: "Valid input, deploying resources into different namespaces",
|
||||
namespace: "test",
|
||||
reader: strings.NewReader(namespacedGuestbookManifest),
|
||||
count: 1,
|
||||
},
|
||||
}
|
||||
|
||||
c := newTestClient(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test for an invalid manifest
|
||||
infos, err := c.BuildTable(tt.reader, false)
|
||||
if err != nil && !tt.err {
|
||||
t.Errorf("Got error message when no error should have occurred: %v", err)
|
||||
} else if err != nil && strings.Contains(err.Error(), "--validate=false") {
|
||||
t.Error("error message was not scrubbed")
|
||||
}
|
||||
|
||||
if len(infos) != tt.count {
|
||||
t.Errorf("expected %d result objects, got %d", tt.count, len(infos))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerform(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
|
|
@ -33,11 +34,13 @@ import (
|
|||
type FailingKubeClient struct {
|
||||
PrintingKubeClient
|
||||
CreateError error
|
||||
GetError error
|
||||
WaitError error
|
||||
DeleteError error
|
||||
WatchUntilReadyError error
|
||||
UpdateError error
|
||||
BuildError error
|
||||
BuildTableError error
|
||||
BuildUnstructuredError error
|
||||
WaitAndGetCompletedPodPhaseError error
|
||||
WaitDuration time.Duration
|
||||
|
|
@ -51,6 +54,14 @@ func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, e
|
|||
return f.PrintingKubeClient.Create(resources)
|
||||
}
|
||||
|
||||
// Get returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) {
|
||||
if f.GetError != nil {
|
||||
return nil, f.GetError
|
||||
}
|
||||
return f.PrintingKubeClient.Get(resources, related)
|
||||
}
|
||||
|
||||
// Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints.
|
||||
func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error {
|
||||
time.Sleep(f.WaitDuration)
|
||||
|
|
@ -108,6 +119,14 @@ func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error
|
|||
return f.PrintingKubeClient.Build(r, false)
|
||||
}
|
||||
|
||||
// BuildTable returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, error) {
|
||||
if f.BuildTableError != nil {
|
||||
return []*resource.Info{}, f.BuildTableError
|
||||
}
|
||||
return f.PrintingKubeClient.BuildTable(r, false)
|
||||
}
|
||||
|
||||
// WaitAndGetCompletedPodPhase returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) {
|
||||
if f.WaitAndGetCompletedPodPhaseError != nil {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
|
|
@ -47,6 +48,14 @@ func (p *PrintingKubeClient) Create(resources kube.ResourceList) (*kube.Result,
|
|||
return &kube.Result{Created: resources}, nil
|
||||
}
|
||||
|
||||
func (p *PrintingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) {
|
||||
_, err := io.Copy(p.Out, bufferize(resources))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return make(map[string][]runtime.Object), nil
|
||||
}
|
||||
|
||||
func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error {
|
||||
_, err := io.Copy(p.Out, bufferize(resources))
|
||||
return err
|
||||
|
|
@ -96,6 +105,11 @@ func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, erro
|
|||
return []*resource.Info{}, nil
|
||||
}
|
||||
|
||||
// BuildTable implements KubeClient BuildTable.
|
||||
func (p *PrintingKubeClient) BuildTable(_ io.Reader, _ bool) (kube.ResourceList, error) {
|
||||
return []*resource.Info{}, nil
|
||||
}
|
||||
|
||||
// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase.
|
||||
func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) {
|
||||
return v1.PodSucceeded, nil
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Interface represents a client capable of communicating with the Kubernetes API.
|
||||
|
|
@ -78,5 +79,28 @@ type InterfaceExt interface {
|
|||
WaitForDelete(resources ResourceList, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// InterfaceResources is introduced to avoid breaking backwards compatibility for Interface implementers.
|
||||
//
|
||||
// TODO Helm 4: Remove InterfaceResources and integrate its method(s) into the Interface.
|
||||
type InterfaceResources interface {
|
||||
// Get details of deployed resources.
|
||||
// The first argument is a list of resources to get. The second argument
|
||||
// specifies if related pods should be fetched. For example, the pods being
|
||||
// managed by a deployment.
|
||||
Get(resources ResourceList, related bool) (map[string][]runtime.Object, error)
|
||||
|
||||
// BuildTable creates a resource list from a Reader. This differs from
|
||||
// Interface.Build() in that a table kind is returned. A table is useful
|
||||
// if you want to use a printer to display the information.
|
||||
//
|
||||
// Reader must contain a YAML stream (one or more YAML documents separated
|
||||
// by "\n---\n")
|
||||
//
|
||||
// Validates against OpenAPI schema if validate is true.
|
||||
// TODO Helm 4: Integrate into Build with an argument
|
||||
BuildTable(reader io.Reader, validate bool) (ResourceList, error)
|
||||
}
|
||||
|
||||
var _ Interface = (*Client)(nil)
|
||||
var _ InterfaceExt = (*Client)(nil)
|
||||
var _ InterfaceResources = (*Client)(nil)
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool {
|
|||
c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled)
|
||||
return false
|
||||
}
|
||||
maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true)
|
||||
maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true)
|
||||
if err != nil {
|
||||
// If for some reason the value is invalid, set max unavailable to the
|
||||
// number of desired replicas. This is the same behavior as the
|
||||
|
|
@ -353,9 +353,16 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool {
|
|||
func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
|
||||
// If the update strategy is not a rolling update, there will be nothing to wait for
|
||||
if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
|
||||
c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type)
|
||||
return true
|
||||
}
|
||||
|
||||
// Make sure the status is up-to-date with the StatefulSet changes
|
||||
if sts.Status.ObservedGeneration < sts.Generation {
|
||||
c.log("StatefulSet is not ready: %s/%s. update has not yet been observed", sts.Namespace, sts.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
// Dereference all the pointers because StatefulSets like them
|
||||
var partition int
|
||||
// 1 is the default for replicas if not set
|
||||
|
|
@ -386,6 +393,13 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
|
|||
c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
|
||||
return false
|
||||
}
|
||||
|
||||
if sts.Status.CurrentRevision != sts.Status.UpdateRevision {
|
||||
c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision)
|
||||
return false
|
||||
}
|
||||
|
||||
c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,20 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) {
|
|||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "statefulset is not ready when status of latest generation has not yet been observed",
|
||||
args: args{
|
||||
sts: newStatefulSetWithNewGeneration("foo", 1, 0, 1, 1),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "statefulset is not ready when current revision for current replicas does not match update revision for updated replicas",
|
||||
args: args{
|
||||
sts: newStatefulSetWithUpdateRevision("foo", 1, 0, 1, 1, "foo-bbbbbbb"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -377,8 +391,9 @@ func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberSchedul
|
|||
func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
|
||||
return &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: defaultNamespace,
|
||||
Name: name,
|
||||
Namespace: defaultNamespace,
|
||||
Generation: int64(1),
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
|
||||
|
|
@ -404,12 +419,27 @@ func newStatefulSet(name string, replicas, partition, readyReplicas, updatedRepl
|
|||
},
|
||||
},
|
||||
Status: appsv1.StatefulSetStatus{
|
||||
UpdatedReplicas: int32(updatedReplicas),
|
||||
ReadyReplicas: int32(readyReplicas),
|
||||
ObservedGeneration: int64(1),
|
||||
CurrentRevision: name + "-aaaaaaa",
|
||||
UpdateRevision: name + "-aaaaaaa",
|
||||
UpdatedReplicas: int32(updatedReplicas),
|
||||
ReadyReplicas: int32(readyReplicas),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newStatefulSetWithNewGeneration(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
|
||||
ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas)
|
||||
ss.Generation++
|
||||
return ss
|
||||
}
|
||||
|
||||
func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string) *appsv1.StatefulSet {
|
||||
ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas)
|
||||
ss.Status.UpdateRevision = updateRevision
|
||||
return ss
|
||||
}
|
||||
|
||||
func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment {
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
|
|
|||
|
|
@ -162,13 +162,13 @@ func (i HTTPInstaller) Path() string {
|
|||
return helmpath.DataPath("plugins", i.PluginName)
|
||||
}
|
||||
|
||||
// CleanJoin resolves dest as a subpath of root.
|
||||
// cleanJoin resolves dest as a subpath of root.
|
||||
//
|
||||
// This function runs several security checks on the path, generating an error if
|
||||
// the supplied `dest` looks suspicious or would result in dubious behavior on the
|
||||
// filesystem.
|
||||
//
|
||||
// CleanJoin assumes that any attempt by `dest` to break out of the CWD is an attempt
|
||||
// cleanJoin assumes that any attempt by `dest` to break out of the CWD is an attempt
|
||||
// to be malicious. (If you don't care about this, use the securejoin-filepath library.)
|
||||
// It will emit an error if it detects paths that _look_ malicious, operating on the
|
||||
// assumption that we don't actually want to do anything with files that already
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrPluginNotAFolder indicates that the plugin path is not a folder.
|
||||
var ErrPluginNotAFolder = errors.New("expected plugin to be a folder")
|
||||
|
||||
// LocalInstaller installs plugins from the filesystem.
|
||||
type LocalInstaller struct {
|
||||
base
|
||||
|
|
@ -43,6 +46,14 @@ func NewLocalInstaller(source string) (*LocalInstaller, error) {
|
|||
//
|
||||
// Implements Installer.
|
||||
func (i *LocalInstaller) Install() error {
|
||||
stat, err := os.Stat(i.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
return ErrPluginNotAFolder
|
||||
}
|
||||
|
||||
if !isPlugin(i.Source) {
|
||||
return ErrMissingMetadata
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,3 +48,19 @@ func TestLocalInstaller(t *testing.T) {
|
|||
}
|
||||
defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm
|
||||
}
|
||||
|
||||
func TestLocalInstallerNotAFolder(t *testing.T) {
|
||||
source := "../testdata/plugdir/good/echo/plugin.yaml"
|
||||
i, err := NewForSource(source, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
err = Install(i)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if err != ErrPluginNotAFolder {
|
||||
t.Fatalf("expected error to equal: %q", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
|
|||
|
||||
// NewOCIPusher constructs a valid OCI client as a Pusher
|
||||
func NewOCIPusher(ops ...Option) (Pusher, error) {
|
||||
registryClient, err := registry.NewClient()
|
||||
registryClient, err := registry.NewClient(
|
||||
registry.ClientOptEnableCache(true),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ a plus (+) when pulling from a registry.`
|
|||
type (
|
||||
// Client works with OCI-compliant registries
|
||||
Client struct {
|
||||
debug bool
|
||||
debug bool
|
||||
enableCache bool
|
||||
// path to repository config file e.g. ~/.docker/config.json
|
||||
credentialsFile string
|
||||
out io.Writer
|
||||
|
|
@ -95,12 +96,18 @@ func NewClient(options ...ClientOption) (*Client, error) {
|
|||
}
|
||||
client.resolver = resolver
|
||||
}
|
||||
|
||||
// allocate a cache if option is set
|
||||
var cache registryauth.Cache
|
||||
if client.enableCache {
|
||||
cache = registryauth.DefaultCache
|
||||
}
|
||||
if client.registryAuthorizer == nil {
|
||||
client.registryAuthorizer = ®istryauth.Client{
|
||||
Header: http.Header{
|
||||
"User-Agent": {version.GetUserAgent()},
|
||||
},
|
||||
Cache: registryauth.DefaultCache,
|
||||
Cache: cache,
|
||||
Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) {
|
||||
dockerClient, ok := client.authorizer.(*dockerauth.Client)
|
||||
if !ok {
|
||||
|
|
@ -138,6 +145,13 @@ func ClientOptDebug(debug bool) ClientOption {
|
|||
}
|
||||
}
|
||||
|
||||
// ClientOptEnableCache returns a function that sets the enableCache setting on a client options set
|
||||
func ClientOptEnableCache(enableCache bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.enableCache = enableCache
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptWriter returns a function that sets the writer setting on client options set
|
||||
func ClientOptWriter(out io.Writer) ClientOption {
|
||||
return func(client *Client) {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ func (suite *RegistryClientTestSuite) SetupSuite() {
|
|||
var err error
|
||||
suite.RegistryClient, err = NewClient(
|
||||
ClientOptDebug(true),
|
||||
ClientOptEnableCache(true),
|
||||
ClientOptWriter(suite.Out),
|
||||
ClientOptCredentialsFile(credentialsFile),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
|||
package release
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
|
|
@ -33,4 +35,6 @@ type Info struct {
|
|||
Status Status `json:"status,omitempty"`
|
||||
// Contains the rendered templates/NOTES.txt if available
|
||||
Notes string `json:"notes,omitempty"`
|
||||
// Contains the deployed resources information
|
||||
Resources map[string][]runtime.Object `json:"resources,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import (
|
|||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
|
@ -116,15 +115,11 @@ func (r *ChartRepository) Load() error {
|
|||
|
||||
// DownloadIndexFile fetches the index from a repository.
|
||||
func (r *ChartRepository) DownloadIndexFile() (string, error) {
|
||||
parsedURL, err := url.Parse(r.Config.URL)
|
||||
indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
|
||||
parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
|
||||
|
||||
indexURL := parsedURL.String()
|
||||
// TODO add user-agent
|
||||
resp, err := r.Client.Get(indexURL,
|
||||
getter.WithURL(r.Config.URL),
|
||||
getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify),
|
||||
|
|
@ -219,7 +214,7 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
|
|||
// but it also receives credentials and TLS verify flag for the chart repository.
|
||||
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
|
||||
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
|
||||
return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, false, getters)
|
||||
return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, insecureSkipTLSverify, false, getters)
|
||||
}
|
||||
|
||||
// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL
|
||||
|
|
@ -253,6 +248,10 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
|
|||
if err != nil {
|
||||
return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)))
|
||||
os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)))
|
||||
}()
|
||||
|
||||
// Read the index file for the repository to get chart information and return chart URL
|
||||
repoIndex, err := LoadIndexFile(idx)
|
||||
|
|
@ -286,18 +285,27 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
|
|||
// ResolveReferenceURL resolves refURL relative to baseURL.
|
||||
// If refURL is absolute, it simply returns refURL.
|
||||
func ResolveReferenceURL(baseURL, refURL string) (string, error) {
|
||||
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
|
||||
parsedBaseURL, err := url.Parse(strings.TrimSuffix(baseURL, "/") + "/")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
|
||||
}
|
||||
|
||||
parsedRefURL, err := url.Parse(refURL)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
|
||||
}
|
||||
|
||||
return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
|
||||
if parsedRefURL.IsAbs() {
|
||||
return refURL, nil
|
||||
}
|
||||
|
||||
parsedBaseURL, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
|
||||
}
|
||||
|
||||
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
|
||||
parsedBaseURL.RawPath = strings.TrimSuffix(parsedBaseURL.RawPath, "/") + "/"
|
||||
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
|
||||
|
||||
resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL)
|
||||
resolvedURL.RawQuery = parsedBaseURL.RawQuery
|
||||
return resolvedURL.String(), nil
|
||||
}
|
||||
|
||||
func (e *Entry) String() string {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -309,8 +310,15 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
|
|||
|
||||
// If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority".
|
||||
_, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{}))
|
||||
|
||||
if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||
// Go communicates with the platform and different platforms return different messages. Go itself tests darwin
|
||||
// differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older
|
||||
// versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test
|
||||
// for both messages.
|
||||
if runtime.GOOS == "darwin" {
|
||||
if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
|
||||
}
|
||||
} else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -377,35 +385,21 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestResolveReferenceURL(t *testing.T) {
|
||||
chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" {
|
||||
t.Errorf("%s", chartURL)
|
||||
}
|
||||
|
||||
chartURL, err = ResolveReferenceURL("http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if chartURL != "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz" {
|
||||
t.Errorf("%s", chartURL)
|
||||
}
|
||||
|
||||
chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" {
|
||||
t.Errorf("%s", chartURL)
|
||||
}
|
||||
|
||||
chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" {
|
||||
t.Errorf("%s", chartURL)
|
||||
for _, tt := range []struct {
|
||||
baseURL, refURL, chartURL string
|
||||
}{
|
||||
{"http://localhost:8123/charts/", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz"},
|
||||
{"http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz", "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz"},
|
||||
{"http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz", "https://charts.helm.sh/stable/nginx-0.2.0.tgz"},
|
||||
{"http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz", "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz"},
|
||||
{"http://localhost:8123/charts?with=queryparameter", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz?with=queryparameter"},
|
||||
} {
|
||||
chartURL, err := ResolveReferenceURL(tt.baseURL, tt.refURL)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error in ResolveReferenceURL(%q, %q): %s", tt.baseURL, tt.refURL, err)
|
||||
}
|
||||
if chartURL != tt.chartURL {
|
||||
t.Errorf("expected ResolveReferenceURL(%q, %q) to equal %q, got %q", tt.baseURL, tt.refURL, tt.chartURL, chartURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ func LoadIndexFile(path string) (*IndexFile, error) {
|
|||
// MustAdd adds a file to the index
|
||||
// This can leave the index in an unsorted state
|
||||
func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error {
|
||||
if i.Entries == nil {
|
||||
return errors.New("entries not initialized")
|
||||
}
|
||||
|
||||
if md.APIVersion == "" {
|
||||
md.APIVersion = chart.APIVersionV1
|
||||
}
|
||||
|
|
@ -339,6 +343,10 @@ func loadIndex(data []byte, source string) (*IndexFile, error) {
|
|||
|
||||
for name, cvs := range i.Entries {
|
||||
for idx := len(cvs) - 1; idx >= 0; idx-- {
|
||||
if cvs[idx] == nil {
|
||||
log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)
|
||||
continue
|
||||
}
|
||||
if cvs[idx].APIVersion == "" {
|
||||
cvs[idx].APIVersion = chart.APIVersionV1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,15 @@ entries:
|
|||
version: 1.0.0
|
||||
home: https://github.com/something
|
||||
digest: "sha256:1234567890abcdef"
|
||||
`
|
||||
indexWithEmptyEntry = `
|
||||
apiVersion: v1
|
||||
entries:
|
||||
grafana:
|
||||
- apiVersion: v2
|
||||
name: grafana
|
||||
foo:
|
||||
-
|
||||
`
|
||||
)
|
||||
|
||||
|
|
@ -152,6 +161,12 @@ func TestLoadIndex_Duplicates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadIndex_EmptyEntry(t *testing.T) {
|
||||
if _, err := loadIndex([]byte(indexWithEmptyEntry), "indexWithEmptyEntry"); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadIndex_Empty(t *testing.T) {
|
||||
if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil {
|
||||
t.Errorf("Expected an error when index.yaml is empty.")
|
||||
|
|
@ -526,3 +541,21 @@ func TestIndexWrite(t *testing.T) {
|
|||
t.Fatal("Index files doesn't contain expected content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFileIndexEntriesNil(t *testing.T) {
|
||||
i := NewIndexFile()
|
||||
i.APIVersion = chart.APIVersionV1
|
||||
i.Entries = nil
|
||||
for _, x := range []struct {
|
||||
md *chart.Metadata
|
||||
filename string
|
||||
baseURL string
|
||||
digest string
|
||||
}{
|
||||
{&chart.Metadata{APIVersion: "v2", Name: " ", Version: "8033-5.apinie+s.r"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"},
|
||||
} {
|
||||
if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err == nil {
|
||||
t.Errorf("expected err to be non-nil when entries not initialized")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,9 @@ func (r *File) Remove(name string) bool {
|
|||
cp := []*Entry{}
|
||||
found := false
|
||||
for _, rf := range r.Repositories {
|
||||
if rf == nil {
|
||||
continue
|
||||
}
|
||||
if rf.Name == name {
|
||||
found = true
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -225,3 +225,34 @@ func TestRepoNotExists(t *testing.T) {
|
|||
t.Errorf("expected prompt `couldn't load repositories file`")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRepositoryInvalidEntries(t *testing.T) {
|
||||
sampleRepository := NewFile()
|
||||
sampleRepository.Add(
|
||||
&Entry{
|
||||
Name: "stable",
|
||||
URL: "https://example.com/stable/charts",
|
||||
},
|
||||
&Entry{
|
||||
Name: "incubator",
|
||||
URL: "https://example.com/incubator",
|
||||
},
|
||||
&Entry{},
|
||||
nil,
|
||||
&Entry{
|
||||
Name: "test",
|
||||
URL: "https://example.com/test",
|
||||
},
|
||||
)
|
||||
|
||||
removeRepository := "stable"
|
||||
found := sampleRepository.Remove(removeRepository)
|
||||
if !found {
|
||||
t.Errorf("expected repository %s not found", removeRepository)
|
||||
}
|
||||
|
||||
found = sampleRepository.Has(removeRepository)
|
||||
if found {
|
||||
t.Errorf("repository %s not deleted", removeRepository)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
|
|||
// init test client
|
||||
registryClient, err := ociRegistry.NewClient(
|
||||
ociRegistry.ClientOptDebug(true),
|
||||
ociRegistry.ClientOptEnableCache(true),
|
||||
ociRegistry.ClientOptWriter(os.Stdout),
|
||||
ociRegistry.ClientOptCredentialsFile(credentialsFile),
|
||||
)
|
||||
|
|
@ -370,7 +371,6 @@ func (s *Server) StartTLS() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tlsConf.BuildNameToCertificate()
|
||||
tlsConf.ServerName = "helm.sh"
|
||||
s.srv.TLS = tlsConf
|
||||
s.srv.StartTLS()
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func decodeRelease(data string) (*rspb.Release, error) {
|
|||
// For backwards compatibility with releases that were stored before
|
||||
// compression was introduced we skip decompression if the
|
||||
// gzip magic header is not found
|
||||
if bytes.Equal(b[0:3], magicGzip) {
|
||||
if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) {
|
||||
r, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -17,10 +17,13 @@ package strvals
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
|
@ -29,6 +32,14 @@ import (
|
|||
// ErrNotList indicates that a non-list was treated as a list.
|
||||
var ErrNotList = errors.New("not a list")
|
||||
|
||||
// MaxIndex is the maximum index that will be allowed by setIndex.
|
||||
// The default value 65536 = 1024 * 64
|
||||
var MaxIndex = 65536
|
||||
|
||||
// MaxNestedNameLevel is the maximum level of nesting for a value name that
|
||||
// will be allowed.
|
||||
var MaxNestedNameLevel = 30
|
||||
|
||||
// ToYAML takes a string of arguments and converts to a YAML document.
|
||||
func ToYAML(s string) (string, error) {
|
||||
m, err := Parse(s)
|
||||
|
|
@ -94,6 +105,18 @@ func ParseIntoString(s string, dest map[string]interface{}) error {
|
|||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseJSON parses a string with format key1=val1, key2=val2, ...
|
||||
// where values are json strings (null, or scalars, or arrays, or objects).
|
||||
// An empty val is treated as null.
|
||||
//
|
||||
// If a key exists in dest, the new value overwrites the dest version.
|
||||
//
|
||||
func ParseJSON(s string, dest map[string]interface{}) error {
|
||||
scanner := bytes.NewBufferString(s)
|
||||
t := newJSONParser(scanner, dest)
|
||||
return t.parse()
|
||||
}
|
||||
|
||||
// ParseIntoFile parses a filevals line and merges the result into dest.
|
||||
//
|
||||
// This method always returns a string as the value.
|
||||
|
|
@ -113,9 +136,10 @@ type RunesValueReader func([]rune) (interface{}, error)
|
|||
// where sc is the source of the original data being parsed
|
||||
// where data is the final parsed data from the parses with correct types
|
||||
type parser struct {
|
||||
sc *bytes.Buffer
|
||||
data map[string]interface{}
|
||||
reader RunesValueReader
|
||||
sc *bytes.Buffer
|
||||
data map[string]interface{}
|
||||
reader RunesValueReader
|
||||
isjsonval bool
|
||||
}
|
||||
|
||||
func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
|
||||
|
|
@ -125,13 +149,17 @@ func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *
|
|||
return &parser{sc: sc, data: data, reader: stringConverter}
|
||||
}
|
||||
|
||||
func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser {
|
||||
return &parser{sc: sc, data: data, reader: nil, isjsonval: true}
|
||||
}
|
||||
|
||||
func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser {
|
||||
return &parser{sc: sc, data: data, reader: reader}
|
||||
}
|
||||
|
||||
func (t *parser) parse() error {
|
||||
for {
|
||||
err := t.key(t.data)
|
||||
err := t.key(t.data, 0)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -150,7 +178,7 @@ func runeSet(r []rune) map[rune]bool {
|
|||
return s
|
||||
}
|
||||
|
||||
func (t *parser) key(data map[string]interface{}) (reterr error) {
|
||||
func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
reterr = fmt.Errorf("unable to parse key: %s", r)
|
||||
|
|
@ -180,10 +208,37 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
|
|||
}
|
||||
|
||||
// Now we need to get the value after the ].
|
||||
list, err = t.listItem(list, i)
|
||||
list, err = t.listItem(list, i, nestedNameLevel)
|
||||
set(data, kk, list)
|
||||
return err
|
||||
case last == '=':
|
||||
if t.isjsonval {
|
||||
empval, err := t.emptyVal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if empval {
|
||||
set(data, string(k), nil)
|
||||
return nil
|
||||
}
|
||||
// parse jsonvals by using Go’s JSON standard library
|
||||
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
|
||||
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
|
||||
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
|
||||
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
|
||||
var jsonval interface{}
|
||||
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
|
||||
if err = dec.Decode(&jsonval); err != nil {
|
||||
return err
|
||||
}
|
||||
set(data, string(k), jsonval)
|
||||
if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil {
|
||||
return err
|
||||
}
|
||||
// skip possible blanks and comma
|
||||
_, err = t.emptyVal()
|
||||
return err
|
||||
}
|
||||
//End of key. Consume =, Get value.
|
||||
// FIXME: Get value list first
|
||||
vl, e := t.valList()
|
||||
|
|
@ -205,12 +260,17 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
|
|||
default:
|
||||
return e
|
||||
}
|
||||
|
||||
case last == ',':
|
||||
// No value given. Set the value to empty string. Return error.
|
||||
set(data, string(k), "")
|
||||
return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
|
||||
case last == '.':
|
||||
// Check value name is within the maximum nested name level
|
||||
nestedNameLevel++
|
||||
if nestedNameLevel > MaxNestedNameLevel {
|
||||
return fmt.Errorf("value name nested level is greater than maximum supported nested level of %d", MaxNestedNameLevel)
|
||||
}
|
||||
|
||||
// First, create or find the target map.
|
||||
inner := map[string]interface{}{}
|
||||
if _, ok := data[string(k)]; ok {
|
||||
|
|
@ -218,11 +278,13 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
|
|||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
if len(inner) == 0 {
|
||||
e := t.key(inner, nestedNameLevel)
|
||||
if e == nil && len(inner) == 0 {
|
||||
return errors.Errorf("key map %q has no value", string(k))
|
||||
}
|
||||
set(data, string(k), inner)
|
||||
if len(inner) != 0 {
|
||||
set(data, string(k), inner)
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
|
@ -249,6 +311,9 @@ func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{},
|
|||
if index < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", index)
|
||||
}
|
||||
if index > MaxIndex {
|
||||
return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex)
|
||||
}
|
||||
if len(list) <= index {
|
||||
newlist := make([]interface{}, index+1)
|
||||
copy(newlist, list)
|
||||
|
|
@ -269,7 +334,7 @@ func (t *parser) keyIndex() (int, error) {
|
|||
return strconv.Atoi(string(v))
|
||||
|
||||
}
|
||||
func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
|
||||
func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) {
|
||||
if i < 0 {
|
||||
return list, fmt.Errorf("negative %d index not allowed", i)
|
||||
}
|
||||
|
|
@ -280,6 +345,34 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
|
|||
case err != nil:
|
||||
return list, err
|
||||
case last == '=':
|
||||
if t.isjsonval {
|
||||
empval, err := t.emptyVal()
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
if empval {
|
||||
return setIndex(list, i, nil)
|
||||
}
|
||||
// parse jsonvals by using Go’s JSON standard library
|
||||
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
|
||||
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
|
||||
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
|
||||
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
|
||||
var jsonval interface{}
|
||||
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
|
||||
if err = dec.Decode(&jsonval); err != nil {
|
||||
return list, err
|
||||
}
|
||||
if list, err = setIndex(list, i, jsonval); err != nil {
|
||||
return list, err
|
||||
}
|
||||
if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil {
|
||||
return list, err
|
||||
}
|
||||
// skip possible blanks and comma
|
||||
_, err = t.emptyVal()
|
||||
return list, err
|
||||
}
|
||||
vl, e := t.valList()
|
||||
switch e {
|
||||
case nil:
|
||||
|
|
@ -314,7 +407,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
|
|||
}
|
||||
}
|
||||
// Now we need to get the value after the ].
|
||||
list2, err := t.listItem(crtList, nextI)
|
||||
list2, err := t.listItem(crtList, nextI, nestedNameLevel)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
|
@ -333,7 +426,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
|
|||
}
|
||||
|
||||
// Recurse
|
||||
e := t.key(inner)
|
||||
e := t.key(inner, nestedNameLevel)
|
||||
if e != nil {
|
||||
return list, e
|
||||
}
|
||||
|
|
@ -343,6 +436,28 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// check for an empty value
|
||||
// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val)
|
||||
// comma and spaces are consumed, while any other char is not cosumed
|
||||
func (t *parser) emptyVal() (bool, error) {
|
||||
for {
|
||||
r, _, e := t.sc.ReadRune()
|
||||
if e == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
if e != nil {
|
||||
return false, e
|
||||
}
|
||||
if r == ',' {
|
||||
return true, nil
|
||||
}
|
||||
if !unicode.IsSpace(r) {
|
||||
t.sc.UnreadRune()
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *parser) val() ([]rune, error) {
|
||||
stop := runeSet([]rune{','})
|
||||
v, _, err := runesUntil(t.sc, stop)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
package strvals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
|
@ -62,6 +63,14 @@ func TestSetIndex(t *testing.T) {
|
|||
val: 4,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "large",
|
||||
initial: []interface{}{0, 1, 2, 3, 4, 5},
|
||||
expect: []interface{}{0, 1, 2, 3, 4, 5},
|
||||
add: MaxIndex + 1,
|
||||
val: 4,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -567,6 +576,107 @@ func TestParseIntoString(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
got map[string]interface{}
|
||||
expect map[string]interface{}
|
||||
err bool
|
||||
}{
|
||||
{ // set json scalars values, and replace one existing key
|
||||
input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"",
|
||||
got: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": "overwrite",
|
||||
"inner2": "value2",
|
||||
},
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": "1",
|
||||
"inner2": "value2",
|
||||
"inner3": 3,
|
||||
"inner4": true,
|
||||
"inner5": "true",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{ // set json objects and arrays, and replace one existing key
|
||||
input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]",
|
||||
got: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": map[string]interface{}{
|
||||
"x": "overwrite",
|
||||
},
|
||||
"inner2": "value2",
|
||||
"inner3": []interface{}{
|
||||
"overwrite",
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}},
|
||||
"inner2": "value2",
|
||||
"inner3": []interface{}{"new value 1", "new value 2"},
|
||||
"inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}},
|
||||
"inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}},
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{ // null assigment, and no value assigned (equivalent to null)
|
||||
input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null",
|
||||
got: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": map[string]interface{}{
|
||||
"x": "overwrite",
|
||||
},
|
||||
"inner2": "value2",
|
||||
},
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner1": nil,
|
||||
"inner2": "value2",
|
||||
"inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}},
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{ // syntax error
|
||||
input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5={\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]",
|
||||
got: nil,
|
||||
expect: nil,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if err := ParseJSON(tt.input, tt.got); err != nil {
|
||||
if tt.err {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("%s: %s", tt.input, err)
|
||||
}
|
||||
if tt.err {
|
||||
t.Fatalf("%s: Expected error. Got nil", tt.input)
|
||||
}
|
||||
y1, err := yaml.Marshal(tt.expect)
|
||||
if err != nil {
|
||||
t.Fatalf("Error serializing expected value: %s", err)
|
||||
}
|
||||
y2, err := yaml.Marshal(tt.got)
|
||||
if err != nil {
|
||||
t.Fatalf("Error serializing parsed value: %s", err)
|
||||
}
|
||||
|
||||
if string(y1) != string(y2) {
|
||||
t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFile(t *testing.T) {
|
||||
input := "name1=path1"
|
||||
expect := map[string]interface{}{
|
||||
|
|
@ -645,3 +755,64 @@ func TestToYAML(t *testing.T) {
|
|||
t.Errorf("Expected %q, got %q", expect, o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSetNestedLevels(t *testing.T) {
|
||||
var keyMultipleNestedLevels string
|
||||
for i := 1; i <= MaxNestedNameLevel+2; i++ {
|
||||
tmpStr := fmt.Sprintf("name%d", i)
|
||||
if i <= MaxNestedNameLevel+1 {
|
||||
tmpStr = tmpStr + "."
|
||||
}
|
||||
keyMultipleNestedLevels += tmpStr
|
||||
}
|
||||
tests := []struct {
|
||||
str string
|
||||
expect map[string]interface{}
|
||||
err bool
|
||||
errStr string
|
||||
}{
|
||||
{
|
||||
"outer.middle.inner=value",
|
||||
map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
str: keyMultipleNestedLevels + "=value",
|
||||
err: true,
|
||||
errStr: fmt.Sprintf("value name nested level is greater than maximum supported nested level of %d",
|
||||
MaxNestedNameLevel),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := Parse(tt.str)
|
||||
if err != nil {
|
||||
if tt.err {
|
||||
if tt.errStr != "" {
|
||||
if err.Error() != tt.errStr {
|
||||
t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.Fatalf("%s: %s", tt.str, err)
|
||||
}
|
||||
if tt.err {
|
||||
t.Errorf("%s: Expected error. Got nil", tt.str)
|
||||
}
|
||||
|
||||
y1, err := yaml.Marshal(tt.expect)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
y2, err := yaml.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("Error serializing parsed value: %s", err)
|
||||
}
|
||||
|
||||
if string(y1) != string(y2) {
|
||||
t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ coverdir=$(mktemp -d /tmp/coverage.XXXXXXXXXX)
|
|||
profile="${coverdir}/cover.out"
|
||||
|
||||
pushd /
|
||||
hash goveralls 2>/dev/null || go get github.com/mattn/goveralls
|
||||
hash goveralls 2>/dev/null || go install github.com/mattn/goveralls@v0.0.11
|
||||
popd
|
||||
|
||||
generate_cover_data() {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
|
|||
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
|
||||
HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
|
||||
HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
|
||||
HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
|
||||
|
||||
# initArch discovers the architecture for this system.
|
||||
initArch() {
|
||||
|
|
@ -97,6 +98,10 @@ verifySupported() {
|
|||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${HAS_GIT}" != "true" ]; then
|
||||
echo "[WARNING] Could not find git. It is required for plugin installation."
|
||||
fi
|
||||
}
|
||||
|
||||
# checkDesiredVersion checks if the desired version is available.
|
||||
|
|
|
|||
54
testdata/crt.pem
vendored
54
testdata/crt.pem
vendored
|
|
@ -2,12 +2,12 @@ Certificate:
|
|||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
55:31:53:9b:41:72:05:dc:90:49:bd:48:13:7c:59:9e:5a:53:5e:86
|
||||
48:5a:94:94:51:de:97:11:3b:62:54:dd:ac:85:63:e6:40:5c:4c:f6
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
|
||||
Validity
|
||||
Not Before: Nov 1 22:51:49 2019 GMT
|
||||
Not After : Oct 29 22:51:49 2029 GMT
|
||||
Not Before: Aug 24 18:07:59 2022 GMT
|
||||
Not After : Aug 21 18:07:59 2032 GMT
|
||||
Subject: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
|
|
@ -36,26 +36,26 @@ Certificate:
|
|||
X509v3 Subject Alternative Name:
|
||||
DNS:helm.sh, IP Address:127.0.0.1
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
4e:17:27:3d:36:4e:6c:2b:f7:d4:28:33:7e:05:26:7a:42:a0:
|
||||
2c:44:57:04:a0:de:df:40:fb:af:70:27:e6:55:20:f1:f8:c0:
|
||||
50:63:ab:b8:f1:31:5d:1e:f4:ca:8d:65:0b:d4:5e:5b:77:2f:
|
||||
2a:af:74:5f:18:2d:92:29:7f:2d:97:fb:ec:aa:e3:1e:db:b3:
|
||||
8d:01:aa:82:1a:f6:28:a8:b3:ee:15:9f:9a:f5:76:37:30:f2:
|
||||
3b:38:13:b2:d4:14:94:c6:38:fa:f9:6e:94:e8:1f:11:0b:b0:
|
||||
69:1a:b3:f9:f1:27:b4:d2:f5:64:54:7c:8f:e7:83:31:f6:0d:
|
||||
a7:0e:0e:66:d8:33:2f:e0:a1:93:56:92:58:bf:50:da:56:8e:
|
||||
db:42:22:f5:0c:6f:f8:4c:ef:f5:7c:2d:a6:b8:60:e4:bb:df:
|
||||
a3:6c:c2:6b:99:0b:d3:0a:ad:7c:f4:74:72:9a:52:5e:81:d9:
|
||||
a2:a2:dd:68:38:fb:b7:54:7f:f6:aa:ee:53:de:3d:3a:0e:86:
|
||||
53:ad:af:72:db:fb:6b:18:ce:ac:e4:64:70:13:68:da:be:e1:
|
||||
6b:46:dd:a0:72:96:9b:3f:ba:cf:11:6e:98:03:0a:69:83:9e:
|
||||
37:25:c9:36:b9:68:4f:73:ca:c6:32:5c:be:46:64:bb:a8:cc:
|
||||
71:25:8f:be
|
||||
d9:95:3b:98:01:6c:cb:a2:92:d8:f7:a7:52:2c:00:c1:04:cd:
|
||||
ef:1b:d8:fa:71:71:29:7d:1d:29:42:ea:03:ce:15:c6:d5:ee:
|
||||
2d:25:51:7e:96:8b:44:2e:d9:19:1b:95:a6:9c:92:52:2b:88:
|
||||
d8:76:6e:1b:87:36:8e:3a:b1:c6:aa:a4:7a:4e:a9:8b:8d:c0:
|
||||
3c:77:95:81:db:9a:50:f4:fb:cc:62:21:36:36:91:3b:6c:6e:
|
||||
37:a8:fa:cc:21:56:f4:31:6f:07:2b:29:0e:1a:06:6c:10:87:
|
||||
fa:6c:be:e1:29:8c:b9:84:b2:ea:4d:07:e8:2b:ff:f6:24:e6:
|
||||
a6:95:72:c7:d8:02:53:c2:c0:68:d3:fc:e9:72:a5:da:6c:39:
|
||||
5a:6b:17:71:86:40:96:ac:94:dd:21:45:9e:aa:85:8a:73:4c:
|
||||
8c:3f:0d:2b:d0:8b:04:ef:61:bb:8e:06:6b:86:46:30:a3:64:
|
||||
6b:97:01:8b:46:56:7d:42:33:f5:e0:ea:fd:80:b4:8a:50:a8:
|
||||
20:2c:f9:ad:61:05:da:ff:b9:b5:da:9c:d6:0e:47:44:0c:9a:
|
||||
8f:11:e0:66:f8:76:0c:0f:43:99:6b:af:44:3c:5c:cb:30:98:
|
||||
6a:24:f7:ea:23:db:cf:23:35:dd:6c:2e:9d:0a:b0:82:77:b8:
|
||||
dc:90:5f:78
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDRDCCAiygAwIBAgIUVTFTm0FyBdyQSb1IE3xZnlpTXoYwDQYJKoZIhvcNAQEL
|
||||
MIIDRDCCAiygAwIBAgIUSFqUlFHelxE7YlTdrIVj5kBcTPYwDQYJKoZIhvcNAQEL
|
||||
BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy
|
||||
MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTE5MTEwMTIyNTE0
|
||||
OVoXDTI5MTAyOTIyNTE0OVowTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAw
|
||||
MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTIyMDgyNDE4MDc1
|
||||
OVoXDTMyMDgyMTE4MDc1OVowTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAw
|
||||
DgYDVQQHDAdCb3VsZGVyMQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNo
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyIlVDQvx2ubAcH3TJ824
|
||||
qIGLfKSJ5dGxeAEd30SIC/zWgTU90Ttej7uTs34o2+3/oBM6cKP+lGsL/vtjALDL
|
||||
|
|
@ -64,10 +64,10 @@ V3SpI5vityJ6FHo96vF+MmtXbC7GT3VU+WtU0srrVByvORWb0HwP+FVRBOra+nuL
|
|||
Yw+sObH2S45O9urpe+a6XlqOke/csX1SP3ODUkaDSEn/8i3KVPI2u0nMWZnAns+O
|
||||
eFVs7X1+g7hZLH34GoHwffUn8tuu1DFUOP5Hsu4WIA/x2y0ov2846xG7mtSyWjpK
|
||||
fwIDAQABoxwwGjAYBgNVHREEETAPggdoZWxtLnNohwR/AAABMA0GCSqGSIb3DQEB
|
||||
CwUAA4IBAQBOFyc9Nk5sK/fUKDN+BSZ6QqAsRFcEoN7fQPuvcCfmVSDx+MBQY6u4
|
||||
8TFdHvTKjWUL1F5bdy8qr3RfGC2SKX8tl/vsquMe27ONAaqCGvYoqLPuFZ+a9XY3
|
||||
MPI7OBOy1BSUxjj6+W6U6B8RC7BpGrP58Se00vVkVHyP54Mx9g2nDg5m2DMv4KGT
|
||||
VpJYv1DaVo7bQiL1DG/4TO/1fC2muGDku9+jbMJrmQvTCq189HRymlJegdmiot1o
|
||||
OPu3VH/2qu5T3j06DoZTra9y2/trGM6s5GRwE2javuFrRt2gcpabP7rPEW6YAwpp
|
||||
g543Jck2uWhPc8rGMly+RmS7qMxxJY++
|
||||
CwUAA4IBAQDZlTuYAWzLopLY96dSLADBBM3vG9j6cXEpfR0pQuoDzhXG1e4tJVF+
|
||||
lotELtkZG5WmnJJSK4jYdm4bhzaOOrHGqqR6TqmLjcA8d5WB25pQ9PvMYiE2NpE7
|
||||
bG43qPrMIVb0MW8HKykOGgZsEIf6bL7hKYy5hLLqTQfoK//2JOamlXLH2AJTwsBo
|
||||
0/zpcqXabDlaaxdxhkCWrJTdIUWeqoWKc0yMPw0r0IsE72G7jgZrhkYwo2RrlwGL
|
||||
RlZ9QjP14Or9gLSKUKggLPmtYQXa/7m12pzWDkdEDJqPEeBm+HYMD0OZa69EPFzL
|
||||
MJhqJPfqI9vPIzXdbC6dCrCCd7jckF94
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
|||
36
testdata/rootca.crt
vendored
36
testdata/rootca.crt
vendored
|
|
@ -1,19 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDITCCAgkCFAasUT/De3J4aee7b1VEESf+3ndyMA0GCSqGSIb3DQEBCwUAME0x
|
||||
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEQMA4GA1UEBwwHQm91bGRlcjENMAsG
|
||||
A1UECgwESGVsbTEQMA4GA1UEAwwHaGVsbS5zaDAeFw0xOTExMDEyMjM2MzZaFw0y
|
||||
MjA4MjEyMjM2MzZaME0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEQMA4GA1UE
|
||||
BwwHQm91bGRlcjENMAsGA1UECgwESGVsbTEQMA4GA1UEAwwHaGVsbS5zaDCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMinBcDJwiG3OVb1bCWQqTAOS3s6
|
||||
QwWkEXkoYyFFpCNvqEzQPtp+OkfD6gczc0ByGQibDLBApEQhq17inqtAxIUrTgXP
|
||||
ym3l+0/U7ejuTka3ue84slkw2lVobfVEvJWGro+93GzbxvVNNYGJcD2BKJqmCCxD
|
||||
I6tdTEL855kzgQUAvGITzDUxABU9+f06CW/9AlZlmBIuwrzRVjFNjflBrcm1PIUG
|
||||
upMCu8zaWat8o1TnLCDKizw1JJzCgCnMxGXfzeAd1MGUG/rOFkBImHf39Jakp/7L
|
||||
Icq+2FDE+0vNai0lpUpxPVTp8dcug8U3//bL3q0OqROA7Ks4wc0URGH71W8CAwEA
|
||||
ATANBgkqhkiG9w0BAQsFAAOCAQEAMJqzeg6cBbUkrh9a6+qa66IFR1Mf3wVB1c61
|
||||
JN6Z70kjgSdOZ/NexxxSu347fIPyKGkmokbnE1MJVEETPmzhpuTkQDcq7KT4IcQF
|
||||
S+H4l0lNn09thIlIiAJmpQrNOlrHVtpLCFB4+YnsqqFKPlcO/dGy9U26L4xfn6+n
|
||||
24/o7pNEu44GnktXPjfcbajaPUSKHxeYibjdftoUEYX/79ROu7E1QnNXj7mXymw0
|
||||
rqOgIlyCUGw8WvRR8RzR6m+1lnwOc+nxFKXzTt0LqOQt9sHI1V71WrxgDE+Lck+W
|
||||
fybfsgodM2Y7VXnH4A4xoKeOHxW1YcqIKt0ribt8602lD1pYBg==
|
||||
MIIDezCCAmOgAwIBAgIUQTwAoToO0ZxUZZCSWuJI4/ROB+4wDQYJKoZIhvcNAQEL
|
||||
BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy
|
||||
MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTIyMDgyNDE4MDYx
|
||||
MVoXDTI4MDQwMjE4MDYxMVowTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAw
|
||||
DgYDVQQHDAdCb3VsZGVyMQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNo
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Z4zHBdV+ID8PdPYRpZp
|
||||
I8QXhDiMV/kgUSWTqfWMxW9n9X7Tg2jTnypKqX3aIxiHBi3+/VryWRTosZReZI6t
|
||||
Xv1iuIDbyJuoWskZlZowwsRNA6n7IBFVmUZvRWJk3ThOgXRcOetojH9HG3LnRjtf
|
||||
HPqmBxq3ZAwDjYw3YzbN3UO2CkXjIc8eEXo/UaUtPFWCuwJNSKAgYTS12Rr1/Ydx
|
||||
9q9u5+fKZoS9WWdRhxu3sHRshs9ekkr1vIhaS06n7YCAO6TCngo+UDi+JG53kqEc
|
||||
LV9R31sbc3618QLZTSa6NKMzdu/bnZ15ID0c2HNSUTHExa8XE85mEc87HgMKoZy2
|
||||
hQIDAQABo1MwUTAdBgNVHQ4EFgQUicAFxDIXaZuRdpc3D265zOceBDQwHwYDVR0j
|
||||
BBgwFoAUicAFxDIXaZuRdpc3D265zOceBDQwDwYDVR0TAQH/BAUwAwEB/zANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAyIndA2vsHWhn+PqxAnaCai0xAJ6awye7CAWKsLmT3rC2
|
||||
zR+EI5dCJgPJ0zrltQyngWz1IgUGoC4klgj/37lY5cG8/HYBJ37IAPya+pVukQuL
|
||||
qqe2RCWqi4XZUPFRHjbJbHoM3AMsFeYOWJy+bTCMKyyYqUO0S7OM77ID9k7gcJFj
|
||||
TZ6fvWvRqWFQCLJpQh95kt5wOkAKyttPf5Qkh37fLHtyrwkpbJCj+Yv3kcdKBYpw
|
||||
kYLbK6DqqbgIKJHRbpu5xGOhKZ0/jnHJRvGAE6g6OKOXJQ/ydIZauoXKQ7hpcV43
|
||||
UAIXGjdbKVoPyLNgMueviW8+64GKqllWONPbBai5jQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
|||
50
testdata/rootca.key
vendored
50
testdata/rootca.key
vendored
|
|
@ -1,27 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAyKcFwMnCIbc5VvVsJZCpMA5LezpDBaQReShjIUWkI2+oTNA+
|
||||
2n46R8PqBzNzQHIZCJsMsECkRCGrXuKeq0DEhStOBc/KbeX7T9Tt6O5ORre57ziy
|
||||
WTDaVWht9US8lYauj73cbNvG9U01gYlwPYEomqYILEMjq11MQvznmTOBBQC8YhPM
|
||||
NTEAFT35/ToJb/0CVmWYEi7CvNFWMU2N+UGtybU8hQa6kwK7zNpZq3yjVOcsIMqL
|
||||
PDUknMKAKczEZd/N4B3UwZQb+s4WQEiYd/f0lqSn/sshyr7YUMT7S81qLSWlSnE9
|
||||
VOnx1y6DxTf/9sverQ6pE4DsqzjBzRREYfvVbwIDAQABAoIBAHwyTbBP8baWx4oY
|
||||
rNDvoplZL8VdgaCbNimNIxa0GW3Jrh2lhFIPcZl8HX5JjVvlg7M87XSm/kYhpQY9
|
||||
NUMA+uMGs+uK+1xcztpSDNRxtMe27wKwUEw+ndXhprX6ztOqop/cP/StcI/jM2wz
|
||||
muKm8HAQttxWzlxCinKoQd4k8AYcnqc728FSODP7EsdDgiU6BhBZDqjgmqggye0y
|
||||
niog+JBPDgwTgGodJWtSYuP/G2iJDUvm7bGU2gftXTJstrATLftGKX8XOgJMmDx9
|
||||
8OgDtU21LzggarOQ/iwUKX2MEfYnP8kgGLgu5nNonJCHWYGeCZoxIn70rs3WoBsU
|
||||
5+FzmHkCgYEA7MFYixlTSxXfen1MwctuZ9YiwoneSLfjmBb+LP0Pfa2r0CVMPaXM
|
||||
OexroIY14h64nunb7y3YifGk01RXzCBpEF5KhsZuYXAl3lGxbjbTjncU5/11Dim+
|
||||
W9g+T4zDimlK2tuweAjMfWz6XG2inZ3xvK73mGkEsUnqhWQKXBRf7VsCgYEA2PZp
|
||||
KAwbpRFSYFwcZoRm81fLijZ5NbmOJtND6oG1LZVaVSYuvljvjQzeVfL4+Iju6FzT
|
||||
zbnEfVsatu0cTs6jMy0yJUl6wRbHlH/G6Ra8UxSvUUEFe1Xap33RmjkK+atzALQi
|
||||
pZPCIfLr+f9qQWrPMdZwzRnws0u2pKepSdXR0H0CgYB9chDdWyTkIwnPmDakdIri
|
||||
X/b5Bx4Nf8oLGxvAcLHVkMD5v9l+zKvCgT+hxZslXcvK//S17Z/Pr4b7JrSChyXE
|
||||
M4HfmaKA5HBcNQMDd+9ujDA6n/R29a1UcubJNbeiThoIjuEZKOhZCPY7JShFxZuB
|
||||
s1+jlPmUiqrF1PUcRvtxAwKBgQDGpuelmWB+hRutyujeHQC+cnaU+EeHH3y+o9Wd
|
||||
lGG1ePia2jkWZAwCU/QHMk8wEQDelJAB38O/G3mcYAH5Tk4zf4BYj6zrutXGbDBO
|
||||
H1kToO7dMPG5+eQYU6Vk1jHsZEUKMeU/QckQmIHkBy7c8tT/Rt9FjCjNodd7b2Ab
|
||||
kMFpaQKBgQDggmgsPFSZmo+yYDZucueXqfc8cbSWd9K1UruKMaPOsyoUWJNYARHA
|
||||
cpHTpaIjDth8MUp2zLIZnPUSDkSgEAOcRH4C5CxmgSkmeJdlEEzWMF2yugczlYGO
|
||||
l9SOX07w4/WJCZFeRWTqRGWs7X6iL8um0P9yFelw3SZt33ON+1fRPg==
|
||||
MIIEogIBAAKCAQEA4Z4zHBdV+ID8PdPYRpZpI8QXhDiMV/kgUSWTqfWMxW9n9X7T
|
||||
g2jTnypKqX3aIxiHBi3+/VryWRTosZReZI6tXv1iuIDbyJuoWskZlZowwsRNA6n7
|
||||
IBFVmUZvRWJk3ThOgXRcOetojH9HG3LnRjtfHPqmBxq3ZAwDjYw3YzbN3UO2CkXj
|
||||
Ic8eEXo/UaUtPFWCuwJNSKAgYTS12Rr1/Ydx9q9u5+fKZoS9WWdRhxu3sHRshs9e
|
||||
kkr1vIhaS06n7YCAO6TCngo+UDi+JG53kqEcLV9R31sbc3618QLZTSa6NKMzdu/b
|
||||
nZ15ID0c2HNSUTHExa8XE85mEc87HgMKoZy2hQIDAQABAoIBACFgRNFQBnDHrAj9
|
||||
cM4obA9Vb+EoeGJ/QS+f7nNDFvsSGv/vLh0PgdbW68qdCosMktTwMvuJ27Yf6Lh0
|
||||
aW5YyP73XwZKUbkghcxAWZ+O+s2lOntjRvocdlxBVi6eeqtbLAnsi8QptgKqxXsj
|
||||
CWGTYOOplKwSYLTVLiVfa8YqklO77HHKQCMpCU7KsDbNpvhpme345nrAkAGX4Sd+
|
||||
STNTM3jdmyzC4jFycMz2eaSbJZjFefn9OkiAL+RNlm4dFo/l9sJIAaIZ5gPV3Jzl
|
||||
+uDRFO0eW5oE/mHmfS450yOMPwl/mf4GxRbq2JNTBFSroYaz+n/p3Ii+3U5oWmi3
|
||||
D9C/EkECgYEA9CiCM5Vc5yPyq4UWjxRD6vedv0Ihur7x7bo1zxTdMBc6feRnJFp2
|
||||
HTz33gTY+mhyjstVshj+58rmIR7Ns0bLBJ5v0GyorxhnqhgfsWn9fiKR0lb79DpS
|
||||
0APrnMdsz0/5NbK45b7qui6p4aDfRxr+EsUlwTUfbEjISn9/YgBk+rECgYEA7I9+
|
||||
S1sXBkRuBEyga8X77m/ZyF0ucqyJGxpXfsvR3udgWB3uyV5mEs4pnpLm0SPowuRl
|
||||
8RUGBF9IUfMwvqcQkGN9qy+f0fpSZmLm0nFOyKD2aE/7A3JlMhY0KsSj2odUotzU
|
||||
rTXqtlS87zsQl7t028B3r1Cw+y10qLcw3Se0BhUCgYAP5oN0MIn4U5L+MJCjiMJT
|
||||
jwSq6/eeXckLnlDax5UQCLM6d6Fv8KQ4izvpLY+j3yF2wy81hgMzvTb3eTYUMswN
|
||||
5POLM0hY/tHhdei6eRiVGlM8y4VlBldWTKsPbr1bUu373UPFUoWe0mMl2oAv9UYO
|
||||
muA2kOsW9jZ1A5CcJUJuQQKBgDEnuASMjwI8Yef+zC7Y2vq2vzhFNIubknnRRXER
|
||||
hTCeP4TP43hwZyFtOXS77b5zicBFmXE4/yEVc3+j2vMi3+xA4DIcGUeWjly8HF6K
|
||||
MOa7m7gdNnmG4cRAnOJuLeYQzONyo7bCR11PylqjmVUOHMA1BCmnyL7IuT79oeey
|
||||
glPpAoGAICOwp+bh1nqPt+nINO1q/zCCdl9hVakGVkQkuCiDK8wLW3R/vNrBtTf+
|
||||
PDM87BasvZkzA2VBcTgtDCcnP/aNDLyy2FDKIUyVtcpfheHgxjlT1txGHBUXJf6z
|
||||
rS1fGWIYbpMb3RSCtGJTa1hyDJdN424nYUD3phL4SPx2Cn5eAPs=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
|
|||
Loading…
Reference in a new issue