diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8028dd01..308154af5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -232,8 +232,9 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan 3. Assigning reviews - Once a review has the `awaiting review` label, maintainers will review them as schedule permits. The maintainer who takes the issue should self-request a review. - - Any PR with the `size/large` label requires 2 review approvals from maintainers before it can - be merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers. + - PRs from a community member with the label `size/S` or larger requires 2 review approvals from + maintainers before it can be merged. Those with `size/XS` are per the judgement of the + maintainers. For more detail see the [Size Labels](#size-labels) section. 4. Reviewing/Discussion - All reviews will be completed using Github review tool. - A "Comment" review should be used when there are questions about the code that should be @@ -313,15 +314,16 @@ makes 30 lines of changes in 1 file, but it changes key functionality, it will l feature, but requires another 150 lines of tests to cover all cases, could be labeled as `size/S` even though the number of lines is greater than defined below. -PRs submitted by a core maintainer, regardless of size, only requires approval from one additional -maintainer. This ensures there are at least two maintainers who are aware of any significant PRs -introduced to the codebase. +Any changes from the community labeled as `size/S` or larger should be thoroughly tested before +merging and always requires approval from 2 core maintainers. PRs submitted by a core maintainer, +regardless of size, only requires approval from one additional maintainer. This ensures there are at +least two maintainers who are aware of any significant PRs introduced to the codebase. | Label | Description | | ----- | ----------- | | `size/XS` | Denotes a PR that changes 0-9 lines, ignoring generated files. Very little testing may be required depending on the change. | | `size/S` | Denotes a PR that changes 10-29 lines, ignoring generated files. Only small amounts of manual testing may be required. | | `size/M` | Denotes a PR that changes 30-99 lines, ignoring generated files. Manual validation should be required. | -| `size/L` | Denotes a PR that changes 100-499 lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. | -| `size/XL` | Denotes a PR that changes 500-999 lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. | -| `size/XXL` | Denotes a PR that changes 1000+ lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. | +| `size/L` | Denotes a PR that changes 100-499 lines, ignoring generated files. | +| `size/XL` | Denotes a PR that changes 500-999 lines, ignoring generated files. | +| `size/XXL` | Denotes a PR that changes 1000+ lines, ignoring generated files. | diff --git a/Makefile b/Makefile index 97f99fd86..79fc31976 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ BINDIR := $(CURDIR)/bin +INSTALL_PATH ?= /usr/local/bin DIST_DIRS := find * -type d -exec TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum @@ -49,6 +50,7 @@ endif LDFLAGS += -X helm.sh/helm/v3/internal/version.metadata=${VERSION_METADATA} LDFLAGS += -X helm.sh/helm/v3/internal/version.gitCommit=${GIT_COMMIT} LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY} +LDFLAGS += $(EXT_LDFLAGS) .PHONY: all all: build @@ -62,6 +64,13 @@ build: $(BINDIR)/$(BINNAME) $(BINDIR)/$(BINNAME): $(SRC) GO111MODULE=on go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm +# ------------------------------------------------------------------------------ +# install + +.PHONY: install +install: build + @install "$(BINDIR)/$(BINNAME)" "$(INSTALL_PATH)/$(BINNAME)" + # ------------------------------------------------------------------------------ # test @@ -156,7 +165,7 @@ fetch-dist: .PHONY: sign sign: - for f in _dist/*.{gz,zip,sha256,sha256sum} ; do \ + for f in $$(ls _dist/*.{gz,zip,sha256,sha256sum} 2>/dev/null) ; do \ gpg --armor --detach-sign $${f} ; \ done @@ -169,7 +178,7 @@ sign: # removed in Helm v4. .PHONY: checksum checksum: - for f in _dist/*.{gz,zip} ; do \ + for f in $$(ls _dist/*.{gz,zip} 2>/dev/null) ; do \ shasum -a 256 "$${f}" | sed 's/_dist\///' > "$${f}.sha256sum" ; \ shasum -a 256 "$${f}" | awk '{print $$1}' > "$${f}.sha256" ; \ done diff --git a/cmd/helm/create.go b/cmd/helm/create.go index 21a7e026c..fe5cc540a 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -107,6 +107,7 @@ func (o *createOptions) run(out io.Writer) error { return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter) } + chartutil.Stderr = out _, err := chartutil.Create(chartname, filepath.Dir(o.name)) return err } diff --git a/cmd/helm/dependency_build_test.go b/cmd/helm/dependency_build_test.go index eeca12fa6..d6dfdabcb 100644 --- a/cmd/helm/dependency_build_test.go +++ b/cmd/helm/dependency_build_test.go @@ -28,7 +28,7 @@ import ( ) func TestDependencyBuildCmd(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") defer srv.Stop() if err != nil { t.Fatal(err) diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go index 1f9d55867..bf27c7b6c 100644 --- a/cmd/helm/dependency_update_test.go +++ b/cmd/helm/dependency_update_test.go @@ -33,7 +33,7 @@ import ( ) func TestDependencyUpdateCmd(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") if err != nil { t.Fatal(err) } @@ -121,7 +121,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { defer resetEnv()() defer ensure.HelmHome(t)() - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") if err != nil { t.Fatal(err) } diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 544fb7608..75b9056f0 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "flag" "fmt" "log" "path/filepath" @@ -24,6 +25,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "k8s.io/klog" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" @@ -69,7 +71,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { formatNames = append(formatNames, format) } } - return formatNames, cobra.ShellCompDirectiveDefault + return formatNames, cobra.ShellCompDirectiveNoFileComp }) if err != nil { @@ -155,3 +157,24 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC return versions, cobra.ShellCompDirectiveNoFileComp } + +// addKlogFlags adds flags from k8s.io/klog +// marks the flags as hidden to avoid polluting the help text +func addKlogFlags(fs *pflag.FlagSet) { + local := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(local) + local.VisitAll(func(fl *flag.Flag) { + fl.Name = normalize(fl.Name) + if fs.Lookup(fl.Name) != nil { + return + } + newflag := pflag.PFlagFromGoFlag(fl) + newflag.Hidden = true + fs.AddFlag(newflag) + }) +} + +// normalize replaces underscores with hyphens +func normalize(s string) string { + return strings.ReplaceAll(s, "_", "-") +} diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 98cb00f43..88a5ddcb9 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -17,7 +17,6 @@ limitations under the License. package main // import "helm.sh/helm/v3/cmd/helm" import ( - "flag" "fmt" "io/ioutil" "log" @@ -25,8 +24,6 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "k8s.io/klog" "sigs.k8s.io/yaml" // Import to initialize client auth plugins. @@ -61,17 +58,7 @@ func warning(format string, v ...interface{}) { fmt.Fprintf(os.Stderr, format, v...) } -func initKubeLogs() { - pflag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc) - gofs := flag.NewFlagSet("klog", flag.ExitOnError) - klog.InitFlags(gofs) - pflag.CommandLine.AddGoFlagSet(gofs) - pflag.CommandLine.Set("logtostderr", "true") -} - func main() { - initKubeLogs() - actionConfig := new(action.Configuration) cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) if err != nil { @@ -101,11 +88,6 @@ func main() { } } -// wordSepNormalizeFunc changes all flags that contain "_" separators -func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { - return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-")) -} - func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error { return func(_ *cobra.Command, _ []string) error { if !FeatureGateOCI.IsEnabled() { diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index a6e0c4eae..83590210a 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -59,7 +59,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { found, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { - fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) + fmt.Fprintf(os.Stderr, "failed to load plugins: %s\n", err) return } @@ -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", "--registry-config", "--repository-cache", "--repository-config"} + kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--registry-config", "--repository-cache", "--repository-config"} knownArg := func(a string) bool { for _, pre := range kvargs { if strings.HasPrefix(a, pre+"=") { diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 00fe0ef11..7134a8784 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -114,6 +114,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package") f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring") + f.StringVar(&client.PassphraseFile, "passphrase-file", "", `location of a file which contains the passphrase for the signing key. Use "-" in order to read from stdin.`) f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version") f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") diff --git a/cmd/helm/plugin_install.go b/cmd/helm/plugin_install.go index 183d3dc57..4e8ee327b 100644 --- a/cmd/helm/plugin_install.go +++ b/cmd/helm/plugin_install.go @@ -19,6 +19,7 @@ import ( "fmt" "io" + "github.com/pkg/errors" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" @@ -81,7 +82,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { debug("loading plugin from %s", i.Path()) p, err := plugin.LoadDir(i.Path()) if err != nil { - return err + return errors.Wrap(err, "plugin is installed but unusable") } if err := runHook(p, plugin.Install); err != nil { diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index cf21d8460..0bf867f2a 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -37,6 +37,9 @@ func TestManuallyProcessArgs(t *testing.T) { "--kubeconfig", "/home/foo", "--kube-context=test1", "--kube-context", "test1", + "--kube-as-user", "pikachu", + "--kube-as-group", "teatime", + "--kube-as-group", "admins", "-n=test2", "-n", "test2", "--namespace=test2", @@ -51,6 +54,9 @@ func TestManuallyProcessArgs(t *testing.T) { "--kubeconfig", "/home/foo", "--kube-context=test1", "--kube-context", "test1", + "--kube-as-user", "pikachu", + "--kube-as-group", "teatime", + "--kube-as-group", "admins", "-n=test2", "-n", "test2", "--namespace=test2", diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 3f769a1bc..1d439e873 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -26,7 +26,7 @@ import ( ) func TestPullCmd(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") if err != nil { t.Fatal(err) } diff --git a/cmd/helm/registry_login.go b/cmd/helm/registry_login.go index e3435bf9d..43228f90a 100644 --- a/cmd/helm/registry_login.go +++ b/cmd/helm/registry_login.go @@ -104,7 +104,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd } } } else { - fmt.Fprintln(os.Stderr, "WARNING! Using --password via the CLI is insecure. Use --password-stdin.") + warning("Using --password via the CLI is insecure. Use --password-stdin.") } return username, password, nil diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 3eeb342f5..f79c213c0 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -38,11 +38,11 @@ import ( ) type repoAddOptions struct { - name string - url string - username string - password string - noUpdate bool + name string + url string + username string + password string + forceUpdate bool certFile string keyFile string @@ -51,6 +51,9 @@ type repoAddOptions struct { repoFile string repoCache string + + // Deprecated, but cannot be removed until Helm 4 + deprecatedNoUpdate bool } func newRepoAddCmd(out io.Writer) *cobra.Command { @@ -74,7 +77,8 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { f := cmd.Flags() f.StringVar(&o.username, "username", "", "chart repository username") f.StringVar(&o.password, "password", "", "chart repository password") - f.BoolVar(&o.noUpdate, "no-update", false, "raise error if repo is already registered") + f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists") + f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.") f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") @@ -112,10 +116,6 @@ func (o *repoAddOptions) run(out io.Writer) error { return err } - if o.noUpdate && f.Has(o.name) { - return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name) - } - if o.username != "" && o.password == "" { fd := int(os.Stdin.Fd()) fmt.Fprint(out, "Password: ") @@ -138,6 +138,23 @@ func (o *repoAddOptions) run(out io.Writer) error { InsecureSkipTLSverify: o.insecureSkipTLSverify, } + // If the repo exists do one of two things: + // 1. If the configuration for the name is the same continue without error + // 2. When the config is different require --force-update + if !o.forceUpdate && f.Has(o.name) { + existing := f.Get(o.name) + if c != *existing { + + // The input coming in for the name is different from what is already + // configured. Return an error. + return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name) + } + + // The add is idempotent so do nothing + fmt.Fprintf(out, "%q already exists with the same configuration, skipping\n", o.name) + return nil + } + r, err := repo.NewChartRepository(&c, getter.All(settings)) if err != nil { return err diff --git a/cmd/helm/repo_add_test.go b/cmd/helm/repo_add_test.go index 9ef64390b..f3bc54985 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -34,26 +34,50 @@ import ( ) func TestRepoAddCmd(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testserver/*.*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") if err != nil { t.Fatal(err) } defer srv.Stop() + // A second test server is setup to verify URL changing + srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") + if err != nil { + t.Fatal(err) + } + defer srv2.Stop() + tmpdir := ensure.TempDir(t) repoFile := filepath.Join(tmpdir, "repositories.yaml") - tests := []cmdTestCase{{ - name: "add a repository", - cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir), - golden: "output/repo-add.txt", - }} + tests := []cmdTestCase{ + { + name: "add a repository", + cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir), + golden: "output/repo-add.txt", + }, + { + name: "add repository second time", + cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir), + golden: "output/repo-add2.txt", + }, + { + name: "add repository different url", + cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv2.URL(), repoFile, tmpdir), + wantError: true, + }, + { + name: "add repository second time", + cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s --force-update", srv2.URL(), repoFile, tmpdir), + golden: "output/repo-add.txt", + }, + } runTestCmd(t, tests) } func TestRepoAdd(t *testing.T) { - ts, err := repotest.NewTempServer("testdata/testserver/*.*") + ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") if err != nil { t.Fatal(err) } @@ -65,10 +89,11 @@ func TestRepoAdd(t *testing.T) { const testRepoName = "test-name" o := &repoAddOptions{ - name: testRepoName, - url: ts.URL(), - noUpdate: true, - repoFile: repoFile, + name: testRepoName, + url: ts.URL(), + forceUpdate: false, + deprecatedNoUpdate: true, + repoFile: repoFile, } os.Setenv(xdg.CacheHomeEnvVar, rootDir) @@ -94,7 +119,7 @@ func TestRepoAdd(t *testing.T) { t.Errorf("Error cache charts file was not created for repository %s", testRepoName) } - o.noUpdate = false + o.forceUpdate = true if err := o.run(ioutil.Discard); err != nil { t.Errorf("Repository was not updated: %s", err) @@ -118,7 +143,7 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) { } func repoAddConcurrent(t *testing.T, testName, repoFile string) { - ts, err := repotest.NewTempServer("testdata/testserver/*.*") + ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") if err != nil { t.Fatal(err) } @@ -130,10 +155,11 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { go func(name string) { defer wg.Done() o := &repoAddOptions{ - name: name, - url: ts.URL(), - noUpdate: true, - repoFile: repoFile, + name: name, + url: ts.URL(), + deprecatedNoUpdate: true, + forceUpdate: false, + repoFile: repoFile, } if err := o.run(ioutil.Discard); err != nil { t.Error(err) diff --git a/cmd/helm/repo_remove_test.go b/cmd/helm/repo_remove_test.go index 0ea1d63d2..cb5c6e9ab 100644 --- a/cmd/helm/repo_remove_test.go +++ b/cmd/helm/repo_remove_test.go @@ -30,7 +30,7 @@ import ( ) func TestRepoRemove(t *testing.T) { - ts, err := repotest.NewTempServer("testdata/testserver/*.*") + ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") if err != nil { t.Fatal(err) } diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index e5e4eb337..4b16a1ea7 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -75,7 +75,7 @@ func TestUpdateCharts(t *testing.T) { defer resetEnv()() defer ensure.HelmHome(t)() - ts, err := repotest.NewTempServer("testdata/testserver/*.*") + ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") if err != nil { t.Fatal(err) } diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 904f11a21..cc97a5541 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -48,11 +48,22 @@ Environment variables: | $HELM_CACHE_HOME | set an alternative location for storing cached files. | | $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | | $HELM_DATA_HOME | set an alternative location for storing Helm data. | +| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode | | $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres | | $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | | $HELM_MAX_HISTORY | set the maximum number of helm release history. | +| $HELM_NAMESPACE | set the namespace used for the helm operations. | | $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | +| $HELM_PLUGINS | set the path to the plugins directory | +| $HELM_REGISTRY_CONFIG | set the path to the registry config file. | +| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory | +| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | +| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | +| $HELM_KUBEASGROUPS | set the Username to impersonate for the operation. | +| $HELM_KUBEASUSER | set the Groups to use for impoersonation using a comma-separated list. | +| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | +| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | Helm stores cache, configuration, and data based on the following configuration order: @@ -82,6 +93,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string flags := cmd.PersistentFlags() settings.AddFlags(flags) + addKlogFlags(flags) // Setup shell completion for the namespace flag err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -193,5 +205,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string // Find and add plugins loadPlugins(cmd, out) + // Check permissions on critical files + checkPerms() + return cmd, nil } diff --git a/cmd/helm/root_unix.go b/cmd/helm/root_unix.go new file mode 100644 index 000000000..b1b0f3896 --- /dev/null +++ b/cmd/helm/root_unix.go @@ -0,0 +1,58 @@ +// +build !windows + +/* +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 main + +import ( + "os" + "os/user" + "path/filepath" +) + +func checkPerms() { + // This function MUST NOT FAIL, as it is just a check for a common permissions problem. + // If for some reason the function hits a stopping condition, it may panic. But only if + // we can be sure that it is panicing because Helm cannot proceed. + + kc := settings.KubeConfig + if kc == "" { + kc = os.Getenv("KUBECONFIG") + } + if kc == "" { + u, err := user.Current() + if err != nil { + // No idea where to find KubeConfig, so return silently. Many helm commands + // can proceed happily without a KUBECONFIG, so this is not a fatal error. + return + } + kc = filepath.Join(u.HomeDir, ".kube", "config") + } + fi, err := os.Stat(kc) + if err != nil { + // DO NOT error if no KubeConfig is found. Not all commands require one. + return + } + + perm := fi.Mode().Perm() + if perm&0040 > 0 { + warning("Kubernetes configuration file is group-readable. This is insecure. Location: %s", kc) + } + if perm&0004 > 0 { + warning("Kubernetes configuration file is world-readable. This is insecure. Location: %s", kc) + } +} diff --git a/cmd/helm/root_unix_test.go b/cmd/helm/root_unix_test.go new file mode 100644 index 000000000..89c3c1eea --- /dev/null +++ b/cmd/helm/root_unix_test.go @@ -0,0 +1,76 @@ +// +build !windows + +/* +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 main + +import ( + "bufio" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCheckPerms(t *testing.T) { + // NOTE(bacongobbler): have to open a new file handler here as the default os.Sterr cannot be read from + stderr, err := os.Open("/dev/stderr") + if err != nil { + t.Fatalf("could not open /dev/stderr for reading: %s", err) + } + defer stderr.Close() + reader := bufio.NewReader(stderr) + + tdir, err := ioutil.TempDir("", "helmtest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + tfile := filepath.Join(tdir, "testconfig") + fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440) + if err != nil { + t.Errorf("Failed to create temp file: %s", err) + } + + tconfig := settings.KubeConfig + settings.KubeConfig = tfile + defer func() { settings.KubeConfig = tconfig }() + + checkPerms() + text, err := reader.ReadString('\n') + if err != nil { + t.Fatalf("could not read from stderr: %s", err) + } + expectPrefix := "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location:" + if !strings.HasPrefix(text, expectPrefix) { + t.Errorf("Expected to get a warning for group perms. Got %q", text) + } + + if err := fh.Chmod(0404); err != nil { + t.Errorf("Could not change mode on file: %s", err) + } + checkPerms() + text, err = reader.ReadString('\n') + if err != nil { + t.Fatalf("could not read from stderr: %s", err) + } + expectPrefix = "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location:" + if !strings.HasPrefix(text, expectPrefix) { + t.Errorf("Expected to get a warning for world perms. Got %q", text) + } +} diff --git a/cmd/helm/root_windows.go b/cmd/helm/root_windows.go new file mode 100644 index 000000000..7b5000f4f --- /dev/null +++ b/cmd/helm/root_windows.go @@ -0,0 +1,22 @@ +/* +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 main + +func checkPerms() { + // Not yet implemented on Windows. If you know how to do a comprehensive perms + // check on Windows, contributions welcomed! +} diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index a7f27f179..bf82a6051 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -22,7 +22,6 @@ import ( "fmt" "io" "io/ioutil" - "os" "path/filepath" "strings" @@ -184,7 +183,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - fmt.Fprintf(os.Stderr, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n) + warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n) continue } diff --git a/cmd/helm/show_test.go b/cmd/helm/show_test.go index 2734faf5e..9781a3de4 100644 --- a/cmd/helm/show_test.go +++ b/cmd/helm/show_test.go @@ -26,7 +26,7 @@ import ( ) func TestShowPreReleaseChart(t *testing.T) { - srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*") if err != nil { t.Fatal(err) } @@ -49,6 +49,13 @@ func TestShowPreReleaseChart(t *testing.T) { fail: true, expectedErr: "failed to download \"test/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\"", + }, { name: "show pre-release chart with 'devel' flag", args: "test/pre-release-chart", diff --git a/cmd/helm/testdata/output/env-comp.txt b/cmd/helm/testdata/output/env-comp.txt index c4b46ae6b..3739d8bc1 100644 --- a/cmd/helm/testdata/output/env-comp.txt +++ b/cmd/helm/testdata/output/env-comp.txt @@ -4,6 +4,8 @@ HELM_CONFIG_HOME HELM_DATA_HOME HELM_DEBUG HELM_KUBEAPISERVER +HELM_KUBEASGROUPS +HELM_KUBEASUSER HELM_KUBECONTEXT HELM_KUBETOKEN HELM_MAX_HISTORY diff --git a/cmd/helm/testdata/output/output-comp.txt b/cmd/helm/testdata/output/output-comp.txt index be574756b..e7799a56b 100644 --- a/cmd/helm/testdata/output/output-comp.txt +++ b/cmd/helm/testdata/output/output-comp.txt @@ -1,5 +1,5 @@ table json yaml -:0 -Completion ended with directive: ShellCompDirectiveDefault +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/repo-add2.txt b/cmd/helm/testdata/output/repo-add2.txt new file mode 100644 index 000000000..263ffa9e4 --- /dev/null +++ b/cmd/helm/testdata/output/repo-add2.txt @@ -0,0 +1 @@ +"test-name" already exists with the same configuration, skipping diff --git a/go.mod b/go.mod index 428de6fa1..4a51e257a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module helm.sh/helm/v3 -go 1.13 +go 1.14 require ( github.com/BurntSushi/toml v0.3.1 @@ -17,13 +17,13 @@ require ( github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce github.com/docker/go-units v0.4.0 - github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b + github.com/evanphx/json-patch v4.9.0+incompatible github.com/gobwas/glob v0.2.3 - github.com/gofrs/flock v0.7.1 - github.com/golangci/golangci-lint v1.30.0 // indirect + github.com/gofrs/flock v0.8.0 github.com/gosuri/uitable v0.0.4 - github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 - github.com/lib/pq v1.7.0 + github.com/jessevdk/go-flags v1.4.0 // indirect + github.com/jmoiron/sqlx v1.2.0 + github.com/lib/pq v1.8.0 github.com/mattn/go-shellwords v1.0.10 github.com/mitchellh/copystructure v1.0.0 github.com/opencontainers/go-digest v1.0.0 @@ -36,13 +36,13 @@ require ( github.com/stretchr/testify v1.6.1 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - k8s.io/api v0.18.8 - k8s.io/apiextensions-apiserver v0.18.8 - k8s.io/apimachinery v0.18.8 - k8s.io/cli-runtime v0.18.8 - k8s.io/client-go v0.18.8 + k8s.io/api v0.19.2 + k8s.io/apiextensions-apiserver v0.19.2 + k8s.io/apimachinery v0.19.2 + k8s.io/cli-runtime v0.19.2 + k8s.io/client-go v0.19.2 k8s.io/klog v1.0.0 - k8s.io/kubectl v0.18.8 + k8s.io/kubectl v0.19.2 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 8a105d432..2112d1b8c 100644 --- a/go.sum +++ b/go.sum @@ -6,11 +6,11 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -21,13 +21,20 @@ github.com/Azure/go-autorest v13.3.2+incompatible h1:VxzPyuhtnlBOzc4IWCZHqpyH2d+ github.com/Azure/go-autorest v13.3.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= @@ -130,6 +137,9 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -210,6 +220,7 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arX github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -230,6 +241,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= @@ -241,7 +254,6 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -250,9 +262,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-critic/go-critic v0.5.0 h1:Ic2p5UCl5fX/2WX2w8nroPpPhxRNsNTMlJzsu/uqwnM= -github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -262,7 +272,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -352,6 +363,8 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6 github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -367,6 +380,7 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -375,11 +389,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -433,6 +449,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -443,7 +460,8 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -502,6 +520,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -527,7 +546,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -544,6 +566,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -559,6 +582,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -605,6 +630,8 @@ github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -625,6 +652,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/5 github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -731,6 +760,8 @@ github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -748,6 +779,8 @@ github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -757,6 +790,8 @@ github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQl github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8 h1:DvnesvLtRPQOvaUbfXfh0tpMHg29by0H7F2U+QIkSu8= @@ -897,9 +932,7 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= @@ -910,7 +943,9 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -944,6 +979,7 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= @@ -956,7 +992,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -966,13 +1002,13 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1000,15 +1036,17 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1030,7 +1068,6 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1051,20 +1088,24 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= @@ -1084,45 +1125,31 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305 h1:yaM5S0KcY0lIoZo7Fl+oi91b/DdlU2zuWpfHrpWbCS0= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -1133,7 +1160,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1152,8 +1179,9 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1172,8 +1200,11 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1181,7 +1212,6 @@ gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -1213,6 +1243,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1225,62 +1256,83 @@ k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/apiextensions-apiserver v0.18.4 h1:Y3HGERmS8t9u12YNUFoOISqefaoGRuTc43AYCLzWmWE= k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= k8s.io/apiextensions-apiserver v0.18.8 h1:pkqYPKTHa0/3lYwH7201RpF9eFm0lmZDFBNzhN+k/sA= k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM= +k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA= +k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg= k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA= k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= +k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= k8s.io/cli-runtime v0.18.4 h1:IUx7quIOb4gbQ4M+B1ksF/PTBovQuL5tXWzplX3t+FM= k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g= k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k= k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M= +k8s.io/cli-runtime v0.19.2 h1:d4uOtKhy3ImdaKqZJ8yQgLrdtUwsJLfP4Dw7L/kVPOo= +k8s.io/cli-runtime v0.19.2/go.mod h1:CMynmJM4Yf02TlkbhKxoSzi4Zf518PukJ5xep/NaNeY= k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc= k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= +k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/component-base v0.18.4 h1:Kr53Fp1iCGNsl9Uv4VcRvLy7YyIqi9oaJOQ7SXtKI98= k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8= k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= +k8s.io/component-base v0.19.2 h1:jW5Y9RcZTb79liEhW3XDVTW7MuvEGP0tQZnfSX6/+gs= +k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kubectl v0.18.4 h1:l9DUYPTEMs1+qNtoqPpTyaJOosvj7l7tQqphCO1K52s= k8s.io/kubectl v0.18.4/go.mod h1:EzB+nfeUWk6fm6giXQ8P4Fayw3dsN+M7Wjy23mTRtB0= k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA= k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM= +k8s.io/kubectl v0.19.2 h1:/Dxz9u7S0GnchLA6Avqi5k1qhZH4Fusgecj8dHsSnbk= +k8s.io/kubectl v0.19.2/go.mod h1:4ib3oj5ma6gF95QukTvC7ZBMxp60+UEAhDPjLuBIrV4= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/metrics v0.18.4/go.mod h1:luze4fyI9JG4eLDZy0kFdYEebqNfi0QrG4xNEbPkHOs= k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA= +k8s.io/metrics v0.19.2/go.mod h1:IlLaAGXN0q7yrtB+SV0q3JIraf6VtlDr+iuTcX21fCU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f h1:gi7cb8HTDZ6q8VqsUpkdoFi3vxwHMneQ6+Q5Ap5hjPE= -mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= diff --git a/pkg/action/action.go b/pkg/action/action.go index 071db709b..79bb4f638 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -58,14 +58,15 @@ var ( errMissingRelease = errors.New("no release provided") // errInvalidRevision indicates that an invalid release revision number was provided. errInvalidRevision = errors.New("invalid release revision") - // errInvalidName indicates that an invalid release name was provided - errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") // errPending indicates that another instance of Helm is already applying an operation on a release. errPending = errors.New("another operation (install/upgrade/rollback) is in progress") ) // ValidName is a regular expression for resource names. // +// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See +// pkg/chartutil.ValidateName for the replacement. +// // According to the Kubernetes help text, the regular expression it uses is: // // [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* @@ -294,7 +295,7 @@ func (c *Configuration) Now() time.Time { } func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) { - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name) } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 0cbdb162b..fedf260fb 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -20,6 +20,7 @@ import ( "flag" "io/ioutil" "net/http" + "os" "path/filepath" "testing" @@ -56,6 +57,8 @@ func actionConfigFixture(t *testing.T) *Configuration { t.Fatal(err) } + t.Cleanup(func() { os.RemoveAll(tdir) }) + cache, err := registry.NewCache( registry.CacheOptDebug(true), registry.CacheOptRoot(filepath.Join(tdir, registry.CacheRootDir)), @@ -316,40 +319,3 @@ func TestGetVersionSet(t *testing.T) { t.Error("Non-existent version is reported found.") } } - -// TestValidName is a regression test for ValidName -// -// Kubernetes has strict naming conventions for resource names. This test represents -// those conventions. -// -// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// NOTE: At the time of this writing, the docs above say that names cannot begin with -// digits. However, `kubectl`'s regular expression explicit allows this, and -// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. -func TestValidName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - } - for input, expectPass := range names { - if ValidName.MatchString(input) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) - } - } -} diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 4a4b8ebad..4c80d0159 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -21,6 +21,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/Masterminds/semver/v3" "github.com/gosuri/uitable" @@ -61,6 +62,7 @@ func (d *Dependency) List(chartpath string, out io.Writer) error { return nil } +// dependecyStatus returns a string describing the status of a dependency viz a viz the parent chart. func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string { filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") @@ -75,35 +77,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p case err != nil: return "bad pattern" case len(archives) > 1: - return "too many matches" - case len(archives) == 1: - archive := archives[0] - if _, err := os.Stat(archive); err == nil { - c, err := loader.Load(archive) - if err != nil { - return "corrupt" + // See if the second part is a SemVer + found := []string{} + for _, arc := range archives { + // we need to trip the prefix dirs and the extension off. + filename = strings.TrimSuffix(filepath.Base(arc), ".tgz") + maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name)) + + if _, err := semver.StrictNewVersion(maybeVersion); err == nil { + // If the version parsed without an error, it is possibly a valid + // version. + found = append(found, arc) } - if c.Name() != dep.Name { - return "misnamed" + } + + if l := len(found); l == 1 { + // If we get here, we do the same thing as in len(archives) == 1. + if r := statArchiveForStatus(found[0], dep); r != "" { + return r } - if c.Metadata.Version != dep.Version { - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return "invalid version" - } + // Fall through and look for directories + } else if l > 1 { + return "too many matches" + } - v, err := semver.NewVersion(c.Metadata.Version) - if err != nil { - return "invalid version" - } + // The sanest thing to do here is to fall through and see if we have any directory + // matches. - if !constraint.Check(v) { - return "wrong version" - } - } - return "ok" + case len(archives) == 1: + archive := archives[0] + if r := statArchiveForStatus(archive, dep); r != "" { + return r } + } // End unnecessary code. @@ -137,6 +144,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p return "unpacked" } +// stat an archive and return a message if the stat is successful +// +// This is a refactor of the code originally in dependencyStatus. It is here to +// support legacy behavior, and should be removed in Helm 4. +func statArchiveForStatus(archive string, dep *chart.Dependency) string { + if _, err := os.Stat(archive); err == nil { + c, err := loader.Load(archive) + if err != nil { + return "corrupt" + } + if c.Name() != dep.Name { + return "misnamed" + } + + if c.Metadata.Version != dep.Version { + constraint, err := semver.NewConstraint(dep.Version) + if err != nil { + return "invalid version" + } + + v, err := semver.NewVersion(c.Metadata.Version) + if err != nil { + return "invalid version" + } + + if !constraint.Check(v) { + return "wrong version" + } + } + return "ok" + } + return "" +} + // printDependencies prints all of the dependencies in the yaml file. func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) { table := uitable.New() diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go index 4f3cb69a5..b5032a377 100644 --- a/pkg/action/dependency_test.go +++ b/pkg/action/dependency_test.go @@ -18,9 +18,16 @@ package action import ( "bytes" + "io/ioutil" + "os" + "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/internal/test" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" ) func TestList(t *testing.T) { @@ -56,3 +63,99 @@ func TestList(t *testing.T) { test.AssertGoldenBytes(t, buf.Bytes(), tcase.golden) } } + +// TestDependencyStatus_Dashes is a regression test to make sure that dashes in +// chart names do not cause resolution problems. +func TestDependencyStatus_Dashes(t *testing.T) { + // Make a temp dir + dir, err := ioutil.TempDir("", "helmtest-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + chartpath := filepath.Join(dir, "charts") + if err := os.MkdirAll(chartpath, 0700); err != nil { + t.Fatal(err) + } + + // Add some fake charts + first := buildChart(withName("first-chart")) + _, err = chartutil.Save(first, chartpath) + if err != nil { + t.Fatal(err) + } + + second := buildChart(withName("first-chart-second-chart")) + _, err = chartutil.Save(second, chartpath) + if err != nil { + t.Fatal(err) + } + + dep := &chart.Dependency{ + Name: "first-chart", + Version: "0.1.0", + } + + // Now try to get the deps + stat := NewDependency().dependencyStatus(dir, dep, first) + if stat != "ok" { + t.Errorf("Unexpected status: %q", stat) + } +} + +func TestStatArchiveForStatus(t *testing.T) { + // Make a temp dir + dir, err := ioutil.TempDir("", "helmtest-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + chartpath := filepath.Join(dir, "charts") + if err := os.MkdirAll(chartpath, 0700); err != nil { + t.Fatal(err) + } + + // unsaved chart + lilith := buildChart(withName("lilith")) + + // dep referring to chart + dep := &chart.Dependency{ + Name: "lilith", + Version: "1.2.3", + } + + is := assert.New(t) + + lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz") + is.Empty(statArchiveForStatus(lilithpath, dep)) + + // save the chart (version 0.1.0, because that is the default) + where, err := chartutil.Save(lilith, chartpath) + is.NoError(err) + + // Should get "wrong version" because we asked for 1.2.3 and got 0.1.0 + is.Equal("wrong version", statArchiveForStatus(where, dep)) + + // Break version on dep + dep = &chart.Dependency{ + Name: "lilith", + Version: "1.2.3.4.5", + } + is.Equal("invalid version", statArchiveForStatus(where, dep)) + + // Break the name + dep = &chart.Dependency{ + Name: "lilith2", + Version: "1.2.3", + } + is.Equal("misnamed", statArchiveForStatus(where, dep)) + + // Now create the right version + dep = &chart.Dependency{ + Name: "lilith", + Version: "0.1.0", + } + is.Equal("ok", statArchiveForStatus(where, dep)) +} diff --git a/pkg/action/history.go b/pkg/action/history.go index a592745e9..f4043609c 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -19,6 +19,7 @@ package action import ( "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" ) @@ -45,7 +46,7 @@ func (h *History) Run(name string) ([]*release.Release, error) { return nil, err } - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, errors.Errorf("release name is invalid: %s", name) } diff --git a/pkg/action/install.go b/pkg/action/install.go index 00fb208b0..caeefca68 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -654,8 +654,8 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( dl.Verify = downloader.VerifyAlways } if c.RepoURL != "" { - chartURL, err := repo.FindChartInAuthRepoURL(c.RepoURL, c.Username, c.Password, name, version, - c.CertFile, c.KeyFile, c.CaFile, getter.All(settings)) + chartURL, err := repo.FindChartInAuthAndTLSRepoURL(c.RepoURL, c.Username, c.Password, name, version, + c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, getter.All(settings)) if err != nil { return "", err } @@ -677,5 +677,9 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( return filename, err } - return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) + atVersion := "" + if version != "" { + atVersion = fmt.Sprintf(" at version %q", version) + } + return filename, errors.Errorf("failed to download %q%s (hint: running `helm repo update` may help)", name, atVersion) } diff --git a/pkg/action/package.go b/pkg/action/package.go index 0a927cd41..8f53bcac4 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "bufio" "fmt" "io/ioutil" "os" @@ -39,6 +40,7 @@ type Package struct { Sign bool Key string Keyring string + PassphraseFile string Version string AppVersion string Destination string @@ -120,7 +122,15 @@ func (p *Package) Clearsign(filename string) error { return err } - if err := signer.DecryptKey(promptUser); err != nil { + passphraseFetcher := promptUser + if p.PassphraseFile != "" { + passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) + if err != nil { + return err + } + } + + if err := signer.DecryptKey(passphraseFetcher); err != nil { return err } @@ -141,3 +151,34 @@ func promptUser(name string) ([]byte, error) { fmt.Println() return pw, err } + +func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { + file, err := openPassphraseFile(passphraseFile, stdin) + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + passphrase, _, err := reader.ReadLine() + if err != nil { + return nil, err + } + return func(name string) ([]byte, error) { + return passphrase, nil + }, nil +} + +func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { + if passphraseFile == "-" { + stat, err := stdin.Stat() + if err != nil { + return nil, err + } + if (stat.Mode() & os.ModeNamedPipe) == 0 { + return nil, errors.New("specified reading passphrase from stdin, without input on stdin") + } + return stdin, nil + } + return os.Open(passphraseFile) +} diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 0f716118d..9a202cde4 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,8 +17,12 @@ limitations under the License. package action import ( + "io/ioutil" + "os" + "path" "testing" + "helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/pkg/chart" ) @@ -42,3 +46,57 @@ func TestSetVersion(t *testing.T) { t.Error("Expected bogus version to return an error.") } } + +func TestPassphraseFileFetcher(t *testing.T) { + secret := "secret" + directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) + defer os.RemoveAll(directory) + + fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + if err != nil { + t.Fatal("Unable to create passphraseFileFetcher", err) + } + + passphrase, err := fetcher("key") + if err != nil { + t.Fatal("Unable to fetch passphrase") + } + + if string(passphrase) != secret { + t.Errorf("Expected %s got %s", secret, string(passphrase)) + } +} + +func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { + secret := "secret" + directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) + defer os.RemoveAll(directory) + + fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + if err != nil { + t.Fatal("Unable to create passphraseFileFetcher", err) + } + + passphrase, err := fetcher("key") + if err != nil { + t.Fatal("Unable to fetch passphrase") + } + + if string(passphrase) != secret { + t.Errorf("Expected %s got %s", secret, string(passphrase)) + } +} + +func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { + directory := ensure.TempDir(t) + defer os.RemoveAll(directory) + + stdin, err := ioutil.TempFile(directory, "non-existing") + if err != nil { + t.Fatal("Unable to create test file", err) + } + + if _, err := passphraseFileFetcher("-", stdin); err == nil { + t.Error("Expected passphraseFileFetcher returning an error") + } +} diff --git a/pkg/action/pull.go b/pkg/action/pull.go index a46e98bae..220ca11b2 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -89,7 +89,7 @@ func (p *Pull) Run(chartRef string) (string, error) { } if p.RepoURL != "" { - chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, getter.All(p.Settings)) + chartURL, err := repo.FindChartInAuthAndTLSRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, getter.All(p.Settings)) if err != nil { return out.String(), err } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 795c3c747..2f6f5cfce 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -25,6 +25,7 @@ import ( "github.com/pkg/errors" v1 "k8s.io/api/core/v1" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" ) @@ -51,7 +52,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { return nil, err } - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name) } diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 8773b6271..542acefae 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -24,6 +24,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" helmtime "helm.sh/helm/v3/pkg/time" ) @@ -90,7 +91,7 @@ func (r *Rollback) Run(name string) error { // prepareRollback finds the previous release and prepares a new release object with // the previous release's configuration func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index a51a283d6..c466c6ee2 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" helmtime "helm.sh/helm/v3/pkg/time" @@ -62,7 +63,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return &release.UninstallReleaseResponse{Release: r}, nil } - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, errors.Errorf("uninstall: Release name is invalid: %s", name) } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index b707e7e69..c439af79d 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -115,7 +115,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface // the user doesn't have to specify both u.Wait = u.Wait || u.Atomic - if err := validateReleaseName(name); err != nil { + if err := chartutil.ValidateReleaseName(name); err != nil { return nil, errors.Errorf("release name is invalid: %s", name) } u.cfg.Log("preparing upgrade for %s", name) @@ -142,19 +142,6 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface return res, nil } -func validateReleaseName(releaseName string) error { - if releaseName == "" { - return errMissingRelease - } - - // Check length first, since that is a less expensive operation. - if len(releaseName) > releaseNameMaxLen || !ValidName.MatchString(releaseName) { - return errInvalidName - } - - return nil -} - // prepareUpgrade builds an upgraded release for an upgrade operation. func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) { if chart == nil { diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 0c40a9c3c..6e074f78b 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -46,7 +46,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN } helper := resource.NewHelper(info.Client, info.Mapping) - existing, err := helper.Get(info.Namespace, info.Name, info.Export) + existing, err := helper.Get(info.Namespace, info.Name) if err != nil { if apierrors.IsNotFound(err) { return nil diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index bd75375a4..a3bed63a3 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -17,6 +17,7 @@ package chart import ( "path/filepath" + "regexp" "strings" ) @@ -26,6 +27,9 @@ const APIVersionV1 = "v1" // APIVersionV2 is the API version number for version 2. const APIVersionV2 = "v2" +// aliasNameFormat defines the characters that are legal in an alias name. +var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + // Chart is a helm package that contains metadata, a default config, zero or more // optionally parameterizable templates, and zero or more charts (dependencies). type Chart struct { diff --git a/pkg/chart/errors.go b/pkg/chart/errors.go index 4cb4189e6..2fad5f370 100644 --- a/pkg/chart/errors.go +++ b/pkg/chart/errors.go @@ -15,9 +15,16 @@ limitations under the License. package chart +import "fmt" + // ValidationError represents a data validation error. type ValidationError string func (v ValidationError) Error() string { return "validation: " + string(v) } + +// ValidationErrorf takes a message and formatting options and creates a ValidationError +func ValidationErrorf(msg string, args ...interface{}) ValidationError { + return ValidationError(fmt.Sprintf(msg, args...)) +} diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go index 96a3965b9..1848eb280 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/metadata.go @@ -81,6 +81,15 @@ func (md *Metadata) Validate() error { if !isValidChartType(md.Type) { return ValidationError("chart.metadata.type must be application or library") } + + // Aliases need to be validated here to make sure that the alias name does + // not contain any illegal characters. + for _, dependency := range md.Dependencies { + if err := validateDependency(dependency); err != nil { + return err + } + } + // TODO validate valid semver here? return nil } @@ -92,3 +101,13 @@ func isValidChartType(in string) bool { } return false } + +// validateDependency checks for common problems with the dependency datastructure in +// the chart. This check must be done at load time before the dependency's charts are +// loaded. +func validateDependency(dep *Dependency) error { + if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) { + return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name) + } + return nil +} diff --git a/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go index 8b436000b..0c7b173dd 100644 --- a/pkg/chart/metadata_test.go +++ b/pkg/chart/metadata_test.go @@ -48,12 +48,60 @@ func TestValidate(t *testing.T) { &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"}, nil, }, + { + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "dependency", Alias: "legal-alias"}, + }, + }, + nil, + }, + { + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "bad", Alias: "illegal alias"}, + }, + }, + ValidationError("dependency \"bad\" has disallowed characters in the alias"), + }, } for _, tt := range tests { result := tt.md.Validate() if result != tt.err { - t.Errorf("expected %s, got %s", tt.err, result) + t.Errorf("expected '%s', got '%s'", tt.err, result) + } + } +} + +func TestValidateDependency(t *testing.T) { + dep := &Dependency{ + Name: "example", + } + for value, shouldFail := range map[string]bool{ + "abcdefghijklmenopQRSTUVWXYZ-0123456780_": false, + "-okay": false, + "_okay": false, + "- bad": true, + " bad": true, + "bad\nvalue": true, + "bad ": true, + "bad$": true, + } { + dep.Alias = value + res := validateDependency(dep) + if res != nil && !shouldFail { + t.Errorf("Failed on case %q", dep.Alias) + } else if res == nil && shouldFail { + t.Errorf("Expected failure for %q", dep.Alias) } } } diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 6e382b961..86681247e 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -18,9 +18,11 @@ package chartutil import ( "fmt" + "io" "io/ioutil" "os" "path/filepath" + "regexp" "strings" "github.com/pkg/errors" @@ -30,6 +32,12 @@ import ( "helm.sh/helm/v3/pkg/chart/loader" ) +// chartName is a regular expression for testing the supplied name of a chart. +// This regular expression is probably stricter than it needs to be. We can relax it +// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be +// problematic. +var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$") + const ( // ChartfileName is the default Chart file name. ChartfileName = "Chart.yaml" @@ -63,6 +71,10 @@ const ( TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml" ) +// maxChartNameLength is lower than the limits we know of with certain file systems, +// and with certain Kubernetes fields. +const maxChartNameLength = 250 + const sep = string(filepath.Separator) const defaultChartfile = `apiVersion: v2 @@ -468,6 +480,12 @@ spec: restartPolicy: Never ` +// Stderr is an io.Writer to which error messages can be written +// +// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward +// compatibility. +var Stderr io.Writer = os.Stderr + // CreateFrom creates a new chart, but scaffolds it from the src chart. func CreateFrom(chartfile *chart.Metadata, dest, src string) error { schart, err := loader.Load(src) @@ -522,6 +540,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { // error. In such a case, this will attempt to clean up by removing the // new chart directory. func Create(name, dir string) (string, error) { + + // Sanity-check the name of a chart so user doesn't create one that causes problems. + if err := validateChartName(name); err != nil { + return "", err + } + path, err := filepath.Abs(dir) if err != nil { return path, err @@ -601,8 +625,8 @@ func Create(name, dir string) (string, error) { for _, file := range files { if _, err := os.Stat(file.path); err == nil { - // File exists and is okay. Skip it. - continue + // There is no handle to a preferred output stream here. + fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) } if err := writeFile(file.path, file.content); err != nil { return cdir, err @@ -627,3 +651,13 @@ func writeFile(name string, content []byte) error { } return ioutil.WriteFile(name, content, 0644) } + +func validateChartName(name string) error { + if name == "" || len(name) > maxChartNameLength { + return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength) + } + if !chartName.MatchString(name) { + return fmt.Errorf("chart name must match the regular expression %q", chartName.String()) + } + return nil +} diff --git a/pkg/chartutil/create_test.go b/pkg/chartutil/create_test.go index a11c45140..9a473fc59 100644 --- a/pkg/chartutil/create_test.go +++ b/pkg/chartutil/create_test.go @@ -117,3 +117,69 @@ func TestCreateFrom(t *testing.T) { } } } + +// TestCreate_Overwrite is a regression test for making sure that files are overwritten. +func TestCreate_Overwrite(t *testing.T) { + tdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + var errlog bytes.Buffer + + if _, err := Create("foo", tdir); err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + tplname := filepath.Join(dir, "templates/hpa.yaml") + writeFile(tplname, []byte("FOO")) + + // Now re-run the create + Stderr = &errlog + if _, err := Create("foo", tdir); err != nil { + t.Fatal(err) + } + + data, err := ioutil.ReadFile(tplname) + if err != nil { + t.Fatal(err) + } + + if string(data) == "FOO" { + t.Fatal("File that should have been modified was not.") + } + + if errlog.Len() == 0 { + t.Errorf("Expected warnings about overwriting files.") + } +} + +func TestValidateChartName(t *testing.T) { + for name, shouldPass := range map[string]bool{ + "": false, + "abcdefghijklmnopqrstuvwxyz-_.": true, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": true, + "$hello": false, + "Hellô": false, + "he%%o": false, + "he\nllo": false, + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, + } { + if err := validateChartName(name); (err != nil) == shouldPass { + t.Errorf("test for %q failed", name) + } + } +} diff --git a/pkg/chartutil/validate_name.go b/pkg/chartutil/validate_name.go new file mode 100644 index 000000000..22132c80e --- /dev/null +++ b/pkg/chartutil/validate_name.go @@ -0,0 +1,99 @@ +/* +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 chartutil + +import ( + "regexp" + + "github.com/pkg/errors" +) + +// validName is a regular expression for resource names. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + +var ( + // errMissingName indicates that a release (name) was not provided. + errMissingName = errors.New("no name provided") + + // errInvalidName indicates that an invalid release name was provided + errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") + + // errInvalidKubernetesName indicates that the name does not meet the Kubernetes + // restrictions on metadata names. + errInvalidKubernetesName = errors.New("invalid metadata name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 253") +) + +const ( + // maxNameLen is the maximum length Helm allows for a release name + maxReleaseNameLen = 53 + // maxMetadataNameLen is the maximum length Kubernetes allows for any name. + maxMetadataNameLen = 253 +) + +// ValidateReleaseName performs checks for an entry for a Helm release name +// +// For Helm to allow a name, it must be below a certain character count (53) and also match +// a reguar expression. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +func ValidateReleaseName(name string) error { + // This case is preserved for backwards compatibility + if name == "" { + return errMissingName + + } + if len(name) > maxReleaseNameLen || !validName.MatchString(name) { + return errInvalidName + } + return nil +} + +// ValidateMetadataName validates the name field of a Kubernetes metadata object. +// +// Empty strings, strings longer than 253 chars, or strings that don't match the regexp +// will fail. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +func ValidateMetadataName(name string) error { + if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { + return errInvalidKubernetesName + } + return nil +} diff --git a/pkg/chartutil/validate_name_test.go b/pkg/chartutil/validate_name_test.go new file mode 100644 index 000000000..5f0792f94 --- /dev/null +++ b/pkg/chartutil/validate_name_test.go @@ -0,0 +1,91 @@ +/* +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 chartutil + +import "testing" + +// TestValidateName is a regression test for ValidateName +// +// Kubernetes has strict naming conventions for resource names. This test represents +// those conventions. +// +// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +// +// NOTE: At the time of this writing, the docs above say that names cannot begin with +// digits. However, `kubectl`'s regular expression explicit allows this, and +// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. +func TestValidateReleaseName(t *testing.T) { + names := map[string]bool{ + "": false, + "foo": true, + "foo.bar1234baz.seventyone": true, + "FOO": false, + "123baz": true, + "foo.BAR.baz": false, + "one-two": true, + "-two": false, + "one_two": false, + "a..b": false, + "%^&#$%*@^*@&#^": false, + "example:com": false, + "example%%com": false, + "a1111111111111111111111111111111111111111111111111111111111z": false, + } + for input, expectPass := range names { + if err := ValidateReleaseName(input); (err == nil) != expectPass { + st := "fail" + if expectPass { + st = "succeed" + } + t.Errorf("Expected %q to %s", input, st) + } + } +} + +func TestValidateMetadataName(t *testing.T) { + names := map[string]bool{ + "": false, + "foo": true, + "foo.bar1234baz.seventyone": true, + "FOO": false, + "123baz": true, + "foo.BAR.baz": false, + "one-two": true, + "-two": false, + "one_two": false, + "a..b": false, + "%^&#$%*@^*@&#^": false, + "example:com": false, + "example%%com": false, + "a1111111111111111111111111111111111111111111111111111111111z": true, + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z": false, + } + for input, expectPass := range names { + if err := ValidateMetadataName(input); (err == nil) != expectPass { + st := "fail" + if expectPass { + st = "succeed" + } + t.Errorf("Expected %q to %s", input, st) + } + } +} diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index a9994f03d..4f3abc08b 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -26,6 +26,7 @@ import ( "fmt" "os" "strconv" + "strings" "github.com/spf13/pflag" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -47,6 +48,10 @@ type EnvSettings struct { KubeContext string // Bearer KubeToken used for authentication KubeToken string + // Username to impersonate for the operation + KubeAsUser string + // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list + KubeAsGroups []string // Kubernetes API Server Endpoint for authentication KubeAPIServer string // Debug indicates whether or not Helm is running in Debug mode. @@ -69,6 +74,8 @@ func New() *EnvSettings { 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"), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")), @@ -79,11 +86,13 @@ func New() *EnvSettings { // bind to kubernetes config flags env.config = &genericclioptions.ConfigFlags{ - Namespace: &env.namespace, - Context: &env.KubeContext, - BearerToken: &env.KubeToken, - APIServer: &env.KubeAPIServer, - KubeConfig: &env.KubeConfig, + Namespace: &env.namespace, + Context: &env.KubeContext, + BearerToken: &env.KubeToken, + APIServer: &env.KubeAPIServer, + KubeConfig: &env.KubeConfig, + Impersonate: &env.KubeAsUser, + ImpersonateGroup: &env.KubeAsGroups, } return env } @@ -94,6 +103,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") + fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "Username to impersonate for the operation") + 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.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") @@ -120,6 +131,14 @@ func envIntOr(name string, def int) int { return ret } +func envCSV(name string) (ls []string) { + trimmed := strings.Trim(os.Getenv(name), ", ") + if trimmed != "" { + ls = strings.Split(trimmed, ",") + } + return +} + func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ "HELM_BIN": os.Args[0], @@ -137,6 +156,8 @@ func (s *EnvSettings) EnvVars() map[string]string { // 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, } if s.KubeConfig != "" { diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 3234a133b..ffdbce68b 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -18,6 +18,7 @@ package cli import ( "os" + "reflect" "strings" "testing" @@ -36,6 +37,8 @@ func TestEnvSettings(t *testing.T) { ns, kcontext string debug bool maxhistory int + kAsUser string + kAsGroups []string }{ { name: "defaults", @@ -44,25 +47,31 @@ func TestEnvSettings(t *testing.T) { }, { name: "with flags set", - args: "--debug --namespace=myns", + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters", ns: "myns", debug: true, maxhistory: defaultMaxHistory, + kAsUser: "poro", + kAsGroups: []string{"admins", "teatime", "snackeaters"}, }, { name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_MAX_HISTORY": "5"}, + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5"}, ns: "yourns", maxhistory: 5, debug: true, + kAsUser: "pikachu", + kAsGroups: []string{"operators", "snackeaters", "partyanimals"}, }, { name: "with flags and envvars set", - args: "--debug --namespace=myns", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5"}, ns: "myns", debug: true, - maxhistory: defaultMaxHistory, + maxhistory: 5, + kAsUser: "poro", + kAsGroups: []string{"admins", "teatime", "snackeaters"}, }, } @@ -92,6 +101,12 @@ func TestEnvSettings(t *testing.T) { if settings.MaxHistory != tt.maxhistory { t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) } + if tt.kAsUser != settings.KubeAsUser { + t.Errorf("expected kAsUser %q, got %q", tt.kAsUser, settings.KubeAsUser) + } + if !reflect.DeepEqual(tt.kAsGroups, settings.KubeAsGroups) { + t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kAsGroups), len(settings.KubeAsGroups)) + } }) } } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index abfb007ff..b9fd3bf87 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -71,7 +71,7 @@ func TestResolveChartRef(t *testing.T) { if tt.fail { continue } - t.Errorf("%s: failed with error %s", tt.name, err) + t.Errorf("%s: failed with error %q", tt.name, err) continue } if got := u.String(); got != tt.expect { @@ -172,7 +172,7 @@ func TestIsTar(t *testing.T) { func TestDownloadTo(t *testing.T) { // Set up a fake repo with basic auth enabled - srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") srv.Stop() if err != nil { t.Fatal(err) @@ -229,7 +229,7 @@ func TestDownloadTo(t *testing.T) { func TestDownloadTo_TLS(t *testing.T) { // Set up mock server w/ tls enabled - srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") srv.Stop() if err != nil { t.Fatal(err) @@ -285,7 +285,7 @@ func TestDownloadTo_VerifyLater(t *testing.T) { dest := ensure.TempDir(t) // Set up a fake repo - srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") if err != nil { t.Fatal(err) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index bcd5dcec4..0efac37bc 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -667,7 +667,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* if err == nil { return } - err = errors.Errorf("chart %s not found in %s", name, repoURL) + err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err) return } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index e60cf7624..9532cca7c 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -183,7 +183,7 @@ func TestGetRepoNames(t *testing.T) { func TestUpdateBeforeBuild(t *testing.T) { // Set up a fake repo - srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") if err != nil { t.Fatal(err) } @@ -257,7 +257,7 @@ func TestUpdateBeforeBuild(t *testing.T) { // If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used. func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) { // Set up a fake repo - srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") if err != nil { t.Fatal(err) } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 5aa0ed8ec..155d50a38 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -40,7 +40,7 @@ type Engine struct { Strict bool // In LintMode, some 'required' template values may be missing, so don't fail LintMode bool - // the rest config to connect to te kubernetes api + // the rest config to connect to the kubernetes api config *rest.Config } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 09b0a4a79..74391668a 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -258,7 +258,7 @@ func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error) } func getResource(info *resource.Info) (runtime.Object, error) { - obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, false) + obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name) if err != nil { return nil, err } @@ -340,7 +340,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err } helper := resource.NewHelper(info.Client, info.Mapping) - if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { + if _, err := helper.Get(info.Namespace, info.Name); err != nil { if !apierrors.IsNotFound(err) { return errors.Wrap(err, "could not get information about the resource") } @@ -536,7 +536,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P // Fetch the current object for the three way merge helper := resource.NewHelper(target.Client, target.Mapping) - currentObj, err := helper.Get(target.Namespace, target.Name, target.Export) + currentObj, err := helper.Get(target.Namespace, target.Name) if err != nil && !apierrors.IsNotFound(err) { return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name) } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 568afa094..de5358aee 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -91,9 +91,12 @@ func newResponse(code int, obj runtime.Object) (*http.Response, error) { return &http.Response{StatusCode: code, Header: header, Body: body}, nil } -func newTestClient() *Client { +func newTestClient(t *testing.T) *Client { + testFactory := cmdtesting.NewTestFactory() + t.Cleanup(testFactory.Cleanup) + return &Client{ - Factory: cmdtesting.NewTestFactory().WithNamespace("default"), + Factory: testFactory.WithNamespace("default"), Log: nopLogger, } } @@ -107,7 +110,7 @@ func TestUpdate(t *testing.T) { var actions []string - c := newTestClient() + c := newTestClient(t) c.Factory.(*cmdtesting.TestFactory).UnstructuredClient = &fake.RESTClient{ NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -232,7 +235,7 @@ func TestBuild(t *testing.T) { }, } - c := newTestClient() + c := newTestClient(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test for an invalid manifest @@ -279,7 +282,7 @@ func TestPerform(t *testing.T) { return nil } - c := newTestClient() + c := newTestClient(t) infos, err := c.Build(tt.reader, false) if err != nil && err.Error() != tt.errMessage { t.Errorf("Error while building manifests: %v", err) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 73d645264..5de0819c4 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -40,14 +40,6 @@ var ( releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`) ) -// validName is a regular expression for names. -// -// This is different than action.ValidName. It conforms to the regular expression -// `kubectl` says it uses, plus it disallows empty names. -// -// For details, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - // Templates lints the templates in the Linter. func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { fpath := "templates/" @@ -199,10 +191,10 @@ func validateMetadataName(obj *K8sYamlStruct) error { } // This will return an error if the characters do not abide by the standard OR if the // name is left empty. - if validName.MatchString(obj.Metadata.Name) { - return nil + if err := chartutil.ValidateMetadataName(obj.Metadata.Name); err != nil { + return errors.Wrapf(err, "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name) } - return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name) + return nil } func validateNoCRDHooks(manifest []byte) error { diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go index 28e50b72b..bcbcbde93 100644 --- a/pkg/plugin/installer/http_installer.go +++ b/pkg/plugin/installer/http_installer.go @@ -59,6 +59,18 @@ var Extractors = map[string]Extractor{ ".tgz": &TarGzExtractor{}, } +// Convert a media type to an extractor extension. +// +// This should be refactored in Helm 4, combined with the extension-based mechanism. +func mediaTypeToExtension(mt string) (string, bool) { + switch strings.ToLower(mt) { + case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar": + return ".tgz", true + default: + return "", false + } +} + // NewExtractor creates a new extractor matching the source file name func NewExtractor(source string) (Extractor, error) { for suffix, extractor := range Extractors { diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index 3eb92ee77..e89fea29d 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -20,9 +20,13 @@ import ( "bytes" "compress/gzip" "encoding/base64" + "fmt" "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" + "strings" "syscall" "testing" @@ -63,9 +67,24 @@ func TestStripName(t *testing.T) { } } +func mockArchiveServer() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasSuffix(r.URL.Path, ".tar.gz") { + w.Header().Add("Content-Type", "text/html") + fmt.Fprintln(w, "broken") + return + } + w.Header().Add("Content-Type", "application/gzip") + fmt.Fprintln(w, "test") + })) +} + func TestHTTPInstaller(t *testing.T) { defer ensure.HelmHome(t)() - source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + + srv := mockArchiveServer() + defer srv.Close() + source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) @@ -111,7 +130,9 @@ func TestHTTPInstaller(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) { defer ensure.HelmHome(t)() - source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" + srv := mockArchiveServer() + defer srv.Close() + source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) @@ -141,7 +162,9 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { } func TestHTTPInstallerUpdate(t *testing.T) { - source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + srv := mockArchiveServer() + defer srv.Close() + source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" defer ensure.HelmHome(t)() if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { @@ -307,3 +330,26 @@ func TestCleanJoin(t *testing.T) { } } + +func TestMediaTypeToExtension(t *testing.T) { + + for mt, shouldPass := range map[string]bool{ + "": false, + "application/gzip": true, + "application/x-gzip": true, + "application/x-tgz": true, + "application/x-gtar": true, + "application/json": false, + } { + ext, ok := mediaTypeToExtension(mt) + if ok != shouldPass { + t.Errorf("Media type %q failed test", mt) + } + if shouldPass && ext == "" { + t.Errorf("Expected an extension but got empty string") + } + if !shouldPass && len(ext) != 0 { + t.Error("Expected extension to be empty for unrecognized type") + } + } +} diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 61b49ab3b..6f01494e5 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -18,6 +18,7 @@ package installer import ( "fmt" "log" + "net/http" "os" "path/filepath" "strings" @@ -89,10 +90,29 @@ func isLocalReference(source string) bool { } // isRemoteHTTPArchive checks if the source is a http/https url and is an archive +// +// It works by checking whether the source looks like a URL and, if it does, running a +// HEAD operation to see if the remote resource is a file that we understand. func isRemoteHTTPArchive(source string) bool { if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + res, err := http.Head(source) + if err != nil { + // If we get an error at the network layer, we can't install it. So + // we return false. + return false + } + + // Next, we look for the content type or content disposition headers to see + // if they have matching extractors. + contentType := res.Header.Get("content-type") + foundSuffix, ok := mediaTypeToExtension(contentType) + if !ok { + // Media type not recognized + return false + } + for suffix := range Extractors { - if strings.HasSuffix(source, suffix) { + if strings.HasSuffix(foundSuffix, suffix) { return true } } diff --git a/pkg/plugin/installer/installer_test.go b/pkg/plugin/installer/installer_test.go new file mode 100644 index 000000000..a11464924 --- /dev/null +++ b/pkg/plugin/installer/installer_test.go @@ -0,0 +1,40 @@ +/* +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 installer + +import "testing" + +func TestIsRemoteHTTPArchive(t *testing.T) { + srv := mockArchiveServer() + defer srv.Close() + source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" + + if isRemoteHTTPArchive("/not/a/URL") { + t.Errorf("Expected non-URL to return false") + } + + if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { + t.Errorf("Bad URL should not have succeeded.") + } + + if !isRemoteHTTPArchive(source) { + t.Errorf("Expected %q to be a valid archive URL", source) + } + + if isRemoteHTTPArchive(source + "-not-an-extension") { + t.Error("Expected media type match to fail") + } +} diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go index 3d9607331..96958ab09 100644 --- a/pkg/plugin/installer/local_installer_test.go +++ b/pkg/plugin/installer/local_installer_test.go @@ -37,7 +37,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "") if err != nil { t.Fatalf("unexpected error: %s", err) diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go index b8dc6b1e2..6785264b3 100644 --- a/pkg/plugin/installer/vcs_installer_test.go +++ b/pkg/plugin/installer/vcs_installer_test.go @@ -56,7 +56,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"}, diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index caa34fbd3..93b5527a1 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -20,9 +20,11 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "runtime" "strings" + "github.com/pkg/errors" "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/cli" @@ -94,6 +96,12 @@ type Metadata struct { // Downloaders field is used if the plugin supply downloader mechanism // for special protocols. Downloaders []Downloaders `json:"downloaders"` + + // UseTunnelDeprecated indicates that this command needs a tunnel. + // Setting this will cause a number of side effects, such as the + // automatic setting of HELM_HOST. + // DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4 + UseTunnelDeprecated bool `json:"useTunnel,omitempty"` } // Plugin represents a plugin. @@ -157,18 +165,51 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { return main, baseArgs, nil } +// validPluginName is a regular expression that validates plugin names. +// +// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. +var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") + +// validatePluginData validates a plugin's YAML data. +func validatePluginData(plug *Plugin, filepath string) error { + if !validPluginName.MatchString(plug.Metadata.Name) { + return fmt.Errorf("invalid plugin name at %q", filepath) + } + // We could also validate SemVer, executable, and other fields should we so choose. + return nil +} + +func detectDuplicates(plugs []*Plugin) error { + names := map[string]string{} + + for _, plug := range plugs { + if oldpath, ok := names[plug.Metadata.Name]; ok { + return fmt.Errorf( + "two plugins claim the name %q at %q and %q", + plug.Metadata.Name, + oldpath, + plug.Dir, + ) + } + names[plug.Metadata.Name] = plug.Dir + } + + return nil +} + // LoadDir loads a plugin from the given directory. func LoadDir(dirname string) (*Plugin, error) { - data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName)) + pluginfile := filepath.Join(dirname, PluginFileName) + data, err := ioutil.ReadFile(pluginfile) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile) } plug := &Plugin{Dir: dirname} - if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { - return nil, err + if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil { + return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) } - return plug, nil + return plug, validatePluginData(plug, pluginfile) } // LoadAll loads all plugins found beneath the base directory. @@ -180,7 +221,7 @@ func LoadAll(basedir string) ([]*Plugin, error) { scanpath := filepath.Join(basedir, "*", PluginFileName) matches, err := filepath.Glob(scanpath) if err != nil { - return plugins, err + return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath) } if matches == nil { @@ -195,7 +236,7 @@ func LoadAll(basedir string) ([]*Plugin, error) { } plugins = append(plugins, p) } - return plugins, nil + return plugins, detectDuplicates(plugins) } // FindPlugins returns a list of YAML files that describe plugins. diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index af0b61846..2c4478953 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -16,6 +16,7 @@ limitations under the License. package plugin // import "helm.sh/helm/v3/pkg/plugin" import ( + "fmt" "os" "path/filepath" "reflect" @@ -177,7 +178,7 @@ func TestNoMatchPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -204,8 +205,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -243,7 +251,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err) @@ -287,7 +295,7 @@ func TestFindPlugins(t *testing.T) { }, { name: "normal", - plugdirs: "./testdata/plugdir", + plugdirs: "./testdata/plugdir/good", expected: 3, }, } @@ -320,3 +328,51 @@ func TestSetupEnv(t *testing.T) { } } } + +func TestValidatePluginData(t *testing.T) { + for i, item := range []struct { + pass bool + plug *Plugin + }{ + {true, mockPlugin("abcdefghijklmnopqrstuvwxyz0123456789_-ABC")}, + {true, mockPlugin("foo-bar-FOO-BAR_1234")}, + {false, mockPlugin("foo -bar")}, + {false, mockPlugin("$foo -bar")}, // Test leading chars + {false, mockPlugin("foo -bar ")}, // Test trailing chars + {false, mockPlugin("foo\nbar")}, // Test newline + } { + err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) + if item.pass && err != nil { + t.Errorf("failed to validate case %d: %s", i, err) + } else if !item.pass && err == nil { + t.Errorf("expected case %d to fail", i) + } + } +} + +func TestDetectDuplicates(t *testing.T) { + plugs := []*Plugin{ + mockPlugin("foo"), + mockPlugin("bar"), + } + if err := detectDuplicates(plugs); err != nil { + t.Error("no duplicates in the first set") + } + plugs = append(plugs, mockPlugin("foo")) + if err := detectDuplicates(plugs); err == nil { + t.Error("duplicates in the second set") + } +} + +func mockPlugin(name string) *Plugin { + return &Plugin{ + Metadata: &Metadata{ + Name: name, + Version: "v0.1.2", + Usage: "Mock plugin", + Description: "Mock plugin for testing", + Command: "echo mock plugin", + }, + Dir: "no-such-dir", + } +} diff --git a/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml b/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml new file mode 100644 index 000000000..66498be96 --- /dev/null +++ b/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml @@ -0,0 +1,11 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different" diff --git a/pkg/plugin/testdata/plugdir/downloader/plugin.yaml b/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml similarity index 100% rename from pkg/plugin/testdata/plugdir/downloader/plugin.yaml rename to pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml diff --git a/pkg/plugin/testdata/plugdir/echo/plugin.yaml b/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml similarity index 100% rename from pkg/plugin/testdata/plugdir/echo/plugin.yaml rename to pkg/plugin/testdata/plugdir/good/echo/plugin.yaml diff --git a/pkg/plugin/testdata/plugdir/hello/hello.sh b/pkg/plugin/testdata/plugdir/good/hello/hello.sh similarity index 100% rename from pkg/plugin/testdata/plugdir/hello/hello.sh rename to pkg/plugin/testdata/plugdir/good/hello/hello.sh diff --git a/pkg/plugin/testdata/plugdir/hello/plugin.yaml b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml similarity index 85% rename from pkg/plugin/testdata/plugdir/hello/plugin.yaml rename to pkg/plugin/testdata/plugdir/good/hello/plugin.yaml index 6a78756d3..2b972da59 100644 --- a/pkg/plugin/testdata/plugdir/hello/plugin.yaml +++ b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml @@ -5,6 +5,5 @@ description: |- description command: "$HELM_PLUGIN_SELF/hello.sh" ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..." diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index c2c366a1e..92892bb85 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -205,6 +205,14 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF // without adding repo to repositories, like FindChartInRepoURL, // but it also receives credentials for the chart repository. func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { + return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters) +} + +// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL +// without adding repo to repositories, like FindChartInRepoURL, +// 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) { // Download and write the index file to a temporary location buf := make([]byte, 20) @@ -212,13 +220,14 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-") c := Entry{ - URL: repoURL, - Username: username, - Password: password, - CertFile: certFile, - KeyFile: keyFile, - CAFile: caFile, - Name: name, + URL: repoURL, + Username: username, + Password: password, + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + Name: name, + InsecureSkipTLSverify: insecureSkipTLSverify, } r, err := NewChartRepository(&c, getters) if err != nil { diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index eceb3009e..800d9ae55 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -276,6 +276,44 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { return httptest.NewServer(handler), nil } +// startLocalTLSServerForTests Start the local helm server with TLS +func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) { + if handler == nil { + fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") + if err != nil { + return nil, err + } + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(fileBytes) + }) + } + + return httptest.NewTLSServer(handler), nil +} + +func TestFindChartInAuthAndTLSRepoURL(t *testing.T) { + srv, err := startLocalTLSServerForTests(nil) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + chartURL, err := FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, getter.All(&cli.EnvSettings{})) + if err != nil { + t.Fatalf("%v", err) + } + if chartURL != "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz" { + t.Errorf("%s is not the valid URL", chartURL) + } + + // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". + _, err = FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, getter.All(&cli.EnvSettings{})) + + if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { + t.Errorf("Expected TLS error for function FindChartInAuthAndTLSRepoURL not found, but got a different error (%v)", err) + } +} + func TestFindChartInRepoURL(t *testing.T) { srv, err := startLocalServerForTests(nil) if err != nil { diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 6ef2cf8b5..55b984eea 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -77,6 +77,8 @@ func (c ChartVersions) Less(a, b int) bool { // IndexFile represents the index file in a chart repository type IndexFile struct { + // This is used ONLY for validation against chartmuseum's index files and is discarded after validation. + ServerInfo map[string]interface{} `json:"serverInfo,omitempty"` APIVersion string `json:"apiVersion"` Generated time.Time `json:"generated"` Entries map[string]ChartVersions `json:"entries"` @@ -228,6 +230,23 @@ type ChartVersion struct { Created time.Time `json:"created,omitempty"` Removed bool `json:"removed,omitempty"` Digest string `json:"digest,omitempty"` + + // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced + // this with Digest. However, with a strict YAML parser enabled, a field must be + // present on the struct for backwards compatibility. + ChecksumDeprecated string `json:"checksum,omitempty"` + + // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict + // YAML parser enabled, this field must be present. + EngineDeprecated string `json:"engine,omitempty"` + + // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict + // YAML parser enabled, this field must be present. + TillerVersionDeprecated string `json:"tillerVersion,omitempty"` + + // URLDeprecated is deprectaed in Helm 3, superseded by URLs. It is ignored. However, + // with a strict YAML parser enabled, this must be present on the struct. + URLDeprecated string `json:"url,omitempty"` } // IndexDirectory reads a (flat) directory and generates an index. @@ -281,7 +300,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) { // This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. func loadIndex(data []byte) (*IndexFile, error) { i := &IndexFile{} - if err := yaml.Unmarshal(data, i); err != nil { + if err := yaml.UnmarshalStrict(data, i); err != nil { return i, err } i.SortEntries() diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index 466a2c306..c22588971 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -35,9 +35,31 @@ import ( ) const ( - testfile = "testdata/local-index.yaml" - unorderedTestfile = "testdata/local-index-unordered.yaml" - testRepo = "test-repo" + testfile = "testdata/local-index.yaml" + chartmuseumtestfile = "testdata/chartmuseum-index.yaml" + unorderedTestfile = "testdata/local-index-unordered.yaml" + testRepo = "test-repo" + indexWithDuplicates = ` +apiVersion: v1 +entries: + nginx: + - urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz + name: nginx + description: string + version: 0.2.0 + home: https://github.com/something/else + digest: "sha256:1234567890abcdef" + nginx: + - urls: + - https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz + - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz + name: alpine + description: string + version: 1.0.0 + home: https://github.com/something + digest: "sha256:1234567890abcdef" +` ) func TestIndexFile(t *testing.T) { @@ -84,15 +106,43 @@ func TestIndexFile(t *testing.T) { } func TestLoadIndex(t *testing.T) { - b, err := ioutil.ReadFile(testfile) - if err != nil { - t.Fatal(err) + + tests := []struct { + Name string + Filename string + }{ + { + Name: "regular index file", + Filename: testfile, + }, + { + Name: "chartmuseum index file", + Filename: chartmuseumtestfile, + }, } - i, err := loadIndex(b) - if err != nil { - t.Fatal(err) + + for _, tc := range tests { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + b, err := ioutil.ReadFile(tc.Filename) + if err != nil { + t.Fatal(err) + } + i, err := loadIndex(b) + if err != nil { + t.Fatal(err) + } + verifyLocalIndex(t, i) + }) + } +} + +// TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages. +func TestLoadIndex_Duplicates(t *testing.T) { + if _, err := loadIndex([]byte(indexWithDuplicates)); err == nil { + t.Errorf("Expected an error when duplicate entries are present") } - verifyLocalIndex(t, i) } func TestLoadIndexFile(t *testing.T) { diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index b18bce49c..270c8958a 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -21,6 +21,7 @@ import ( "net/http/httptest" "os" "path/filepath" + "testing" "helm.sh/helm/v3/internal/tlsutil" @@ -29,6 +30,19 @@ import ( "helm.sh/helm/v3/pkg/repo" ) +// NewTempServerWithCleanup creates a server inside of a temp dir. +// +// If the passed in string is not "", it will be treated as a shell glob, and files +// will be copied from that path to the server's docroot. +// +// The caller is responsible for stopping the server. +// The temp dir will be removed by testing package automatically when test finished. +func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) { + srv, err := NewTempServer(glob) + t.Cleanup(func() { os.RemoveAll(srv.docroot) }) + return srv, err +} + // NewTempServer creates a server inside of a temp dir. // // If the passed in string is not "", it will be treated as a shell glob, and files @@ -36,6 +50,8 @@ import ( // // The caller is responsible for destroying the temp directory as well as stopping // the server. +// +// Deprecated: use NewTempServerWithCleanup func NewTempServer(glob string) (*Server, error) { tdir, err := ioutil.TempDir("", "helm-repotest-") if err != nil { diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go index ee62791af..6d71071da 100644 --- a/pkg/repo/repotest/server_test.go +++ b/pkg/repo/repotest/server_test.go @@ -99,7 +99,7 @@ func TestServer(t *testing.T) { func TestNewTempServer(t *testing.T) { defer ensure.HelmHome(t)() - srv, err := NewTempServer("testdata/examplechart-0.1.0.tgz") + srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz") if err != nil { t.Fatal(err) } diff --git a/pkg/repo/testdata/chartmuseum-index.yaml b/pkg/repo/testdata/chartmuseum-index.yaml new file mode 100644 index 000000000..3077596f4 --- /dev/null +++ b/pkg/repo/testdata/chartmuseum-index.yaml @@ -0,0 +1,50 @@ +serverInfo: + contextPath: /v1/helm +apiVersion: v1 +entries: + nginx: + - urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz + name: nginx + description: string + version: 0.2.0 + home: https://github.com/something/else + digest: "sha256:1234567890abcdef" + keywords: + - popular + - web server + - proxy + - urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz + name: nginx + description: string + version: 0.1.0 + home: https://github.com/something + digest: "sha256:1234567890abcdef" + keywords: + - popular + - web server + - proxy + alpine: + - urls: + - https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz + - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz + name: alpine + description: string + version: 1.0.0 + home: https://github.com/something + keywords: + - linux + - alpine + - small + - sumtin + digest: "sha256:1234567890abcdef" + chartWithNoURL: + - name: chartWithNoURL + description: string + version: 1.0.0 + home: https://github.com/something + keywords: + - small + - sumtin + digest: "sha256:1234567890abcdef" diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index f2495e444..08d0e14ca 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -19,8 +19,16 @@ : ${BINARY_NAME:="helm"} : ${USE_SUDO:="true"} +: ${DEBUG:="false"} +: ${VERIFY_CHECKSUM:="true"} +: ${VERIFY_SIGNATURES:="false"} : ${HELM_INSTALL_DIR:="/usr/local/bin"} +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)" + # initArch discovers the architecture for this system. initArch() { ARCH=$(uname -m) @@ -58,7 +66,7 @@ runAsRoot() { } # verifySupported checks that the os/arch combination is supported for -# binary builds. +# binary builds, as well whether or not necessary tools are present. verifySupported() { local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then @@ -67,10 +75,29 @@ verifySupported() { exit 1 fi - if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then + if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then echo "Either curl or wget is required" exit 1 fi + + if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then + echo "In order to verify checksum, openssl must first be installed." + echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment." + exit 1 + fi + + if [ "${VERIFY_SIGNATURES}" == "true" ]; then + if [ "${HAS_GPG}" != "true" ]; then + echo "In order to verify signatures, gpg must first be installed." + echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment." + exit 1 + fi + if [ "${OS}" != "linux" ]; then + echo "Signature verification is currently only supported on Linux." + echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually." + exit 1 + fi + fi } # checkDesiredVersion checks if the desired version is available. @@ -78,9 +105,9 @@ checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL local latest_release_url="https://github.com/helm/helm/releases" - if type "curl" > /dev/null; then + if [ "${HAS_CURL}" == "true" ]; then TAG=$(curl -Ls $latest_release_url | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}') - elif type "wget" > /dev/null; then + elif [ "${HAS_WGET}" == "true" ]; then TAG=$(wget $latest_release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}') fi else @@ -115,35 +142,94 @@ downloadFile() { HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST" HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256" echo "Downloading $DOWNLOAD_URL" - if type "curl" > /dev/null; then + if [ "${HAS_CURL}" == "true" ]; then curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE" - elif type "wget" > /dev/null; then - wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL" - fi - if type "curl" > /dev/null; then curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE" - elif type "wget" > /dev/null; then + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL" wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL" fi } -# installFile verifies the SHA256 for the file, then unpacks and -# installs it. +# verifyFile verifies the SHA256 checksum of the binary package +# and the GPG signatures for both the package and checksum file +# (depending on settings in environment). +verifyFile() { + if [ "${VERIFY_CHECKSUM}" == "true" ]; then + verifyChecksum + fi + if [ "${VERIFY_SIGNATURES}" == "true" ]; then + verifySignatures + fi +} + +# installFile installs the Helm binary. installFile() { HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME" + mkdir -p "$HELM_TMP" + tar xf "$HELM_TMP_FILE" -C "$HELM_TMP" + HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm" + echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}" + runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME" + echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME" +} + +# verifyChecksum verifies the SHA256 checksum of the binary package. +verifyChecksum() { + printf "Verifying checksum... " local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}') local expected_sum=$(cat ${HELM_SUM_FILE}) if [ "$sum" != "$expected_sum" ]; then echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting." exit 1 fi + echo "Done." +} - mkdir -p "$HELM_TMP" - tar xf "$HELM_TMP_FILE" -C "$HELM_TMP" - HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm" - echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}" - runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME" - echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME" +# verifySignatures obtains the latest KEYS file from GitHub master branch +# as well as the signature .asc files from the specific GitHub release, +# then verifies that the release artifacts were signed by a maintainer's key. +verifySignatures() { + printf "Verifying signatures... " + local keys_filename="KEYS" + local github_keys_url="https://raw.githubusercontent.com/helm/helm/master/${keys_filename}" + if [ "${HAS_CURL}" == "true" ]; then + curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}" + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}" + fi + local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg" + local gpg_homedir="${HELM_TMP_ROOT}/gnupg" + mkdir -p -m 0700 "${gpg_homedir}" + local gpg_stderr_device="/dev/null" + if [ "${DEBUG}" == "true" ]; then + gpg_stderr_device="/dev/stderr" + fi + gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}" + gpg --batch --no-default-keyring --keyring "${gpg_homedir}/pubring.kbx" --export > "${gpg_keyring}" + local github_release_url="https://github.com/helm/helm/releases/download/${TAG}" + if [ "${HAS_CURL}" == "true" ]; then + curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" + curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" + wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" + fi + local error_text="If you think this might be a potential security issue," + error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md" + local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)') + if [[ ${num_goodlines_sha} -lt 2 ]]; then + echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!" + echo -e "${error_text}" + exit 1 + fi + local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)') + if [[ ${num_goodlines_tar} -lt 2 ]]; then + echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!" + echo -e "${error_text}" + exit 1 + fi + echo "Done." } # fail_trap is executed if an error occurs. @@ -195,6 +281,11 @@ cleanup() { trap "fail_trap" EXIT set -e +# Set debug if desired +if [ "${DEBUG}" == "true" ]; then + set -x +fi + # Parsing input arguments (if any) export INPUT_ARGUMENTS="${@}" set -u @@ -229,6 +320,7 @@ verifySupported checkDesiredVersion if ! checkHelmInstalledVersion; then downloadFile + verifyFile installFile fi testVersion diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index 3625aaa9a..b024a958d 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -90,7 +90,7 @@ Download Helm ${RELEASE}. The common platform binaries are here: - [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) - [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) -The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`. +The [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`. ## What's Next