From 1ab52fa79c100332bc8014095cf7aed6937cae8a Mon Sep 17 00:00:00 2001 From: Matt Morrissette Date: Fri, 21 Feb 2020 09:22:42 -0800 Subject: [PATCH 01/59] fix(helm): stdin values for helm upgrade --install Signed-off-by: Matt Morrissette --- cmd/helm/helm_test.go | 20 +++++++++-- cmd/helm/upgrade.go | 32 +++++++++--------- cmd/helm/upgrade_test.go | 64 ++++++++++++++++++++++++++++++++++++ pkg/action/rollback.go | 2 +- pkg/storage/driver/memory.go | 5 +++ 5 files changed, 105 insertions(+), 18 deletions(-) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 94646a5a3..9ba9d78fb 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -97,10 +97,15 @@ func storageFixture() *storage.Storage { } func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) { + return executeActionCommandStdinC(store, nil, cmd) +} + +func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) { args, err := shellwords.Parse(cmd) if err != nil { return nil, "", err } + buf := new(bytes.Buffer) actionConfig := &action.Configuration{ @@ -111,15 +116,26 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, } root := newRootCmd(actionConfig, buf, args) - root.SetOutput(buf) + root.SetOut(buf) + root.SetErr(buf) root.SetArgs(args) + oldStdin := os.Stdin + if in != nil { + root.SetIn(in) + os.Stdin = in + } + if mem, ok := store.Driver.(*driver.Memory); ok { mem.SetNamespace(settings.Namespace()) } c, err := root.ExecuteC() - return c, buf.String(), err + result := buf.String() + + os.Stdin = oldStdin + + return c, result, err } // cmdTestCase describes a test case that works with releases. diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index dea866e4d..dd5a6c478 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -75,21 +75,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() - if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") - client.Version = ">0.0.0-0" - } - - vals, err := valueOpts.MergeValues(getter.All(settings)) - if err != nil { - return err - } - - chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings) - if err != nil { - return err - } - + // Fixes #7002 - Support reading values from STDIN for `upgrade` command + // Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice if client.Install { // If a release does not exist, install it. If another error occurs during // the check, ignore the error and continue with the upgrade. @@ -121,6 +108,21 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } + if client.Version == "" && client.Devel { + debug("setting version to >0.0.0-0") + client.Version = ">0.0.0-0" + } + + chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings) + if err != nil { + return err + } + + vals, err := valueOpts.MergeValues(getter.All(settings)) + if err != nil { + return err + } + // Check chart dependencies to make sure all are present in /charts ch, err := loader.Load(chartPath) if err != nil { diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 3cecbe6d3..1e413811a 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io/ioutil" + "os" "path/filepath" "strings" "testing" @@ -224,6 +225,69 @@ func TestUpgradeWithValuesFile(t *testing.T) { } +func TestUpgradeWithValuesFromStdin(t *testing.T) { + + releaseName := "funny-bunny-v5" + relMock, ch, chartPath := prepareMockRelease(releaseName, t) + + defer resetEnv()() + + store := storageFixture() + + store.Create(relMock(releaseName, 3, ch)) + + in, err := os.Open("testdata/testcharts/upgradetest/values.yaml") + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + cmd := fmt.Sprintf("upgrade %s --values - '%s'", releaseName, chartPath) + _, _, err = executeActionCommandStdinC(store, in, cmd) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + updatedRel, err := store.Get(releaseName, 4) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + if !strings.Contains(updatedRel.Manifest, "drink: beer") { + t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) + } +} + +func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { + + releaseName := "funny-bunny-v6" + _, _, chartPath := prepareMockRelease(releaseName, t) + + defer resetEnv()() + + store := storageFixture() + + in, err := os.Open("testdata/testcharts/upgradetest/values.yaml") + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + cmd := fmt.Sprintf("upgrade %s -f - --install '%s'", releaseName, chartPath) + _, _, err = executeActionCommandStdinC(store, in, cmd) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + updatedRel, err := store.Get(releaseName, 1) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + if !strings.Contains(updatedRel.Manifest, "drink: beer") { + t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) + } + +} + func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { tmpChart := ensure.TempDir(t) configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 942c9d8af..81812983f 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -211,7 +211,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil { + if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { return nil, err } // Supersede all previous deployments, see issue #2941. diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index a99b36ef0..91378f588 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -141,6 +141,11 @@ func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { break } } + + if len(ls) == 0 { + return nil, ErrReleaseNotFound + } + return ls, nil } From cca68288063d235a4c32ef1ba822dd487317c8dc Mon Sep 17 00:00:00 2001 From: liuming216448 Date: Fri, 13 Mar 2020 18:36:19 +0800 Subject: [PATCH 02/59] fix: allow to rollback to previous version even if no deployed releases(#6978) Signed-off-by: liuming --- pkg/action/rollback.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 942c9d8af..0e09f8b6f 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -210,8 +210,14 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } } + targetRelease.Info.Status = release.StatusDeployed + deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) if err != nil { + if strings.Contains(err.Error(), "has no deployed releases") { + r.cfg.Log(err.Error()) + return targetRelease, nil + } return nil, err } // Supersede all previous deployments, see issue #2941. @@ -221,7 +227,5 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.recordRelease(rel) } - targetRelease.Info.Status = release.StatusDeployed - return targetRelease, nil } From cead0efa69d97cb5b6d5a21fc0f05971b5128886 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 23 Mar 2020 18:54:38 +0100 Subject: [PATCH 03/59] helm create command's templates more consistent - Removed most right whitespace chomps except those directly following a template definition where it make sense to not lead with a blank line. The system applied is now to almost always left whitespace chomp but also whitespace chomp right if its the first thing in a file or template definition. - Updated indentation to be systematic throughout all the boilerplace files. Signed-off-by: Erik Sundell --- pkg/chartutil/create.go | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 24eb1e277..16fbcd929 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -199,29 +199,29 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} spec: -{{- if .Values.ingress.tls }} + {{- if .Values.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.ingress.tls }} - hosts: - {{- range .hosts }} + {{- range .hosts }} - {{ . | quote }} - {{- end }} + {{- end }} secretName: {{ .secretName }} + {{- end }} {{- end }} -{{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: - {{- range .paths }} + {{- range .paths }} - path: {{ . }} backend: serviceName: {{ $fullName }} servicePort: {{ $svcPort }} - {{- end }} + {{- end }} + {{- end }} {{- end }} -{{- end }} ` const defaultDeployment = `apiVersion: apps/v1 @@ -240,10 +240,10 @@ spec: labels: {{- include ".selectorLabels" . | nindent 8 }} spec: - {{- with .Values.imagePullSecrets }} + {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} serviceAccountName: {{ include ".serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} @@ -271,14 +271,14 @@ spec: nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} + {{- end }} + {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} ` const defaultService = `apiVersion: v1 @@ -309,7 +309,7 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} -{{- end -}} +{{- end }} ` const defaultNotes = `1. Get the application URL by running these commands: @@ -340,8 +340,8 @@ const defaultHelpers = `{{/* vim: set filetype=mustache: */}} Expand the name of the chart. */}} {{- define ".name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} {{/* Create a default fully qualified app name. @@ -349,24 +349,24 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this If release name contains chart name it will be used as a full name. */}} {{- define ".fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define ".chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} {{/* Common labels @@ -378,7 +378,7 @@ helm.sh/chart: {{ include ".chart" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} +{{- end }} {{/* Selector labels @@ -386,18 +386,18 @@ Selector labels {{- define ".selectorLabels" -}} app.kubernetes.io/name: {{ include ".name" . }} app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} +{{- end }} {{/* Create the name of the service account to use */}} {{- define ".serviceAccountName" -}} -{{- if .Values.serviceAccount.create -}} - {{ default (include ".fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} +{{- if .Values.serviceAccount.create }} +{{- default (include ".fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} ` const defaultTestConnection = `apiVersion: v1 From f3350defec881dc36217f4774703f252d3c895af Mon Sep 17 00:00:00 2001 From: Andrey Voronkov Date: Sat, 4 Apr 2020 04:22:43 +0300 Subject: [PATCH 04/59] Avoid downloading same chart multiple times Signed-off-by: Andrey Voronkov --- pkg/downloader/manager.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index ff451a6e8..00198de0c 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -239,6 +239,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps)) var saveError error + churls := make(map[string]struct{}) for _, dep := range deps { // No repository means the chart is in charts directory if dep.Repository == "" { @@ -278,8 +279,6 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { continue } - fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) - // Any failure to resolve/download a chart should fail: // https://github.com/helm/helm/issues/1439 churl, username, password, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) @@ -288,6 +287,13 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { break } + if _, ok := churls[churl]; ok { + fmt.Fprintf(m.Out, "Already downloaded %s from repo %s\n", dep.Name, dep.Repository) + continue + } + + fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) + dl := ChartDownloader{ Out: m.Out, Verify: m.Verify, @@ -304,6 +310,8 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { saveError = errors.Wrapf(err, "could not download %s", churl) break } + + churls[churl] = struct{}{} } if saveError == nil { From 0d9727d6abb466f4628db16a3ee81cddd4f31d22 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 10 Apr 2020 12:15:27 -0400 Subject: [PATCH 05/59] Adding template docs to the version command Signed-off-by: Matt Farina --- cmd/helm/version.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/helm/version.go b/cmd/helm/version.go index 3067f7289..b3f831e07 100644 --- a/cmd/helm/version.go +++ b/cmd/helm/version.go @@ -39,6 +39,14 @@ version.BuildInfo{Version:"v2.0.0", GitCommit:"ff52399e51bb880526e9cd0ed8386f643 - GitCommit is the SHA for the commit that this version was built from. - GitTreeState is "clean" if there are no local code changes when this binary was built, and "dirty" if the binary was built from locally modified code. + +When using the --template flag the following properties are available to use in +the template: + +- .Version contains the semantic version of Helm +- .GitCommit is the git commit +- .GitTreeState is the state of the git tree when Helm was built +- .GoVersion contains the version of Go that Helm was compiled with ` type versionOptions struct { From 65f28c153a5eeb77584658ccca875b005a8ddff6 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 13 Apr 2020 11:53:43 -0400 Subject: [PATCH 06/59] chore(comp): Remove unnecessary code After the introduction of lazy loading of the Kubernetes client as part of PR #7831, there is no longer a need to protect against an incomplete --kube-context value when doing completion. Signed-off-by: Marc Khouzam --- cmd/helm/helm.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 257387547..7e1fcb6e1 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -32,7 +32,6 @@ import ( // Import to initialize client auth plugins. _ "k8s.io/client-go/plugin/pkg/client/auth" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/gates" @@ -73,16 +72,6 @@ func main() { actionConfig := new(action.Configuration) cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) - if calledCmd, _, err := cmd.Find(os.Args[1:]); err == nil && calledCmd.Name() == completion.CompRequestCmd { - // If completion is being called, we have to check if the completion is for the "--kube-context" - // value; if it is, we cannot call the action.Init() method with an incomplete kube-context value - // or else it will fail immediately. So, we simply unset the invalid kube-context value. - if args := os.Args[1:]; len(args) > 2 && args[len(args)-2] == "--kube-context" { - // We are completing the kube-context value! Reset it as the current value is not valid. - settings.KubeContext = "" - } - } - helmDriver := os.Getenv("HELM_DRIVER") if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { log.Fatal(err) From 469837b92cefa0c633397dea1e49606ba0105d56 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 13 Apr 2020 12:05:33 -0600 Subject: [PATCH 07/59] fixed capitalization in a few help messages. (#7898) Signed-off-by: Matt Butcher --- cmd/helm/completion.go | 2 +- cmd/helm/create.go | 2 +- cmd/helm/env.go | 2 +- cmd/helm/release_testing.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index 1601cb448..c1f7790bc 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -52,7 +52,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "completion SHELL", - Short: "Generate autocompletions script for the specified shell (bash or zsh)", + Short: "generate autocompletions script for the specified shell (bash or zsh)", Long: completionDesc, RunE: func(cmd *cobra.Command, args []string) error { return runCompletion(out, cmd, args) diff --git a/cmd/helm/create.go b/cmd/helm/create.go index e8ff757cf..5bdda05cb 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -71,7 +71,7 @@ func newCreateCmd(out io.Writer) *cobra.Command { }, } - cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "The name or absolute path to Helm starter scaffold") + cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold") return cmd } diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 2687272ba..efb6dfea9 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -40,7 +40,7 @@ func newEnvCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "env", - Short: "Helm client environment information", + Short: "helm client environment information", Long: envHelp, Args: require.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index e4690b9d4..036d96794 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -82,7 +82,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command f := cmd.Flags() f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.BoolVar(&outputLogs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") + f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") return cmd } From b9445616b522fe701620b18becdae3a783c054cb Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 13 Apr 2020 13:57:30 -0500 Subject: [PATCH 08/59] fix(storage): preserve last deployed revision (#7806) Signed-off-by: Eric Bailey --- pkg/storage/storage.go | 40 ++++++++++++++++++++++++----- pkg/storage/storage_test.go | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 89183355f..3e62ae9ee 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -169,21 +169,37 @@ func (s *Storage) removeLeastRecent(name string, max int) error { if len(h) <= max { return nil } - overage := len(h) - max // We want oldest to newest relutil.SortByRevision(h) + lastDeployed, err := s.Deployed(name) + if err != nil { + return err + } + + var toDelete []*rspb.Release + for _, rel := range h { + // once we have enough releases to delete to reach the max, stop + if len(h)-len(toDelete) == max { + break + } + if lastDeployed != nil { + if rel.Version != lastDeployed.Version { + toDelete = append(toDelete, rel) + } + } else { + toDelete = append(toDelete, rel) + } + } + // Delete as many as possible. In the case of API throughput limitations, // multiple invocations of this function will eventually delete them all. - toDelete := h[0:overage] errs := []error{} for _, rel := range toDelete { - key := makeKey(name, rel.Version) - _, innerErr := s.Delete(name, rel.Version) - if innerErr != nil { - s.Log("error pruning %s from release history: %s", key, innerErr) - errs = append(errs, innerErr) + err = s.deleteReleaseVersion(name, rel.Version) + if err != nil { + errs = append(errs, err) } } @@ -198,6 +214,16 @@ func (s *Storage) removeLeastRecent(name string, max int) error { } } +func (s *Storage) deleteReleaseVersion(name string, version int) error { + key := makeKey(name, version) + _, err := s.Delete(name, version) + if err != nil { + s.Log("error pruning %s from release history: %s", key, err) + return err + } + return nil +} + // Last fetches the last revision of the named release. func (s *Storage) Last(name string) (*rspb.Release, error) { s.Log("getting last revision of %q", name) diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index ee9c68b80..934a3842c 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -333,6 +333,57 @@ func TestStorageRemoveLeastRecent(t *testing.T) { } } +func TestStorageDontDeleteDeployed(t *testing.T) { + storage := Init(driver.NewMemory()) + storage.Log = t.Logf + storage.MaxHistory = 3 + + const name = "angry-bird" + + // setup storage with test releases + setup := func() { + // release records + rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() + rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease() + rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease() + rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() + + // create the release records in the storage + assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + } + setup() + + rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease() + assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") + + // On inserting the 5th record, we expect a total of 3 releases, but we expect version 2 + // (the only deployed release), to still exist + hist, err := storage.History(name) + if err != nil { + t.Fatal(err) + } else if len(hist) != storage.MaxHistory { + for _, item := range hist { + t.Logf("%s %v", item.Name, item.Version) + } + t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist)) + } + + expectedVersions := map[int]bool{ + 2: true, + 4: true, + 5: true, + } + + for _, item := range hist { + if !expectedVersions[item.Version] { + t.Errorf("Release version %d, found when not expected", item.Version) + } + } +} + func TestStorageLast(t *testing.T) { storage := Init(driver.NewMemory()) From a806326d18e008740a522440ac02856add556e33 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 11 Apr 2020 13:07:42 -0400 Subject: [PATCH 09/59] feat(cmd/helm): Update Cobra to 1.0.0 release Signed-off-by: Marc Khouzam --- go.mod | 2 +- go.sum | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3413112cd..72cc667cc 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/opencontainers/image-spec v1.0.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.4.2 - github.com/spf13/cobra v0.0.5 + github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.4.0 github.com/xeipuuv/gojsonschema v1.1.0 diff --git a/go.sum b/go.sum index 3c08dba4b..872673ab2 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tT github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -78,6 +79,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= 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= @@ -94,6 +96,7 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -103,8 +106,11 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= @@ -119,6 +125,7 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 h1:FwssHbCDJD025h+BchanCwE1Q8fyMgqDr2mOQAWOLGw= github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -170,6 +177,7 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 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-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 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= @@ -233,6 +241,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -278,8 +288,10 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= @@ -365,6 +377,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -403,6 +416,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -413,19 +427,27 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 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-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -434,6 +456,7 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= @@ -442,6 +465,8 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -450,6 +475,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -461,6 +487,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -481,6 +509,7 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +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/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -490,6 +519,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -528,6 +558,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -616,6 +647,7 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= From 0c87ca544f6fc760065d590bed43f25040e276ea Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 13 Apr 2020 18:03:53 -0600 Subject: [PATCH 10/59] updated help text for install --atomic, which was completely inaccurate (#7912) Signed-off-by: Matt Butcher --- cmd/helm/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 719dc9014..40535e4e3 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -148,7 +148,7 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") - f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used") + f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") addValueOptionsFlags(f, valueOpts) From 8c55de381869005e61cbc769bce1660404741987 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Thu, 2 Apr 2020 13:49:20 +0800 Subject: [PATCH 11/59] add unit test for IsRoot Signed-off-by: Zhou Hao --- pkg/chart/chart_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index 4f19bf87d..51dc8cb4f 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -96,3 +96,24 @@ func TestMetadata(t *testing.T) { is.Equal("1.0.0", chrt.AppVersion()) is.Equal(nil, chrt.Validate()) } + +func TestIsRoot(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal(false, chrt1.IsRoot()) + is.Equal(true, chrt2.IsRoot()) +} From a3d3fa396423b82c14fa3c3619eaf03e79ad8884 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Thu, 2 Apr 2020 13:58:58 +0800 Subject: [PATCH 12/59] add unit test for ChartPath Signed-off-by: Zhou Hao --- pkg/chart/chart_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index 51dc8cb4f..6f352adda 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -117,3 +117,24 @@ func TestIsRoot(t *testing.T) { is.Equal(false, chrt1.IsRoot()) is.Equal(true, chrt2.IsRoot()) } + +func TestChartPath(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal("foo.", chrt1.ChartPath()) + is.Equal("foo", chrt2.ChartPath()) +} From b439d34a43b886ee4f2272e922e03b05ee5e898d Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Thu, 2 Apr 2020 13:59:37 +0800 Subject: [PATCH 13/59] add unit test for ChartFullPath Signed-off-by: Zhou Hao --- pkg/chart/chart_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index 6f352adda..1b8669ac8 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -138,3 +138,24 @@ func TestChartPath(t *testing.T) { is.Equal("foo.", chrt1.ChartPath()) is.Equal("foo", chrt2.ChartPath()) } + +func TestChartFullPath(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal("foo/charts/", chrt1.ChartFullPath()) + is.Equal("foo", chrt2.ChartFullPath()) +} From 20c7909756f03e5c7a266f9bef294cfd9cf64088 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Thu, 2 Apr 2020 14:44:00 +0800 Subject: [PATCH 14/59] add unit test for metadata Validate Signed-off-by: Zhou Hao --- pkg/chart/metadata_test.go | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 pkg/chart/metadata_test.go diff --git a/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go new file mode 100644 index 000000000..8b436000b --- /dev/null +++ b/pkg/chart/metadata_test.go @@ -0,0 +1,59 @@ +/* +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 chart + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + tests := []struct { + md *Metadata + err error + }{ + { + nil, + ValidationError("chart.metadata is required"), + }, + { + &Metadata{Name: "test", Version: "1.0"}, + ValidationError("chart.metadata.apiVersion is required"), + }, + { + &Metadata{APIVersion: "v2", Version: "1.0"}, + ValidationError("chart.metadata.name is required"), + }, + { + &Metadata{Name: "test", APIVersion: "v2"}, + ValidationError("chart.metadata.version is required"), + }, + { + &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "test"}, + ValidationError("chart.metadata.type must be application or library"), + }, + { + &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"}, + nil, + }, + } + + for _, tt := range tests { + result := tt.md.Validate() + if result != tt.err { + t.Errorf("expected %s, got %s", tt.err, result) + } + } +} From ad3ba46de1fa5dcba2a90003bf8f368ab47c06df Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 14 Apr 2020 19:55:50 -0600 Subject: [PATCH 15/59] docs: Update inline docs on action/upgrade.go (#7834) * docs: Update inline docs on action/upgrade.go Signed-off-by: Matt Butcher * clarify atomic and cleanup-on-fail Signed-off-by: Matt Butcher * updated the post-render documentation on action.Upgrade Signed-off-by: Matt Butcher --- pkg/action/upgrade.go | 62 ++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index b73e2ac8b..9d05217e9 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -43,27 +43,57 @@ type Upgrade struct { ChartPathOptions - Install bool - Devel bool + // Install is a purely informative flag that indicates whether this upgrade was done in "install" mode. + // + // Applications may use this to determine whether this Upgrade operation was done as part of a + // pure upgrade (Upgrade.Install == false) or as part of an install-or-upgrade operation + // (Upgrade.Install == true). + // + // Setting this to `true` will NOT cause `Upgrade` to perform an install if the release does not exist. + // That process must be handled by creating an Install action directly. See cmd/upgrade.go for an + // example of how this flag is used. + Install bool + // Devel indicates that the operation is done in devel mode. + Devel bool + // Namespace is the namespace in which this operation should be performed. Namespace string - // SkipCRDs skip installing CRDs when install flag is enabled during upgrade - SkipCRDs bool - Timeout time.Duration - Wait bool + // SkipCRDs skips installing CRDs when install flag is enabled during upgrade + SkipCRDs bool + // Timeout is the timeout for this operation + Timeout time.Duration + // Wait determines whether the wait operation should be performed after the upgrade is requested. + Wait bool + // DisableHooks disables hook processing if set to true. DisableHooks bool - DryRun bool - Force bool - ResetValues bool - ReuseValues bool + // DryRun controls whether the operation is prepared, but not executed. + // If `true`, the upgrade is prepared but not performed. + DryRun bool + // Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway. + // + // This should be used with caution. + Force bool + // ResetValues will reset the values to the chart's built-ins rather than merging with existing. + ResetValues bool + // ReuseValues will re-use the user's last supplied values. + ReuseValues bool // Recreate will (if true) recreate pods after a rollback. Recreate bool // MaxHistory limits the maximum number of revisions saved per release - MaxHistory int - Atomic bool - CleanupOnFail bool - SubNotes bool - Description string - PostRenderer postrender.PostRenderer + MaxHistory int + // Atomic, if true, will roll back on failure. + Atomic bool + // CleanupOnFail will, if true, cause the upgrade to delete newly-created resources on a failed update. + CleanupOnFail bool + // SubNotes determines whether sub-notes are rendered in the chart. + SubNotes bool + // Description is the description of this operation + Description string + // PostRender is an optional post-renderer + // + // If this is non-nil, then after templates are rendered, they will be sent to the + // post renderer before sending to the Kuberntes API server. + PostRenderer postrender.PostRenderer + // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. DisableOpenAPIValidation bool } From 9e1f381bf2b0c3880cdf4bad3a0dbc21026b54c6 Mon Sep 17 00:00:00 2001 From: Lu Fengqi Date: Wed, 15 Apr 2020 10:07:02 +0800 Subject: [PATCH 16/59] Add unit test for Secrets/ConfigMaps (#7765) * test(pkg/storage/secrets): make MockSecretsInterface.List follow ListOptions Signed-off-by: Lu Fengqi * test(pkg/storage/secrets): add unit test for Secrets.Query Signed-off-by: Lu Fengqi * test(pkg/storage/cfgmaps): make MockConfigMapsInterface.List follow ListOptions Signed-off-by: Lu Fengqi * test(pkg/storage/cfgmaps): add unit test for ConfigMaps.Query Signed-off-by: Lu Fengqi --- pkg/storage/driver/cfgmaps_test.go | 24 ++++++++++++++++++++++++ pkg/storage/driver/mock_test.go | 21 +++++++++++++++++++-- pkg/storage/driver/secrets_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index e40247d3c..626c36cb9 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -130,6 +130,30 @@ func TestConfigMapList(t *testing.T) { } } +func TestConfigMapQuery(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", rspb.StatusUninstalled), + releaseStub("key-2", 1, "default", rspb.StatusUninstalled), + releaseStub("key-3", 1, "default", rspb.StatusDeployed), + releaseStub("key-4", 1, "default", rspb.StatusDeployed), + releaseStub("key-5", 1, "default", rspb.StatusSuperseded), + releaseStub("key-6", 1, "default", rspb.StatusSuperseded), + }...) + + rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) + if err != nil { + t.Errorf("Failed to query: %s", err) + } + if len(rls) != 2 { + t.Errorf("Expected 2 results, got %d", len(rls)) + } + + _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) + if err != ErrReleaseNotFound { + t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) + } +} + func TestConfigMapCreate(t *testing.T) { cfgmaps := newTestFixtureCfgMaps(t) diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 22e6454a1..0ef498c4e 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -24,6 +24,7 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" rspb "helm.sh/helm/v3/pkg/release" @@ -114,8 +115,16 @@ func (mock *MockConfigMapsInterface) Get(_ context.Context, name string, _ metav // List returns the a of ConfigMaps. func (mock *MockConfigMapsInterface) List(_ context.Context, _ metav1.ListOptions) (*v1.ConfigMapList, error) { var list v1.ConfigMapList + + labelSelector, err := kblabels.Parse(opts.LabelSelector) + if err != nil { + return nil, err + } + for _, cfgmap := range mock.objects { - list.Items = append(list.Items, *cfgmap) + if labelSelector.Matches(kblabels.Set(cfgmap.ObjectMeta.Labels)) { + list.Items = append(list.Items, *cfgmap) + } } return &list, nil } @@ -192,8 +201,16 @@ func (mock *MockSecretsInterface) Get(_ context.Context, name string, _ metav1.G // List returns the a of Secret. func (mock *MockSecretsInterface) List(_ context.Context, _ metav1.ListOptions) (*v1.SecretList, error) { var list v1.SecretList + + labelSelector, err := kblabels.Parse(opts.LabelSelector) + if err != nil { + return nil, err + } + for _, secret := range mock.objects { - list.Items = append(list.Items, *secret) + if labelSelector.Matches(kblabels.Set(secret.ObjectMeta.Labels)) { + list.Items = append(list.Items, *secret) + } } return &list, nil } diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index 5f0ecc8bb..d509c7b3a 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -130,6 +130,30 @@ func TestSecretList(t *testing.T) { } } +func TestSecretQuery(t *testing.T) { + secrets := newTestFixtureSecrets(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", rspb.StatusUninstalled), + releaseStub("key-2", 1, "default", rspb.StatusUninstalled), + releaseStub("key-3", 1, "default", rspb.StatusDeployed), + releaseStub("key-4", 1, "default", rspb.StatusDeployed), + releaseStub("key-5", 1, "default", rspb.StatusSuperseded), + releaseStub("key-6", 1, "default", rspb.StatusSuperseded), + }...) + + rls, err := secrets.Query(map[string]string{"status": "deployed"}) + if err != nil { + t.Fatalf("Failed to query: %s", err) + } + if len(rls) != 2 { + t.Fatalf("Expected 2 results, actual %d", len(rls)) + } + + _, err = secrets.Query(map[string]string{"name": "notExist"}) + if err != ErrReleaseNotFound { + t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) + } +} + func TestSecretCreate(t *testing.T) { secrets := newTestFixtureSecrets(t) From e1d046bc43ad1ec53c4c0d38aa7723c82e2ff9d2 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 14 Apr 2020 21:34:08 -0700 Subject: [PATCH 17/59] fix(tests): fix broken unit tests in storage (#7928) --- pkg/storage/driver/mock_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 0ef498c4e..77ddca43d 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -113,7 +113,7 @@ func (mock *MockConfigMapsInterface) Get(_ context.Context, name string, _ metav } // List returns the a of ConfigMaps. -func (mock *MockConfigMapsInterface) List(_ context.Context, _ metav1.ListOptions) (*v1.ConfigMapList, error) { +func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { var list v1.ConfigMapList labelSelector, err := kblabels.Parse(opts.LabelSelector) @@ -199,7 +199,7 @@ func (mock *MockSecretsInterface) Get(_ context.Context, name string, _ metav1.G } // List returns the a of Secret. -func (mock *MockSecretsInterface) List(_ context.Context, _ metav1.ListOptions) (*v1.SecretList, error) { +func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { var list v1.SecretList labelSelector, err := kblabels.Parse(opts.LabelSelector) From bdf6f48704ed9e09d7fa636f025a3e2d344d42d4 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 14 Apr 2020 22:27:53 -0700 Subject: [PATCH 18/59] fix(pkg/kube): continue deleting objects when one fails * Continue deleting objects when one fails to minimize the risk of an upgrade ending in an unrecoverable state * Exclude failed deleted object from the returned result set Signed-off-by: Adam Reese --- pkg/kube/client.go | 12 ++++-------- pkg/kube/client_test.go | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 05b26b12a..8a4831ffb 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -223,6 +223,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err if err := info.Get(); err != nil { c.Log("Unable to get obj %q, err: %s", info.Name, err) + continue } annotations, err := metadataAccessor.Annotations(info.Object) if err != nil { @@ -232,16 +233,11 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy) continue } - - res.Deleted = append(res.Deleted, info) if err := deleteResource(info); err != nil { - if apierrors.IsNotFound(err) { - c.Log("Attempted to delete %q, but the resource was missing", info.Name) - } else { - c.Log("Failed to delete %q, err: %s", info.Name, err) - return res, errors.Wrapf(err, "Failed to delete %q", info.Name) - } + c.Log("Failed to delete %q, err: %s", info.ObjectName(), err) + continue } + res.Deleted = append(res.Deleted, info) } return res, nil } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index aa081423c..568afa094 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -164,9 +164,21 @@ func TestUpdate(t *testing.T) { t.Fatal(err) } - if _, err := c.Update(first, second, false); err != nil { + result, err := c.Update(first, second, false) + if err != nil { t.Fatal(err) } + + if len(result.Created) != 1 { + t.Errorf("expected 1 resource created, got %d", len(result.Created)) + } + if len(result.Updated) != 2 { + t.Errorf("expected 2 resource updated, got %d", len(result.Updated)) + } + if len(result.Deleted) != 1 { + t.Errorf("expected 1 resource deleted, got %d", len(result.Deleted)) + } + // TODO: Find a way to test methods that use Client Set // Test with a wait // if err := c.Update("test", objBody(codec, &listB), objBody(codec, &listC), false, 300, true); err != nil { @@ -190,8 +202,7 @@ func TestUpdate(t *testing.T) { "/namespaces/default/pods/squid:DELETE", } if len(expectedActions) != len(actions) { - t.Errorf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) - return + t.Fatalf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) } for k, v := range expectedActions { if actions[k] != v { From 0b1cba8474eeb704230fe86f70a7f0b5ac6bd440 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Tue, 14 Apr 2020 22:30:41 +0000 Subject: [PATCH 19/59] Add an improved user error message for removed k8s apis The error message returned from Kubernetes when APIs are removed is not very informative. This PR adds additional information to the user. It covers the current release manifest APIs. Partial #7219 Signed-off-by: Martin Hickey --- pkg/action/upgrade.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 9d05217e9..7565da5a9 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -233,6 +233,13 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) { current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) if err != nil { + // Checking for removed Kubernetes API error so can provide a more informative error message to the user + // Ref: https://github.com/helm/helm/issues/7219 + if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") { + return upgradedRelease, errors.Wrap(err, "current release manifest contains removed kubernetes api(s) for this "+ + "kubernetes version and it is therefore unable to build the kubernetes "+ + "objects for performing the diff. error from kubernetes") + } return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) From 549193dbcb04c7883fef59e844f981d32a92c887 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 15 Apr 2020 11:48:26 -0600 Subject: [PATCH 20/59] test: forward-port regression test from Helm 2 (#7927) Signed-off-by: Matt Butcher --- pkg/lint/lint_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index b51939d76..2c110009d 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -17,9 +17,12 @@ limitations under the License. package lint import ( + "io/ioutil" + "os" "strings" "testing" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/lint/support" ) @@ -104,3 +107,30 @@ func TestGoodChart(t *testing.T) { t.Errorf("All failed but shouldn't have: %#v", m) } } + +// TestHelmCreateChart tests that a `helm create` always passes a `helm lint` test. +// +// See https://github.com/helm/helm/issues/7923 +func TestHelmCreateChart(t *testing.T) { + dir, err := ioutil.TempDir("", "-helm-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir) + if err != nil { + t.Error(err) + // Fatal is bad because of the defer. + return + } + + // Note: we test with strict=true here, even though others have + // strict = false. + m := All(createdChart, values, namespace, true).Messages + if ll := len(m); ll != 1 { + t.Errorf("All should have had exactly 1 error. Got %d", ll) + } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { + t.Errorf("Unexpected lint error: %s", msg) + } +} From 48e6ea0caec94682d80f20bbd66658392b27edd3 Mon Sep 17 00:00:00 2001 From: Riccardo Piccoli Date: Wed, 15 Apr 2020 19:50:18 +0200 Subject: [PATCH 21/59] add softonic to adopters (#7918) Signed-off-by: Riccardo Piccoli Co-authored-by: Riccardo Piccoli --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 46b42b8a0..9d5365b72 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,6 +10,7 @@ - [Microsoft](https://microsoft.com) - [Qovery](https://www.qovery.com/) - [Samsung SDS](https://www.samsungsds.com/) +- [Softonic](https://hello.softonic.com/) - [Ville de Montreal](https://montreal.ca) _This file is part of the CNCF official documentation for projects._ From 4276acdf4b22c993ac6d1b1aeb1c4d2246ff7309 Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Wed, 15 Apr 2020 18:10:16 -0400 Subject: [PATCH 22/59] Make get script eaiser for helm versions to live side by side (helm3 etc) (#7752) * Make get script eaiser for helm versions to live side by side (helm3 etc) Signed-off-by: Scott Rigby * Change PROJECT_NAME to BINARY_NAME for purpose clarity Signed-off-by: Scott Rigby --- scripts/get-helm-3 | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index a974d97b6..adadf5953 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -17,8 +17,7 @@ # The install script is based off of the MIT-licensed script from glide, # the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get -PROJECT_NAME="helm" - +: ${BINARY_NAME:="helm"} : ${USE_SUDO:="true"} : ${HELM_INSTALL_DIR:="/usr/local/bin"} @@ -92,8 +91,8 @@ checkDesiredVersion() { # checkHelmInstalledVersion checks which version of helm is installed and # if it needs to be changed. checkHelmInstalledVersion() { - if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then - local version=$("${HELM_INSTALL_DIR}/${PROJECT_NAME}" version --template="{{ .Version }}") + if [[ -f "${HELM_INSTALL_DIR}/${BINARY_NAME}" ]]; then + local version=$("${HELM_INSTALL_DIR}/${BINARY_NAME}" version --template="{{ .Version }}") if [[ "$version" == "$TAG" ]]; then echo "Helm ${version} is already ${DESIRED_VERSION:-latest}" return 0 @@ -131,7 +130,7 @@ downloadFile() { # installFile verifies the SHA256 for the file, then unpacks and # installs it. installFile() { - HELM_TMP="$HELM_TMP_ROOT/$PROJECT_NAME" + HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME" local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}') local expected_sum=$(cat ${HELM_SUM_FILE}) if [ "$sum" != "$expected_sum" ]; then @@ -141,10 +140,10 @@ installFile() { mkdir -p "$HELM_TMP" tar xf "$HELM_TMP_FILE" -C "$HELM_TMP" - HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/$PROJECT_NAME" - echo "Preparing to install $PROJECT_NAME into ${HELM_INSTALL_DIR}" - runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR" - echo "$PROJECT_NAME installed into $HELM_INSTALL_DIR/$PROJECT_NAME" + 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" } # fail_trap is executed if an error occurs. @@ -152,10 +151,10 @@ fail_trap() { result=$? if [ "$result" != "0" ]; then if [[ -n "$INPUT_ARGUMENTS" ]]; then - echo "Failed to install $PROJECT_NAME with the arguments provided: $INPUT_ARGUMENTS" + echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS" help else - echo "Failed to install $PROJECT_NAME" + echo "Failed to install $BINARY_NAME" fi echo -e "\tFor support, go to https://github.com/helm/helm." fi @@ -166,9 +165,9 @@ fail_trap() { # testVersion tests the installed client to make sure it is working. testVersion() { set +e - HELM="$(which $PROJECT_NAME)" + HELM="$(which $BINARY_NAME)" if [ "$?" = "1" ]; then - echo "$PROJECT_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?' + echo "$BINARY_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?' exit 1 fi set -e From fa5eb64f32c99a2771acf326c4b0e01481d23066 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 15 Apr 2020 16:17:57 -0600 Subject: [PATCH 23/59] fix: rebuild chart after dependency update on install (#7897) * fix: rebuild chart after dependency update on install Signed-off-by: Matt Butcher * add correct debug settings Signed-off-by: Matt Butcher --- cmd/helm/install.go | 5 +++++ cmd/helm/install_test.go | 6 ++++++ cmd/helm/testdata/output/chart-with-subchart-update.txt | 8 ++++++++ .../testcharts/chart-with-subchart-update/Chart.yaml | 8 ++++++++ .../charts/subchart-with-notes/Chart.yaml | 4 ++++ .../charts/subchart-with-notes/templates/NOTES.txt | 1 + .../chart-with-subchart-update/templates/NOTES.txt | 1 + 7 files changed, 33 insertions(+) create mode 100644 cmd/helm/testdata/output/chart-with-subchart-update.txt create mode 100644 cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt create mode 100644 cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 40535e4e3..21a41b9f9 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -210,10 +210,15 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options Getters: p, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, } if err := man.Update(); err != nil { return nil, err } + // Reload the chart with the updated Chart.lock file. + if chartRequested, err = loader.Load(cp); err != nil { + return nil, errors.Wrap(err, "failed reloading chart after repo update") + } } else { return nil, err } diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 57972024f..e3013d713 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -111,6 +111,12 @@ func TestInstall(t *testing.T) { cmd: "install nodeps testdata/testcharts/chart-missing-deps", wantError: true, }, + // Install chart with update-dependency + { + name: "install chart with missing dependencies", + cmd: "install --dependency-update updeps testdata/testcharts/chart-with-subchart-update", + golden: "output/chart-with-subchart-update.txt", + }, // Install, chart with bad dependencies in Chart.yaml in /charts { name: "install chart with bad dependencies in Chart.yaml", diff --git a/cmd/helm/testdata/output/chart-with-subchart-update.txt b/cmd/helm/testdata/output/chart-with-subchart-update.txt new file mode 100644 index 000000000..a4135c782 --- /dev/null +++ b/cmd/helm/testdata/output/chart-with-subchart-update.txt @@ -0,0 +1,8 @@ +NAME: updeps +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +PARENT NOTES diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml new file mode 100644 index 000000000..1bc230200 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-subchart-update/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +description: Chart with subchart that needs to be fetched +name: chart-with-subchart-update +version: 0.0.1 +dependencies: + - name: subchart-with-notes + version: 0.0.1 + repository: file://../chart-with-subchart-notes/charts diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml new file mode 100644 index 000000000..f0fead9ee --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: Subchart with notes +name: subchart-with-notes +version: 0.0.1 diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt b/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt new file mode 100644 index 000000000..1f61a294e --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-subchart-update/charts/subchart-with-notes/templates/NOTES.txt @@ -0,0 +1 @@ +SUBCHART NOTES diff --git a/cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt b/cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt new file mode 100644 index 000000000..9e166d370 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-subchart-update/templates/NOTES.txt @@ -0,0 +1 @@ +PARENT NOTES From a1685b737dd6846bdc35c281bf841b9cc43cb104 Mon Sep 17 00:00:00 2001 From: Liu Ming Date: Thu, 16 Apr 2020 10:11:07 +0800 Subject: [PATCH 24/59] Merge remote-tracking branch 'helm/master' Signed-off-by: Liu Ming --- pkg/action/rollback.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index f717610c8..81812983f 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -210,10 +210,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } } - targetRelease.Info.Status = release.StatusDeployed - deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { return nil, err } @@ -224,5 +221,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.recordRelease(rel) } + targetRelease.Info.Status = release.StatusDeployed + return targetRelease, nil } From df9cf87cbe1164d40845e664a4d193188c77aee9 Mon Sep 17 00:00:00 2001 From: ZouYu Date: Thu, 16 Apr 2020 14:12:40 +0800 Subject: [PATCH 25/59] add unit test for function FindPlugins Signed-off-by: ZouYu --- pkg/plugin/plugin_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index c869e4c86..7bbc3b442 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -264,6 +264,43 @@ func TestLoadAll(t *testing.T) { } } +func TestFindPlugins(t *testing.T) { + cases := []struct { + name string + plugdirs string + expected int + }{ + { + name: "plugdirs is empty", + plugdirs: "", + expected: 0, + }, + { + name: "plugdirs isn't dir", + plugdirs: "./plugin_test.go", + expected: 0, + }, + { + name: "plugdirs doens't have plugin", + plugdirs: ".", + expected: 0, + }, + { + name: "normal", + plugdirs: "./testdata/plugdir", + expected: 3, + }, + } + for _, c := range cases { + t.Run(t.Name(), func(t *testing.T) { + plugin, _ := FindPlugins(c.plugdirs) + if len(plugin) != c.expected { + t.Errorf("expected: %v, got: %v", c.expected, len(plugin)) + } + }) + } +} + func TestSetupEnv(t *testing.T) { name := "pequod" base := filepath.Join("testdata/helmhome/helm/plugins", name) From 3b8521c1f0555f1c56799117ea638fd3e6663336 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 16 Apr 2020 14:12:30 -0400 Subject: [PATCH 26/59] Updating sprig and semver to newer versions Note, there is an issue with a dependency of sprig changing behavior. A test has been added with a description to catch if a behavior breaking change of mergo is used. See https://github.com/imdario/mergo/issues/139 for the mergo issue and sprig for further details on handling this in the future. Closes #7533 Signed-off-by: Matt Farina --- go.mod | 8 ++--- go.sum | 16 ++++++++++ pkg/engine/funcs_test.go | 68 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 72cc667cc..3d6977a5c 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 - github.com/Masterminds/semver/v3 v3.0.3 - github.com/Masterminds/sprig/v3 v3.0.2 + github.com/Masterminds/semver/v3 v3.1.0 + github.com/Masterminds/sprig/v3 v3.1.0 github.com/Masterminds/vcs v1.13.1 github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 github.com/containerd/containerd v1.3.2 @@ -26,9 +26,9 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 github.com/xeipuuv/gojsonschema v1.1.0 - golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 + golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 k8s.io/api v0.18.0 k8s.io/apiextensions-apiserver v0.18.0 k8s.io/apimachinery v0.18.0 diff --git a/go.sum b/go.sum index 872673ab2..c5ed4ef82 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,12 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= +github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= @@ -151,6 +155,7 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= @@ -303,9 +308,13 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/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= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -383,6 +392,7 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= @@ -461,6 +471,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= @@ -484,6 +496,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -536,6 +550,8 @@ golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H 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= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index a405c1c47..ddcf6624c 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -103,3 +103,71 @@ func TestFuncs(t *testing.T) { assert.Equal(t, tt.expect, b.String(), tt.tpl) } } + +// This test to check a function provided by sprig is due to a change in a +// dependency of sprig. mergo in v0.3.9 changed the way it merges and only does +// public fields (i.e. those starting with a capital letter). This test, from +// sprig, fails in the new version. This is a behavior change for mergo that +// impacts sprig and Helm users. This test will help us to not update to a +// version of mergo (even accidentally) that causes a breaking change. See +// sprig changelog and notes for more details. +// Note, Go modules assume semver is never broken. So, there is no way to tell +// the tooling to not update to a minor or patch version. `go get -u` could be +// used to accidentally update mergo. This test and message should catch the +// problem and explain why it's happening. +func TestMerge(t *testing.T) { + dict := map[string]interface{}{ + "src2": map[string]interface{}{ + "h": 10, + "i": "i", + "j": "j", + }, + "src1": map[string]interface{}{ + "a": 1, + "b": 2, + "d": map[string]interface{}{ + "e": "four", + }, + "g": []int{6, 7}, + "i": "aye", + "j": "jay", + "k": map[string]interface{}{ + "l": false, + }, + }, + "dst": map[string]interface{}{ + "a": "one", + "c": 3, + "d": map[string]interface{}{ + "f": 5, + }, + "g": []int{8, 9}, + "i": "eye", + "k": map[string]interface{}{ + "l": true, + }, + }, + } + tpl := `{{merge .dst .src1 .src2}}` + var b strings.Builder + err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) + assert.NoError(t, err) + + expected := map[string]interface{}{ + "a": "one", // key overridden + "b": 2, // merged from src1 + "c": 3, // merged from dst + "d": map[string]interface{}{ // deep merge + "e": "four", + "f": 5, + }, + "g": []int{8, 9}, // overridden - arrays are not merged + "h": 10, // merged from src2 + "i": "eye", // overridden twice + "j": "jay", // overridden and merged + "k": map[string]interface{}{ + "l": true, // overridden + }, + } + assert.Equal(t, expected, dict["dst"]) +} From a34f3115395474fbf3b8a167ef3473eb8b0952e9 Mon Sep 17 00:00:00 2001 From: uzxmx Date: Fri, 17 Apr 2020 03:53:39 +0800 Subject: [PATCH 27/59] Fix nested null value overrides (#7743) * Fix nested null value overrides Signed-off-by: Mingxiang Xue * Fix subchart value deletion Signed-off-by: Mingxiang Xue --- pkg/chartutil/coalesce.go | 24 +++++------ pkg/chartutil/coalesce_test.go | 40 +++++++++++++++++-- .../charts/pequod/charts/ahab/values.yaml | 4 ++ pkg/chartutil/testdata/moby/values.yaml | 2 + 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go index bbdd5f21c..a5ff66aed 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chartutil/coalesce.go @@ -47,10 +47,7 @@ func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, err if valsCopy == nil { valsCopy = make(map[string]interface{}) } - if _, err := coalesce(chrt, valsCopy); err != nil { - return valsCopy, err - } - return coalesceDeps(chrt, valsCopy) + return coalesce(chrt, valsCopy) } // coalesce coalesces the dest values and the chart values, giving priority to the dest values. @@ -186,19 +183,18 @@ func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { // Because dest has higher precedence than src, dest values override src // values. for key, val := range src { - if istable(val) { - switch innerdst, ok := dst[key]; { - case !ok: - dst[key] = val - case istable(innerdst): - CoalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{})) - default: + if dv, ok := dst[key]; ok && dv == nil { + delete(dst, key) + } else if !ok { + dst[key] = val + } else if istable(val) { + if istable(dv) { + CoalesceTables(dv.(map[string]interface{}), val.(map[string]interface{})) + } else { log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val) } - } else if dv, ok := dst[key]; ok && istable(dv) { + } else if istable(dv) { log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val) - } else if !ok { // <- ok is still in scope from preceding conditional. - dst[key] = val } } return dst diff --git a/pkg/chartutil/coalesce_test.go b/pkg/chartutil/coalesce_test.go index 6e82de590..dc1017385 100644 --- a/pkg/chartutil/coalesce_test.go +++ b/pkg/chartutil/coalesce_test.go @@ -31,6 +31,8 @@ right: Null left: NULL front: ~ back: "" +nested: + boat: null global: name: Ishmael @@ -47,6 +49,10 @@ pequod: sail: true ahab: scope: whale + boat: null + nested: + foo: true + bar: null `) func TestCoalesceValues(t *testing.T) { @@ -86,6 +92,7 @@ func TestCoalesceValues(t *testing.T) { {"{{.pequod.name}}", "pequod"}, {"{{.pequod.ahab.name}}", "ahab"}, {"{{.pequod.ahab.scope}}", "whale"}, + {"{{.pequod.ahab.nested.foo}}", "true"}, {"{{.pequod.ahab.global.name}}", "Ishmael"}, {"{{.pequod.ahab.global.subject}}", "Queequeg"}, {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, @@ -114,6 +121,19 @@ func TestCoalesceValues(t *testing.T) { } } + if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { + t.Error("Expected nested boat key to be removed, still present") + } + + subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) + if _, ok := subchart["boat"]; ok { + t.Error("Expected subchart boat key to be removed, still present") + } + + if _, ok := subchart["nested"].(map[string]interface{})["bar"]; ok { + t.Error("Expected subchart nested bar key to be removed, still present") + } + // CoalesceValues should not mutate the passed arguments is.Equal(valsCopy, vals) } @@ -122,24 +142,28 @@ func TestCoalesceTables(t *testing.T) { dst := map[string]interface{}{ "name": "Ishmael", "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": nil, }, "details": map[string]interface{}{ "friends": []string{"Tashtego"}, }, "boat": "pequod", + "hole": nil, } src := map[string]interface{}{ "occupation": "whaler", "address": map[string]interface{}{ - "state": "MA", - "street": "234 Spouter Inn Ct.", + "state": "MA", + "street": "234 Spouter Inn Ct.", + "country": "US", }, "details": "empty", "boat": map[string]interface{}{ "mast": true, }, + "hole": "black", } // What we expect is that anything in dst overrides anything in src, but that @@ -170,6 +194,10 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected state: %v", addr["state"]) } + if _, ok = addr["country"]; ok { + t.Error("The country is not left out.") + } + if det, ok := dst["details"].(map[string]interface{}); !ok { t.Fatalf("Details is the wrong type: %v", dst["details"]) } else if _, ok := det["friends"]; !ok { @@ -179,4 +207,8 @@ func TestCoalesceTables(t *testing.T) { if dst["boat"].(string) != "pequod" { t.Errorf("Expected boat string, got %v", dst["boat"]) } + + if _, ok = dst["hole"]; ok { + t.Error("The hole still exists.") + } } diff --git a/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml b/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml index 86c3f63aa..eee6980fa 100644 --- a/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml +++ b/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml @@ -1,2 +1,6 @@ scope: ahab name: ahab +boat: true +nested: + foo: false + bar: true diff --git a/pkg/chartutil/testdata/moby/values.yaml b/pkg/chartutil/testdata/moby/values.yaml index 54e1ce463..2169d7566 100644 --- a/pkg/chartutil/testdata/moby/values.yaml +++ b/pkg/chartutil/testdata/moby/values.yaml @@ -7,3 +7,5 @@ right: exists left: exists front: exists back: exists +nested: + boat: true From 7b89e66e0c350c7d61443ed6de647298fc9e9a2b Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 16 Apr 2020 14:31:45 -0600 Subject: [PATCH 28/59] fix: Fixed a regression that was introduced with changed nil handling (#7938) Signed-off-by: Matt Butcher --- pkg/chartutil/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index a02cf4b98..28fb28e00 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -113,7 +113,7 @@ serviceAccount: annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: + name: "" podAnnotations: {} From 21d2aa7f2b28e0ecec0cd17825a422a692c71752 Mon Sep 17 00:00:00 2001 From: Elliot Maincourt Date: Thu, 16 Apr 2020 22:53:40 +0200 Subject: [PATCH 29/59] Migrate SQL storage driver to Helm 3 (#7635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Migrate SQL storage driver to Helm 3 Signed-off-by: Elliot Maincourt * Update pkg/storage/driver/sql.go Co-Authored-By: Sebastian Pöhn Signed-off-by: Elliot Maincourt * Add authentication to releases_v3 Signed-off-by: Elliot Maincourt * Fix migration Signed-off-by: Elliot Maincourt * Template the init migration Signed-off-by: Elliot Maincourt * Prevent potential SQL injection Signed-off-by: Elliot Maincourt * Use an SQL querybuilder Signed-off-by: Elliot Maincourt * Remove references to HELM_DRIVER_SQL_DIALECT Signed-off-by: Elliot Maincourt Co-authored-by: Sebastian Pöhn Co-authored-by: Matt Butcher --- cmd/helm/root.go | 21 +- go.mod | 5 + go.sum | 122 ++++---- pkg/action/action.go | 12 + pkg/storage/driver/mock_test.go | 20 ++ pkg/storage/driver/sql.go | 492 ++++++++++++++++++++++++++++++++ pkg/storage/driver/sql_test.go | 442 ++++++++++++++++++++++++++++ 7 files changed, 1048 insertions(+), 66 deletions(-) create mode 100644 pkg/storage/driver/sql.go create mode 100644 pkg/storage/driver/sql_test.go diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 3e3dfa012..2c66d3a09 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -43,16 +43,17 @@ Common actions for Helm: Environment variables: -+------------------+-----------------------------------------------------------------------------+ -| Name | Description | -+------------------+-----------------------------------------------------------------------------+ -| $XDG_CACHE_HOME | set an alternative location for storing cached files. | -| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | -| $XDG_DATA_HOME | set an alternative location for storing Helm data. | -| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory | -| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | -| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | -+------------------+-----------------------------------------------------------------------------+ ++------------------+--------------------------------------------------------------------------------------------------------+ +| Name | Description | ++------------------+--------------------------------------------------------------------------------------------------------+ +| $XDG_CACHE_HOME | set an alternative location for storing cached files. | +| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | +| $XDG_DATA_HOME | set an alternative location for storing Helm data. | +| $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_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | +| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | ++------------------+--------------------------------------------------------------------------------------------------------+ Helm stores configuration based on the XDG base directory specification, so diff --git a/go.mod b/go.mod index 3d6977a5c..64ebfe307 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 + github.com/DATA-DOG/go-sqlmock v1.4.1 github.com/Masterminds/semver/v3 v3.1.0 github.com/Masterminds/sprig/v3 v3.1.0 + github.com/Masterminds/squirrel v1.2.0 github.com/Masterminds/vcs v1.13.1 github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 github.com/containerd/containerd v1.3.2 @@ -18,11 +20,14 @@ require ( github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.7.1 github.com/gosuri/uitable v0.0.4 + github.com/jmoiron/sqlx v1.2.0 + github.com/lib/pq v1.3.0 github.com/mattn/go-shellwords v1.0.10 github.com/mitchellh/copystructure v1.0.0 github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/image-spec v1.0.1 github.com/pkg/errors v0.9.1 + github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index c5ed4ef82..0a51a72e8 100644 --- a/go.sum +++ b/go.sum @@ -23,7 +23,8 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM= +github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= @@ -32,10 +33,10 @@ github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3s github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= -github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= +github.com/Masterminds/squirrel v1.2.0 h1:K1NhbTO21BWG47IVR0OnIZuE0LZcXAYqywrC3Ko53KI= +github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= @@ -58,6 +59,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= @@ -118,13 +120,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/deislabs/oras v0.8.1 h1:If674KraJVpujYR00rzdi0QAmW4BxzMJPVAZJKuhQ0c= github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -231,7 +233,19 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -239,9 +253,9 @@ 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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= @@ -265,7 +279,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -299,7 +312,9 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -319,8 +334,11 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= @@ -331,8 +349,9 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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= @@ -340,6 +359,14 @@ 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= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +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.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= @@ -354,14 +381,20 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -375,7 +408,6 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx 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= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -388,6 +420,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= @@ -420,9 +454,9 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -448,8 +482,14 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW/vf4vopBbhYsRFTGfQWHYXzC0/qYwxI= +github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -491,7 +531,6 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= @@ -523,6 +562,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +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/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -542,9 +583,10 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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= @@ -553,14 +595,10 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 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= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -575,6 +613,7 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -594,6 +633,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -602,12 +642,12 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-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= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -630,34 +670,30 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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-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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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-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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 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/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -676,9 +712,12 @@ 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/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= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -689,8 +728,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= @@ -698,66 +737,37 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 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= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0= -k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/apiextensions-apiserver v0.17.3 h1:WDZWkPcbgvchEdDd7ysL21GGPx3UKZQLDZXEkevT6n4= -k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY= k8s.io/apiextensions-apiserver v0.18.0 h1:HN4/P8vpGZFvB5SOMuPPH2Wt9Y/ryX+KRvIyAkchu1Q= k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= -k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg= -k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY= k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= -k8s.io/cli-runtime v0.17.3 h1:0ZlDdJgJBKsu77trRUynNiWsRuAvAVPBNaQfnt/1qtc= -k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA= k8s.io/cli-runtime v0.18.0 h1:jG8XpSqQ5TrV0N+EZ3PFz6+gqlCk71dkggWCCq9Mq34= k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= -k8s.io/client-go v0.17.3 h1:deUna1Ksx05XeESH6XGCyONNFfiQmDdqeqUvicvP6nU= -k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4= k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/component-base v0.17.3 h1:hQzTSshY14aLSR6WGIYvmw+w+u6V4d+iDR2iDGMrlUg= -k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8= k8s.io/component-base v0.18.0 h1:I+lP0fNfsEdTDpHaL61bCAqTZLoiWjEEP304Mo5ZQgE= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/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/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kubectl v0.17.3 h1:9HHYj07kuFkM+sMJMOyQX29CKWq4lvKAG1UIPxNPMQ4= -k8s.io/kubectl v0.17.3/go.mod h1:NUn4IBY7f7yCMwSop2HCXlw/MVYP4HJBiUmOR3n9w28= k8s.io/kubectl v0.18.0 h1:hu52Ndq/d099YW+3sS3VARxFz61Wheiq8K9S7oa82Dk= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.17.3/go.mod h1:HEJGy1fhHOjHggW9rMDBJBD3YuGroH3Y1pnIRw9FFaI= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= 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 v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= 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= diff --git a/pkg/action/action.go b/pkg/action/action.go index 05a133abf..5da901635 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "fmt" + "os" "path" "regexp" @@ -255,6 +257,16 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac } d.SetNamespace(namespace) store = storage.Init(d) + case "sql": + d, err := driver.NewSQL( + os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), + log, + namespace, + ) + if err != nil { + panic(fmt.Sprintf("Unable to instantiate SQL driver: %v", err)) + } + store = storage.Init(d) default: // Not sure what to do here. panic("Unknown driver in HELM_DRIVER: " + helmDriver) diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 77ddca43d..c0236ece8 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -21,6 +21,10 @@ import ( "fmt" "testing" + sqlmock "github.com/DATA-DOG/go-sqlmock" + sq "github.com/Masterminds/squirrel" + "github.com/jmoiron/sqlx" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -243,3 +247,19 @@ func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav delete(mock.objects, name) return nil } + +// newTestFixtureSQL mocks the SQL database (for testing purposes) +func newTestFixtureSQL(t *testing.T, releases ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { + sqlDB, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("error when opening stub database connection: %v", err) + } + + sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") + return &SQL{ + db: sqlxDB, + Log: func(a string, b ...interface{}) {}, + namespace: "default", + statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + }, mock +} diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go new file mode 100644 index 000000000..f68f50f54 --- /dev/null +++ b/pkg/storage/driver/sql.go @@ -0,0 +1,492 @@ +/* +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 driver // import "helm.sh/helm/v3/pkg/storage/driver" + +import ( + "fmt" + "sort" + "time" + + "github.com/jmoiron/sqlx" + migrate "github.com/rubenv/sql-migrate" + + sq "github.com/Masterminds/squirrel" + + // Import pq for postgres dialect + _ "github.com/lib/pq" + + rspb "helm.sh/helm/v3/pkg/release" +) + +var _ Driver = (*SQL)(nil) + +var labelMap = map[string]struct{}{ + "modifiedAt": {}, + "createdAt": {}, + "version": {}, + "status": {}, + "owner": {}, + "name": {}, +} + +const postgreSQLDialect = "postgres" + +// SQLDriverName is the string name of this driver. +const SQLDriverName = "SQL" + +const sqlReleaseTableName = "releases_v1" + +const ( + sqlReleaseTableKeyColumn = "key" + sqlReleaseTableTypeColumn = "type" + sqlReleaseTableBodyColumn = "body" + sqlReleaseTableNameColumn = "name" + sqlReleaseTableNamespaceColumn = "namespace" + sqlReleaseTableVersionColumn = "version" + sqlReleaseTableStatusColumn = "status" + sqlReleaseTableOwnerColumn = "owner" + sqlReleaseTableCreatedAtColumn = "createdAt" + sqlReleaseTableModifiedAtColumn = "modifiedAt" +) + +const ( + sqlReleaseDefaultOwner = "helm" + sqlReleaseDefaultType = "helm.sh/release.v1" +) + +// SQL is the sql storage driver implementation. +type SQL struct { + db *sqlx.DB + namespace string + statementBuilder sq.StatementBuilderType + + Log func(string, ...interface{}) +} + +// Name returns the name of the driver. +func (s *SQL) Name() string { + return SQLDriverName +} + +func (s *SQL) ensureDBSetup() error { + // Populate the database with the relations we need if they don't exist yet + migrations := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "init", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(67), + %s VARCHAR(64) NOT NULL, + %s TEXT NOT NULL, + %s VARCHAR(64) NOT NULL, + %s VARCHAR(64) NOT NULL, + %s INTEGER NOT NULL, + %s TEXT NOT NULL, + %s TEXT NOT NULL, + %s INTEGER NOT NULL, + %s INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY(%s, %s) + ); + CREATE INDEX ON %s (%s, %s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + + GRANT ALL ON %s TO PUBLIC; + + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableVersionColumn, + sqlReleaseTableName, + sqlReleaseTableStatusColumn, + sqlReleaseTableName, + sqlReleaseTableOwnerColumn, + sqlReleaseTableName, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableName, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableName, + sqlReleaseTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DROP TABLE %s; + `, sqlReleaseTableName), + }, + }, + }, + } + + _, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up) + return err +} + +// SQLReleaseWrapper describes how Helm releases are stored in an SQL database +type SQLReleaseWrapper struct { + // The primary key, made of {release-name}.{release-version} + Key string `db:"key"` + + // See https://github.com/helm/helm/blob/master/pkg/storage/driver/secrets.go#L236 + Type string `db:"type"` + + // The rspb.Release body, as a base64-encoded string + Body string `db:"body"` + + // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) + // we implemented. Note that allowing Helm users to filter against new dimensions will require a + // new migration to be added, and the Create and/or update functions to be updated accordingly. + Name string `db:"name"` + Namespace string `db:"namespace"` + Version int `db:"version"` + Status string `db:"status"` + Owner string `db:"owner"` + CreatedAt int `db:"createdAt"` + ModifiedAt int `db:"modifiedAt"` +} + +// NewSQL initializes a new sql driver. +func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { + db, err := sqlx.Connect(postgreSQLDialect, connectionString) + if err != nil { + return nil, err + } + + driver := &SQL{ + db: db, + Log: logger, + statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + } + + if err := driver.ensureDBSetup(); err != nil { + return nil, err + } + + driver.namespace = namespace + + return driver, nil +} + +// Get returns the release named by key. +func (s *SQL) Get(key string) (*rspb.Release, error) { + var record SQLReleaseWrapper + + qb := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + + query, args, err := qb.ToSql() + if err != nil { + s.Log("failed to build query: %v", err) + return nil, err + } + + // Get will return an error if the result is empty + if err := s.db.Get(&record, query, args...); err != nil { + s.Log("got SQL error when getting release %s: %v", key, err) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Log("get: failed to decode data %q: %v", key, err) + return nil, err + } + + return release, nil +} + +// List returns the list of all releases such that filter(release) == true +func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + query, args, err := sb.ToSql() + if err != nil { + s.Log("failed to build query: %v", err) + return nil, err + } + + var records = []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Log("list: failed to list: %v", err) + return nil, err + } + + var releases []*rspb.Release + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Log("list: failed to decode release: %v: %v", record, err) + continue + } + if filter(release) { + releases = append(releases, release) + } + } + + return releases, nil +} + +// Query returns the set of releases that match the provided set of labels. +func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName) + + keys := make([]string, 0, len(labels)) + for key := range labels { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + if _, ok := labelMap[key]; ok { + sb = sb.Where(sq.Eq{key: labels[key]}) + } else { + s.Log("unknown label %s", key) + return nil, fmt.Errorf("unknow label %s", key) + } + } + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + // Build our query + query, args, err := sb.ToSql() + if err != nil { + s.Log("failed to build query: %v", err) + return nil, err + } + + var records = []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Log("list: failed to query with labels: %v", err) + return nil, err + } + + var releases []*rspb.Release + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Log("list: failed to decode release: %v: %v", record, err) + continue + } + releases = append(releases, release) + } + + if len(releases) == 0 { + return nil, ErrReleaseNotFound + } + + return releases, nil +} + +// Create creates a new release. +func (s *SQL) Create(key string, rls *rspb.Release) error { + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Log("failed to encode release: %v", err) + return err + } + + transaction, err := s.db.Beginx() + if err != nil { + s.Log("failed to start SQL transaction: %v", err) + return fmt.Errorf("error beginning transaction: %v", err) + } + + insertQuery, args, err := s.statementBuilder. + Insert(sqlReleaseTableName). + Columns( + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ). + Values( + key, + sqlReleaseDefaultType, + body, + rls.Name, + namespace, + int(rls.Version), + rls.Info.Status.String(), + sqlReleaseDefaultOwner, + int(time.Now().Unix()), + ).ToSql() + if err != nil { + s.Log("failed to build insert query: %v", err) + return err + } + + if _, err := transaction.Exec(insertQuery, args...); err != nil { + defer transaction.Rollback() + + selectQuery, args, buildErr := s.statementBuilder. + Select(sqlReleaseTableKeyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if buildErr != nil { + s.Log("failed to build select query: %v", buildErr) + return err + } + + var record SQLReleaseWrapper + if err := transaction.Get(&record, selectQuery, args...); err == nil { + s.Log("release %s already exists", key) + return ErrReleaseExists + } + + s.Log("failed to store release %s in SQL database: %v", key, err) + return err + } + defer transaction.Commit() + + return nil +} + +// Update updates a release. +func (s *SQL) Update(key string, rls *rspb.Release) error { + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Log("failed to encode release: %v", err) + return err + } + + query, args, err := s.statementBuilder. + Update(sqlReleaseTableName). + Set(sqlReleaseTableBodyColumn, body). + Set(sqlReleaseTableNameColumn, rls.Name). + Set(sqlReleaseTableVersionColumn, int(rls.Version)). + Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()). + Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner). + Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). + ToSql() + + if err != nil { + s.Log("failed to build update query: %v", err) + return err + } + + if _, err := s.db.Exec(query, args...); err != nil { + s.Log("failed to update release %s in SQL database: %v", key, err) + return err + } + + return nil +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (s *SQL) Delete(key string) (*rspb.Release, error) { + transaction, err := s.db.Beginx() + if err != nil { + s.Log("failed to start SQL transaction: %v", err) + return nil, fmt.Errorf("error beginning transaction: %v", err) + } + + selectQuery, args, err := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Log("failed to build select query: %v", err) + return nil, err + } + + var record SQLReleaseWrapper + err = transaction.Get(&record, selectQuery, args...) + if err != nil { + s.Log("release %s not found: %v", key, err) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Log("failed to decode release %s: %v", key, err) + transaction.Rollback() + return nil, err + } + defer transaction.Commit() + + deleteQuery, args, err := s.statementBuilder. + Delete(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Log("failed to build select query: %v", err) + return nil, err + } + + _, err = transaction.Exec(deleteQuery, args...) + return release, err +} diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go new file mode 100644 index 000000000..1562a90aa --- /dev/null +++ b/pkg/storage/driver/sql_test.go @@ -0,0 +1,442 @@ +/* +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 driver + +import ( + "fmt" + "reflect" + "regexp" + "testing" + "time" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + + rspb "helm.sh/helm/v3/pkg/release" +) + +func TestSQLName(t *testing.T) { + sqlDriver, _ := newTestFixtureSQL(t) + if sqlDriver.Name() != SQLDriverName { + t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) + } +} + +func TestSQLGet(t *testing.T) { + vers := int(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + body, _ := encodeRelease(rel) + + sqlDriver, mock := newTestFixtureSQL(t) + + query := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(query). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + body, + ), + ).RowsWillBeClosed() + + got, err := sqlDriver.Get(key) + if err != nil { + t.Fatalf("Failed to get release: %v", err) + } + + if !reflect.DeepEqual(rel, got) { + t.Errorf("Expected release {%v}, got {%v}", rel, got) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSQLList(t *testing.T) { + body1, _ := encodeRelease(releaseStub("key-1", 1, "default", rspb.StatusUninstalled)) + body2, _ := encodeRelease(releaseStub("key-2", 1, "default", rspb.StatusUninstalled)) + body3, _ := encodeRelease(releaseStub("key-3", 1, "default", rspb.StatusDeployed)) + body4, _ := encodeRelease(releaseStub("key-4", 1, "default", rspb.StatusDeployed)) + body5, _ := encodeRelease(releaseStub("key-5", 1, "default", rspb.StatusSuperseded)) + body6, _ := encodeRelease(releaseStub("key-6", 1, "default", rspb.StatusSuperseded)) + + sqlDriver, mock := newTestFixtureSQL(t) + + for i := 0; i < 3; i++ { + query := fmt.Sprintf( + "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableOwnerColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }). + AddRow(body1). + AddRow(body2). + AddRow(body3). + AddRow(body4). + AddRow(body5). + AddRow(body6), + ).RowsWillBeClosed() + } + + // list all deleted releases + del, err := sqlDriver.List(func(rel *rspb.Release) bool { + return rel.Info.Status == rspb.StatusUninstalled + }) + // check + if err != nil { + t.Errorf("Failed to list deleted: %v", err) + } + if len(del) != 2 { + t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) + } + + // list all deployed releases + dpl, err := sqlDriver.List(func(rel *rspb.Release) bool { + return rel.Info.Status == rspb.StatusDeployed + }) + // check + if err != nil { + t.Errorf("Failed to list deployed: %v", err) + } + if len(dpl) != 2 { + t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) + } + + // list all superseded releases + ssd, err := sqlDriver.List(func(rel *rspb.Release) bool { + return rel.Info.Status == rspb.StatusSuperseded + }) + // check + if err != nil { + t.Errorf("Failed to list superseded: %v", err) + } + if len(ssd) != 2 { + t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSqlCreate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + query := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ) + + mock.ExpectBegin() + mock. + ExpectExec(regexp.QuoteMeta(query)). + WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + if err := sqlDriver.Create(key, rel); err != nil { + t.Fatalf("failed to create release with key %s: %v", key, err) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSqlCreateAlreadyExists(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + insertQuery := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ) + + // Insert fails (primary key already exists) + mock.ExpectBegin() + mock. + ExpectExec(regexp.QuoteMeta(insertQuery)). + WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). + WillReturnError(fmt.Errorf("dialect dependent SQL error")) + + selectQuery := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlReleaseTableKeyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + // Let's check that we do make sure the error is due to a release already existing + mock. + ExpectQuery(selectQuery). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableKeyColumn, + }).AddRow( + key, + ), + ).RowsWillBeClosed() + mock.ExpectRollback() + + if err := sqlDriver.Create(key, rel); err == nil { + t.Fatalf("failed to create release with key %s: %v", key, err) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSqlUpdate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + query := fmt.Sprintf( + "UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6 WHERE %s = $7 AND %s = $8", + sqlReleaseTableName, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectExec(regexp.QuoteMeta(query)). + WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix()), key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + + if err := sqlDriver.Update(key, rel); err != nil { + t.Fatalf("failed to update release with key %s: %v", key, err) + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSqlQuery(t *testing.T) { + // Reflect actual use cases in ../storage.go + labelSetDeployed := map[string]string{ + "name": "smug-pigeon", + "owner": sqlReleaseDefaultOwner, + "status": "deployed", + } + labelSetAll := map[string]string{ + "name": "smug-pigeon", + "owner": sqlReleaseDefaultOwner, + } + + supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded) + supersededReleaseBody, _ := encodeRelease(supersededRelease) + deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed) + deployedReleaseBody, _ := encodeRelease(deployedRelease) + + // Let's actually start our test + sqlDriver, mock := newTestFixtureSQL(t) + + query := fmt.Sprintf( + "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4", + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableNameColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default"). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + deployedReleaseBody, + ), + ).RowsWillBeClosed() + + query = fmt.Sprintf( + "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableNameColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default"). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + supersededReleaseBody, + ).AddRow( + deployedReleaseBody, + ), + ).RowsWillBeClosed() + + results, err := sqlDriver.Query(labelSetDeployed) + if err != nil { + t.Fatalf("failed to query for deployed smug-pigeon release: %v", err) + } + + for _, res := range results { + if !reflect.DeepEqual(res, deployedRelease) { + t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res) + } + } + + results, err = sqlDriver.Query(labelSetAll) + if err != nil { + t.Fatalf("failed to query release history for smug-pigeon: %v", err) + } + + if len(results) != 2 { + t.Errorf("expected a resultset of size 2, got %d", len(results)) + } + + for _, res := range results { + if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) { + t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) + } + } + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } +} + +func TestSqlDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + body, _ := encodeRelease(rel) + + sqlDriver, mock := newTestFixtureSQL(t) + + selectQuery := fmt.Sprintf( + "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock.ExpectBegin() + mock. + ExpectQuery(regexp.QuoteMeta(selectQuery)). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + body, + ), + ).RowsWillBeClosed() + + deleteQuery := fmt.Sprintf( + "DELETE FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectExec(regexp.QuoteMeta(deleteQuery)). + WithArgs(key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + deletedRelease, err := sqlDriver.Delete(key) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("sql expectations weren't met: %v", err) + } + if err != nil { + t.Fatalf("failed to delete release with key %q: %v", key, err) + } + + if !reflect.DeepEqual(rel, deletedRelease) { + t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease) + } +} From 853ba2de16a04d0715c44937162e0b58752a99d6 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 16 Apr 2020 14:54:15 -0600 Subject: [PATCH 30/59] fix: removed inaccurate comment (#7937) Signed-off-by: Matt Butcher --- pkg/chartutil/coalesce.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go index a5ff66aed..94b7f35fa 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chartutil/coalesce.go @@ -35,8 +35,6 @@ import ( // - A chart has access to all of the variables for it, as well as all of // the values destined for its dependencies. func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) { - // create a copy of vals and then pass it to coalesce - // and coalesceDeps, as both will mutate the passed values v, err := copystructure.Copy(vals) if err != nil { return vals, err From 8e1c34ef045b350c68974f8ca12e2cb6d245d3f3 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 17 Apr 2020 10:42:52 -0400 Subject: [PATCH 31/59] Updating get stripts to skip pre-releases A recent change to the get scripts causes them to pickup pre-releases in addition to stable releases. This update causes only stable releases to be fetched by the get scripts. Fixed #7941 Signed-off-by: Matt Farina --- scripts/get | 4 ++-- scripts/get-helm-3 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/get b/scripts/get index 3da11d4a4..afa02bbb1 100755 --- a/scripts/get +++ b/scripts/get @@ -82,9 +82,9 @@ checkDesiredVersion() { local release_url="https://github.com/helm/helm/releases" if type "curl" > /dev/null; then - TAG=$(curl -Ls $release_url | grep 'href="/helm/helm/releases/tag/v2.' | 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}') + TAG=$(curl -Ls $release_url | grep 'href="/helm/helm/releases/tag/v2.[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 - TAG=$(wget $release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v2.' | 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}') + TAG=$(wget $release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v2.[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 TAG=$DESIRED_VERSION diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index adadf5953..201065717 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -79,9 +79,9 @@ checkDesiredVersion() { # Get tag from release URL local latest_release_url="https://github.com/helm/helm/releases" if type "curl" > /dev/null; then - TAG=$(curl -Ls $latest_release_url | grep 'href="/helm/helm/releases/tag/v3.' | 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}') + 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 - TAG=$(wget $latest_release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v3.' | 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}') + 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 TAG=$DESIRED_VERSION From 1911870958098b774973c6fe56bfdf4441f61596 Mon Sep 17 00:00:00 2001 From: Matthew Morrissette Date: Fri, 17 Apr 2020 10:56:29 -0700 Subject: [PATCH 32/59] fix(helm): allow a previously failed release to be upgraded (#7653) Signed-off-by: Matt Morrissette --- ...e-with-bad-or-missing-existing-release.txt | 1 + cmd/helm/upgrade_test.go | 23 +++++++++++++ pkg/action/upgrade.go | 32 ++++++++++++++----- pkg/storage/driver/driver.go | 25 ++++++++++++++- pkg/storage/storage.go | 4 +-- 5 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt diff --git a/cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt b/cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt new file mode 100644 index 000000000..8f24574a6 --- /dev/null +++ b/cmd/helm/testdata/output/upgrade-with-bad-or-missing-existing-release.txt @@ -0,0 +1 @@ +Error: UPGRADE FAILED: "funny-bunny" has no deployed releases diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 3a6d75adc..6f260ae57 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -80,6 +80,10 @@ func TestUpgradeCmd(t *testing.T) { missingDepsPath := "testdata/testcharts/chart-missing-deps" badDepsPath := "testdata/testcharts/chart-bad-requirements" + relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release { + return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status}) + } + relMock := func(n string, v int, ch *chart.Chart) *release.Release { return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch}) } @@ -139,6 +143,25 @@ func TestUpgradeCmd(t *testing.T) { golden: "output/upgrade-with-bad-dependencies.txt", wantError: true, }, + { + name: "upgrade a non-existent release", + cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath), + golden: "output/upgrade-with-bad-or-missing-existing-release.txt", + wantError: true, + }, + { + name: "upgrade a failed release", + cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath), + golden: "output/upgrade.txt", + rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusFailed)}, + }, + { + name: "upgrade a pending install release", + cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath), + golden: "output/upgrade-with-bad-or-missing-existing-release.txt", + wantError: true, + rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusPendingInstall)}, + }, } runTestCmd(t, tests) } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 7565da5a9..67872aa2f 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -33,6 +33,7 @@ import ( "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/storage/driver" ) // Upgrade is the action for upgrading releases. @@ -159,12 +160,33 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, errMissingChart } - // finds the deployed release with the given name - currentRelease, err := u.cfg.Releases.Deployed(name) + // finds the last non-deleted release with the given name + lastRelease, err := u.cfg.Releases.Last(name) if err != nil { + // to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist + if errors.Is(err, driver.ErrReleaseNotFound) { + return nil, nil, driver.NewErrNoDeployedReleases(name) + } return nil, nil, err } + var currentRelease *release.Release + if lastRelease.Info.Status == release.StatusDeployed { + // no need to retrieve the last deployed release from storage as the last release is deployed + currentRelease = lastRelease + } else { + // finds the deployed release with the given name + currentRelease, err = u.cfg.Releases.Deployed(name) + if err != nil { + if errors.Is(err, driver.ErrNoDeployedReleases) && + (lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded) { + currentRelease = lastRelease + } else { + return nil, nil, err + } + } + } + // determine if values will be reused vals, err = u.reuseValues(chart, currentRelease, vals) if err != nil { @@ -175,12 +197,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - // finds the non-deleted release with the given name - lastRelease, err := u.cfg.Releases.Last(name) - if err != nil { - return nil, nil, err - } - // Increment revision count. This is passed to templates, and also stored on // the release object. revision := lastRelease.Version + 1 diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go index 9a1fbc579..9c01f3766 100644 --- a/pkg/storage/driver/driver.go +++ b/pkg/storage/driver/driver.go @@ -17,6 +17,8 @@ limitations under the License. package driver // import "helm.sh/helm/v3/pkg/storage/driver" import ( + "fmt" + "github.com/pkg/errors" rspb "helm.sh/helm/v3/pkg/release" @@ -28,9 +30,30 @@ var ( // ErrReleaseExists indicates that a release already exists. ErrReleaseExists = errors.New("release: already exists") // ErrInvalidKey indicates that a release key could not be parsed. - ErrInvalidKey = errors.Errorf("release: invalid key") + ErrInvalidKey = errors.New("release: invalid key") + // ErrNoDeployedReleases indicates that there are no releases with the given key in the deployed state + ErrNoDeployedReleases = errors.New("has no deployed releases") ) +// StorageDriverError records an error and the release name that caused it +type StorageDriverError struct { + ReleaseName string + Err error +} + +func (e *StorageDriverError) Error() string { + return fmt.Sprintf("%q %s", e.ReleaseName, e.Err.Error()) +} + +func (e *StorageDriverError) Unwrap() error { return e.Err } + +func NewErrNoDeployedReleases(releaseName string) error { + return &StorageDriverError{ + ReleaseName: releaseName, + Err: ErrNoDeployedReleases, + } +} + // Creator is the interface that wraps the Create method. // // Create stores the release or returns ErrReleaseExists diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 3e62ae9ee..c195120cd 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -116,7 +116,7 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { } if len(ls) == 0 { - return nil, errors.Errorf("%q has no deployed releases", name) + return nil, driver.NewErrNoDeployedReleases(name) } // If executed concurrently, Helm's database gets corrupted @@ -140,7 +140,7 @@ func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { return ls, nil } if strings.Contains(err.Error(), "not found") { - return nil, errors.Errorf("%q has no deployed releases", name) + return nil, driver.NewErrNoDeployedReleases(name) } return nil, err } From b83d3d415c8a55ad2af0f2ace0642445b0dbde0c Mon Sep 17 00:00:00 2001 From: Predrag Knezevic Date: Mon, 20 Apr 2020 14:48:11 +0200 Subject: [PATCH 33/59] fs_test: use os.Getuid() instead user.Current() to determine if a test is executed with root privileges. This change lower the expectations on test env setup, i.e. tests could be executed in a container under a random UID, without require an user in /etc/passwd Signed-off-by: Predrag Knezevic --- internal/third_party/dep/fs/fs_test.go | 49 +++++++------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index bf4b803f8..d3ff2c694 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -35,7 +35,6 @@ import ( "io/ioutil" "os" "os/exec" - "os/user" "path/filepath" "runtime" "sync" @@ -175,13 +174,9 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var currentUser, err = user.Current() + var currentUID = os.Getuid() - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } - - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -214,13 +209,9 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var currentUser, err = user.Current() + var currentUID = os.Getuid() - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } - - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -314,13 +305,9 @@ func TestCopyDirFailOpen(t *testing.T) { t.Skip("skipping on windows") } - var currentUser, err = user.Current() - - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } + var currentUID = os.Getuid() - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -483,13 +470,9 @@ func TestCopyFileFail(t *testing.T) { t.Skip("skipping on windows") } - var currentUser, err = user.Current() + var currentUID = os.Getuid() - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } - - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -574,13 +557,9 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { func TestIsDir(t *testing.T) { - var currentUser, err = user.Current() + var currentUID = os.Getuid() - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } - - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -631,13 +610,9 @@ func TestIsDir(t *testing.T) { func TestIsSymlink(t *testing.T) { - var currentUser, err = user.Current() - - if err != nil { - t.Fatalf("Failed to get name of current user: %s", err) - } + var currentUID = os.Getuid() - if currentUser.Name == "root" { + if currentUID == 0 { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } From d0726e07abed91554f1c62351dc62da4bf21f469 Mon Sep 17 00:00:00 2001 From: Andre Sencioles Date: Wed, 22 Apr 2020 07:16:55 +1200 Subject: [PATCH 34/59] Parse reference templates in predictable order (#7702) * Parse reference templates in predictable order Fix issue #7701 Signed-off-by: Andre Sencioles * Add test case for issue #7701 regression Signed-off-by: Andre Sencioles * gofmt Signed-off-by: Andre Sencioles --- pkg/engine/engine.go | 4 +++- pkg/engine/engine_test.go | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 1cc94d685..94ab1da95 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -213,6 +213,7 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) // We want to parse the templates in a predictable order. The order favors // higher-level (in file system) templates over deeply nested templates. keys := sortTemplates(tpls) + referenceKeys := sortTemplates(referenceTpls) for _, filename := range keys { r := tpls[filename] @@ -223,8 +224,9 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) // Adding the reference templates to the template context // so they can be referenced in the tpl function - for filename, r := range referenceTpls { + for _, filename := range referenceKeys { if t.Lookup(filename) == nil { + r := referenceTpls[filename] if _, err := t.New(filename).Parse(r.tpl); err != nil { return map[string]string{}, cleanupParseError(filename, err) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index d5f36aac8..c1cdf625e 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -126,6 +126,46 @@ func TestRender(t *testing.T) { } } +func TestRenderRefsOrdering(t *testing.T) { + parentChart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "parent", + Version: "1.2.3", + }, + Templates: []*chart.File{ + {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)}, + {Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)}, + }, + } + childChart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "child", + Version: "1.2.3", + }, + Templates: []*chart.File{ + {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)}, + }, + } + parentChart.AddDependency(childChart) + + expect := map[string]string{ + "parent/templates/test.yaml": "parent value", + } + + for i := 0; i < 100; i++ { + out, err := Render(parentChart, chartutil.Values{}) + if err != nil { + t.Fatalf("Failed to render templates: %s", err) + } + + for name, data := range expect { + if out[name] != data { + t.Fatalf("Expected %q, got %q (iteraction %d)", data, out[name], i+1) + } + } + } +} + func TestRenderInternals(t *testing.T) { // Test the internals of the rendering tool. From bb47286f09331271e88e6047c51fd6e0ea936506 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 22 Apr 2020 10:09:34 -0600 Subject: [PATCH 35/59] fix linting error with lookup function (#7969) Signed-off-by: Matt Butcher --- pkg/action/action.go | 133 +++++++++++++++++++++++++++++++++++++ pkg/action/install.go | 122 +--------------------------------- pkg/action/install_test.go | 22 ++++++ pkg/action/upgrade.go | 2 +- pkg/engine/engine.go | 5 +- pkg/engine/engine_test.go | 2 +- pkg/engine/funcs.go | 5 ++ pkg/engine/funcs_test.go | 5 ++ pkg/engine/lookup_func.go | 8 ++- 9 files changed, 178 insertions(+), 126 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index 5da901635..a8437d729 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -17,10 +17,13 @@ limitations under the License. package action import ( + "bytes" "fmt" "os" "path" + "path/filepath" "regexp" + "strings" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -30,9 +33,13 @@ import ( "k8s.io/client-go/rest" "helm.sh/helm/v3/internal/experimental/registry" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/engine" "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/time" @@ -86,6 +93,132 @@ type Configuration struct { Log func(string, ...interface{}) } +// renderResources renders the templates in a chart +// +// TODO: This function is badly in need of a refactor. +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { + hs := []*release.Hook{} + b := bytes.NewBuffer(nil) + + caps, err := c.getCapabilities() + if err != nil { + return hs, b, "", err + } + + if ch.Metadata.KubeVersion != "" { + if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { + return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) + } + } + + var files map[string]string + var err2 error + + // A `helm template` or `helm install --dry-run` should not talk to the remote cluster. + // It will break in interesting and exotic ways because other data (e.g. discovery) + // is mocked. It is not up to the template author to decide when the user wants to + // connect to the cluster. So when the user says to dry run, respect the user's + // wishes and do not connect to the cluster. + if !dryRun && c.RESTClientGetter != nil { + rest, err := c.RESTClientGetter.ToRESTConfig() + if err != nil { + return hs, b, "", err + } + files, err2 = engine.RenderWithClient(ch, values, rest) + } else { + files, err2 = engine.Render(ch, values) + } + + if err2 != nil { + return hs, b, "", err2 + } + + // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, + // pull it out of here into a separate file so that we can actually use the output of the rendered + // text file. We have to spin through this map because the file contains path information, so we + // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip + // it in the sortHooks. + var notesBuffer bytes.Buffer + for k, v := range files { + if strings.HasSuffix(k, notesFileSuffix) { + if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) { + // If buffer contains data, add newline before adding more + if notesBuffer.Len() > 0 { + notesBuffer.WriteString("\n") + } + notesBuffer.WriteString(v) + } + delete(files, k) + } + } + notes := notesBuffer.String() + + // Sort hooks, manifests, and partials. Only hooks and manifests are returned, + // as partials are not used after renderer.Render. Empty manifests are also + // removed here. + hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder) + if err != nil { + // By catching parse errors here, we can prevent bogus releases from going + // to Kubernetes. + // + // We return the files as a big blob of data to help the user debug parser + // errors. + for name, content := range files { + if strings.TrimSpace(content) == "" { + continue + } + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) + } + return hs, b, "", err + } + + // Aggregate all valid manifests into one big doc. + fileWritten := make(map[string]bool) + + if includeCrds { + for _, crd := range ch.CRDObjects() { + if outputDir == "" { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) + } else { + err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) + if err != nil { + return hs, b, "", err + } + fileWritten[crd.Name] = true + } + } + } + + for _, m := range manifests { + if outputDir == "" { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + } else { + newDir := outputDir + if useReleaseName { + newDir = filepath.Join(outputDir, releaseName) + } + // NOTE: We do not have to worry about the post-renderer because + // output dir is only used by `helm template`. In the next major + // release, we should move this logic to template only as it is not + // used by install or upgrade + err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) + if err != nil { + return hs, b, "", err + } + fileWritten[m.Name] = true + } + } + + if pr != nil { + b, err = pr.Run(b) + if err != nil { + return hs, b, notes, errors.Wrap(err, "error while running post render on files") + } + } + + return hs, b, notes, nil +} + // RESTClientGetter gets the rest client type RESTClientGetter interface { ToRESTConfig() (*rest.Config, error) diff --git a/pkg/action/install.go b/pkg/action/install.go index 4b4dd9214..10a9644dd 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -39,7 +39,6 @@ import ( "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/engine" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/kube" kubefake "helm.sh/helm/v3/pkg/kube/fake" @@ -232,7 +231,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() @@ -475,125 +474,6 @@ func (i *Install) replaceRelease(rel *release.Release) error { return i.recordRelease(last) } -// renderResources renders the templates in a chart -func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer) ([]*release.Hook, *bytes.Buffer, string, error) { - hs := []*release.Hook{} - b := bytes.NewBuffer(nil) - - caps, err := c.getCapabilities() - if err != nil { - return hs, b, "", err - } - - if ch.Metadata.KubeVersion != "" { - if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) - } - } - - var files map[string]string - var err2 error - - if c.RESTClientGetter != nil { - rest, err := c.RESTClientGetter.ToRESTConfig() - if err != nil { - return hs, b, "", err - } - files, err2 = engine.RenderWithClient(ch, values, rest) - } else { - files, err2 = engine.Render(ch, values) - } - - if err2 != nil { - return hs, b, "", err2 - } - - // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, - // pull it out of here into a separate file so that we can actually use the output of the rendered - // text file. We have to spin through this map because the file contains path information, so we - // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip - // it in the sortHooks. - var notesBuffer bytes.Buffer - for k, v := range files { - if strings.HasSuffix(k, notesFileSuffix) { - if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) { - // If buffer contains data, add newline before adding more - if notesBuffer.Len() > 0 { - notesBuffer.WriteString("\n") - } - notesBuffer.WriteString(v) - } - delete(files, k) - } - } - notes := notesBuffer.String() - - // Sort hooks, manifests, and partials. Only hooks and manifests are returned, - // as partials are not used after renderer.Render. Empty manifests are also - // removed here. - hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder) - if err != nil { - // By catching parse errors here, we can prevent bogus releases from going - // to Kubernetes. - // - // We return the files as a big blob of data to help the user debug parser - // errors. - for name, content := range files { - if strings.TrimSpace(content) == "" { - continue - } - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) - } - return hs, b, "", err - } - - // Aggregate all valid manifests into one big doc. - fileWritten := make(map[string]bool) - - if includeCrds { - for _, crd := range ch.CRDObjects() { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) - } else { - err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[crd.Name] = true - } - } - } - - for _, m := range manifests { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) - } else { - newDir := outputDir - if useReleaseName { - newDir = filepath.Join(outputDir, releaseName) - } - // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not - // used by install or upgrade - err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[m.Name] = true - } - } - - if pr != nil { - b, err = pr.Run(b) - if err != nil { - return hs, b, notes, errors.Wrap(err, "error while running post render on files") - } - } - - return hs, b, notes, nil -} - // write the to /. controls if the file is created or content will be appended func writeToFile(outputDir string, name string, data string, append bool) error { outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index bf47895a1..6c4012cfd 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/assert" "helm.sh/helm/v3/internal/test" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" kubefake "helm.sh/helm/v3/pkg/kube/fake" "helm.sh/helm/v3/pkg/release" @@ -240,6 +241,27 @@ func TestInstallRelease_DryRun(t *testing.T) { is.Equal(res.Info.Description, "Dry run complete") } +// Regression test for #7955: Lookup must not connect to Kubernetes on a dry-run. +func TestInstallRelease_DryRun_Lookup(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + instAction.DryRun = true + vals := map[string]interface{}{} + + mockChart := buildChart(withSampleTemplates()) + mockChart.Templates = append(mockChart.Templates, &chart.File{ + Name: "templates/lookup", + Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`), + }) + + res, err := instAction.Run(mockChart, vals) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + is.Contains(res.Manifest, "goodbye: map[]") +} + func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { is := assert.New(t) instAction := installAction(t) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 67872aa2f..fc289dbab 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -217,7 +217,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) if err != nil { return nil, nil, err } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 94ab1da95..c5d064ad5 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -172,7 +172,10 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render } return val, nil } - if e.config != nil { + + // If we are not linting and have a cluster connection, provide a Kubernetes-backed + // implementation. + if !e.LintMode && e.config != nil { funcMap["lookup"] = NewLookupFunction(e.config) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c1cdf625e..87e84c48b 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -70,7 +70,7 @@ func TestFuncMap(t *testing.T) { } // Test for Engine-specific template functions. - expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"} + expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"} for _, f := range expect { if _, ok := fns[f]; !ok { t.Errorf("Expected add-on function %q", f) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index e5769cbe0..92b4c3383 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -62,6 +62,11 @@ func funcMap() template.FuncMap { "include": func(string, interface{}) string { return "not implemented" }, "tpl": func(string, interface{}) interface{} { return "not implemented" }, "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, + // Provide a placeholder for the "lookup" function, which requires a kubernetes + // connection. + "lookup": func(string, string, string, string) (map[string]interface{}, error) { + return map[string]interface{}{}, nil + }, } for k, v := range extra { diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index ddcf6624c..62c63ec2b 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -94,6 +94,11 @@ func TestFuncs(t *testing.T) { tpl: `{{ fromYamlArray . }}`, expect: `[error unmarshaling JSON: while decoding JSON: json: cannot unmarshal object into Go value of type []interface {}]`, vars: `hello: world`, + }, { + // This should never result in a network lookup. Regression for #7955 + tpl: `{{ lookup "v1" "Namespace" "" "unlikelynamespace99999999" }}`, + expect: `map[]`, + vars: `["one", "two"]`, }} for _, tt := range tests { diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index 2527954fa..20be9189e 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -32,8 +32,12 @@ import ( type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) -// NewLookupFunction returns a function for looking up objects in the cluster. If the resource does not exist, no error -// is raised. +// NewLookupFunction returns a function for looking up objects in the cluster. +// +// If the resource does not exist, no error is raised. +// +// This function is considered deprecated, and will be renamed in Helm 4. It will no +// longer be a public function. func NewLookupFunction(config *rest.Config) lookupFunc { return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) { var client dynamic.ResourceInterface From 1cdd0a20488637c77d92e9a4844e89e8cefd578d Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 22 Apr 2020 15:33:01 -0700 Subject: [PATCH 36/59] fix(pkg/plugin): copy plugins directly to the data directory (#7962) Copy plugins from the cache rather than create a symlink. fixes: #7206 Signed-off-by: Adam Reese --- cmd/helm/load_plugins.go | 16 +------ cmd/helm/plugin_list.go | 6 ++- cmd/helm/plugin_uninstall.go | 2 +- cmd/helm/plugin_update.go | 2 +- pkg/plugin/installer/base.go | 9 +--- pkg/plugin/installer/http_installer.go | 28 ++++--------- pkg/plugin/installer/http_installer_test.go | 44 ++++++++++---------- pkg/plugin/installer/installer.go | 6 ++- pkg/plugin/installer/local_installer.go | 4 +- pkg/plugin/installer/local_installer_test.go | 4 +- pkg/plugin/installer/vcs_installer.go | 8 ++-- pkg/plugin/installer/vcs_installer_test.go | 36 ++++++++-------- pkg/plugin/plugin_test.go | 24 +++++------ 13 files changed, 83 insertions(+), 106 deletions(-) diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index e56feab40..a23a067fb 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -58,7 +58,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { return } - found, err := findPlugins(settings.PluginsDirectory) + found, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) return @@ -238,20 +238,6 @@ func manuallyProcessArgs(args []string) ([]string, []string) { return known, unknown } -// findPlugins returns a list of YAML files that describe plugins. -func findPlugins(plugdirs string) ([]*plugin.Plugin, error) { - found := []*plugin.Plugin{} - // Let's get all UNIXy and allow path separators - for _, p := range filepath.SplitList(plugdirs) { - matches, err := plugin.LoadAll(p) - if err != nil { - return matches, err - } - found = append(found, matches...) - } - return found, nil -} - // pluginCommand represents the optional completion.yaml file of a plugin type pluginCommand struct { Name string `json:"name"` diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go index 0440b0b5e..49a454963 100644 --- a/cmd/helm/plugin_list.go +++ b/cmd/helm/plugin_list.go @@ -22,6 +22,8 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" + + "helm.sh/helm/v3/pkg/plugin" ) func newPluginListCmd(out io.Writer) *cobra.Command { @@ -31,7 +33,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { Short: "list installed Helm plugins", RunE: func(cmd *cobra.Command, args []string) error { debug("pluginDirs: %s", settings.PluginsDirectory) - plugins, err := findPlugins(settings.PluginsDirectory) + plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err } @@ -51,7 +53,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { // Provide dynamic auto-completion for plugin names func compListPlugins(toComplete string) []string { var pNames []string - plugins, err := findPlugins(settings.PluginsDirectory) + plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err == nil { for _, p := range plugins { if strings.HasPrefix(p.Metadata.Name, toComplete) { diff --git a/cmd/helm/plugin_uninstall.go b/cmd/helm/plugin_uninstall.go index f703ddcfb..66cdaccdc 100644 --- a/cmd/helm/plugin_uninstall.go +++ b/cmd/helm/plugin_uninstall.go @@ -68,7 +68,7 @@ func (o *pluginUninstallOptions) complete(args []string) error { func (o *pluginUninstallOptions) run(out io.Writer) error { debug("loading installed plugins from %s", settings.PluginsDirectory) - plugins, err := findPlugins(settings.PluginsDirectory) + plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err } diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go index a24e80518..23f840b4a 100644 --- a/cmd/helm/plugin_update.go +++ b/cmd/helm/plugin_update.go @@ -70,7 +70,7 @@ func (o *pluginUpdateOptions) complete(args []string) error { func (o *pluginUpdateOptions) run(out io.Writer) error { installer.Debug = settings.Debug debug("loading installed plugins from %s", settings.PluginsDirectory) - plugins, err := findPlugins(settings.PluginsDirectory) + plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err } diff --git a/pkg/plugin/installer/base.go b/pkg/plugin/installer/base.go index a8ec97416..dcc3ad644 100644 --- a/pkg/plugin/installer/base.go +++ b/pkg/plugin/installer/base.go @@ -16,7 +16,6 @@ limitations under the License. package installer // import "helm.sh/helm/v3/pkg/plugin/installer" import ( - "os" "path/filepath" "helm.sh/helm/v3/pkg/helmpath" @@ -31,13 +30,7 @@ func newBase(source string) base { return base{source} } -// link creates a symlink from the plugin source to the base path. -func (b *base) link(from string) error { - debug("symlinking %s to %s", from, b.Path()) - return os.Symlink(from, b.Path()) -} - -// Path is where the plugin will be symlinked to. +// Path is where the plugin will be installed. func (b *base) Path() string { if b.Source == "" { return "" diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go index ea4ac7bcd..629bbec39 100644 --- a/pkg/plugin/installer/http_installer.go +++ b/pkg/plugin/installer/http_installer.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/internal/third_party/dep/fs" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/helmpath" @@ -68,7 +69,6 @@ func NewExtractor(source string) (Extractor, error) { // NewHTTPInstaller creates a new HttpInstaller. func NewHTTPInstaller(source string) (*HTTPInstaller, error) { - key, err := cache.Key(source) if err != nil { return nil, err @@ -108,18 +108,16 @@ func stripPluginName(name string) string { } // Install downloads and extracts the tarball into the cache directory -// and creates a symlink to the plugin directory. +// and installs into the plugin directory. // // Implements Installer. func (i *HTTPInstaller) Install() error { - pluginData, err := i.getter.Get(i.Source) if err != nil { return err } - err = i.extractor.Extract(pluginData, i.CacheDir) - if err != nil { + if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil { return err } @@ -132,7 +130,8 @@ func (i *HTTPInstaller) Install() error { return err } - return i.link(src) + debug("copying %s to %s", src, i.Path()) + return fs.CopyDir(src, i.Path()) } // Update updates a local repository @@ -141,12 +140,6 @@ func (i *HTTPInstaller) Update() error { return errors.Errorf("method Update() not implemented for HttpInstaller") } -// Override link because we want to use HttpInstaller.Path() not base.Path() -func (i *HTTPInstaller) link(from string) error { - debug("symlinking %s to %s", from, i.Path()) - return os.Symlink(from, i.Path()) -} - // Path is overridden because we want to join on the plugin name not the file name func (i HTTPInstaller) Path() string { if i.base.Source == "" { @@ -164,17 +157,16 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { return err } - tarReader := tar.NewReader(uncompressedStream) - - os.MkdirAll(targetDir, 0755) + if err := os.MkdirAll(targetDir, 0755); err != nil { + return err + } + tarReader := tar.NewReader(uncompressedStream) for { header, err := tarReader.Next() - if err == io.EOF { break } - if err != nil { return err } @@ -200,7 +192,5 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) } } - return nil - } diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index cfa2e4cbe..b496a1b01 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -73,13 +73,13 @@ func TestHTTPInstaller(t *testing.T) { i, err := NewForSource(source, "0.0.1") if err != nil { - t.Errorf("unexpected error: %s", err) + t.Fatalf("unexpected error: %s", err) } // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) if !ok { - t.Error("expected a HTTPInstaller") + t.Fatal("expected a HTTPInstaller") } // inject fake http client responding with minimal plugin tarball @@ -94,17 +94,17 @@ func TestHTTPInstaller(t *testing.T) { // install the plugin if err := Install(i); err != nil { - t.Error(err) + t.Fatal(err) } if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) + t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) } // Install again to test plugin exists error if err := Install(i); err == nil { - t.Error("expected error for plugin exists, got none") + t.Fatal("expected error for plugin exists, got none") } else if err.Error() != "plugin already exists" { - t.Errorf("expected error for plugin exists, got (%v)", err) + t.Fatalf("expected error for plugin exists, got (%v)", err) } } @@ -119,13 +119,13 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { i, err := NewForSource(source, "0.0.2") if err != nil { - t.Errorf("unexpected error: %s", err) + t.Fatalf("unexpected error: %s", err) } // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) if !ok { - t.Error("expected a HTTPInstaller") + t.Fatal("expected a HTTPInstaller") } // inject fake http client responding with error @@ -135,7 +135,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { // attempt to install the plugin if err := Install(i); err == nil { - t.Error("expected error from http client") + t.Fatal("expected error from http client") } } @@ -150,13 +150,13 @@ func TestHTTPInstallerUpdate(t *testing.T) { i, err := NewForSource(source, "0.0.1") if err != nil { - t.Errorf("unexpected error: %s", err) + t.Fatalf("unexpected error: %s", err) } // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) if !ok { - t.Error("expected a HTTPInstaller") + t.Fatal("expected a HTTPInstaller") } // inject fake http client responding with minimal plugin tarball @@ -171,15 +171,15 @@ func TestHTTPInstallerUpdate(t *testing.T) { // install the plugin before updating if err := Install(i); err != nil { - t.Error(err) + t.Fatal(err) } if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) + t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) } // Update plugin, should fail because it is not implemented if err := Update(i); err == nil { - t.Error("update method not implemented for http installer") + t.Fatal("update method not implemented for http installer") } } @@ -240,29 +240,27 @@ func TestExtract(t *testing.T) { } if err = extractor.Extract(&buf, tempDir); err != nil { - t.Errorf("Did not expect error but got error: %v", err) + t.Fatalf("Did not expect error but got error: %v", err) } pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") if info, err := os.Stat(pluginYAMLFullPath); err != nil { if os.IsNotExist(err) { - t.Errorf("Expected %s to exist but doesn't", pluginYAMLFullPath) - } else { - t.Error(err) + t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath) } + t.Fatal(err) } else if info.Mode().Perm() != 0600 { - t.Errorf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) + t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) } readmeFullPath := filepath.Join(tempDir, "README.md") if info, err := os.Stat(readmeFullPath); err != nil { if os.IsNotExist(err) { - t.Errorf("Expected %s to exist but doesn't", readmeFullPath) - } else { - t.Error(err) + t.Fatalf("Expected %s to exist but doesn't", readmeFullPath) } + t.Fatal(err) } else if info.Mode().Perm() != 0777 { - t.Errorf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) + t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) } } diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 14a02a87e..65c61cd7d 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -17,6 +17,7 @@ package installer import ( "fmt" + "log" "os" "path/filepath" "strings" @@ -103,9 +104,10 @@ func isPlugin(dirname string) bool { return err == nil } +var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile) + func debug(format string, args ...interface{}) { if Debug { - format = fmt.Sprintf("[debug] %s\n", format) - fmt.Printf(format, args...) + logger.Output(2, fmt.Sprintf(format, args...)) } } diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go index 662ef5b29..c92bc3fb0 100644 --- a/pkg/plugin/installer/local_installer.go +++ b/pkg/plugin/installer/local_installer.go @@ -16,6 +16,7 @@ limitations under the License. package installer // import "helm.sh/helm/v3/pkg/plugin/installer" import ( + "os" "path/filepath" "github.com/pkg/errors" @@ -45,7 +46,8 @@ func (i *LocalInstaller) Install() error { if !isPlugin(i.Source) { return ErrMissingMetadata } - return i.link(i.Source) + debug("symlinking %s to %s", i.Source, i.Path()) + return os.Symlink(i.Source, i.Path()) } // Update updates a local repository diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go index d11e44860..3d9607331 100644 --- a/pkg/plugin/installer/local_installer_test.go +++ b/pkg/plugin/installer/local_installer_test.go @@ -40,7 +40,7 @@ func TestLocalInstaller(t *testing.T) { source := "../testdata/plugdir/echo" i, err := NewForSource(source, "") if err != nil { - t.Errorf("unexpected error: %s", err) + t.Fatalf("unexpected error: %s", err) } if err := Install(i); err != nil { @@ -48,6 +48,6 @@ func TestLocalInstaller(t *testing.T) { } if i.Path() != helmpath.DataPath("plugins", "echo") { - t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) + t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) } } diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go index 1a5d74cca..f7df5b322 100644 --- a/pkg/plugin/installer/vcs_installer.go +++ b/pkg/plugin/installer/vcs_installer.go @@ -23,6 +23,7 @@ import ( "github.com/Masterminds/vcs" "github.com/pkg/errors" + "helm.sh/helm/v3/internal/third_party/dep/fs" "helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/plugin/cache" ) @@ -43,7 +44,7 @@ func existingVCSRepo(location string) (Installer, error) { Repo: repo, base: newBase(repo.Remote()), } - return i, err + return i, nil } // NewVCSInstaller creates a new VCSInstaller. @@ -65,7 +66,7 @@ func NewVCSInstaller(source, version string) (*VCSInstaller, error) { return i, err } -// Install clones a remote repository and creates a symlink to the plugin directory. +// Install clones a remote repository and installs into the plugin directory. // // Implements Installer. func (i *VCSInstaller) Install() error { @@ -87,7 +88,8 @@ func (i *VCSInstaller) Install() error { return ErrMissingMetadata } - return i.link(i.Repo.LocalPath()) + debug("copying %s to %s", i.Repo.LocalPath(), i.Path()) + return fs.CopyDir(i.Repo.LocalPath(), i.Path()) } // Update updates a remote repository diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go index ce1ee609e..b8dc6b1e2 100644 --- a/pkg/plugin/installer/vcs_installer_test.go +++ b/pkg/plugin/installer/vcs_installer_test.go @@ -80,24 +80,24 @@ func TestVCSInstaller(t *testing.T) { t.Fatal(err) } if repo.current != "0.1.1" { - t.Errorf("expected version '0.1.1', got %q", repo.current) + t.Fatalf("expected version '0.1.1', got %q", repo.current) } if i.Path() != helmpath.DataPath("plugins", "helm-env") { - t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) + t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) } // Install again to test plugin exists error if err := Install(i); err == nil { - t.Error("expected error for plugin exists, got none") + t.Fatalf("expected error for plugin exists, got none") } else if err.Error() != "plugin already exists" { - t.Errorf("expected error for plugin exists, got (%v)", err) + t.Fatalf("expected error for plugin exists, got (%v)", err) } // Testing FindSource method, expect error because plugin code is not a cloned repository if _, err := FindSource(i.Path()); err == nil { - t.Error("expected error for inability to find plugin source, got none") + t.Fatalf("expected error for inability to find plugin source, got none") } else if err.Error() != "cannot get information about plugin source" { - t.Errorf("expected error for inability to find plugin source, got (%v)", err) + t.Fatalf("expected error for inability to find plugin source, got (%v)", err) } } @@ -113,15 +113,14 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { } // ensure a VCSInstaller was returned - _, ok := i.(*VCSInstaller) - if !ok { + if _, ok := i.(*VCSInstaller); !ok { t.Fatal("expected a VCSInstaller") } if err := Install(i); err == nil { - t.Error("expected error for version does not exists, got none") + t.Fatalf("expected error for version does not exists, got none") } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { - t.Errorf("expected error for version does not exists, got (%v)", err) + t.Fatalf("expected error for version does not exists, got (%v)", err) } } func TestVCSInstallerUpdate(t *testing.T) { @@ -135,8 +134,7 @@ func TestVCSInstallerUpdate(t *testing.T) { } // ensure a VCSInstaller was returned - _, ok := i.(*VCSInstaller) - if !ok { + if _, ok := i.(*VCSInstaller); !ok { t.Fatal("expected a VCSInstaller") } @@ -157,7 +155,9 @@ func TestVCSInstallerUpdate(t *testing.T) { t.Fatal(err) } - repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote() + vcsInstaller := pluginInfo.(*VCSInstaller) + + repoRemote := vcsInstaller.Repo.Remote() if repoRemote != source { t.Fatalf("invalid source found, expected %q got %q", source, repoRemote) } @@ -168,12 +168,14 @@ func TestVCSInstallerUpdate(t *testing.T) { } // Test update failure - os.Remove(filepath.Join(i.Path(), "plugin.yaml")) + if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil { + t.Fatal(err) + } // Testing update for error - if err := Update(i); err == nil { - t.Error("expected error for plugin modified, got none") + if err := Update(vcsInstaller); err == nil { + t.Fatalf("expected error for plugin modified, got none") } else if err.Error() != "plugin repo was modified" { - t.Errorf("expected error for plugin modified, got (%v)", err) + t.Fatalf("expected error for plugin modified, got (%v)", err) } } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index 7bbc3b442..a869255c4 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -28,14 +28,14 @@ import ( func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { cmd, args, err := p.PrepareCommand(extraArgs) if err != nil { - t.Errorf(err.Error()) + t.Fatal(err) } if cmd != "echo" { - t.Errorf("Expected echo, got %q", cmd) + t.Fatalf("Expected echo, got %q", cmd) } if l := len(args); l != 5 { - t.Errorf("expected 5 args, got %d", l) + t.Fatalf("expected 5 args, got %d", l) } expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} @@ -49,13 +49,13 @@ func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) p.Metadata.IgnoreFlags = true cmd, args, err = p.PrepareCommand(extraArgs) if err != nil { - t.Errorf(err.Error()) + t.Fatal(err) } if cmd != "echo" { - t.Errorf("Expected echo, got %q", cmd) + t.Fatalf("Expected echo, got %q", cmd) } if l := len(args); l != 2 { - t.Errorf("expected 2 args, got %d", l) + t.Fatalf("expected 2 args, got %d", l) } expect = []string{"-n", osStrCmp} for i := 0; i < len(args); i++ { @@ -155,7 +155,7 @@ func TestNoPrepareCommand(t *testing.T) { _, _, err := p.PrepareCommand(argv) if err == nil { - t.Errorf("Expected error to be returned") + t.Fatalf("Expected error to be returned") } } @@ -172,7 +172,7 @@ func TestNoMatchPrepareCommand(t *testing.T) { argv := []string{"--debug", "--foo", "bar"} if _, _, err := p.PrepareCommand(argv); err == nil { - t.Errorf("Expected error to be returned") + t.Fatalf("Expected error to be returned") } } @@ -184,7 +184,7 @@ func TestLoadDir(t *testing.T) { } if plug.Dir != dirname { - t.Errorf("Expected dir %q, got %q", dirname, plug.Dir) + t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) } expect := &Metadata{ @@ -200,7 +200,7 @@ func TestLoadDir(t *testing.T) { } if !reflect.DeepEqual(expect, plug.Metadata) { - t.Errorf("Expected plugin metadata %v, got %v", expect, plug.Metadata) + t.Fatalf("Expected plugin metadata %v, got %v", expect, plug.Metadata) } } @@ -212,7 +212,7 @@ func TestDownloader(t *testing.T) { } if plug.Dir != dirname { - t.Errorf("Expected dir %q, got %q", dirname, plug.Dir) + t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) } expect := &Metadata{ @@ -230,7 +230,7 @@ func TestDownloader(t *testing.T) { } if !reflect.DeepEqual(expect, plug.Metadata) { - t.Errorf("Expected metadata %v, got %v", expect, plug.Metadata) + t.Fatalf("Expected metadata %v, got %v", expect, plug.Metadata) } } From 6aefbdcf38e14998d0bcb24030061822b1e8888d Mon Sep 17 00:00:00 2001 From: David Pait Date: Wed, 22 Apr 2020 18:55:26 -0400 Subject: [PATCH 37/59] Helm upgrades with --reuse-values and nil user values -- with tests (#7959) * return the new values if modifications dont yet exist Signed-off-by: David Pait * fix tests Signed-off-by: David Pait * removed outter if statement as its not needed now Signed-off-by: David Pait --- pkg/chartutil/coalesce.go | 6 +++- pkg/chartutil/coalesce_test.go | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go index 94b7f35fa..1d3d45e99 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chartutil/coalesce.go @@ -175,7 +175,11 @@ func coalesceValues(c *chart.Chart, v map[string]interface{}) { // // dest is considered authoritative. func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { - if dst == nil || src == nil { + // When --reuse-values is set but there are no modifications yet, return new values + if src == nil { + return dst + } + if dst == nil { return src } // Because dest has higher precedence than src, dest values override src diff --git a/pkg/chartutil/coalesce_test.go b/pkg/chartutil/coalesce_test.go index dc1017385..2a3d848fa 100644 --- a/pkg/chartutil/coalesce_test.go +++ b/pkg/chartutil/coalesce_test.go @@ -211,4 +211,57 @@ func TestCoalesceTables(t *testing.T) { if _, ok = dst["hole"]; ok { t.Error("The hole still exists.") } + + dst2 := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": "US", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": "black", + } + + // What we expect is that anything in dst should have all values set, + // this happens when the --reuse-values flag is set but the chart has no modifications yet + CoalesceTables(dst2, nil) + + if dst2["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst2["name"]) + } + + addr2, ok := dst2["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr2["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr2["street"]) + } + + if addr2["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr2["city"]) + } + + if addr2["country"].(string) != "US" { + t.Errorf("Unexpected Country: %v", addr2["country"]) + } + + if det2, ok := dst2["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst2["details"]) + } else if _, ok := det2["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst2["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst2["boat"]) + } + + if dst2["hole"].(string) != "black" { + t.Errorf("Expected hole string, got %v", dst2["boat"]) + } } From 27ebfa8c561e758b60872b9bb081253036e559ff Mon Sep 17 00:00:00 2001 From: Thomas FREYSS Date: Thu, 23 Apr 2020 14:47:08 +0200 Subject: [PATCH 38/59] fix(*): remove bom in utf files when loading chart files (#6081) Removes the BOM prefix if present, in read files before processing the data. Affects the following pkg: - pkg/chart/loader: directory and archive loader - internal/ignore: when loading .helmignore file Signed-off-by: Thomas FREYSS --- internal/ignore/rules.go | 13 +++++- pkg/chart/loader/archive.go | 4 +- pkg/chart/loader/directory.go | 5 +++ pkg/chart/loader/load_test.go | 40 ++++++++++++++++++ .../loader/testdata/frobnitz_with_bom.tgz | Bin 0 -> 3523 bytes .../testdata/frobnitz_with_bom/.helmignore | 1 + .../testdata/frobnitz_with_bom/Chart.lock | 8 ++++ .../testdata/frobnitz_with_bom/Chart.yaml | 27 ++++++++++++ .../testdata/frobnitz_with_bom/INSTALL.txt | 1 + .../loader/testdata/frobnitz_with_bom/LICENSE | 1 + .../testdata/frobnitz_with_bom/README.md | 11 +++++ .../frobnitz_with_bom/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 +++ .../frobnitz_with_bom/charts/alpine/README.md | 9 ++++ .../charts/alpine/charts/mast1/Chart.yaml | 5 +++ .../charts/alpine/charts/mast1/values.yaml | 4 ++ .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 ++++++ .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../testdata/frobnitz_with_bom/docs/README.md | 1 + .../testdata/frobnitz_with_bom/icon.svg | 8 ++++ .../testdata/frobnitz_with_bom/ignore/me.txt | 0 .../frobnitz_with_bom/templates/template.tpl | 1 + .../testdata/frobnitz_with_bom/values.yaml | 6 +++ 25 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom.tgz create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/README.md create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl create mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml diff --git a/internal/ignore/rules.go b/internal/ignore/rules.go index 9049aff0d..a80923baf 100644 --- a/internal/ignore/rules.go +++ b/internal/ignore/rules.go @@ -18,6 +18,7 @@ package ignore import ( "bufio" + "bytes" "io" "log" "os" @@ -65,8 +66,18 @@ func Parse(file io.Reader) (*Rules, error) { r := &Rules{patterns: []*pattern{}} s := bufio.NewScanner(file) + currentLine := 0 + utf8bom := []byte{0xEF, 0xBB, 0xBF} for s.Scan() { - if err := r.parseRule(s.Text()); err != nil { + scannedBytes := s.Bytes() + // We trim UTF8 BOM + if currentLine == 0 { + scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) + } + line := string(scannedBytes) + currentLine++ + + if err := r.parseRule(line); err != nil { return r, err } } diff --git a/pkg/chart/loader/archive.go b/pkg/chart/loader/archive.go index 7e187a170..8b38cb89f 100644 --- a/pkg/chart/loader/archive.go +++ b/pkg/chart/loader/archive.go @@ -173,7 +173,9 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, err } - files = append(files, &BufferedFile{Name: n, Data: b.Bytes()}) + data := bytes.TrimPrefix(b.Bytes(), utf8bom) + + files = append(files, &BufferedFile{Name: n, Data: data}) b.Reset() } diff --git a/pkg/chart/loader/directory.go b/pkg/chart/loader/directory.go index a12c5158e..bbe543870 100644 --- a/pkg/chart/loader/directory.go +++ b/pkg/chart/loader/directory.go @@ -17,6 +17,7 @@ limitations under the License. package loader import ( + "bytes" "fmt" "io/ioutil" "os" @@ -30,6 +31,8 @@ import ( "helm.sh/helm/v3/pkg/chart" ) +var utf8bom = []byte{0xEF, 0xBB, 0xBF} + // DirLoader loads a chart from a directory type DirLoader string @@ -104,6 +107,8 @@ func LoadDir(dir string) (*chart.Chart, error) { return errors.Wrapf(err, "error reading %s", n) } + data = bytes.TrimPrefix(data, utf8bom) + files = append(files, &BufferedFile{Name: n, Data: data}) return nil } diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 26513d359..3fa6bba0d 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -85,6 +85,38 @@ func TestLoadDirWithSymlink(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirWithUTFBOM(t *testing.T) { + l, err := Loader("testdata/frobnitz_with_bom") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) + verifyBomStripped(t, c.Files) +} + +func TestLoadArchiveWithUTFBOM(t *testing.T) { + l, err := Loader("testdata/frobnitz_with_bom.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) + verifyBomStripped(t, c.Files) +} + func TestLoadV1(t *testing.T) { l, err := Loader("testdata/frobnitz.v1") if err != nil { @@ -465,3 +497,11 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { } } } + +func verifyBomStripped(t *testing.T, files []*chart.File) { + for _, file := range files { + if bytes.HasPrefix(file.Data, utf8bom) { + t.Errorf("Byte Order Mark still present in processed file %s", file.Name) + } + } +} diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom.tgz new file mode 100644 index 0000000000000000000000000000000000000000..be0cd027db34d392a875cf8151642f9922016dbd GIT binary patch literal 3523 zcmV;!4LtH6iwFpsexY6f17>n>Vs2@4dS7>GbZB2l` znyB~YxdYx5VfMWn1PB3&iueFgG=d@A)?MdMF5{FG{)R-w{q^;#sSRI4>wKcMw~Q(D5t@dOKi zA7!-`+)B<<`fn83E%ZMmB{eNBIT_~*crVotZ@BcY(W;T0Qmaxb{D8ts z_1_YIaQ)Y8DN8d^96jbvO|I-O_N{vddll(tl;3oR_>LlRN{?j%e|Fv3;H2(Vp zjr~7>TstE)F*Jz==xkxQcDaEBPcas0JpMhRCI6F$#3!btCVH_BkNj6Fn%jSBwZwm) z0P)|Y@W$+yxc)~$Oa8};_P(7k)84AX1OJsuWy|=lP9^c*7eM?ctW-K=IW)ybf;^?n zLYQGBaQ6!t2{|K6S$Q$J!BlXk z1%gDHgN?IT7z0Dvn`Gh`8*7BF8cjTJNEv94Q{pM$OwytaT-rzO|XDx3G{X%omY7TZ4=r_AR!ZE#RS*2Gl_&&Xd$7+NWmuW zhM=wJeTh%l@bVS75?5g2%?2O(#tjrbdahA{W`Y$I(5yrauEtfZD)CNtW(loE%kkf- zOK(O1+Tj1|)GEpU_XTdM|4iqyK&JWjSAjg)|HuGnHvc1OY5w;K-2OjjVr(=C3=lZ= zjxOzDXqILO+5!cF<_vI)XPD*)$no^E>` zp6q|ULSH}rYjnc+uh;05690XHKC=V-*8OGC6MpT_yp(I|Hu%rm*Cb8IC=QPas9e!g zE*rY@z}YiL{@3fl$KUr)4oMhwctFL(W9+cYPrd&7@VQucQrz6}pR6i=wfI7OW#=dR zl-zrtc6^sPYd)(k8v2F-3|d?H9;U1~b97$FKSwQ@U3~sXR(4Ic!?Eegz<*C4r|x>m zt||rr?GoFaT~&IvU+vNAeVx1X%}Bp@RQHolbm==PEh_f&>J_6O)jjjFpBDkmn}YS-*cw3 z@jJr|^B1b3Ws|CtP0PTEeA()Us))BgExwi;*e`x_yN?0~x9i%m=t@ce@nBbyyV$?0 zN`7t{{$$@b3$~`kc6w#~&Q7^I?`QqGO@H&ARkOg37tLSS_-*f(UDa!?HNHzi;B!@R zC7s`yIVgGXlzplx{rlL)Jd!zNzy;ZdJ3CG~Yc4smrTox)UnKV|sQg(ztTZ$r5rFF|=V;9}m_XP*lVf*QUz1}>X+1b{i>J`dw<{C%& z$ZNx%yFS0tv3p^MW6J_~{+iw6zhH_h*eQ|FG7EE2b$H52uYt`~^ zvE2*zC6Pt{JzVm)={wHV^hhWxKU}B@3i_da_#VT_x~wEe$+nlfZ@e73bniP?9Fa3; z7iL^sS>p(Oy`v#Eur}l8`Tm>c8l%gqHQ|@%oZK{?So%OropsFQQKx1q7f-IB>GHtv zsM=~vy1nZCi{C%J{o{?P+AAyLd%XAX?g{y7)!Oa{tM;#hpPpUa-8isn+LQ^E70btl z_KqKTsP_buvhZn)N3<#&3|#?>WUa^<`R4Q)!Kx=me@8v<~X?ZskK{c z_Z~aY>*DE66`xg9z=KCGUO!d>@wMumYp&1I>c2@g+o!Dgp!S0ek1hY<+>nUJ zA}>{ky-;>&UY$*IKyBN<^>l~Ay?ahg+g!G8rFFx?KABrCo>yP~c5cb~k*`Kv8NcLx zCUw``{YMW@I(_!EwXbCq_BdYh$imIfU!7L_Qqd(H~ z@yh46`}MmG{@--`cRHC~aRXX!|Lc?r$^Q2VJn7#X3xS^Oe~rF*{9muuO8dWj0=NC| z-tCc3a3BXF!;j#Rfp6m|OOEIU7#Xvfu#g~2h`#|N3sJf&5S4`sTS6en;vp&m-RI<4 zfTUPx6aq#lrx@Q8f`)kzedj2#A}d1z7CPf_KO|tr&sHw z{a-%8FVw%8;CN+QdqJ!9uTp4~YDxdTK&$lMMq|L^{IAwGoBvg6wG{vN32rw3iwbW# z{l_^Mc=o?qQBeGD*n5Cu+|O$p7^g)Hn;y(T4DZ|`2Xue zJpYl<(s>pYj?01@ZTZQF8=f4F(#!-e#0CT(To6crMw(!uxRu1&Ly-4QvB)iFgvCND z!ExeJA^K`Z-hRCU&Iy@?OduLyVm@iO@I6|=e^t0RxVL^S&=ddF`uhFHN1(&}Vi)H}S&J-){hsG+JRy8m>#WM`#V|bOo8JrN*a5=tiWc>xPe|pCl}#J#7>d zE!Y8mr}wTc{YcTx|4@bf`KY*>lKm58Kj8SYTPD9fI80+6^3}7&HTWQYRZ($T*F7J+ zri)+D{`h=t%!!h!Z#Mqp?3{NN2KvP=g=_B#8r!dKa^1XBL*EJ;5wc?A?r}9mPX#WG z$=$zymSW=ikmGM{=wH3;(^sdQEX6-@6h^H4uKXv>pV_Y?H=P(;-Ah)TXbkJFN|_&o z_XcD7f3jtQq4T513l+8z(@HxWnsoh{B|kg*e{rSo?{&|dFAp*vJQ0K*)N!O}Z>C?T zJN)AB5_sZ&Lrk~rb+p0%Qwsc-{J&oxzg+*#BI&KD!_)Y$Q>vQTe``h8bg=`*WbQW-Q`ECnKu76Z6TBGW30h*i3loto`e&nGC}wS$^6qbEcy zaS|p{jYl{)QM;DVBEbYqZlEl3j)u^Rg<%nh0@%bmWczyJe;ZxCCh)|6g+|kS{YRyh z`0o>l{-3bGDy+u}UD*}?!OZq>XBUvXvfndW@?SXEf^&H}-mAkC|FsS4zpmeZLzmL| zPoE%aV1b!-ANq;Lgfm0~V`NcC{{SUexJYyrjk(T0<>%+)`D&bDbL1+dH#thfoMN{W z5obSVX$3T8$-R-VG9n^EE~a9Qk0Ci*1oA18H$`JAtqwyIK9ytQU0*0N8bc-mkHw%| zQ9`&nCegKlL+Hm*a`BdsCTvF$#j`0E$%6t^0tW2jUsv%OMMLR2be&DnbTnom^6Z$r zX&D?ug_B|-O08Im#zQGAB!xnvgclNX7mcVb`R@&nuPIu;{)?;tjpYCN0=LqC{Z=X7 zdRu3KJ==fH&;ROmdMW-za0w`V9Evj@UhfyrdZ5E3^B z)JKRgaS#y?2D;KD|M70d&3^xbW{kPss>9>@pP~i-RT{m-e_sIc|2OwD6C{;`T)s#& x%AjG3BBk*~f&>W?BuJ1TL4pJc5+q2FAVGoz2@)hokRYKE{tus-sa61Z0036V1Lgn# literal 0 HcmV?d00001 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore new file mode 100644 index 000000000..7a4b92da2 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock new file mode 100644 index 000000000..ed43b227f --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml new file mode 100644 index 000000000..21b21f0b5 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt new file mode 100644 index 000000000..77c4e724a --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE b/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE new file mode 100644 index 000000000..c27b00bf2 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/README.md new file mode 100644 index 000000000..e9c40031b --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me new file mode 100644 index 000000000..a7e3a38b7 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml new file mode 100644 index 000000000..adb9853c6 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md new file mode 100644 index 000000000..ea7526bee --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..1ad84b346 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..f690d53c4 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..f3e662a28 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml new file mode 100644 index 000000000..6b7cb2596 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md new file mode 100644 index 000000000..816c3e431 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl new file mode 100644 index 000000000..bb29c5491 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml new file mode 100644 index 000000000..c24ceadf9 --- /dev/null +++ b/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" From 9ced0165aba1f0d90990396306d6f7e7a6725a91 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 23 Apr 2020 11:21:04 -0700 Subject: [PATCH 39/59] fix(cmd/env): make helm env command respect cli flags (#7978) Running `helm env` should respect cli flag overrides. Signed-off-by: Adam Reese --- cmd/helm/env.go | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/cmd/helm/env.go b/cmd/helm/env.go index efb6dfea9..0fbfb9da4 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -21,53 +21,35 @@ import ( "io" "sort" - "helm.sh/helm/v3/pkg/cli" - "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" ) -var ( - envHelp = ` +var envHelp = ` Env prints out all the environment information in use by Helm. ` -) func newEnvCmd(out io.Writer) *cobra.Command { - o := &envOptions{} - o.settings = cli.New() - cmd := &cobra.Command{ Use: "env", Short: "helm client environment information", Long: envHelp, Args: require.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return o.run(out) + Run: func(cmd *cobra.Command, args []string) { + envVars := settings.EnvVars() + + // Sort the variables by alphabetical order. + // This allows for a constant output across calls to 'helm env'. + var keys []string + for k := range envVars { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintf(out, "%s=\"%s\"\n", k, envVars[k]) + } }, } - return cmd } - -type envOptions struct { - settings *cli.EnvSettings -} - -func (o *envOptions) run(out io.Writer) error { - envVars := o.settings.EnvVars() - - // Sort the variables by alphabetical order. - // This allows for a constant output across calls to 'helm env'. - var keys []string - for k := range envVars { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - fmt.Printf("%s=\"%s\"\n", k, envVars[k]) - } - return nil -} From 4a0dfbe53b2191e09a7034ce97e4caf84989d6db Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 23 Apr 2020 12:20:14 -0700 Subject: [PATCH 40/59] fix(pkg/cli): ensure correct configuration from kubeconfig file Bind Helm flags to Kubernetes configuration loader to get a merged config with kubeconfig. Fixes: #7539 Signed-off-by: Adam Reese --- cmd/helm/helm.go | 21 ++++++------- pkg/cli/environment.go | 53 +++++++++++++-------------------- pkg/getter/getter_test.go | 14 +++++---- pkg/getter/httpgetter_test.go | 2 +- pkg/getter/plugingetter_test.go | 17 +++++------ pkg/kube/config.go | 2 ++ pkg/plugin/plugin_test.go | 5 ++-- 7 files changed, 53 insertions(+), 61 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 7e1fcb6e1..fcc7315f5 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -43,9 +43,7 @@ import ( // FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI") -var ( - settings = cli.New() -) +var settings = cli.New() func init() { log.SetFlags(log.Lshortfile) @@ -72,13 +70,16 @@ func main() { actionConfig := new(action.Configuration) cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) - helmDriver := os.Getenv("HELM_DRIVER") - if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { - log.Fatal(err) - } - if helmDriver == "memory" { - loadReleasesInMemory(actionConfig) - } + // run when each command's execute method is called + cobra.OnInitialize(func() { + helmDriver := os.Getenv("HELM_DRIVER") + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { + log.Fatal(err) + } + if helmDriver == "memory" { + loadReleasesInMemory(actionConfig) + } + }) if err := cmd.Execute(); err != nil { debug("%+v", err) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index e279331b0..2e64e0810 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -26,21 +26,17 @@ import ( "fmt" "os" "strconv" - "sync" "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/kube" ) // EnvSettings describes all of the environment settings. type EnvSettings struct { - namespace string - config genericclioptions.RESTClientGetter - configOnce sync.Once + namespace string + config *genericclioptions.ConfigFlags // KubeConfig is the path to the kubeconfig file KubeConfig string @@ -63,8 +59,7 @@ type EnvSettings struct { } func New() *EnvSettings { - - env := EnvSettings{ + env := &EnvSettings{ namespace: os.Getenv("HELM_NAMESPACE"), KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeToken: os.Getenv("HELM_KUBETOKEN"), @@ -75,7 +70,16 @@ func New() *EnvSettings { RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) - return &env + + // bind to kubernetes config flags + env.config = &genericclioptions.ConfigFlags{ + Namespace: &env.namespace, + Context: &env.KubeContext, + BearerToken: &env.KubeToken, + APIServer: &env.KubeAPIServer, + KubeConfig: &env.KubeConfig, + } + return env } // AddFlags binds flags to the given flagset. @@ -107,42 +111,27 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_REPOSITORY_CACHE": s.RepositoryCache, "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_NAMESPACE": s.Namespace(), - "HELM_KUBECONTEXT": s.KubeContext, - "HELM_KUBETOKEN": s.KubeToken, - "HELM_KUBEAPISERVER": s.KubeAPIServer, - } + // broken, these are populated from helm flags and not kubeconfig. + "HELM_KUBECONTEXT": s.KubeContext, + "HELM_KUBETOKEN": s.KubeToken, + "HELM_KUBEAPISERVER": s.KubeAPIServer, + } if s.KubeConfig != "" { envvars["KUBECONFIG"] = s.KubeConfig } - return envvars } -//Namespace gets the namespace from the configuration +// Namespace gets the namespace from the configuration func (s *EnvSettings) Namespace() string { - if s.namespace != "" { - return s.namespace - } - - if ns, _, err := s.RESTClientGetter().ToRawKubeConfigLoader().Namespace(); err == nil { + if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { return ns } return "default" } -//RESTClientGetter gets the kubeconfig from EnvSettings +// RESTClientGetter gets the kubeconfig from EnvSettings func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { - s.configOnce.Do(func() { - clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace) - if s.KubeToken != "" { - clientConfig.BearerToken = &s.KubeToken - } - if s.KubeAPIServer != "" { - clientConfig.APIServer = &s.KubeAPIServer - } - - s.config = clientConfig - }) return s.config } diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go index 60eb4738e..79a3338e9 100644 --- a/pkg/getter/getter_test.go +++ b/pkg/getter/getter_test.go @@ -53,9 +53,10 @@ func TestProviders(t *testing.T) { } func TestAll(t *testing.T) { - all := All(&cli.EnvSettings{ - PluginsDirectory: pluginDir, - }) + env := cli.New() + env.PluginsDirectory = pluginDir + + all := All(env) if len(all) != 3 { t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all)) } @@ -66,9 +67,10 @@ func TestAll(t *testing.T) { } func TestByScheme(t *testing.T) { - g := All(&cli.EnvSettings{ - PluginsDirectory: pluginDir, - }) + env := cli.New() + env.PluginsDirectory = pluginDir + + g := All(env) if _, err := g.ByScheme("test"); err != nil { t.Error(err) } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index a1288bf47..4d7ada852 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -122,7 +122,7 @@ func TestDownload(t *testing.T) { })) defer srv.Close() - g, err := All(new(cli.EnvSettings)).ByScheme("http") + g, err := All(cli.New()).ByScheme("http") if err != nil { t.Fatal(err) } diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index 71563e169..a18fa302b 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -24,9 +24,9 @@ import ( ) func TestCollectPlugins(t *testing.T) { - env := &cli.EnvSettings{ - PluginsDirectory: pluginDir, - } + env := cli.New() + env.PluginsDirectory = pluginDir + p, err := collectPlugins(env) if err != nil { t.Fatal(err) @@ -54,9 +54,8 @@ func TestPluginGetter(t *testing.T) { t.Skip("TODO: refactor this test to work on windows") } - env := &cli.EnvSettings{ - PluginsDirectory: pluginDir, - } + env := cli.New() + env.PluginsDirectory = pluginDir pg := NewPluginGetter("echo", env, "test", ".") g, err := pg() if err != nil { @@ -80,9 +79,9 @@ func TestPluginSubCommands(t *testing.T) { t.Skip("TODO: refactor this test to work on windows") } - env := &cli.EnvSettings{ - PluginsDirectory: pluginDir, - } + env := cli.New() + env.PluginsDirectory = pluginDir + pg := NewPluginGetter("echo -n", env, "test", ".") g, err := pg() if err != nil { diff --git a/pkg/kube/config.go b/pkg/kube/config.go index 624c4a1f7..e00c9acb1 100644 --- a/pkg/kube/config.go +++ b/pkg/kube/config.go @@ -19,6 +19,8 @@ package kube // import "helm.sh/helm/v3/pkg/kube" import "k8s.io/cli-runtime/pkg/genericclioptions" // GetConfig returns a Kubernetes client config. +// +// Deprecated func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { cf := genericclioptions.NewConfigFlags(true) cf.Namespace = &namespace diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index a869255c4..af0b61846 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -305,9 +305,8 @@ func TestSetupEnv(t *testing.T) { name := "pequod" base := filepath.Join("testdata/helmhome/helm/plugins", name) - s := &cli.EnvSettings{ - PluginsDirectory: "testdata/helmhome/helm/plugins", - } + s := cli.New() + s.PluginsDirectory = "testdata/helmhome/helm/plugins" SetupPluginEnv(s, name, base) for _, tt := range []struct { From 78d57d3d2059de200ed0b1c9c7cb1fb0104630c3 Mon Sep 17 00:00:00 2001 From: Josh Dolitsky <393494+jdolitsky@users.noreply.github.com> Date: Thu, 23 Apr 2020 14:30:31 -0500 Subject: [PATCH 41/59] Modify Circle config to use Go 1.14 (#7980) Signed-off-by: Josh Dolitsky <393494+jdolitsky@users.noreply.github.com> --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ef19b8ee7..e6ce2e242 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ jobs: build: working_directory: ~/helm.sh/helm docker: - - image: circleci/golang:1.13 + - image: circleci/golang:1.14 environment: GOCACHE: "/tmp/go/cache" From 54e5088a500bde62a84eeba89d39abd6d7f28daa Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 23 Apr 2020 19:49:43 -0400 Subject: [PATCH 42/59] Fixing docs from version to appVersion (#7975) In the created chart from `helm create` is notes a tag overrides version. It actually overrides appVersion. Updating the docs to reflect reality. Signed-off-by: Matt Farina --- pkg/chartutil/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 28fb28e00..0e87c7b47 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -99,7 +99,7 @@ replicaCount: 1 image: repository: nginx pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart version. + # Overrides the image tag whose default is the chart appVersion. tag: "" imagePullSecrets: [] From c422e51ca13b9f213d5f7ed42c187d401ffec245 Mon Sep 17 00:00:00 2001 From: Thomas FREYSS Date: Fri, 24 Apr 2020 11:09:27 +0200 Subject: [PATCH 43/59] test: add test for bom test data integrity Signed-off-by: Thomas FREYSS --- pkg/chart/loader/load_test.go | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 3fa6bba0d..40b86dec2 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "io" "io/ioutil" "os" "path/filepath" @@ -85,6 +86,54 @@ func TestLoadDirWithSymlink(t *testing.T) { verifyDependenciesLock(t, c) } +func TestBomTestData(t *testing.T) { + testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} + for _, file := range testFiles { + data, err := ioutil.ReadFile("testdata/" + file) + if err != nil || !bytes.HasPrefix(data, utf8bom) { + t.Errorf("Test file has no BOM or is invalid: testdata/%s", file) + } + } + + archive, err := ioutil.ReadFile("testdata/frobnitz_with_bom.tgz") + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + unzipped, err := gzip.NewReader(bytes.NewReader(archive)) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + defer unzipped.Close() + for _, testFile := range testFiles { + data := make([]byte, 3) + err := unzipped.Reset(bytes.NewReader(archive)) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + tr := tar.NewReader(unzipped) + for { + file, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + if file != nil && strings.EqualFold(file.Name, testFile) { + _, err := tr.Read(data) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } else { + break + } + } + } + if !bytes.Equal(data, utf8bom) { + t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) + } + } +} + func TestLoadDirWithUTFBOM(t *testing.T) { l, err := Loader("testdata/frobnitz_with_bom") if err != nil { From 984d2ac7676874ae78a7617f7417513a7a9b5ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Fri, 24 Apr 2020 23:03:47 +0200 Subject: [PATCH 44/59] fix: write index.yaml file atomically (#7954) * fix: write index.yaml file atomically This refactors the already-existing `AtomicWriteFile` utility to a central location and uses it to write index files atomically. This is done to avoid having half-written index files break client requests. Drive-bys: - Add test for AtomicWriteFile. - Add test IndexFile.WriteFile. Signed-off-by: rabadin * Review fix: use RenameWithFallback instead of os.Rename Signed-off-by: rabadin Co-authored-by: rabadin --- internal/fileutil/fileutil.go | 51 ++++++++++++++++++++++++ internal/fileutil/fileutil_test.go | 62 ++++++++++++++++++++++++++++++ pkg/downloader/chart_downloader.go | 31 ++------------- pkg/repo/index.go | 4 +- pkg/repo/index_test.go | 20 ++++++++++ 5 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 internal/fileutil/fileutil.go create mode 100644 internal/fileutil/fileutil_test.go diff --git a/internal/fileutil/fileutil.go b/internal/fileutil/fileutil.go new file mode 100644 index 000000000..739093f3b --- /dev/null +++ b/internal/fileutil/fileutil.go @@ -0,0 +1,51 @@ +/* +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 fileutil + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + + "helm.sh/helm/v3/internal/third_party/dep/fs" +) + +// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a +// disk. +func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + tempFile, err := ioutil.TempFile(filepath.Split(filename)) + if err != nil { + return err + } + tempName := tempFile.Name() + + if _, err := io.Copy(tempFile, reader); err != nil { + tempFile.Close() // return value is ignored as we are already on error path + return err + } + + if err := tempFile.Close(); err != nil { + return err + } + + if err := os.Chmod(tempName, mode); err != nil { + return err + } + + return fs.RenameWithFallback(tempName, filename) +} diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go new file mode 100644 index 000000000..9a4bc32c9 --- /dev/null +++ b/internal/fileutil/fileutil_test.go @@ -0,0 +1,62 @@ +/* +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 fileutil + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestAtomicWriteFile(t *testing.T) { + dir, err := ioutil.TempDir("", "helm-tmp") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + testpath := filepath.Join(dir, "test") + stringContent := "Test content" + reader := bytes.NewReader([]byte(stringContent)) + mode := os.FileMode(0644) + + err = AtomicWriteFile(testpath, reader, mode) + if err != nil { + t.Errorf("AtomicWriteFile error: %s", err) + } + + got, err := ioutil.ReadFile(testpath) + if err != nil { + t.Fatal(err) + } + + if stringContent != string(got) { + t.Fatalf("expected: %s, got: %s", stringContent, string(got)) + } + + gotinfo, err := os.Stat(testpath) + if err != nil { + t.Fatal(err) + } + + if mode != gotinfo.Mode() { + t.Fatalf("expected %s: to be the same mode as %s", + mode, gotinfo.Mode()) + } +} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 0013dbdf0..ef26f3348 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -18,7 +18,6 @@ package downloader import ( "fmt" "io" - "io/ioutil" "net/url" "os" "path/filepath" @@ -26,6 +25,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/internal/fileutil" "helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/helmpath" @@ -72,31 +72,6 @@ type ChartDownloader struct { RepositoryCache string } -// atomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a -// disk. -func atomicWriteFile(filename string, body io.Reader, mode os.FileMode) error { - tempFile, err := ioutil.TempFile(filepath.Split(filename)) - if err != nil { - return err - } - tempName := tempFile.Name() - - if _, err := io.Copy(tempFile, body); err != nil { - tempFile.Close() // return value is ignored as we are already on error path - return err - } - - if err := tempFile.Close(); err != nil { - return err - } - - if err := os.Chmod(tempName, mode); err != nil { - return err - } - - return os.Rename(tempName, filename) -} - // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. // // If Verify is set to VerifyNever, the verification will be nil. @@ -126,7 +101,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven name := filepath.Base(u.Path) destfile := filepath.Join(dest, name) - if err := atomicWriteFile(destfile, data, 0644); err != nil { + if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { return destfile, nil, err } @@ -142,7 +117,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven return destfile, ver, nil } provfile := destfile + ".prov" - if err := atomicWriteFile(provfile, body, 0644); err != nil { + if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil { return destfile, nil, err } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 36386665e..6ef2cf8b5 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -17,6 +17,7 @@ limitations under the License. package repo import ( + "bytes" "io/ioutil" "os" "path" @@ -29,6 +30,7 @@ import ( "github.com/pkg/errors" "sigs.k8s.io/yaml" + "helm.sh/helm/v3/internal/fileutil" "helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -197,7 +199,7 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { if err != nil { return err } - return ioutil.WriteFile(dest, b, mode) + return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) } // Merge merges the given index file into this index. diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index 5dbd5e551..466a2c306 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -428,3 +428,23 @@ func TestIndexAdd(t *testing.T) { t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) } } + +func TestIndexWrite(t *testing.T) { + i := NewIndexFile() + i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890") + dir, err := ioutil.TempDir("", "helm-tmp") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + testpath := filepath.Join(dir, "test") + i.WriteFile(testpath, 0600) + + got, err := ioutil.ReadFile(testpath) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(got), "clipper-0.1.0.tgz") { + t.Fatal("Index files doesn't contain expected content") + } +} From 6bc4a948be0ef1e32f6a605b655cc8cd5f0c2f06 Mon Sep 17 00:00:00 2001 From: Hu Shuai Date: Mon, 27 Apr 2020 14:19:02 +0800 Subject: [PATCH 45/59] Add unit test for pkg/chart/chart.go Signed-off-by: Hu Shuai --- pkg/chart/chart_test.go | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index 1b8669ac8..ef8cec3ad 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -159,3 +159,53 @@ func TestChartFullPath(t *testing.T) { is.Equal("foo/charts/", chrt1.ChartFullPath()) is.Equal("foo", chrt2.ChartFullPath()) } + +func TestCRDObjects(t *testing.T) { + chrt := Chart{ + Files: []*File{ + { + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + { + Name: "bar.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crdsfoo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/README.md", + Data: []byte("# hello"), + }, + }, + } + + expected := []CRD{ + { + Name: "crds/foo.yaml", + Filename: "crds/foo.yaml", + File: &File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + }, + { + Name: "crds/foo/bar/baz.yaml", + Filename: "crds/foo/bar/baz.yaml", + File: &File{ + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + }, + } + + is := assert.New(t) + crds := chrt.CRDObjects() + is.Equal(expected, crds) +} From f7d1bfd0063a0d43aaef0764222a819fe3472056 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Mon, 27 Apr 2020 09:02:14 -0500 Subject: [PATCH 46/59] Adding PR template from dev-v2 branch Signed-off-by: Bridget Kromhout --- .github/pull_request_template.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..595b50218 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ + + +**What this PR does / why we need it**: + +**Special notes for your reviewer**: + +**If applicable**: +- [ ] this PR contains documentation +- [ ] this PR contains unit tests +- [ ] this PR has been tested for backwards compatibility From 0a318beba32626997abb6af915ebad9399430103 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Mon, 27 Apr 2020 09:17:09 -0500 Subject: [PATCH 47/59] Updating CONTRIBUTING to match current practice Signed-off-by: Bridget Kromhout --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63780365e..a637f9255 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,9 +184,9 @@ contributing to Helm. All issue types follow the same general lifecycle. Differe ## How to Contribute a Patch -1. If you haven't already done so, sign a Contributor License Agreement (see details above). -2. Fork the desired repo, develop and test your code changes. -3. Submit a pull request. +1. Identify or create the related issue. +2. Fork the desired repo; develop and test your code changes. +3. Submit a pull request, making sure to sign your work and link the related issue. Coding conventions and standards are explained in the [official developer docs](https://helm.sh/docs/developers/). From cd50d0c3621ad91b3848f14b7ef3a8d6aa29d2c9 Mon Sep 17 00:00:00 2001 From: Anshul Verma Date: Tue, 28 Apr 2020 01:41:47 +0530 Subject: [PATCH 48/59] Fix : Prints empty list in json/yaml is no repositories are present (#7949) * Prints empty repolist in json/yaml if there are no repos and output format is given as json/yaml rather that printing the error msg "no repositories to show" Signed-off-by: Anshul Verma * Prints empty repolist in json/yaml if there are no repos and output format is given as json/yaml rather that printing the error msg "no repositories to show" Signed-off-by: Anshul Verma --- cmd/helm/repo_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/repo_list.go b/cmd/helm/repo_list.go index 51b4f0d58..ed1c9573c 100644 --- a/cmd/helm/repo_list.go +++ b/cmd/helm/repo_list.go @@ -38,7 +38,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command { Args: require.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { f, err := repo.LoadFile(settings.RepositoryConfig) - if isNotExist(err) || len(f.Repositories) == 0 { + if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) { return errors.New("no repositories to show") } From 2334195a01a6e47d597940890890a1369f8bcba4 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 23 Apr 2020 14:50:31 -0400 Subject: [PATCH 49/59] Adding Helm env vars where XDG exposed Helm had been exposing XDG based variables to end users. This lead to confusion. For example, if a user wanted to change the cache location Helm used should they change the XDG variable? Since this would be like changing the HOME environment variable the answer is no. This change adds HELM_*_HOME environment variables to be used in addition to XDG ones of the same name. Helm will now look for the Helm specific variable. If not set, Helm will fall back to XDG locations. If those are not set a default location will be used. This keeps XDG in use as a default when present, provides users with the ability to set the location, and removes XDG from being exposed to end users to avoid confusion. Closes #7919 Signed-off-by: Matt Farina --- cmd/helm/root.go | 14 +++++++------- cmd/helm/root_test.go | 18 ++++++++++++++++++ internal/test/ensure/ensure.go | 4 ++++ pkg/cli/environment.go | 3 +++ pkg/helmpath/lazypath.go | 33 ++++++++++++++++++++++++++++----- 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 2c66d3a09..e9bc26fe4 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -46,20 +46,20 @@ Environment variables: +------------------+--------------------------------------------------------------------------------------------------------+ | Name | Description | +------------------+--------------------------------------------------------------------------------------------------------+ -| $XDG_CACHE_HOME | set an alternative location for storing cached files. | -| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | -| $XDG_DATA_HOME | set an alternative location for storing Helm data. | +| $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_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_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | +------------------+--------------------------------------------------------------------------------------------------------+ -Helm stores configuration based on the XDG base directory specification, so +Helm stores cache, configuration, and data based on the following configuration order: -- cached files are stored in $XDG_CACHE_HOME/helm -- configuration is stored in $XDG_CONFIG_HOME/helm -- data is stored in $XDG_DATA_HOME/helm +- If a HELM_*_HOME environment variable is set, it will be used +- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used +- When no other location is set a default location will be used based on the operating system By default, the default directories depend on the Operating System. The defaults are listed below: diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go index e1fa1fc27..891bb698e 100644 --- a/cmd/helm/root_test.go +++ b/cmd/helm/root_test.go @@ -55,6 +55,24 @@ func TestRootCmd(t *testing.T) { envvars: map[string]string{xdg.DataHomeEnvVar: "/bar"}, dataPath: "/bar/helm", }, + { + name: "with $HELM_CACHE_HOME set", + args: "env", + envvars: map[string]string{helmpath.CacheHomeEnvVar: "/foo/helm"}, + cachePath: "/foo/helm", + }, + { + name: "with $HELM_CONFIG_HOME set", + args: "env", + envvars: map[string]string{helmpath.ConfigHomeEnvVar: "/foo/helm"}, + configPath: "/foo/helm", + }, + { + name: "with $HELM_DATA_HOME set", + args: "env", + envvars: map[string]string{helmpath.DataHomeEnvVar: "/foo/helm"}, + dataPath: "/foo/helm", + }, } for _, tt := range tests { diff --git a/internal/test/ensure/ensure.go b/internal/test/ensure/ensure.go index b4775df80..6219ad626 100644 --- a/internal/test/ensure/ensure.go +++ b/internal/test/ensure/ensure.go @@ -21,6 +21,7 @@ import ( "os" "testing" + "helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath/xdg" ) @@ -31,6 +32,9 @@ func HelmHome(t *testing.T) func() { os.Setenv(xdg.CacheHomeEnvVar, base) os.Setenv(xdg.ConfigHomeEnvVar, base) os.Setenv(xdg.DataHomeEnvVar, base) + os.Setenv(helmpath.CacheHomeEnvVar, "") + os.Setenv(helmpath.ConfigHomeEnvVar, "") + os.Setenv(helmpath.DataHomeEnvVar, "") return func() { os.RemoveAll(base) } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index e279331b0..8e8f4ce8d 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -101,6 +101,9 @@ func envOr(name, def string) string { func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ "HELM_BIN": os.Args[0], + "HELM_CACHE_HOME": helmpath.CachePath(""), + "HELM_CONFIG_HOME": helmpath.ConfigPath(""), + "HELM_DATA_HOME": helmpath.DataPath(""), "HELM_DEBUG": fmt.Sprint(s.Debug), "HELM_PLUGINS": s.PluginsDirectory, "HELM_REGISTRY_CONFIG": s.RegistryConfig, diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go index 0b9068671..a8a64bfab 100644 --- a/pkg/helmpath/lazypath.go +++ b/pkg/helmpath/lazypath.go @@ -20,11 +20,34 @@ import ( "helm.sh/helm/v3/pkg/helmpath/xdg" ) +const ( + // CacheHomeEnvVar is the environment variable used by Helm + // for the cache directory. When no value is set a default is used. + CacheHomeEnvVar = "HELM_CACHE_HOME" + + // ConfigHomeEnvVar is the environment variable used by Helm + // for the config directory. When no value is set a default is used. + ConfigHomeEnvVar = "HELM_CONFIG_HOME" + + // DataHomeEnvVar is the environment variable used by Helm + // for the data directory. When no value is set a default is used. + DataHomeEnvVar = "HELM_DATA_HOME" +) + // lazypath is an lazy-loaded path buffer for the XDG base directory specification. type lazypath string -func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) string { - base := os.Getenv(envVar) +func (l lazypath) path(helmEnvVar, XDGEnvVar string, defaultFn func() string, elem ...string) string { + + // There is an order to checking for a path. + // 1. See if a Helm specific environment variable has been set. + // 2. Check if an XDG environment variable is set + // 3. Fall back to a default + base := os.Getenv(helmEnvVar) + if base != "" { + return filepath.Join(base, filepath.Join(elem...)) + } + base = os.Getenv(XDGEnvVar) if base == "" { base = defaultFn() } @@ -34,16 +57,16 @@ func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) s // cachePath defines the base directory relative to which user specific non-essential data files // should be stored. func (l lazypath) cachePath(elem ...string) string { - return l.path(xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...)) + return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...)) } // configPath defines the base directory relative to which user specific configuration files should // be stored. func (l lazypath) configPath(elem ...string) string { - return l.path(xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...)) + return l.path(ConfigHomeEnvVar, xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...)) } // dataPath defines the base directory relative to which user specific data files should be stored. func (l lazypath) dataPath(elem ...string) string { - return l.path(xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...)) + return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...)) } From 6fc93530569813580c1b6554a7154f4ec0fda0b4 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 28 Apr 2020 17:12:14 -0600 Subject: [PATCH 50/59] feat: lint the names of templated resources (#8011) Signed-off-by: Matt Butcher --- pkg/lint/lint_test.go | 8 ++- pkg/lint/rules/template.go | 53 ++++++++++++++----- pkg/lint/rules/template_test.go | 29 ++++++++++ .../testdata/goodone/templates/goodone.yaml | 2 +- pkg/lint/rules/testdata/goodone/values.yaml | 2 +- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 2c110009d..e7ff4cd7a 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -104,7 +104,10 @@ func TestBadValues(t *testing.T) { func TestGoodChart(t *testing.T) { m := All(goodChartDir, values, namespace, strict).Messages if len(m) != 0 { - t.Errorf("All failed but shouldn't have: %#v", m) + t.Error("All returned linter messages when it shouldn't have") + for i, msg := range m { + t.Logf("Message %d: %s", i, msg) + } } } @@ -130,6 +133,9 @@ func TestHelmCreateChart(t *testing.T) { m := All(createdChart, values, namespace, true).Messages if ll := len(m); ll != 1 { t.Errorf("All should have had exactly 1 error. Got %d", ll) + for i, msg := range m { + t.Logf("Message %d: %s", i, msg.Error()) + } } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { t.Errorf("Unexpected lint error: %s", msg) } diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 3d388f81b..e27c6a345 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -17,9 +17,11 @@ limitations under the License. package rules import ( + "fmt" "os" "path/filepath" "regexp" + "strings" "github.com/pkg/errors" "sigs.k8s.io/yaml" @@ -35,6 +37,14 @@ 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) { path := "templates/" @@ -57,7 +67,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace } options := chartutil.ReleaseOptions{ - Name: "testRelease", + Name: "test-release", Namespace: namespace, } @@ -111,14 +121,17 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // linter.RunLinterRule(support.WarningSev, path, validateQuotes(string(preExecutedTemplate))) renderedContent := renderedContentMap[filepath.Join(chart.Name(), fileName)] - var yamlStruct K8sYamlStruct - // Even though K8sYamlStruct only defines Metadata namespace, an error in any other - // key will be raised as well - err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) - - // If YAML linting fails, we sill progress. So we don't capture the returned state - // on this linter run. - linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) + if strings.TrimSpace(renderedContent) != "" { + var yamlStruct K8sYamlStruct + // Even though K8sYamlStruct only defines a few fields, an error in any other + // key will be raised as well + err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) + + // If YAML linting fails, we sill progress. So we don't capture the returned state + // on this linter run. + linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) + linter.RunLinterRule(support.ErrorSev, path, validateMetadataName(&yamlStruct)) + } } } @@ -149,6 +162,15 @@ func validateYamlContent(err error) error { return errors.Wrap(err, "unable to parse YAML") } +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 + } + return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name) +} + func validateNoCRDHooks(manifest []byte) error { if crdHookSearch.Match(manifest) { return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart") @@ -164,9 +186,14 @@ func validateNoReleaseTime(manifest []byte) error { } // K8sYamlStruct stubs a Kubernetes YAML file. -// Need to access for now to Namespace only +// +// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within +// the rules package. type K8sYamlStruct struct { - Metadata struct { - Namespace string - } + Metadata k8sYamlMetadata +} + +type k8sYamlMetadata struct { + Namespace string + Name string } diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index ddb46aba0..c924de0e7 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -101,3 +101,32 @@ func TestV3Fail(t *testing.T) { t.Errorf("Unexpected error: %s", res[2].Err) } } + +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, + } + for input, expectPass := range names { + obj := K8sYamlStruct{Metadata: k8sYamlMetadata{Name: input}} + if err := validateMetadataName(&obj); (err == nil) != expectPass { + st := "fail" + if expectPass { + st = "succeed" + } + t.Errorf("Expected %q to %s", input, st) + if err != nil { + t.Log(err) + } + } + } +} diff --git a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml b/pkg/lint/rules/testdata/goodone/templates/goodone.yaml index 0e77f46f2..cd46f62c7 100644 --- a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml +++ b/pkg/lint/rules/testdata/goodone/templates/goodone.yaml @@ -1,2 +1,2 @@ metadata: - name: {{.Values.name | default "foo" | title}} + name: {{ .Values.name | default "foo" | lower }} diff --git a/pkg/lint/rules/testdata/goodone/values.yaml b/pkg/lint/rules/testdata/goodone/values.yaml index fe9abd983..92c3d9bb9 100644 --- a/pkg/lint/rules/testdata/goodone/values.yaml +++ b/pkg/lint/rules/testdata/goodone/values.yaml @@ -1 +1 @@ -name: "goodone here" +name: "goodone-here" From fb829c2c843df01ad1dd5ffd13c4e923be4ab9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BCchinger=20Dominic?= Date: Wed, 29 Apr 2020 09:14:22 +0200 Subject: [PATCH 51/59] Fix markdown table in helm command doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tables aren't rendered correctly because the syntax was wrong. Fixed it by applying the https://www.markdownguide.org/extended-syntax/#tables table syntax. See https://helm.sh/docs/helm/helm/#synopsis for the wrong rendering. ![Broken table in html](https://imgur.com/bniKxO6) Fix for helm/helm-www#575 Signed-off-by: Lüchinger Dominic --- cmd/helm/root.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 2c66d3a09..ea66a061a 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -43,17 +43,15 @@ Common actions for Helm: Environment variables: -+------------------+--------------------------------------------------------------------------------------------------------+ -| Name | Description | -+------------------+--------------------------------------------------------------------------------------------------------+ -| $XDG_CACHE_HOME | set an alternative location for storing cached files. | -| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | -| $XDG_DATA_HOME | set an alternative location for storing Helm data. | -| $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_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | -| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | -+------------------+--------------------------------------------------------------------------------------------------------+ +| Name | Description | +|------------------------------------|-----------------------------------------------------------------------------------| +| $XDG_CACHE_HOME | set an alternative location for storing cached files. | +| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | +| $XDG_DATA_HOME | set an alternative location for storing Helm data. | +| $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_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | +| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | Helm stores configuration based on the XDG base directory specification, so @@ -63,13 +61,11 @@ Helm stores configuration based on the XDG base directory specification, so By default, the default directories depend on the Operating System. The defaults are listed below: -+------------------+---------------------------+--------------------------------+-------------------------+ | Operating System | Cache Path | Configuration Path | Data Path | -+------------------+---------------------------+--------------------------------+-------------------------+ +|------------------|---------------------------|--------------------------------|-------------------------| | Linux | $HOME/.cache/helm | $HOME/.config/helm | $HOME/.local/share/helm | | macOS | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm | | Windows | %TEMP%\helm | %APPDATA%\helm | %APPDATA%\helm | -+------------------+---------------------------+--------------------------------+-------------------------+ ` func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command { From ff3ed53b7c0c07aab2bc8cc59344d18063e8a719 Mon Sep 17 00:00:00 2001 From: Liu Ming Date: Thu, 30 Apr 2020 17:31:27 +0800 Subject: [PATCH 52/59] polish to keep the same log style Signed-off-by: Liu Ming --- pkg/kube/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 8a4831ffb..c1de2b299 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -439,7 +439,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, if err != nil { return errors.Wrap(err, "failed to replace object") } - c.Log("Replaced %q with kind %s for kind %s\n", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) + c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) } else { // send patch to server obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) From bf9d629dc02e759a1ba09ff8f6784dd0fb585cf3 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 1 May 2020 14:01:15 -0600 Subject: [PATCH 53/59] feat: implement deprecation warnings in helm lint (#7986) * feat: implement deprecation warnings in helm lint Signed-off-by: Matt Butcher * added more deprecated APIs Signed-off-by: Matt Butcher --- pkg/chartutil/save.go | 3 ++ pkg/lint/rules/deprecations.go | 64 +++++++++++++++++++++++++++++ pkg/lint/rules/deprecations_test.go | 42 +++++++++++++++++++ pkg/lint/rules/template.go | 5 ++- pkg/lint/rules/template_test.go | 44 ++++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 pkg/lint/rules/deprecations.go create mode 100644 pkg/lint/rules/deprecations_test.go diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index be5d151d7..1011436b5 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -34,6 +34,9 @@ import ( var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. +// +// This takes the chart name, and creates a new subdirectory inside of the given dest +// directory, writing the chart's contents to that subdirectory. func SaveDir(c *chart.Chart, dest string) error { // Create the chart directory outdir := filepath.Join(dest, c.Name()) diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go new file mode 100644 index 000000000..c14fedec6 --- /dev/null +++ b/pkg/lint/rules/deprecations.go @@ -0,0 +1,64 @@ +/* +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 rules // import "helm.sh/helm/v3/pkg/lint/rules" + +import "fmt" + +// deprecatedAPIs lists APIs that are deprecated (left) with suggested alternatives (right). +// +// An empty rvalue indicates that the API is completely deprecated. +var deprecatedAPIs = map[string]string{ + "extensions/v1 Deployment": "apps/v1 Deployment", + "extensions/v1 DaemonSet": "apps/v1 DaemonSet", + "extensions/v1 ReplicaSet": "apps/v1 ReplicaSet", + "extensions/v1beta1 PodSecurityPolicy": "policy/v1beta1 PodSecurityPolicy", + "extensions/v1beta1 NetworkPolicy": "networking.k8s.io/v1beta1 NetworkPolicy", + "extensions/v1beta1 Ingress": "networking.k8s.io/v1beta1 Ingress", + "apps/v1beta1 Deployment": "apps/v1 Deployment", + "apps/v1beta1 StatefulSet": "apps/v1 StatefulSet", + "apps/v1beta1 DaemonSet": "apps/v1 DaemonSet", + "apps/v1beta1 ReplicaSet": "apps/v1 ReplicaSet", + "apps/v1beta2 Deployment": "apps/v1 Deployment", + "apps/v1beta2 StatefulSet": "apps/v1 StatefulSet", + "apps/v1beta2 DaemonSet": "apps/v1 DaemonSet", + "apps/v1beta2 ReplicaSet": "apps/v1 ReplicaSet", +} + +// deprecatedAPIError indicates than an API is deprecated in Kubernetes +type deprecatedAPIError struct { + Deprecated string + Alternative string +} + +func (e deprecatedAPIError) Error() string { + msg := fmt.Sprintf("the kind %q is deprecated", e.Deprecated) + if e.Alternative != "" { + msg += fmt.Sprintf(" in favor of %q", e.Alternative) + } + return msg +} + +func validateNoDeprecations(resource *K8sYamlStruct) error { + gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) + if alt, ok := deprecatedAPIs[gvk]; ok { + return deprecatedAPIError{ + Deprecated: gvk, + Alternative: alt, + } + } + return nil +} diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go new file mode 100644 index 000000000..f85d58a0c --- /dev/null +++ b/pkg/lint/rules/deprecations_test.go @@ -0,0 +1,42 @@ +/* +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 rules // import "helm.sh/helm/v3/pkg/lint/rules" + +import "testing" + +func TestValidateNoDeprecations(t *testing.T) { + deprecated := &K8sYamlStruct{ + APIVersion: "extensions/v1", + Kind: "Deployment", + } + err := validateNoDeprecations(deprecated) + if err == nil { + t.Fatal("Expected deprecated extension to be flagged") + } + + depErr := err.(deprecatedAPIError) + if depErr.Alternative != "apps/v1 Deployment" { + t.Errorf("Expected %q to be replaced by %q", depErr.Deprecated, depErr.Alternative) + } + + if err := validateNoDeprecations(&K8sYamlStruct{ + APIVersion: "v1", + Kind: "Pod", + }); err != nil { + t.Errorf("Expected a v1 Pod to not be deprecated") + } +} diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index e27c6a345..b76e4260a 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -131,6 +131,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // on this linter run. linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) linter.RunLinterRule(support.ErrorSev, path, validateMetadataName(&yamlStruct)) + linter.RunLinterRule(support.ErrorSev, path, validateNoDeprecations(&yamlStruct)) } } } @@ -190,7 +191,9 @@ func validateNoReleaseTime(manifest []byte) error { // DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within // the rules package. type K8sYamlStruct struct { - Metadata k8sYamlMetadata + APIVersion string `json:"apiVersion"` + Kind string + Metadata k8sYamlMetadata } type k8sYamlMetadata struct { diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index c924de0e7..1a047edf2 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -22,6 +22,9 @@ import ( "strings" "testing" + "helm.sh/helm/v3/internal/test/ensure" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/lint/support" ) @@ -130,3 +133,44 @@ func TestValidateMetadataName(t *testing.T) { } } } + +func TestDeprecatedAPIFails(t *testing.T) { + mychart := chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: "v2", + Name: "failapi", + Version: "0.1.0", + Icon: "satisfy-the-linting-gods.gif", + }, + Templates: []*chart.File{ + { + Name: "templates/baddeployment.yaml", + Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep"), + }, + { + Name: "templates/goodsecret.yaml", + Data: []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: goodsecret"), + }, + }, + } + tmpdir := ensure.TempDir(t) + defer os.RemoveAll(tmpdir) + + if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { + t.Fatal(err) + } + + linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} + Templates(&linter, values, namespace, strict) + if l := len(linter.Messages); l != 1 { + for i, msg := range linter.Messages { + t.Logf("Message %d: %s", i, msg) + } + t.Fatalf("Expected 1 lint error, got %d", l) + } + + err := linter.Messages[0].Err.(deprecatedAPIError) + if err.Deprecated != "apps/v1beta1 Deployment" { + t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) + } +} From 524150c662f9c030d2caa9ad8f79d2ff9521c431 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 1 May 2020 14:02:47 -0600 Subject: [PATCH 54/59] fix: use correct regular expression for Kubernetes names (#8013) Signed-off-by: Matt Butcher --- pkg/action/action.go | 13 +++++++------ pkg/action/action_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index a8437d729..bb9ef5f71 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -62,16 +62,17 @@ var ( 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") ) -// ValidName is a regular expression for names. +// ValidName is a regular expression for resource names. // // According to the Kubernetes help text, the regular expression it uses is: // -// (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])? +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* // -// We modified that. First, we added start and end delimiters. Second, we changed -// the final ? to + to require that the pattern match at least once. This modification -// prevents an empty string from matching. -var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-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])?)*$`) // Configuration injects the dependencies that all actions share. type Configuration struct { diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 36ef261a3..0cbdb162b 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -316,3 +316,40 @@ 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) + } + } +} From 08e546f169ff3d5694863f0766c3132da2f095b7 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 4 May 2020 13:55:42 -0600 Subject: [PATCH 55/59] fix: removed strict template errors in linter (#8017) Signed-off-by: Matt Butcher --- pkg/lint/rules/template.go | 1 - pkg/lint/rules/template_test.go | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index b76e4260a..787c5b26a 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -81,7 +81,6 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace return } var e engine.Engine - e.Strict = strict e.LintMode = true renderedContentMap, err := e.Render(chart, valuesToRender) diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 1a047edf2..991c6c2f6 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -174,3 +174,55 @@ func TestDeprecatedAPIFails(t *testing.T) { t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) } } + +const manifest = `apiVersion: v1 +kind: ConfigMap +metadata: + name: foo +data: + myval1: {{default "val" .Values.mymap.key1 }} + myval2: {{default "val" .Values.mymap.key2 }} +` + +// TestSTrictTemplatePrasingMapError is a regression test. +// +// The template engine should not produce an error when a map in values.yaml does +// not contain all possible keys. +// +// See https://github.com/helm/helm/issues/7483 +func TestStrictTemplateParsingMapError(t *testing.T) { + + ch := chart.Chart{ + Metadata: &chart.Metadata{ + Name: "regression7483", + APIVersion: "v2", + Version: "0.1.0", + }, + Values: map[string]interface{}{ + "mymap": map[string]string{ + "key1": "val1", + }, + }, + Templates: []*chart.File{ + { + Name: "templates/configmap.yaml", + Data: []byte(manifest), + }, + }, + } + dir := ensure.TempDir(t) + defer os.RemoveAll(dir) + if err := chartutil.SaveDir(&ch, dir); err != nil { + t.Fatal(err) + } + linter := &support.Linter{ + ChartDir: filepath.Join(dir, ch.Metadata.Name), + } + Templates(linter, ch.Values, namespace, strict) + if len(linter.Messages) != 0 { + t.Errorf("expected zero messages, got %d", len(linter.Messages)) + for i, msg := range linter.Messages { + t.Logf("Message %d: %q", i, msg) + } + } +} From 8316a403ed16c76c35ce673e163c0ee30b1e8871 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 16 Apr 2020 15:51:31 -0600 Subject: [PATCH 56/59] bump version to v3.2 Signed-off-by: Matt Butcher (cherry picked from commit 7bffac813db894e06d17bac91d14ea819b5c2310) --- cmd/helm/testdata/output/version-client-shorthand.txt | 2 +- cmd/helm/testdata/output/version-client.txt | 2 +- cmd/helm/testdata/output/version-short.txt | 2 +- cmd/helm/testdata/output/version-template.txt | 2 +- cmd/helm/testdata/output/version.txt | 2 +- internal/version/version.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/helm/testdata/output/version-client-shorthand.txt b/cmd/helm/testdata/output/version-client-shorthand.txt index 8f9ed6136..d613309fe 100644 --- a/cmd/helm/testdata/output/version-client-shorthand.txt +++ b/cmd/helm/testdata/output/version-client-shorthand.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-client.txt b/cmd/helm/testdata/output/version-client.txt index 8f9ed6136..d613309fe 100644 --- a/cmd/helm/testdata/output/version-client.txt +++ b/cmd/helm/testdata/output/version-client.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-short.txt b/cmd/helm/testdata/output/version-short.txt index 861668947..4d5034cea 100644 --- a/cmd/helm/testdata/output/version-short.txt +++ b/cmd/helm/testdata/output/version-short.txt @@ -1 +1 @@ -v3.1 +v3.2 diff --git a/cmd/helm/testdata/output/version-template.txt b/cmd/helm/testdata/output/version-template.txt index e5a779bbf..7c09e8d57 100644 --- a/cmd/helm/testdata/output/version-template.txt +++ b/cmd/helm/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v3.1 \ No newline at end of file +Version: v3.2 \ No newline at end of file diff --git a/cmd/helm/testdata/output/version.txt b/cmd/helm/testdata/output/version.txt index 8f9ed6136..d613309fe 100644 --- a/cmd/helm/testdata/output/version.txt +++ b/cmd/helm/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/internal/version/version.go b/internal/version/version.go index fd0616920..baa65a028 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -30,7 +30,7 @@ var ( // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. // Increment patch number for critical fixes to existing releases. - version = "v3.1" + version = "v3.2" // metadata is extra build time data metadata = "" From be38084eb4d6e0bfb79566083833413947f8bfc8 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 5 May 2020 10:27:47 -0400 Subject: [PATCH 57/59] Fixing argument to be lower case Signed-off-by: Matt Farina --- pkg/helmpath/lazypath.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go index a8a64bfab..22d7bf0a1 100644 --- a/pkg/helmpath/lazypath.go +++ b/pkg/helmpath/lazypath.go @@ -37,7 +37,7 @@ const ( // lazypath is an lazy-loaded path buffer for the XDG base directory specification. type lazypath string -func (l lazypath) path(helmEnvVar, XDGEnvVar string, defaultFn func() string, elem ...string) string { +func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { // There is an order to checking for a path. // 1. See if a Helm specific environment variable has been set. @@ -47,7 +47,7 @@ func (l lazypath) path(helmEnvVar, XDGEnvVar string, defaultFn func() string, el if base != "" { return filepath.Join(base, filepath.Join(elem...)) } - base = os.Getenv(XDGEnvVar) + base = os.Getenv(xdgEnvVar) if base == "" { base = defaultFn() } From e1aaf995a6c238f04eb8449f67feb5f2cb95028f Mon Sep 17 00:00:00 2001 From: Liu Ming Date: Tue, 5 May 2020 17:44:47 +0800 Subject: [PATCH 58/59] refactor: alter constant `pluginFileName` to `PluginFileName` in order to reuse the "plugin.yaml" value in installer package and avoid magic value in installer.go Signed-off-by: Liu Ming --- pkg/plugin/installer/installer.go | 4 +++- pkg/plugin/plugin.go | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 65c61cd7d..61b49ab3b 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -23,6 +23,8 @@ import ( "strings" "github.com/pkg/errors" + + "helm.sh/helm/v3/pkg/plugin" ) // ErrMissingMetadata indicates that plugin.yaml is missing. @@ -100,7 +102,7 @@ func isRemoteHTTPArchive(source string) bool { // isPlugin checks if the directory contains a plugin.yaml file. func isPlugin(dirname string) bool { - _, err := os.Stat(filepath.Join(dirname, "plugin.yaml")) + _, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName)) return err == nil } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 2eb354fca..caa34fbd3 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -28,7 +28,7 @@ import ( "helm.sh/helm/v3/pkg/cli" ) -const pluginFileName = "plugin.yaml" +const PluginFileName = "plugin.yaml" // Downloaders represents the plugins capability if it can retrieve // charts from special sources @@ -159,7 +159,7 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { // LoadDir loads a plugin from the given directory. func LoadDir(dirname string) (*Plugin, error) { - data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName)) + data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName)) if err != nil { return nil, err } @@ -177,7 +177,7 @@ func LoadDir(dirname string) (*Plugin, error) { func LoadAll(basedir string) ([]*Plugin, error) { plugins := []*Plugin{} // We want basedir/*/plugin.yaml - scanpath := filepath.Join(basedir, "*", pluginFileName) + scanpath := filepath.Join(basedir, "*", PluginFileName) matches, err := filepath.Glob(scanpath) if err != nil { return plugins, err From e4768e646095204c16a124b8176c2c6542561a08 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Tue, 5 May 2020 21:04:14 +0000 Subject: [PATCH 59/59] Update lint deprecation list Add api group: - apiextensions.k8s.io/v1beta1 - rbac.authorization.k8s.io/v1alpha1 Also, some kinds moved from extensions/v1 to extensions/v1beta1 Signed-off-by: Martin Hickey --- pkg/lint/rules/deprecations.go | 44 ++++++++++++++++++++--------- pkg/lint/rules/deprecations_test.go | 2 +- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go index c14fedec6..88921408d 100644 --- a/pkg/lint/rules/deprecations.go +++ b/pkg/lint/rules/deprecations.go @@ -22,20 +22,36 @@ import "fmt" // // An empty rvalue indicates that the API is completely deprecated. var deprecatedAPIs = map[string]string{ - "extensions/v1 Deployment": "apps/v1 Deployment", - "extensions/v1 DaemonSet": "apps/v1 DaemonSet", - "extensions/v1 ReplicaSet": "apps/v1 ReplicaSet", - "extensions/v1beta1 PodSecurityPolicy": "policy/v1beta1 PodSecurityPolicy", - "extensions/v1beta1 NetworkPolicy": "networking.k8s.io/v1beta1 NetworkPolicy", - "extensions/v1beta1 Ingress": "networking.k8s.io/v1beta1 Ingress", - "apps/v1beta1 Deployment": "apps/v1 Deployment", - "apps/v1beta1 StatefulSet": "apps/v1 StatefulSet", - "apps/v1beta1 DaemonSet": "apps/v1 DaemonSet", - "apps/v1beta1 ReplicaSet": "apps/v1 ReplicaSet", - "apps/v1beta2 Deployment": "apps/v1 Deployment", - "apps/v1beta2 StatefulSet": "apps/v1 StatefulSet", - "apps/v1beta2 DaemonSet": "apps/v1 DaemonSet", - "apps/v1beta2 ReplicaSet": "apps/v1 ReplicaSet", + "extensions/v1beta1 Deployment": "apps/v1 Deployment", + "extensions/v1beta1 DaemonSet": "apps/v1 DaemonSet", + "extensions/v1beta1 ReplicaSet": "apps/v1 ReplicaSet", + "extensions/v1beta1 PodSecurityPolicy": "policy/v1beta1 PodSecurityPolicy", + "extensions/v1beta1 NetworkPolicy": "networking.k8s.io/v1beta1 NetworkPolicy", + "extensions/v1beta1 Ingress": "networking.k8s.io/v1beta1 Ingress", + "apps/v1beta1 Deployment": "apps/v1 Deployment", + "apps/v1beta1 StatefulSet": "apps/v1 StatefulSet", + "apps/v1beta1 ReplicaSet": "apps/v1 ReplicaSet", + "apps/v1beta2 Deployment": "apps/v1 Deployment", + "apps/v1beta2 StatefulSet": "apps/v1 StatefulSet", + "apps/v1beta2 DaemonSet": "apps/v1 DaemonSet", + "apps/v1beta2 ReplicaSet": "apps/v1 ReplicaSet", + "apiextensions.k8s.io/v1beta1 CustomResourceDefinition": "apiextensions.k8s.io/v1 CustomResourceDefinition", + "rbac.authorization.k8s.io/v1alpha1 ClusterRole": "rbac.authorization.k8s.io/v1 ClusterRole", + "rbac.authorization.k8s.io/v1alpha1 ClusterRoleList": "rbac.authorization.k8s.io/v1 ClusterRoleList", + "rbac.authorization.k8s.io/v1alpha1 ClusterRoleBinding": "rbac.authorization.k8s.io/v1 ClusterRoleBinding", + "rbac.authorization.k8s.io/v1alpha1 ClusterRoleBindingList": "rbac.authorization.k8s.io/v1 ClusterRoleBindingList", + "rbac.authorization.k8s.io/v1alpha1 Role": "rbac.authorization.k8s.io/v1 Role", + "rbac.authorization.k8s.io/v1alpha1 RoleList": "rbac.authorization.k8s.io/v1 RoleList", + "rbac.authorization.k8s.io/v1alpha1 RoleBinding": "rbac.authorization.k8s.io/v1 RoleBinding", + "rbac.authorization.k8s.io/v1alpha1 RoleBindingList": "rbac.authorization.k8s.io/v1 RoleBindingList", + "rbac.authorization.k8s.io/v1beta1 ClusterRole": "rbac.authorization.k8s.io/v1 ClusterRole", + "rbac.authorization.k8s.io/v1beta1 ClusterRoleList": "rbac.authorization.k8s.io/v1 ClusterRoleList", + "rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding": "rbac.authorization.k8s.io/v1 ClusterRoleBinding", + "rbac.authorization.k8s.io/v1beta1 ClusterRoleBindingList": "rbac.authorization.k8s.io/v1 ClusterRoleBindingList", + "rbac.authorization.k8s.io/v1beta1 Role": "rbac.authorization.k8s.io/v1 Role", + "rbac.authorization.k8s.io/v1beta1 RoleList": "rbac.authorization.k8s.io/v1 RoleList", + "rbac.authorization.k8s.io/v1beta1 RoleBinding": "rbac.authorization.k8s.io/v1 RoleBinding", + "rbac.authorization.k8s.io/v1beta1 RoleBindingList": "rbac.authorization.k8s.io/v1 RoleBindingList", } // deprecatedAPIError indicates than an API is deprecated in Kubernetes diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go index f85d58a0c..1e8d34702 100644 --- a/pkg/lint/rules/deprecations_test.go +++ b/pkg/lint/rules/deprecations_test.go @@ -20,7 +20,7 @@ import "testing" func TestValidateNoDeprecations(t *testing.T) { deprecated := &K8sYamlStruct{ - APIVersion: "extensions/v1", + APIVersion: "extensions/v1beta1", Kind: "Deployment", } err := validateNoDeprecations(deprecated)