From e9bf446fa8faf5fa31352aa708829a951404200e Mon Sep 17 00:00:00 2001 From: James McElwain Date: Thu, 5 Dec 2019 07:21:58 -0800 Subject: [PATCH 01/76] fix(install): use ca file for install (#7140) Fixes a few bugs related to tls config when installing charts: 1. When installing via relative path, tls config for the selected repository was not being set. 2. The `--ca-file` flag was not being passed when constructing the downloader. 3. Setting tls config was not checking for zero value in repo config, causing flag to get overwritten with empty string. There's still a few oddities here. I would expect that the flag passed in on the command line would override the repo config, but that's not currently the case. Also, we always set the cert, key and ca files as a trio, when they should be set individually depending on combination of flags / repo config. Signed-off-by: James McElwain --- pkg/action/install.go | 1 + pkg/downloader/chart_downloader.go | 19 ++++----- pkg/downloader/chart_downloader_test.go | 52 +++++++++++++++++++++++++ pkg/repo/repotest/server.go | 36 +++++++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 8cd270693..85d963a54 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -648,6 +648,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( Getters: getter.All(settings), Options: []getter.Option{ getter.WithBasicAuth(c.Username, c.Password), + getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), }, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index f3d4321c5..039661de4 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -181,8 +181,10 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er c.Options = append( c.Options, getter.WithURL(rc.URL), - getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile), ) + if rc.CertFile != "" || rc.KeyFile != "" || rc.CAFile != "" { + getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile) + } if rc.Username != "" && rc.Password != "" { c.Options = append( c.Options, @@ -210,12 +212,14 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er if err != nil { return u, err } - if r != nil && r.Config != nil && r.Config.Username != "" && r.Config.Password != "" { - c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password)) - } - if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { - c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) + if r != nil && r.Config != nil { + if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { + c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) + } + if r.Config.Username != "" && r.Config.Password != "" { + c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password)) + } } // Next, we need to load the index, and actually look up the chart. @@ -255,9 +259,6 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er if _, err := getter.NewHTTPGetter(getter.WithURL(rc.URL)); err != nil { return repoURL, err } - if r != nil && r.Config != nil && r.Config.Username != "" && r.Config.Password != "" { - c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password)) - } return u, err } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index e0692c8c8..abfb007ff 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -227,6 +227,58 @@ func TestDownloadTo(t *testing.T) { } } +func TestDownloadTo_TLS(t *testing.T) { + // Set up mock server w/ tls enabled + srv, err := repotest.NewTempServer("testdata/*.tgz*") + srv.Stop() + if err != nil { + t.Fatal(err) + } + srv.StartTLS() + defer srv.Stop() + if err := srv.CreateIndex(); err != nil { + t.Fatal(err) + } + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + repoConfig := filepath.Join(srv.Root(), "repositories.yaml") + repoCache := srv.Root() + + c := ChartDownloader{ + Out: os.Stderr, + Verify: VerifyAlways, + Keyring: "testdata/helm-test-key.pub", + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + Getters: getter.All(&cli.EnvSettings{ + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + }), + Options: []getter.Option{}, + } + cname := "test/signtest" + dest := srv.Root() + where, v, err := c.DownloadTo(cname, "", dest) + if err != nil { + t.Fatal(err) + } + + target := filepath.Join(dest, "signtest-0.1.0.tgz") + if expect := target; where != expect { + t.Errorf("Expected download to %s, got %s", expect, where) + } + + if v.FileHash == "" { + t.Error("File hash was empty, but verification is required.") + } + + if _, err := os.Stat(target); err != nil { + t.Error(err) + } +} + func TestDownloadTo_VerifyLater(t *testing.T) { defer ensure.HelmHome(t)() diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index 96a8bbfcc..b18bce49c 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -22,6 +22,8 @@ import ( "os" "path/filepath" + "helm.sh/helm/v3/internal/tlsutil" + "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/repo" @@ -143,6 +145,40 @@ func (s *Server) Start() { })) } +func (s *Server) StartTLS() { + cd := "../../testdata" + ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") + + s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if s.middleware != nil { + s.middleware.ServeHTTP(w, r) + } + http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r) + })) + tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) + if err != nil { + panic(err) + } + tlsConf.BuildNameToCertificate() + tlsConf.ServerName = "helm.sh" + s.srv.TLS = tlsConf + s.srv.StartTLS() + + // Set up repositories config with ca file + repoConfig := filepath.Join(s.Root(), "repositories.yaml") + + r := repo.NewFile() + r.Add(&repo.Entry{ + Name: "test", + URL: s.URL(), + CAFile: filepath.Join("../../testdata", "rootca.crt"), + }) + + if err := r.WriteFile(repoConfig, 0644); err != nil { + panic(err) + } +} + // Stop stops the server and closes all connections. // // It should be called explicitly. From e3976ab7a286ecbe1038a725fbc4149b95267abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Mac=C3=ADk?= Date: Mon, 9 Dec 2019 12:56:55 +0100 Subject: [PATCH 02/76] Repair failing unit tests - failure caused by os.Stat return values for directory size on Linux. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pavel MacĂ­k --- pkg/chartutil/expand_test.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pkg/chartutil/expand_test.go b/pkg/chartutil/expand_test.go index 0eb35aedb..9a85e3247 100644 --- a/pkg/chartutil/expand_test.go +++ b/pkg/chartutil/expand_test.go @@ -39,19 +39,6 @@ func TestExpand(t *testing.T) { t.Fatal(err) } - files, err := ioutil.ReadDir(dest) - if err != nil { - t.Fatalf("error reading output directory %s: %s", dest, err) - } - - if len(files) != 1 { - t.Fatalf("expected a single chart directory in output directory %s", dest) - } - - if !files[0].IsDir() { - t.Fatalf("expected a chart directory in output directory %s", dest) - } - expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) if err != nil { @@ -81,8 +68,14 @@ func TestExpand(t *testing.T) { if err != nil { t.Fatal(err) } - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + // os.Stat can return different values for directories, based on the OS + // for Linux, for example, os.Stat alwaty returns the size of the directory + // (value-4096) regardless of the size of the contents of the directory + mode := expect.Mode() + if !mode.IsDir() { + if fi.Size() != expect.Size() { + t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + } } } } @@ -127,8 +120,14 @@ func TestExpandFile(t *testing.T) { if err != nil { t.Fatal(err) } - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + // os.Stat can return different values for directories, based on the OS + // for Linux, for example, os.Stat alwaty returns the size of the directory + // (value-4096) regardless of the size of the contents of the directory + mode := expect.Mode() + if !mode.IsDir() { + if fi.Size() != expect.Size() { + t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + } } } } From 2f705f94b6f6c1d7846ea5f7748ba16c803f8797 Mon Sep 17 00:00:00 2001 From: Guangwen Feng Date: Thu, 9 Jan 2020 16:27:25 +0800 Subject: [PATCH 03/76] Add corresponding unit test to the function in parser.go Signed-off-by: Guangwen Feng --- pkg/strvals/parser_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 7f38efa52..44d317220 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -449,6 +449,39 @@ func TestParseIntoString(t *testing.T) { } } +func TestParseFile(t *testing.T) { + input := "name1=path1" + expect := map[string]interface{}{ + "name1": "value1", + } + rs2v := func(rs []rune) (interface{}, error) { + v := string(rs) + if v != "path1" { + t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) + return "", nil + } + return "value1", nil + } + + got, err := ParseFile(input, rs2v) + if err != nil { + t.Fatal(err) + } + + y1, err := yaml.Marshal(expect) + if err != nil { + t.Fatal(err) + } + y2, err := yaml.Marshal(got) + if err != nil { + t.Fatalf("Error serializing parsed value: %s", err) + } + + if string(y1) != string(y2) { + t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + } +} + func TestParseIntoFile(t *testing.T) { got := map[string]interface{}{} input := "name1=path1" From 10b44a114deef82be64195ab446519b76fd037fb Mon Sep 17 00:00:00 2001 From: Dao Cong Tien Date: Thu, 16 Jan 2020 18:40:24 +0700 Subject: [PATCH 04/76] Add unit test for Reverse() in pkg/releaseutil.go Signed-off-by: Dao Cong Tien --- pkg/releaseutil/sorter_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkg/releaseutil/sorter_test.go b/pkg/releaseutil/sorter_test.go index 69a6543ad..9544d2018 100644 --- a/pkg/releaseutil/sorter_test.go +++ b/pkg/releaseutil/sorter_test.go @@ -79,3 +79,30 @@ func TestSortByRevision(t *testing.T) { return vi < vj }) } + +func TestReverseSortByName(t *testing.T) { + Reverse(releases, SortByName) + check(t, "ByName", func(i, j int) bool { + ni := releases[i].Name + nj := releases[j].Name + return ni > nj + }) +} + +func TestReverseSortByDate(t *testing.T) { + Reverse(releases, SortByDate) + check(t, "ByDate", func(i, j int) bool { + ti := releases[i].Info.LastDeployed.Second() + tj := releases[j].Info.LastDeployed.Second() + return ti > tj + }) +} + +func TestReverseSortByRevision(t *testing.T) { + Reverse(releases, SortByRevision) + check(t, "ByRevision", func(i, j int) bool { + vi := releases[i].Version + vj := releases[j].Version + return vi > vj + }) +} From d39543013b4feba87b73e5a7366ad08a75ce884c Mon Sep 17 00:00:00 2001 From: Daniel Poelzleithner Date: Fri, 17 Jan 2020 17:43:38 +0100 Subject: [PATCH 05/76] Use /usr/bin/env for bash After this change, make works on nixos. Signed-off-by: Daniel Poelzleithner --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7d254e919..0f9d6a8a2 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ GOFLAGS := SRC := $(shell find . -type f -name '*.go' -print) # Required for globs to work correctly -SHELL = /bin/bash +SHELL = /usr/bin/env bash GIT_COMMIT = $(shell git rev-parse HEAD) GIT_SHA = $(shell git rev-parse --short HEAD) From d03db32c250bc7906c9d4b0e0858b0412c55dfcf Mon Sep 17 00:00:00 2001 From: Florian Hopfensperger Date: Mon, 3 Feb 2020 11:10:52 +0100 Subject: [PATCH 06/76] fixed dependencies processing in case of helm install or upgrade for disabled/enabled sub charts Signed-off-by: Florian Hopfensperger --- pkg/chartutil/dependencies.go | 11 +++++++ pkg/chartutil/dependencies_test.go | 30 ++++++++++++++++++ .../parent-chart/Chart.lock | 9 ++++++ .../parent-chart/Chart.yaml | 22 +++++++++++++ .../parent-chart/charts/dev-v0.1.0.tgz | Bin 0 -> 333 bytes .../parent-chart/charts/prod-v0.1.0.tgz | Bin 0 -> 336 bytes .../parent-chart/envs/dev/Chart.yaml | 4 +++ .../parent-chart/envs/dev/values.yaml | 9 ++++++ .../parent-chart/envs/prod/Chart.yaml | 4 +++ .../parent-chart/envs/prod/values.yaml | 9 ++++++ .../parent-chart/templates/autoscaler.yaml | 16 ++++++++++ .../parent-chart/values.yaml | 10 ++++++ 12 files changed, 124 insertions(+) create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml create mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go index 4b389dc22..8783b89bd 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chartutil/dependencies.go @@ -173,6 +173,14 @@ Loop: cd = append(cd, n) } } + // don't keep disabled charts in metadata + cdMetadata := []*chart.Dependency{} + copy(cdMetadata, c.Metadata.Dependencies[:0]) + for _, n := range c.Metadata.Dependencies { + if _, ok := rm[n.Name]; !ok { + cdMetadata = append(cdMetadata, n) + } + } // recursively call self to process sub dependencies for _, t := range cd { @@ -181,6 +189,9 @@ Loop: return err } } + // set the correct dependencies in metadata + c.Metadata.Dependencies = nil + c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...) c.SetDependencies(cd...) return nil diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go index ecd632540..342d7fe87 100644 --- a/pkg/chartutil/dependencies_test.go +++ b/pkg/chartutil/dependencies_test.go @@ -239,6 +239,36 @@ func TestProcessDependencyImportValues(t *testing.T) { } } +func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { + c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") + nameOverride := "parent-chart-prod" + + if err := processDependencyImportValues(c); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 1 { + t.Fatal("expected no changes in dependencies") + } + + if len(c.Metadata.Dependencies) != 1 { + t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) + } + + prodDependencyValues := c.Dependencies()[0].Values + if prodDependencyValues["nameOverride"] != nameOverride { + t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) + } +} + func TestGetAliasDependency(t *testing.T) { c := loadChart(t, "testdata/frobnitz") req := c.Metadata.Dependencies diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock new file mode 100644 index 000000000..b2f17fb39 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: dev + repository: file://envs/dev + version: v0.1.0 +- name: prod + repository: file://envs/prod + version: v0.1.0 +digest: sha256:9403fc24f6cf9d6055820126cf7633b4bd1fed3c77e4880c674059f536346182 +generated: "2020-02-03T10:38:51.180474+01:00" diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml new file mode 100644 index 000000000..24b26d9e5 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v2 +name: parent-chart +version: v0.1.0 +appVersion: v0.1.0 +dependencies: + - name: dev + repository: "file://envs/dev" + version: ">= 0.0.1" + condition: dev.enabled,global.dev.enabled + tags: + - dev + import-values: + - data + + - name: prod + repository: "file://envs/prod" + version: ">= 0.0.1" + condition: prod.enabled,global.prod.enabled + tags: + - prod + import-values: + - data \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d28e1621c86a56affb0617a912930d982ee5d09c GIT binary patch literal 333 zcmV-T0kZxdiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK}PYr`-Mg>%lY5bWGeZqs!5+TB+M-CZQ2GbE0Y9n>MvEZ~_~beXUgrQc z1sW=VuODc zVQyr3R8em|NM&qo0PK~)YJ)%!hCTZf13f1l6W36$d4NhGy$?F13%a|^u9EiYi-y+X zrIcVxVZX~T{~R1){(qg==KlCX61K0@waFSFA{Kc*RYY7?#Dhw*eUYg{p-|-sX1h%7 z6TnrrSY98ByIH2oEUQmBkeoRjtJ5jyR=-iu i)>JGtn?PqS;UNZ5Boc}Ioc90#0RR69wG({+3;+PL5}8~8 literal 0 HcmV?d00001 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml new file mode 100644 index 000000000..80a52f538 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: dev +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml new file mode 100644 index 000000000..38f03484d --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml @@ -0,0 +1,9 @@ +# Dev values parent-chart +nameOverride: parent-chart-dev +exports: + data: + resources: + autoscaler: + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml new file mode 100644 index 000000000..bda4be458 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: prod +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml new file mode 100644 index 000000000..10cc756b2 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml @@ -0,0 +1,9 @@ +# Prod values parent-chart +nameOverride: parent-chart-prod +exports: + data: + resources: + autoscaler: + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 90 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml new file mode 100644 index 000000000..976e5a8f1 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml @@ -0,0 +1,16 @@ +################################################################################################### +# parent-chart horizontal pod autoscaler +################################################################################################### +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Release.Name }}-autoscaler + namespace: {{ .Release.Namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1beta1 + kind: Deployment + name: {{ .Release.Name }} + minReplicas: {{ required "A valid .Values.resources.autoscaler.minReplicas entry required!" .Values.resources.autoscaler.minReplicas }} + maxReplicas: {{ required "A valid .Values.resources.autoscaler.maxReplicas entry required!" .Values.resources.autoscaler.maxReplicas }} + targetCPUUtilizationPercentage: {{ required "A valid .Values.resources.autoscaler.targetCPUUtilizationPercentage!" .Values.resources.autoscaler.targetCPUUtilizationPercentage }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml new file mode 100644 index 000000000..b812f0a33 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml @@ -0,0 +1,10 @@ +# Default values for parent-chart. +nameOverride: parent-chart +tags: + dev: false + prod: true +resources: + autoscaler: + minReplicas: 0 + maxReplicas: 0 + targetCPUUtilizationPercentage: 99 \ No newline at end of file From 438eaec971774b2e30e97aa640abd5cfe8b3ef40 Mon Sep 17 00:00:00 2001 From: Federico Bevione Date: Wed, 5 Feb 2020 16:21:01 +0100 Subject: [PATCH 07/76] fix(helm): Don't wait for service to be ready when external IP are set Resolves #7513 As the externalIPs are not managed by k8s (according to the doc: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#servicespec-v1-core) helm should not wait for services which set al least one externalIPs. Signed-off-by: Federico Bevione --- pkg/kube/wait.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index f0005a61e..74b1fe6fb 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -188,12 +188,24 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { } // Make sure the service is not explicitly set to "None" before checking the IP - if (s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "") || - // This checks if the service has a LoadBalancer and that balancer has an Ingress defined - (s.Spec.Type == corev1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil) { - w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName()) + if s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "" { return false } + + // This checks if the service has a LoadBalancer and that balancer has an Ingress defined + if s.Spec.Type == corev1.ServiceTypeLoadBalancer { + // do not wait when at least 1 external IP is set + if len(s.Spec.ExternalIPs) > 0 { + w.log("Service has externaIPs addresses: %s/%s (%v)", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) + return true + } + + if s.Status.LoadBalancer.Ingress == nil { + w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName()) + return false + } + } + return true } From 077503f17502ea2ad59d73a08897f238dc72ebb0 Mon Sep 17 00:00:00 2001 From: Federico Bevione Date: Fri, 7 Feb 2020 19:28:30 +0100 Subject: [PATCH 08/76] fix(helm): Reworded logs for clarity Signed-off-by: Federico Bevione --- pkg/kube/wait.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 74b1fe6fb..7ea02d382 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -189,6 +189,7 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { // Make sure the service is not explicitly set to "None" before checking the IP if s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "" { + w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName()) return false } @@ -196,7 +197,7 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { if s.Spec.Type == corev1.ServiceTypeLoadBalancer { // do not wait when at least 1 external IP is set if len(s.Spec.ExternalIPs) > 0 { - w.log("Service has externaIPs addresses: %s/%s (%v)", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) + w.log("Service %s/%s has external IP addresses (%v), marking as ready", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) return true } From 9daca76f16b7b17c6ecb36b30ff7832a4b83b70f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 7 Feb 2020 11:53:41 -0700 Subject: [PATCH 09/76] fixed missing bullet Signed-off-by: Matt Butcher --- ADOPTERS.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 5dccb8fc6..a72f51e09 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,5 +1,7 @@ - To add your organization to this list, simply add your organization's name, - optionally with a link. The list is in alphabetical order. + To add your organization to this list, open a pull request that adds your + organization's name, optionally with a link. The list is in alphabetical order. + + (Remember to use `git commit --signoff` to comply with the DCO) # Organizations Using Helm @@ -8,5 +10,6 @@ - [IBM](https://www.ibm.com) - [Qovery](https://www.qovery.com/) - [Samsung SDS](https://www.samsungsds.com/) -[Ville de Montreal](https://montreal.ca) +- [Ville de Montreal](https://montreal.ca) + _This file is part of the CNCF official documentation for projects._ From 2a73967ca214d38ed22f5f3ecfda3d1dcc2b4773 Mon Sep 17 00:00:00 2001 From: Reinhard Naegele Date: Fri, 7 Feb 2020 19:52:58 +0100 Subject: [PATCH 10/76] Fix 'helm template' to also print invalid yaml Signed-off-by: Reinhard Naegele --- cmd/helm/template.go | 81 ++++++++++--------- cmd/helm/template_test.go | 6 ++ .../output/template-with-invalid-yaml.txt | 13 +++ .../Chart.yaml | 8 ++ .../README.md | 13 +++ .../templates/alpine-pod.yaml | 10 +++ .../values.yaml | 1 + 7 files changed, 92 insertions(+), 40 deletions(-) create mode 100644 cmd/helm/testdata/output/template-with-invalid-yaml.txt create mode 100644 cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md create mode 100644 cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 320718344..54c3426c7 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -64,57 +64,58 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.APIVersions = chartutil.VersionSet(extraAPIs) client.IncludeCRDs = includeCrds rel, err := runInstall(args, client, valueOpts, out) - if err != nil { - return err - } - var manifests bytes.Buffer - fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) + // We ignore a potential error here because we always want to print the YAML, + // even if it is not valid. The error is still returned afterwards. + if rel != nil { + var manifests bytes.Buffer + fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) - if !client.DisableHooks { - for _, m := range rel.Hooks { - fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) + if !client.DisableHooks { + for _, m := range rel.Hooks { + fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) + } } - } - // if we have a list of files to render, then check that each of the - // provided files exists in the chart. - if len(showFiles) > 0 { - splitManifests := releaseutil.SplitManifests(manifests.String()) - manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)") - var manifestsToRender []string - for _, f := range showFiles { - missing := true - for _, manifest := range splitManifests { - submatch := manifestNameRegex.FindStringSubmatch(manifest) - if len(submatch) == 0 { - continue + // if we have a list of files to render, then check that each of the + // provided files exists in the chart. + if len(showFiles) > 0 { + splitManifests := releaseutil.SplitManifests(manifests.String()) + manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)") + var manifestsToRender []string + for _, f := range showFiles { + missing := true + for _, manifest := range splitManifests { + submatch := manifestNameRegex.FindStringSubmatch(manifest) + if len(submatch) == 0 { + continue + } + manifestName := submatch[1] + // manifest.Name is rendered using linux-style filepath separators on Windows as + // well as macOS/linux. + manifestPathSplit := strings.Split(manifestName, "/") + manifestPath := filepath.Join(manifestPathSplit...) + + // if the filepath provided matches a manifest path in the + // chart, render that manifest + if f == manifestPath { + manifestsToRender = append(manifestsToRender, manifest) + missing = false + } } - manifestName := submatch[1] - // manifest.Name is rendered using linux-style filepath separators on Windows as - // well as macOS/linux. - manifestPathSplit := strings.Split(manifestName, "/") - manifestPath := filepath.Join(manifestPathSplit...) - - // if the filepath provided matches a manifest path in the - // chart, render that manifest - if f == manifestPath { - manifestsToRender = append(manifestsToRender, manifest) - missing = false + if missing { + return fmt.Errorf("could not find template %s in chart", f) } } - if missing { - return fmt.Errorf("could not find template %s in chart", f) + for _, m := range manifestsToRender { + fmt.Fprintf(out, "---\n%s\n", m) } + } else { + fmt.Fprintf(out, "%s", manifests.String()) } - for _, m := range manifestsToRender { - fmt.Fprintf(out, "---\n%s\n", m) - } - } else { - fmt.Fprintf(out, "%s", manifests.String()) } - return nil + return err }, } diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index dc7987d01..9e3087596 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -102,6 +102,12 @@ func TestTemplateCmd(t *testing.T) { // don't accidentally get the expected result. repeat: 10, }, + { + name: "chart with template with invalid yaml", + cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-with-invalid-yaml"), + wantError: true, + golden: "output/template-with-invalid-yaml.txt", + }, } runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml.txt b/cmd/helm/testdata/output/template-with-invalid-yaml.txt new file mode 100644 index 000000000..c1f51185c --- /dev/null +++ b/cmd/helm/testdata/output/template-with-invalid-yaml.txt @@ -0,0 +1,13 @@ +--- +# Source: chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml +apiVersion: v1 +kind: Pod +metadata: + name: "RELEASE-NAME-my-alpine" +spec: + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] +invalid +Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':' diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml new file mode 100644 index 000000000..29b477b06 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +description: Deploy a basic Alpine Linux pod +home: https://helm.sh/helm +name: chart-with-template-with-invalid-yaml +sources: + - https://github.com/helm/helm +version: 0.1.0 +type: application diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md new file mode 100644 index 000000000..fcf7ee017 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md @@ -0,0 +1,13 @@ +#Alpine: A simple Helm chart + +Run a single pod of Alpine Linux. + +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.yaml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml new file mode 100644 index 000000000..697cb50fe --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-{{.Values.Name}}" +spec: + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] +invalid diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml new file mode 100644 index 000000000..807e12aea --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml @@ -0,0 +1 @@ +Name: my-alpine From af0007c9087c3e714aafc2bdac80bb55df6dbffd Mon Sep 17 00:00:00 2001 From: Federico Bevione Date: Tue, 11 Feb 2020 09:53:32 +0100 Subject: [PATCH 11/76] fix(helm): improved logs Signed-off-by: Federico Bevione --- pkg/kube/wait.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 7ea02d382..0254a60bb 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -189,7 +189,7 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { // Make sure the service is not explicitly set to "None" before checking the IP if s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "" { - w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName()) + w.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) return false } @@ -202,7 +202,7 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { } if s.Status.LoadBalancer.Ingress == nil { - w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName()) + w.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName()) return false } } From 5e638e3587bff303cd54fe34066d8687a1a60165 Mon Sep 17 00:00:00 2001 From: Benn Linger Date: Tue, 11 Feb 2020 15:46:28 -0500 Subject: [PATCH 12/76] Revert "Do not delete templated CRDs" This reverts commit 9711d1c6bfd9cd0cebe47b069a18cfc83ac3bfee. Resolves issue #7505. Signed-off-by: Benn Linger --- pkg/kube/client.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 215fc8308..9243f4361 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -209,11 +209,6 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err } for _, info := range original.Difference(target) { - if info.Mapping.GroupVersionKind.Kind == "CustomResourceDefinition" { - c.Log("Skipping the deletion of CustomResourceDefinition %q", info.Name) - continue - } - c.Log("Deleting %q in %s...", info.Name, info.Namespace) res.Deleted = append(res.Deleted, info) if err := deleteResource(info); err != nil { @@ -236,11 +231,6 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) { var errs []error res := &Result{} err := perform(resources, func(info *resource.Info) error { - if info.Mapping.GroupVersionKind.Kind == "CustomResourceDefinition" { - c.Log("Skipping the deletion of CustomResourceDefinition %q", info.Name) - return nil - } - c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) if err := c.skipIfNotFound(deleteResource(info)); err != nil { // Collect the error and continue on From b55224ebb9541b690daca59f6d85867c6e275d75 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Tue, 14 Jan 2020 17:08:44 +0100 Subject: [PATCH 13/76] fix(kube): use non global Scheme to convert But instead use a newly initialized Scheme with only Kubernetes native resources added. This ensures the 3-way-merge patch strategy is not accidentally chosen for custom resources due to them being added to the global Scheme by e.g. versioned clients while using Helm as a package, and not a self-contained binary. Signed-off-by: Hidde Beydals --- pkg/kube/converter.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index a5a6ae1f7..7f062213c 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -17,9 +17,12 @@ limitations under the License. package kube // import "helm.sh/helm/v3/pkg/kube" import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/kubernetes/scheme" ) @@ -33,12 +36,28 @@ func AsVersioned(info *resource.Info) runtime.Object { // convertWithMapper converts the given object with the optional provided // RESTMapping. If no mapping is provided, the default schema versioner is used func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { - var gv = runtime.GroupVersioner(schema.GroupVersions(scheme.Scheme.PrioritizedVersionsAllGroups())) + s := kubernetesNativeScheme() + var gv = runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) if mapping != nil { gv = mapping.GroupVersionKind.GroupVersion() } - if obj, err := runtime.ObjectConvertor(scheme.Scheme).ConvertToVersion(obj, gv); err == nil { + if obj, err := runtime.ObjectConvertor(s).ConvertToVersion(obj, gv); err == nil { return obj } return obj } + +// kubernetesNativeScheme returns a clean *runtime.Scheme with _only_ Kubernetes +// native resources added to it. This is required to break free of custom resources +// that may have been added to scheme.Scheme due to Helm being used as a package in +// combination with e.g. a versioned kube client. If we would not do this, the client +// may attempt to perform e.g. a 3-way-merge strategy patch for custom resources. +func kubernetesNativeScheme() *runtime.Scheme { + s := runtime.NewScheme() + utilruntime.Must(scheme.AddToScheme(s)) + // API extensions are not in the above scheme set, + // and must thus be added separately. + utilruntime.Must(apiextensionsv1beta1.AddToScheme(s)) + utilruntime.Must(apiextensionsv1.AddToScheme(s)) + return s +} From e41184a585800dd672856d422b4f8a2bd3d430e0 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Tue, 11 Feb 2020 23:52:00 +0100 Subject: [PATCH 14/76] fix(kube): generate k8s native scheme only once Signed-off-by: Hidde Beydals --- pkg/kube/converter.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index 7f062213c..3bf0e358c 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -17,16 +17,20 @@ limitations under the License. package kube // import "helm.sh/helm/v3/pkg/kube" import ( + "sync" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/kubernetes/scheme" ) +var k8sNativeScheme *runtime.Scheme +var k8sNativeSchemeOnce sync.Once + // AsVersioned converts the given info into a runtime.Object with the correct // group and version set func AsVersioned(info *resource.Info) runtime.Object { @@ -53,11 +57,13 @@ func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Ob // combination with e.g. a versioned kube client. If we would not do this, the client // may attempt to perform e.g. a 3-way-merge strategy patch for custom resources. func kubernetesNativeScheme() *runtime.Scheme { - s := runtime.NewScheme() - utilruntime.Must(scheme.AddToScheme(s)) - // API extensions are not in the above scheme set, - // and must thus be added separately. - utilruntime.Must(apiextensionsv1beta1.AddToScheme(s)) - utilruntime.Must(apiextensionsv1.AddToScheme(s)) - return s + k8sNativeSchemeOnce.Do(func() { + k8sNativeScheme = runtime.NewScheme() + scheme.AddToScheme(k8sNativeScheme) + // API extensions are not in the above scheme set, + // and must thus be added separately. + apiextensionsv1beta1.AddToScheme(k8sNativeScheme) + apiextensionsv1.AddToScheme(k8sNativeScheme) + }) + return k8sNativeScheme } From a35e124b6674d303dbed1c2b27b4b7728b379a55 Mon Sep 17 00:00:00 2001 From: Reinhard Naegele Date: Wed, 12 Feb 2020 20:28:46 +0100 Subject: [PATCH 15/76] Place rendering invalid YAML under --debug flag Signed-off-by: Reinhard Naegele --- cmd/helm/template.go | 11 +++++++++-- cmd/helm/template_test.go | 6 ++++++ .../output/template-with-invalid-yaml-debug.txt | 13 +++++++++++++ .../testdata/output/template-with-invalid-yaml.txt | 14 ++------------ 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 54c3426c7..22565d3e3 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -65,8 +65,15 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.IncludeCRDs = includeCrds rel, err := runInstall(args, client, valueOpts, out) - // We ignore a potential error here because we always want to print the YAML, - // even if it is not valid. The error is still returned afterwards. + if err != nil && !settings.Debug { + if rel != nil { + return fmt.Errorf("%w\n\nUse --debug flag to render out invalid YAML", err) + } + return err + } + + // We ignore a potential error here because, when the --debug flag was specified, + // we always want to print the YAML, even if it is not valid. The error is still returned afterwards. if rel != nil { var manifests bytes.Buffer fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index 9e3087596..3fd139fad 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -108,6 +108,12 @@ func TestTemplateCmd(t *testing.T) { wantError: true, golden: "output/template-with-invalid-yaml.txt", }, + { + name: "chart with template with invalid yaml (--debug)", + cmd: fmt.Sprintf("template '%s' --debug", "testdata/testcharts/chart-with-template-with-invalid-yaml"), + wantError: true, + golden: "output/template-with-invalid-yaml-debug.txt", + }, } runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt new file mode 100644 index 000000000..c1f51185c --- /dev/null +++ b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt @@ -0,0 +1,13 @@ +--- +# Source: chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml +apiVersion: v1 +kind: Pod +metadata: + name: "RELEASE-NAME-my-alpine" +spec: + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] +invalid +Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':' diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml.txt b/cmd/helm/testdata/output/template-with-invalid-yaml.txt index c1f51185c..687227b90 100644 --- a/cmd/helm/testdata/output/template-with-invalid-yaml.txt +++ b/cmd/helm/testdata/output/template-with-invalid-yaml.txt @@ -1,13 +1,3 @@ ---- -# Source: chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml -apiVersion: v1 -kind: Pod -metadata: - name: "RELEASE-NAME-my-alpine" -spec: - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] -invalid Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':' + +Use --debug flag to render out invalid YAML From afdfb75234af3907b5e7fdf19b92ea4f6ce35126 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Sat, 14 Dec 2019 05:59:32 +0530 Subject: [PATCH 16/76] Pass kube user token via cli, introduce HELM_KUBETOKEN envvar Signed-off-by: Vibhav Bobade --- pkg/cli/environment.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 5f947aec7..16127c1cd 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -46,6 +46,8 @@ type EnvSettings struct { KubeConfig string // KubeContext is the name of the kubeconfig context. KubeContext string + // Bearer Token used for authentication + Token string // Debug indicates whether or not Helm is running in Debug mode. Debug bool // RegistryConfig is the path to the registry config file. @@ -77,6 +79,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") + fs.StringVar(&s.Token, "token", s.Token, "bearer token used for authentication") fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") @@ -100,6 +103,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_NAMESPACE": s.Namespace(), "HELM_KUBECONTEXT": s.KubeContext, + "HELM_KUBETOKEN": s.Token, } if s.KubeConfig != "" { @@ -124,7 +128,9 @@ func (s *EnvSettings) Namespace() string { //RESTClientGetter gets the kubeconfig from EnvSettings func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { s.configOnce.Do(func() { - s.config = kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace) + clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace) + clientConfig.BearerToken = &s.Token + s.config = clientConfig }) return s.config } From 89fdbdf3d0cd4777c961330a8f0de7628a461768 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 13 Feb 2020 12:02:23 -0500 Subject: [PATCH 17/76] Making fetch-dist get the sha256sum files Fetching these files is part of the release process. When the new file type was added this step was missed. It will cause the sign make target to fail. Signed-off-by: Matt Farina --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eea3b1e9c..d7954de50 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BINDIR := $(CURDIR)/bin DIST_DIRS := find * -type d -exec TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 -TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-386.tar.gz linux-386.tar.gz.sha256 linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-s390x.tar.gz linux-s390x.tar.gz.sha256 windows-amd64.zip windows-amd64.zip.sha256 +TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum BINNAME ?= helm GOPATH = $(shell go env GOPATH) From 2a7421299148694471ded8e2f60a82e8c74c7fc8 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 13 Feb 2020 12:19:05 -0500 Subject: [PATCH 18/76] ref(go.mod): k8s api 0.17.3 Signed-off-by: Rui Chen --- go.mod | 12 ++++++------ go.sum | 36 +++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 696c2b6d2..9c22ea9e5 100644 --- a/go.mod +++ b/go.mod @@ -29,13 +29,13 @@ require ( github.com/stretchr/testify v1.4.0 github.com/xeipuuv/gojsonschema v1.1.0 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d - k8s.io/api v0.17.2 - k8s.io/apiextensions-apiserver v0.17.2 - k8s.io/apimachinery v0.17.2 - k8s.io/cli-runtime v0.17.2 - k8s.io/client-go v0.17.2 + k8s.io/api v0.17.3 + k8s.io/apiextensions-apiserver v0.17.3 + k8s.io/apimachinery v0.17.3 + k8s.io/cli-runtime v0.17.3 + k8s.io/client-go v0.17.3 k8s.io/klog v1.0.0 - k8s.io/kubectl v0.17.2 + k8s.io/kubectl v0.17.3 sigs.k8s.io/yaml v1.1.0 ) diff --git a/go.sum b/go.sum index cc6b8dd9c..89c7762d5 100644 --- a/go.sum +++ b/go.sum @@ -630,25 +630,27 @@ 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.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= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc= -k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= -k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss= -k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= -k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4= -k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= -k8s.io/cli-runtime v0.17.2 h1:YH4txSplyGudvxjhAJeHEtXc7Tr/16clKGfN076ydGk= -k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= -k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= -k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= -k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= -k8s.io/component-base v0.17.2 h1:0XHf+cerTvL9I5Xwn9v+0jmqzGAZI7zNydv4tL6Cw6A= -k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= +k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0= +k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= +k8s.io/apiextensions-apiserver v0.17.3 h1:WDZWkPcbgvchEdDd7ysL21GGPx3UKZQLDZXEkevT6n4= +k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY= +k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg= +k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY= +k8s.io/cli-runtime v0.17.3 h1:0ZlDdJgJBKsu77trRUynNiWsRuAvAVPBNaQfnt/1qtc= +k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA= +k8s.io/client-go v0.17.3 h1:deUna1Ksx05XeESH6XGCyONNFfiQmDdqeqUvicvP6nU= +k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= +k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ= +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/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/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -657,10 +659,10 @@ 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/kubectl v0.17.2 h1:QZR8Q6lWiVRjwKslekdbN5WPMp53dS/17j5e+oi5XVU= -k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk= +k8s.io/kubectl v0.17.3 h1:9HHYj07kuFkM+sMJMOyQX29CKWq4lvKAG1UIPxNPMQ4= +k8s.io/kubectl v0.17.3/go.mod h1:NUn4IBY7f7yCMwSop2HCXlw/MVYP4HJBiUmOR3n9w28= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw= +k8s.io/metrics v0.17.3/go.mod h1:HEJGy1fhHOjHggW9rMDBJBD3YuGroH3Y1pnIRw9FFaI= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= From 561971c381bfee69fbfae1c396641856f7bb2c9e Mon Sep 17 00:00:00 2001 From: Sebastian Voinea Date: Tue, 11 Feb 2020 21:44:15 +0100 Subject: [PATCH 19/76] feat(upgrade): introduce --disable-openapi-validation: This is a copy of the --disable-openapi-validation flag from the install command as introduced by Matthew Fisher. See commit 67e57a5fbb7b210e534157b8f67c15ffc3445453 It allows upgrading releases without the need to validate the Kubernetes OpenAPI Schema. Signed-off-by: Sebastian Voinea --- cmd/helm/upgrade.go | 2 ++ pkg/action/upgrade.go | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 54badb32c..119d79f8f 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -109,6 +109,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.Namespace = client.Namespace instClient.Atomic = client.Atomic instClient.PostRenderer = client.PostRenderer + instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { @@ -165,6 +166,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") + f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 825920793..08b638171 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -55,12 +55,13 @@ type Upgrade struct { // 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 bool + CleanupOnFail bool + SubNotes bool + Description string + PostRenderer postrender.PostRenderer + DisableOpenAPIValidation bool } // NewUpgrade creates a new Upgrade object with the given configuration. @@ -188,7 +189,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin if len(notesTxt) > 0 { upgradedRelease.Info.Notes = notesTxt } - err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes()) + err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) return currentRelease, upgradedRelease, err } @@ -197,7 +198,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea if err != nil { return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") } - target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), true) + target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) if err != nil { return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } @@ -383,8 +384,8 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV return newVals, nil } -func validateManifest(c kube.Interface, manifest []byte) error { - _, err := c.Build(bytes.NewReader(manifest), true) +func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool) error { + _, err := c.Build(bytes.NewReader(manifest), openAPIValidation) return err } From 0087d838073abcc93fb9ea694256e0b93238ae23 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Thu, 13 Feb 2020 16:20:15 -0800 Subject: [PATCH 20/76] fix(scripts): scrape for the latest v2/v3 release from the releases page Signed-off-by: Matthew Fisher --- scripts/get | 16 +++++++--------- scripts/get-helm-3 | 6 +++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/scripts/get b/scripts/get index 711635ee3..3da11d4a4 100755 --- a/scripts/get +++ b/scripts/get @@ -78,16 +78,14 @@ verifySupported() { # checkDesiredVersion checks if the desired version is available. checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then - # FIXME(bacongobbler): hard code the desired version for the time being. - # A better fix would be to filter for Helm 2 release pages. - TAG="v2.16.1" # Get tag from release URL - # local latest_release_url="https://github.com/helm/helm/releases/latest" - # if type "curl" > /dev/null; then - # TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" ) - # elif type "wget" > /dev/null; then - # TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$") - # fi + 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}') + 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}') + fi else TAG=$DESIRED_VERSION fi diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index c1655a68e..a974d97b6 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -78,11 +78,11 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - local latest_release_url="https://github.com/helm/helm/releases/latest" + local latest_release_url="https://github.com/helm/helm/releases" if type "curl" > /dev/null; then - TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" ) + 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}') elif type "wget" > /dev/null; then - TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$") + 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}') fi else TAG=$DESIRED_VERSION From 54ca8790955f0393ad60acc36383dba032755c9f Mon Sep 17 00:00:00 2001 From: ylvmw Date: Fri, 14 Feb 2020 09:57:08 +0800 Subject: [PATCH 21/76] IsReachable() needs to give detailed error message. Signed-off-by: ylvmw --- 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 9243f4361..31dabcc5d 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -88,7 +88,7 @@ func (c *Client) IsReachable() error { client, _ := c.Factory.KubernetesClientSet() _, err := client.ServerVersion() if err != nil { - return errors.New("Kubernetes cluster unreachable") + return fmt.Errorf("Kubernetes cluster unreachable: %s", err.Error()) } return nil } From 7b9dc71c25f0dea9013d2174b45f16fa202468c3 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Fri, 14 Feb 2020 15:30:26 +0000 Subject: [PATCH 22/76] Fix render error not being propogated Signed-off-by: Martin Hickey --- pkg/action/action_test.go | 14 ++++++++++++++ pkg/action/install.go | 2 +- pkg/action/install_test.go | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index df6a48e7f..36ef261a3 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -230,6 +230,20 @@ func withSampleTemplates() chartOption { } } +func withSampleIncludingIncorrectTemplates() chartOption { + return func(opts *chartOptions) { + sampleTemplates := []*chart.File{ + // This adds basic templates and partials. + {Name: "templates/goodbye", Data: []byte("goodbye: world")}, + {Name: "templates/empty", Data: []byte("")}, + {Name: "templates/incorrect", Data: []byte("{{ .Values.bad.doh }}")}, + {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, + {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, + } + opts.Templates = append(opts.Templates, sampleTemplates...) + } +} + func withMultipleManifestTemplate() chartOption { return func(opts *chartOptions) { sampleTemplates := []*chart.File{ diff --git a/pkg/action/install.go b/pkg/action/install.go index 49c9b5728..79abfae33 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -460,7 +460,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values } if err2 != nil { - return hs, b, "", err + return hs, b, "", err2 } // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index ba350819d..bf47895a1 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -240,6 +240,21 @@ func TestInstallRelease_DryRun(t *testing.T) { is.Equal(res.Info.Description, "Dry run complete") } +func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + instAction.DryRun = true + vals := map[string]interface{}{} + _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) + expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" + if err == nil { + t.Fatalf("Install should fail containing error: %s", expectedErr) + } + if err != nil { + is.Contains(err.Error(), expectedErr) + } +} + func TestInstallRelease_NoHooks(t *testing.T) { is := assert.New(t) instAction := installAction(t) From 03b04639f38f11b9f909ac0ddd86a82f7c43bb4c Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 17 Feb 2020 13:48:12 +0800 Subject: [PATCH 23/76] pkg/gates: add unit test for String Signed-off-by: Zhou Hao --- pkg/gates/gates_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/gates/gates_test.go b/pkg/gates/gates_test.go index a71d4905b..6bdd17ed6 100644 --- a/pkg/gates/gates_test.go +++ b/pkg/gates/gates_test.go @@ -45,3 +45,12 @@ func TestError(t *testing.T) { t.Errorf("incorrect error message. Received %s", g.Error().Error()) } } + +func TestString(t *testing.T) { + os.Unsetenv(name) + g := Gate(name) + + if g.String() != "HELM_EXPERIMENTAL_FEATURE" { + t.Errorf("incorrect string representation. Received %s", g.String()) + } +} From e085bfcb462a573aeb6e61613154d21981fe6035 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 17 Feb 2020 14:21:13 +0800 Subject: [PATCH 24/76] cmd/helm/search_repo: print info to stderr Signed-off-by: Zhou Hao --- cmd/helm/search_repo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index 9f5af1e3c..8a8ac379d 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "path/filepath" "strings" @@ -102,7 +103,7 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command { func (o *searchRepoOptions) run(out io.Writer, args []string) error { o.setupSearchedVersion() - index, err := o.buildIndex(out) + index, err := o.buildIndex() if err != nil { return err } @@ -171,7 +172,7 @@ func (o *searchRepoOptions) applyConstraint(res []*search.Result) ([]*search.Res return data, nil } -func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) { +func (o *searchRepoOptions) buildIndex() (*search.Index, error) { // Load the repositories.yaml rf, err := repo.LoadFile(o.repoFile) if isNotExist(err) || len(rf.Repositories) == 0 { @@ -184,8 +185,7 @@ func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - // TODO should print to stderr - fmt.Fprintf(out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n) + fmt.Fprintf(os.Stderr, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n) continue } From e9f40ed7a51b40966e4d91957357e6f6efc60251 Mon Sep 17 00:00:00 2001 From: Song Shukun Date: Tue, 18 Feb 2020 23:15:56 +0900 Subject: [PATCH 25/76] fix golint failure in pkg/action Signed-off-by: Song Shukun --- pkg/action/action.go | 3 ++- pkg/action/lint.go | 1 + pkg/action/list.go | 2 +- pkg/action/package.go | 1 + pkg/action/show.go | 9 +++++++-- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index a97533696..1af5b3d9a 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -135,6 +135,7 @@ func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) { return c.Capabilities, nil } +// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { conf, err := c.RESTClientGetter.ToRESTConfig() if err != nil { @@ -219,7 +220,7 @@ func (c *Configuration) recordRelease(r *release.Release) { } } -// InitActionConfig initializes the action configuration +// Init initializes the action configuration func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace string, helmDriver string, log DebugLog) error { kc := kube.New(getter) kc.Log = log diff --git a/pkg/action/lint.go b/pkg/action/lint.go index ddb0101c7..2292c14bf 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -38,6 +38,7 @@ type Lint struct { WithSubcharts bool } +// LintResult is the result of Lint type LintResult struct { TotalChartsLinted int Messages []support.Message diff --git a/pkg/action/list.go b/pkg/action/list.go index 5be60ac42..532f18385 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -238,7 +238,7 @@ func filterList(releases []*release.Release) []*release.Release { return list } -// setStateMask calculates the state mask based on parameters. +// SetStateMask calculates the state mask based on parameters. func (l *List) SetStateMask() { if l.All { l.StateMask = ListAll diff --git a/pkg/action/package.go b/pkg/action/package.go index b48fc65f0..19d845cf3 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -112,6 +112,7 @@ func setVersion(ch *chart.Chart, ver string) error { return nil } +// Clearsign signs a chart func (p *Package) Clearsign(filename string) error { // Load keyring signer, err := provenance.NewFromKeyring(p.Keyring, p.Key) diff --git a/pkg/action/show.go b/pkg/action/show.go index b29107d4e..14b59a5ea 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -27,12 +27,17 @@ import ( "helm.sh/helm/v3/pkg/chartutil" ) +// ShowOutputFormat is the format of the output of `helm show` type ShowOutputFormat string const ( - ShowAll ShowOutputFormat = "all" - ShowChart ShowOutputFormat = "chart" + // ShowAll is the format which shows all the information of a chart + ShowAll ShowOutputFormat = "all" + // ShowChart is the format which only shows the chart's definition + ShowChart ShowOutputFormat = "chart" + // ShowValues is the format which only shows the chart's values ShowValues ShowOutputFormat = "values" + // ShowReadme is the format which only shows the chart's README ShowReadme ShowOutputFormat = "readme" ) From eda60a59b61abc7fb20063d9dc0608fe877b3206 Mon Sep 17 00:00:00 2001 From: Song Shukun Date: Wed, 19 Feb 2020 16:52:28 +0900 Subject: [PATCH 26/76] pkg/helmpath: fix unit test for Windows Signed-off-by: Song Shukun --- pkg/helmpath/home_windows_test.go | 6 +++--- pkg/helmpath/lazypath_windows_test.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/helmpath/home_windows_test.go b/pkg/helmpath/home_windows_test.go index af74558a8..796ced62c 100644 --- a/pkg/helmpath/home_windows_test.go +++ b/pkg/helmpath/home_windows_test.go @@ -23,9 +23,9 @@ import ( ) func TestHelmHome(t *testing.T) { - os.Setenv(xdg.XDGCacheHomeEnvVar, "c:\\") - os.Setenv(xdg.XDGConfigHomeEnvVar, "d:\\") - os.Setenv(xdg.XDGDataHomeEnvVar, "e:\\") + os.Setenv(xdg.CacheHomeEnvVar, "c:\\") + os.Setenv(xdg.ConfigHomeEnvVar, "d:\\") + os.Setenv(xdg.DataHomeEnvVar, "e:\\") isEq := func(t *testing.T, a, b string) { if a != b { t.Errorf("Expected %q, got %q", b, a) diff --git a/pkg/helmpath/lazypath_windows_test.go b/pkg/helmpath/lazypath_windows_test.go index d02a0a7f6..866e7b9d9 100644 --- a/pkg/helmpath/lazypath_windows_test.go +++ b/pkg/helmpath/lazypath_windows_test.go @@ -32,7 +32,7 @@ const ( ) func TestDataPath(t *testing.T) { - os.Unsetenv(DataHomeEnvVar) + os.Unsetenv(xdg.DataHomeEnvVar) os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo")) expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) @@ -41,7 +41,7 @@ func TestDataPath(t *testing.T) { t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) } - os.Setenv(DataHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) + os.Setenv(xdg.DataHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) @@ -70,8 +70,8 @@ func TestConfigPath(t *testing.T) { } func TestCachePath(t *testing.T) { - os.Unsetenv(CacheHomeEnvVar) - os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo")) + os.Unsetenv(xdg.CacheHomeEnvVar) + os.Setenv("TEMP", filepath.Join(homedir.HomeDir(), "foo")) expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) @@ -79,7 +79,7 @@ func TestCachePath(t *testing.T) { t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) } - os.Setenv(CacheHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) + os.Setenv(xdg.CacheHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) From f1416a69793aa554b18966a550ff6916e2964128 Mon Sep 17 00:00:00 2001 From: Paul Czarkowski Date: Wed, 19 Feb 2020 14:20:50 -0600 Subject: [PATCH 27/76] Adds script to help craft release notes Signed-off-by: Paul Czarkowski --- Makefile | 15 +++++++ scripts/release-notes.sh | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100755 scripts/release-notes.sh diff --git a/Makefile b/Makefile index eea3b1e9c..730497c4a 100644 --- a/Makefile +++ b/Makefile @@ -181,6 +181,21 @@ checksum: clean: @rm -rf $(BINDIR) ./_dist +.PHONY: release-notes +release-notes: + @if [ ! -d "./_dist" ]; then \ + echo "please run 'make fetch-release' first" && \ + exit 1; \ + fi + @if [ -z "${PREVIOUS_RELEASE}" ]; then \ + echo "please set PREVIOUS_RELEASE environment variable" \ + && exit 1; \ + fi + + @./scripts/release-notes.sh ${PREVIOUS_RELEASE} ${VERSION} + + + .PHONY: info info: @echo "Version: ${VERSION}" diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 000000000..ab7e58d26 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +RELEASE=${RELEASE:-$2} +PREVIOUS_RELEASE=${PREVIOUS_RELEASE:-$1} + +## Ensure Correct Usage +if [[ -z "${PREVIOUS_RELEASE}" || -z "${RELEASE}" ]]; then + echo Usage: + echo ./scripts/release-notes.sh v3.0.0 v3.1.0 + echo or + echo PREVIOUS_RELEASE=v3.0.0 + echo RELEASE=v3.1.0 + echo ./scripts/release-notes.sh + exit 1 +fi + +## validate git tags +for tag in $RELEASE $PREVIOUS_RELEASE; do + OK=$(git tag -l ${tag} | wc -l) + if [[ "$OK" == "0" ]]; then + echo ${tag} is not a valid release version + exit 1 + fi +done + +## Check for hints that checksum files were downloaded +## from `make fetch-dist` +if [[ ! -e "./_dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256" ]]; then + echo "checksum file ./_dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256 not found in ./_dist/" + echo "Did you forget to run \`make fetch-dist\` first ?" + exit 1 +fi + +## Generate CHANGELOG from git log +CHANGELOG=$(git log --no-merges --pretty=format:'- %s %H (%aN)' ${PREVIOUS_RELEASE}..${RELEASE}) +if [[ ! $? -eq 0 ]]; then + echo "Error creating changelog" + echo "try running \`git log --no-merges --pretty=format:'- %s %H (%aN)' ${PREVIOUS_RELEASE}..${RELEASE}\`" + exit 1 +fi + +## guess at MAJOR / MINOR / PATCH versions +MAJOR=$(echo ${RELEASE} | sed 's/^v//' | cut -f1 -d.) +MINOR=$(echo ${RELEASE} | sed 's/^v//' | cut -f2 -d.) +PATCH=$(echo ${RELEASE} | sed 's/^v//' | cut -f3 -d.) + +## Print release notes to stdout +cat <. Users are encouraged to upgrade for the best experience. + +The community keeps growing, and we'd love to see you there! + +- Join the discussion in [Kubernetes Slack](https://kubernetes.slack.com): + - `#helm-users` for questions and just to hang out + - `#helm-dev` for discussing PRs, code, and bugs +- Hang out at the Public Developer Call: Thursday, 9:30 Pacific via [Zoom](https://zoom.us/j/696660622) +- Test, debug, and contribute charts: [GitHub/helm/charts](https://github.com/helm/charts) + +## Notable Changes + +- Add list of +- notable changes here + +## Installation and Upgrading + +Download Helm ${RELEASE}. The common platform binaries are here: + +- [MacOS amd64](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) +- [Linux amd64](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-amd64.tar.gz.sha256)) +- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256)) +- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256)) +- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256)) +- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256)) +- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) +- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) + +The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`. + +## What's Next + +- ${MAJOR}.${MINOR}.$(expr ${PATCH} + 1) will contain only bug fixes. +- ${MAJOR}.$(expr ${MINOR} + 1).${PATCH} is the next feature release. This release will focus on ... + +## Changelog + +${CHANGELOG} +EOF From 239a5e09303e278a69d4c1c103617d351fb14409 Mon Sep 17 00:00:00 2001 From: Paul Czarkowski Date: Wed, 19 Feb 2020 14:27:03 -0600 Subject: [PATCH 28/76] add license headers to release-notes.sh script Signed-off-by: Paul Czarkowski --- scripts/release-notes.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index ab7e58d26..3299eeddc 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -1,4 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash + +# 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. RELEASE=${RELEASE:-$2} PREVIOUS_RELEASE=${PREVIOUS_RELEASE:-$1} From 4bd3b8fc06d3750174d52e3950800e67cfc63210 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Mon, 16 Dec 2019 13:28:36 +0530 Subject: [PATCH 29/76] Pass the apiserver address/port via cli, introduce HELM_KUBEAPISERVER envvar Signed-off-by: Vibhav Bobade --- cmd/helm/load_plugins.go | 2 +- pkg/cli/environment.go | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index f2fb5c01d..610b187d9 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -127,7 +127,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { func manuallyProcessArgs(args []string) ([]string, []string) { known := []string{} unknown := []string{} - kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config"} + kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--registry-config", "--repository-cache", "--repository-config"} knownArg := func(a string) bool { for _, pre := range kvargs { if strings.HasPrefix(a, pre+"=") { diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 16127c1cd..1e3b23617 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -46,8 +46,10 @@ type EnvSettings struct { KubeConfig string // KubeContext is the name of the kubeconfig context. KubeContext string - // Bearer Token used for authentication - Token string + // Bearer KubeToken used for authentication + KubeToken string + // Kubernetes API Server Endpoint for authentication + KubeAPIServer string // Debug indicates whether or not Helm is running in Debug mode. Debug bool // RegistryConfig is the path to the registry config file. @@ -65,6 +67,8 @@ func New() *EnvSettings { env := EnvSettings{ namespace: os.Getenv("HELM_NAMESPACE"), KubeContext: os.Getenv("HELM_KUBECONTEXT"), + KubeToken: os.Getenv("HELM_KUBETOKEN"), + KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")), RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), @@ -79,7 +83,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") - fs.StringVar(&s.Token, "token", s.Token, "bearer token used for authentication") + fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") + fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") @@ -103,7 +108,8 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_NAMESPACE": s.Namespace(), "HELM_KUBECONTEXT": s.KubeContext, - "HELM_KUBETOKEN": s.Token, + "HELM_KUBETOKEN": s.KubeToken, + "HELM_KUBEAPISERVER": s.KubeAPIServer, } if s.KubeConfig != "" { @@ -129,7 +135,13 @@ func (s *EnvSettings) Namespace() string { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { s.configOnce.Do(func() { clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace) - clientConfig.BearerToken = &s.Token + if len(s.KubeToken) > 0 { + clientConfig.BearerToken = &s.KubeToken + } + if len(s.KubeAPIServer) > 0 { + clientConfig.APIServer = &s.KubeAPIServer + } + s.config = clientConfig }) return s.config From 1ff7202a9841b0a6a8d409342305ead6eb1503da Mon Sep 17 00:00:00 2001 From: Song Shukun Date: Thu, 20 Feb 2020 20:52:26 +0900 Subject: [PATCH 30/76] Fix output of list action when it is failed Signed-off-by: Song Shukun --- pkg/action/list.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/action/list.go b/pkg/action/list.go index 532f18385..ac6fd1b75 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -165,6 +165,10 @@ func (l *List) Run() ([]*release.Release, error) { return true }) + if err != nil { + return nil, err + } + if results == nil { return results, nil } From 694cf61aacba6bc4ed59a65d23acd20f6c8b95bd Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Thu, 20 Feb 2020 11:56:03 -0800 Subject: [PATCH 31/76] feat(install): introduce --create-namespace Signed-off-by: Matthew Fisher --- cmd/helm/install.go | 1 + cmd/helm/upgrade.go | 3 +++ pkg/action/install.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index ec2c75a12..719dc9014 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -136,6 +136,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { + f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 119d79f8f..dea866e4d 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -65,6 +65,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewUpgrade(cfg) valueOpts := &values.Options{} var outfmt output.Format + var createNamespace bool cmd := &cobra.Command{ Use: "upgrade [RELEASE] [CHART]", @@ -100,6 +101,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) } instClient := action.NewInstall(cfg) + instClient.CreateNamespace = createNamespace instClient.ChartPathOptions = client.ChartPathOptions instClient.DryRun = client.DryRun instClient.DisableHooks = client.DisableHooks @@ -159,6 +161,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }) f := cmd.Flags() + f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") 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.DryRun, "dry-run", false, "simulate an upgrade") diff --git a/pkg/action/install.go b/pkg/action/install.go index 79abfae33..e7b481d47 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -29,8 +29,11 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" @@ -69,6 +72,7 @@ type Install struct { ChartPathOptions ClientOnly bool + CreateNamespace bool DryRun bool DisableHooks bool Replace bool @@ -265,6 +269,32 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. return rel, nil } + if i.CreateNamespace { + ns := &v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: i.Namespace, + Labels: map[string]string{ + "name": i.Namespace, + }, + }, + } + buf, err := yaml.Marshal(ns) + if err != nil { + return nil, err + } + resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) + if err != nil { + return nil, err + } + if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { + return nil, err + } + } + // If Replace is true, we need to supercede the last release. if i.Replace { if err := i.replaceRelease(rel); err != nil { From d6fad6b3c6aea1ce6ca3d58824dccf62e481bb69 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 14 Feb 2020 21:03:26 -0500 Subject: [PATCH 32/76] feat(comp): Move kube-context completion to Go Signed-off-by: Marc Khouzam --- cmd/helm/helm.go | 11 ++++++ cmd/helm/root.go | 65 +++++++++++++-------------------- internal/completion/complete.go | 10 ++--- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 112d5123f..0ce40732b 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -30,6 +30,7 @@ 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" @@ -67,6 +68,16 @@ 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 = "" + } + } + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), debug); err != nil { log.Fatal(err) } diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 6ce1dcbf4..3c6a0d18e 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" "helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/internal/completion" @@ -31,30 +32,6 @@ import ( "helm.sh/helm/v3/pkg/action" ) -const ( - contextCompFunc = ` -__helm_get_contexts() -{ - __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - local template out - template="{{ range .contexts }}{{ .name }} {{ end }}" - if out=$(kubectl config -o template --template="${template}" view 2>/dev/null); then - COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) - fi -} -` -) - -var ( - // Mapping of global flags that can have dynamic completion and the - // completion function to be used. - bashCompletionFlags = map[string]string{ - // Cannot convert the kube-context flag to Go completion yet because - // an incomplete kube-context will make actionConfig.Init() fail at the very start - "kube-context": "__helm_get_contexts", - } -) - var globalUsage = `The Kubernetes package manager Common actions for Helm: @@ -101,14 +78,14 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string Long: globalUsage, SilenceUsage: true, Args: require.NoArgs, - BashCompletionFunction: fmt.Sprintf("%s%s", contextCompFunc, completion.GetBashCustomFunction()), + BashCompletionFunction: completion.GetBashCustomFunction(), } flags := cmd.PersistentFlags() settings.AddFlags(flags) - flag := flags.Lookup("namespace") // Setup shell completion for the namespace flag + flag := flags.Lookup("namespace") completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { if client, err := actionConfig.KubernetesClientSet(); err == nil { // Choose a long enough timeout that the user notices somethings is not working @@ -129,6 +106,29 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string return nil, completion.BashCompDirectiveDefault }) + // Setup shell completion for the kube-context flag + flag = flags.Lookup("kube-context") + completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + completion.CompDebugln("About to get the different kube-contexts") + + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + if len(settings.KubeConfig) > 0 { + loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig} + } + if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loadingRules, + &clientcmd.ConfigOverrides{}).RawConfig(); err == nil { + ctxs := []string{} + for name := range config.Contexts { + if strings.HasPrefix(name, toComplete) { + ctxs = append(ctxs, name) + } + } + return ctxs, completion.BashCompDirectiveNoFileComp + } + return nil, completion.BashCompDirectiveNoFileComp + }) + // We can safely ignore any errors that flags.Parse encounters since // those errors will be caught later during the call to cmd.Execution. // This call is required to gather configuration information prior to @@ -173,19 +173,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string completion.NewCompleteCmd(settings, out), ) - // Add annotation to flags for which we can generate completion choices - for name, completion := range bashCompletionFlags { - if cmd.Flag(name) != nil { - if cmd.Flag(name).Annotations == nil { - cmd.Flag(name).Annotations = map[string][]string{} - } - cmd.Flag(name).Annotations[cobra.BashCompCustom] = append( - cmd.Flag(name).Annotations[cobra.BashCompCustom], - completion, - ) - } - } - // Add *experimental* subcommands registryClient, err := registry.NewClient( registry.ClientOptDebug(settings.Debug), diff --git a/internal/completion/complete.go b/internal/completion/complete.go index a24390fc0..aa0d134b9 100644 --- a/internal/completion/complete.go +++ b/internal/completion/complete.go @@ -35,9 +35,9 @@ import ( // This should ultimately be pushed down into Cobra. // ================================================================================== -// compRequestCmd Hidden command to request completion results from the program. +// CompRequestCmd Hidden command to request completion results from the program. // Used by the shell completion script. -const compRequestCmd = "__complete" +const CompRequestCmd = "__complete" // Global map allowing to find completion functions for commands or flags. var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){} @@ -123,7 +123,7 @@ __helm_custom_func() done < <(compgen -W "${out[*]}" -- "$cur") fi } -`, compRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp) +`, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp) } // RegisterValidArgsFunc should be called to register a function to provide argument completion for a command @@ -177,14 +177,14 @@ func (d BashCompDirective) string() string { func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { debug = settings.Debug return &cobra.Command{ - Use: fmt.Sprintf("%s [command-line]", compRequestCmd), + Use: fmt.Sprintf("%s [command-line]", CompRequestCmd), DisableFlagsInUseLine: true, Hidden: true, DisableFlagParsing: true, Args: require.MinimumNArgs(1), Short: "Request shell completion choices for the specified command-line", Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s", - compRequestCmd, "to request completion choices for the specified command-line."), + CompRequestCmd, "to request completion choices for the specified command-line."), Run: func(cmd *cobra.Command, args []string) { CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args)) From a29365b3c663f1c182ea78461825ae28b8573915 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Thu, 20 Feb 2020 19:42:47 -0500 Subject: [PATCH 33/76] Adopt resources into release with correct instance and managed-by labels Signed-off-by: Jacob LeGrone --- pkg/action/install.go | 8 ++++++-- pkg/action/upgrade.go | 13 ++++++++++-- pkg/action/validate.go | 45 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 79abfae33..ac57b921c 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -38,6 +38,7 @@ import ( "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" "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" @@ -242,6 +243,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // Mark this release as in-progress rel.SetStatus(release.StatusPendingInstall, "Initial install underway") + var adoptedResources kube.ResourceList resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") @@ -254,9 +256,11 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // deleting the release because the manifest will be pointing at that // resource if !i.ClientOnly && !isUpgrade { - if err := existingResourceConflict(resources); err != nil { + toBeUpdated, err := existingResourceConflict(resources, rel.Name) + if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } + adoptedResources = toBeUpdated } // Bail out here if it is a dry run @@ -291,7 +295,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // At this point, we can do the install. Note that before we were detecting whether to // do an update, but it's not clear whether we WANT to do an update if the re-use is set // to true, since that is basically an upgrade operation. - if _, err := i.cfg.KubeClient.Create(resources); err != nil { + if _, err := i.cfg.KubeClient.Update(adoptedResources, resources, false); err != nil { return i.failRelease(rel, err) } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 08b638171..be64fa618 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -216,10 +216,19 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea } } - if err := existingResourceConflict(toBeCreated); err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a new resource that already exists. Unable to continue with update") + toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name) + if err != nil { + return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") } + toBeUpdated.Visit(func(r *resource.Info, err error) error { + if err != nil { + return err + } + current.Append(r) + return nil + }) + if u.DryRun { u.cfg.Log("dry run for %s", upgradedRelease.Name) if len(u.Description) > 0 { diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 6bbfc5e8d..feecc4ff3 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -21,13 +21,37 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/cli-runtime/pkg/resource" "helm.sh/helm/v3/pkg/kube" ) -func existingResourceConflict(resources kube.ResourceList) error { - err := resources.Visit(func(info *resource.Info, err error) error { +var ( + managedByReq, _ = labels.NewRequirement("app.kubernetes.io/managed-by", selection.Equals, []string{"Helm"}) + accessor = meta.NewAccessor() +) + +func newReleaseSelector(release string) (labels.Selector, error) { + releaseReq, err := labels.NewRequirement("app.kubernetes.io/instance", selection.Equals, []string{release}) + if err != nil { + return nil, err + } + + return labels.Parse(fmt.Sprintf("%s,%s", releaseReq, managedByReq)) +} + +func existingResourceConflict(resources kube.ResourceList, release string) (kube.ResourceList, error) { + sel, err := newReleaseSelector(release) + if err != nil { + return nil, err + } + + requireUpdate := kube.ResourceList{} + + err = resources.Visit(func(info *resource.Info, err error) error { if err != nil { return err } @@ -42,7 +66,20 @@ func existingResourceConflict(resources kube.ResourceList) error { return errors.Wrap(err, "could not get information about the resource") } - return fmt.Errorf("existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind) + // Allow adoption of the resource if it already has app.kubernetes.io/instance and app.kubernetes.io/managed-by labels. + lbls, err := accessor.Labels(existing) + if err == nil { + set := labels.Set(lbls) + if sel.Matches(set) { + requireUpdate = append(requireUpdate, info) + return nil + } + } + + return fmt.Errorf( + "existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", + info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind) }) - return err + + return requireUpdate, err } From 271cb2268bfd94012639f4866e8d52189e12a97b Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Thu, 20 Feb 2020 20:53:27 -0500 Subject: [PATCH 34/76] Alternative: annotation-only solution Signed-off-by: Jacob LeGrone --- pkg/action/install.go | 7 +++++- pkg/action/upgrade.go | 7 +++++- pkg/action/validate.go | 57 +++++++++++++++++++++--------------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index ac57b921c..cab55309c 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -249,6 +249,11 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } + err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace)) + if err != nil { + return nil, err + } + // Install requires an extra validation step of checking that resources // don't already exist before we actually create resources. If we continue // forward and create the release object with resources that already exist, @@ -256,7 +261,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // deleting the release because the manifest will be pointing at that // resource if !i.ClientOnly && !isUpgrade { - toBeUpdated, err := existingResourceConflict(resources, rel.Name) + toBeUpdated, err := existingResourceConflict(resources, rel.Name, rel.Namespace) if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index be64fa618..b42192cda 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -203,6 +203,11 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } + err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace)) + if err != nil { + return upgradedRelease, err + } + // Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist existingResources := make(map[string]bool) for _, r := range current { @@ -216,7 +221,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea } } - toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name) + toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace) if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") } diff --git a/pkg/action/validate.go b/pkg/action/validate.go index feecc4ff3..17a61863e 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -22,36 +22,22 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" "k8s.io/cli-runtime/pkg/resource" "helm.sh/helm/v3/pkg/kube" ) -var ( - managedByReq, _ = labels.NewRequirement("app.kubernetes.io/managed-by", selection.Equals, []string{"Helm"}) - accessor = meta.NewAccessor() -) - -func newReleaseSelector(release string) (labels.Selector, error) { - releaseReq, err := labels.NewRequirement("app.kubernetes.io/instance", selection.Equals, []string{release}) - if err != nil { - return nil, err - } - - return labels.Parse(fmt.Sprintf("%s,%s", releaseReq, managedByReq)) -} +var accessor = meta.NewAccessor() -func existingResourceConflict(resources kube.ResourceList, release string) (kube.ResourceList, error) { - sel, err := newReleaseSelector(release) - if err != nil { - return nil, err - } +const ( + helmReleaseNameAnnotation = "meta.helm.sh/release-name" + helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" +) +func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { requireUpdate := kube.ResourceList{} - err = resources.Visit(func(info *resource.Info, err error) error { + err := resources.Visit(func(info *resource.Info, err error) error { if err != nil { return err } @@ -67,13 +53,10 @@ func existingResourceConflict(resources kube.ResourceList, release string) (kube } // Allow adoption of the resource if it already has app.kubernetes.io/instance and app.kubernetes.io/managed-by labels. - lbls, err := accessor.Labels(existing) - if err == nil { - set := labels.Set(lbls) - if sel.Matches(set) { - requireUpdate = append(requireUpdate, info) - return nil - } + anno, err := accessor.Annotations(existing) + if err == nil && anno != nil && anno[helmReleaseNameAnnotation] == releaseName && anno[helmReleaseNamespaceAnnotation] == releaseNamespace { + requireUpdate = append(requireUpdate, info) + return nil } return fmt.Errorf( @@ -83,3 +66,21 @@ func existingResourceConflict(resources kube.ResourceList, release string) (kube return requireUpdate, err } + +func setMetadataVisitor(releaseName, releaseNamespace string) resource.VisitorFunc { + return func(info *resource.Info, err error) error { + if err != nil { + return err + } + anno, err := accessor.Annotations(info.Object) + if err != nil { + return err + } + if anno == nil { + anno = make(map[string]string) + } + anno[helmReleaseNameAnnotation] = releaseName + anno[helmReleaseNamespaceAnnotation] = releaseNamespace + return accessor.SetAnnotations(info.Object, anno) + } +} From 7654c18c6b561a781f9e7fb7676165ca3ec05a4c Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 25 Nov 2019 21:47:18 -0500 Subject: [PATCH 35/76] feat(comp): Static completion for plugins Allow plugins to optionally specify their sub-cmds and flags through a simple yaml file. When generating the completion script with the command 'helm completion ' (and only then), helm will look for that yaml file in the plugin's directory. If the file exists, helm will create cobra commands and flags so that the completion script will handle them. Signed-off-by: Marc Khouzam --- cmd/helm/load_plugins.go | 128 ++++++++++++++++++ cmd/helm/plugin_test.go | 91 +++++++++++++ .../helm/plugins/echo/completion.yaml | 0 .../helmhome/helm/plugins/env/completion.yaml | 13 ++ .../helm/plugins/exitwith/completion.yaml | 5 + .../helm/plugins/fullenv/completion.yaml | 19 +++ 6 files changed, 256 insertions(+) create mode 100644 cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml create mode 100644 cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml create mode 100644 cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml create mode 100644 cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index f2fb5c01d..b8f34c19f 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -18,6 +18,8 @@ package main import ( "fmt" "io" + "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -26,10 +28,13 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/plugin" ) +const pluginStaticCompletionFile = "completion.yaml" + type pluginError struct { error code int @@ -61,6 +66,13 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { return u, nil } + // If we are dealing with the completion command, we try to load more details about the plugins + // if available, so as to allow for command and flag completion + if subCmd, _, err := baseCmd.Find(os.Args[1:]); err == nil && subCmd.Name() == "completion" { + loadPluginsForCompletion(baseCmd, found) + return + } + // Now we create commands for all of these. for _, plug := range found { plug := plug @@ -180,3 +192,119 @@ func findPlugins(plugdirs string) ([]*plugin.Plugin, error) { } return found, nil } + +// pluginCommand represents the optional completion.yaml file of a plugin +type pluginCommand struct { + Name string `json:"name"` + ValidArgs []string `json:"validArgs"` + Flags []string `json:"flags"` + Commands []pluginCommand `json:"commands"` +} + +// loadPluginsForCompletion will load and parse any completion.yaml provided by the plugins +func loadPluginsForCompletion(baseCmd *cobra.Command, plugins []*plugin.Plugin) { + for _, plug := range plugins { + // Parse the yaml file providing the plugin's subcmds and flags + cmds, err := loadFile(strings.Join( + []string{plug.Dir, pluginStaticCompletionFile}, string(filepath.Separator))) + + if err != nil { + // The file could be missing or invalid. Either way, we at least create the command + // for the plugin name. + if settings.Debug { + log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) + } + cmds = &pluginCommand{Name: plug.Metadata.Name} + } + + // We know what the plugin name must be. + // Let's set it in case the Name field was not specified correctly in the file. + // This insures that we will at least get the plugin name to complete, even if + // there is a problem with the completion.yaml file + cmds.Name = plug.Metadata.Name + + addPluginCommands(baseCmd, cmds) + } +} + +// addPluginCommands is a recursive method that adds the different levels +// of sub-commands and flags for the plugins that provide such information +func addPluginCommands(baseCmd *cobra.Command, cmds *pluginCommand) { + if cmds == nil { + return + } + + if len(cmds.Name) == 0 { + // Missing name for a command + if settings.Debug { + log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) + } + return + } + + // Create a fake command just so the completion script will include it + c := &cobra.Command{ + Use: cmds.Name, + ValidArgs: cmds.ValidArgs, + // A Run is required for it to be a valid command without subcommands + Run: func(cmd *cobra.Command, args []string) {}, + } + baseCmd.AddCommand(c) + + // Create fake flags. + if len(cmds.Flags) > 0 { + // The flags can be created with any type, since we only need them for completion. + // pflag does not allow to create short flags without a corresponding long form + // so we look for all short flags and match them to any long flag. This will allow + // plugins to provide short flags without a long form. + // If there are more short-flags than long ones, we'll create an extra long flag with + // the same single letter as the short form. + shorts := []string{} + longs := []string{} + for _, flag := range cmds.Flags { + if len(flag) == 1 { + shorts = append(shorts, flag) + } else { + longs = append(longs, flag) + } + } + + f := c.Flags() + if len(longs) >= len(shorts) { + for i := range longs { + if i < len(shorts) { + f.BoolP(longs[i], shorts[i], false, "") + } else { + f.Bool(longs[i], false, "") + } + } + } else { + for i := range shorts { + if i < len(longs) { + f.BoolP(longs[i], shorts[i], false, "") + } else { + // Create a long flag with the same name as the short flag. + // Not a perfect solution, but its better than ignoring the extra short flags. + f.BoolP(shorts[i], shorts[i], false, "") + } + } + } + } + + // Recursively add any sub-commands + for _, cmd := range cmds.Commands { + addPluginCommands(c, &cmd) + } +} + +// loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object +func loadFile(path string) (*pluginCommand, error) { + cmds := new(pluginCommand) + b, err := ioutil.ReadFile(path) + if err != nil { + return cmds, errors.New(fmt.Sprintf("File (%s) not provided by plugin. No plugin auto-completion possible.", path)) + } + + err = yaml.Unmarshal(b, cmds) + return cmds, err +} diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index 3fd3a4197..2d9a24ba9 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -19,10 +19,12 @@ import ( "bytes" "os" "runtime" + "sort" "strings" "testing" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) func TestManuallyProcessArgs(t *testing.T) { @@ -151,6 +153,95 @@ func TestLoadPlugins(t *testing.T) { } } +type staticCompletionDetails struct { + use string + validArgs []string + flags []string + next []staticCompletionDetails +} + +func TestLoadPluginsForCompletion(t *testing.T) { + settings.PluginsDirectory = "testdata/helmhome/helm/plugins" + + var out bytes.Buffer + + cmd := &cobra.Command{ + Use: "completion", + } + + loadPlugins(cmd, &out) + + tests := []staticCompletionDetails{ + {"args", []string{}, []string{}, []staticCompletionDetails{}}, + {"echo", []string{}, []string{}, []staticCompletionDetails{}}, + {"env", []string{}, []string{"global"}, []staticCompletionDetails{ + {"list", []string{}, []string{"a", "all", "log"}, []staticCompletionDetails{}}, + {"remove", []string{"all", "one"}, []string{}, []staticCompletionDetails{}}, + }}, + {"exitwith", []string{}, []string{}, []staticCompletionDetails{ + {"code", []string{}, []string{"a", "b"}, []staticCompletionDetails{}}, + }}, + {"fullenv", []string{}, []string{"q", "z"}, []staticCompletionDetails{ + {"empty", []string{}, []string{}, []staticCompletionDetails{}}, + {"full", []string{}, []string{}, []staticCompletionDetails{ + {"less", []string{}, []string{"a", "all"}, []staticCompletionDetails{}}, + {"more", []string{"one", "two"}, []string{"b", "ball"}, []staticCompletionDetails{}}, + }}, + }}, + } + checkCommand(t, cmd.Commands(), tests) +} + +func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompletionDetails) { + if len(plugins) != len(tests) { + t.Fatalf("Expected commands %v, got %v", tests, plugins) + } + + for i := 0; i < len(plugins); i++ { + pp := plugins[i] + tt := tests[i] + if pp.Use != tt.use { + t.Errorf("%s: Expected Use=%q, got %q", pp.Name(), tt.use, pp.Use) + } + + targs := tt.validArgs + pargs := pp.ValidArgs + if len(targs) != len(pargs) { + t.Fatalf("%s: expected args %v, got %v", pp.Name(), targs, pargs) + } + + sort.Strings(targs) + sort.Strings(pargs) + for j := range targs { + if targs[j] != pargs[j] { + t.Errorf("%s: expected validArg=%q, got %q", pp.Name(), targs[j], pargs[j]) + } + } + + tflags := tt.flags + var pflags []string + pp.LocalFlags().VisitAll(func(flag *pflag.Flag) { + pflags = append(pflags, flag.Name) + if len(flag.Shorthand) > 0 && flag.Shorthand != flag.Name { + pflags = append(pflags, flag.Shorthand) + } + }) + if len(tflags) != len(pflags) { + t.Fatalf("%s: expected flags %v, got %v", pp.Name(), tflags, pflags) + } + + sort.Strings(tflags) + sort.Strings(pflags) + for j := range tflags { + if tflags[j] != pflags[j] { + t.Errorf("%s: expected flag=%q, got %q", pp.Name(), tflags[j], pflags[j]) + } + } + // Check the next level + checkCommand(t, pp.Commands(), tt.next) + } +} + func TestLoadPlugins_HelmNoPlugins(t *testing.T) { settings.PluginsDirectory = "testdata/helmhome/helm/plugins" settings.RepositoryConfig = "testdata/helmhome/helm/repository" diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/echo/completion.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml new file mode 100644 index 000000000..e479a0503 --- /dev/null +++ b/cmd/helm/testdata/helmhome/helm/plugins/env/completion.yaml @@ -0,0 +1,13 @@ +name: env +commands: + - name: list + flags: + - a + - all + - log + - name: remove + validArgs: + - all + - one +flags: +- global diff --git a/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml new file mode 100644 index 000000000..e5bf440f6 --- /dev/null +++ b/cmd/helm/testdata/helmhome/helm/plugins/exitwith/completion.yaml @@ -0,0 +1,5 @@ +commands: + - name: code + flags: + - a + - b diff --git a/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml new file mode 100644 index 000000000..e0b161c69 --- /dev/null +++ b/cmd/helm/testdata/helmhome/helm/plugins/fullenv/completion.yaml @@ -0,0 +1,19 @@ +name: wrongname +commands: + - name: empty + - name: full + commands: + - name: more + validArgs: + - one + - two + flags: + - b + - ball + - name: less + flags: + - a + - all +flags: +- z +- q From 97e353bda56f1eff370a14283589be469de2000e Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 21 Jan 2020 22:27:13 -0500 Subject: [PATCH 36/76] feat(comp): Dynamic completion for plugins For each plugin, helm registers a ValidArgsFunction which the completion script will call when it needs dynamic completion. This ValidArgsFunction will setup the plugin environment and then call the executable `plugin.complete`, if it is provided by the plugin. The output of the call to `plugin.complete` will be used as completion choices. The last line of the output can optionally be used by the plugin to specify a completion directive. Signed-off-by: Marc Khouzam --- cmd/helm/load_plugins.go | 105 ++++++++++++++---- cmd/helm/plugin_test.go | 41 +++++++ .../helm/plugins/args/plugin.complete | 13 +++ .../helm/plugins/echo/plugin.complete | 14 +++ cmd/helm/testdata/output/plugin_args_comp.txt | 5 + .../testdata/output/plugin_args_flag_comp.txt | 5 + .../output/plugin_args_many_args_comp.txt | 5 + .../testdata/output/plugin_args_ns_comp.txt | 5 + .../output/plugin_echo_bad_directive.txt | 5 + .../output/plugin_echo_no_directive.txt | 5 + internal/completion/complete.go | 57 ++++++---- 11 files changed, 214 insertions(+), 46 deletions(-) create mode 100755 cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete create mode 100755 cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete create mode 100644 cmd/helm/testdata/output/plugin_args_comp.txt create mode 100644 cmd/helm/testdata/output/plugin_args_flag_comp.txt create mode 100644 cmd/helm/testdata/output/plugin_args_many_args_comp.txt create mode 100644 cmd/helm/testdata/output/plugin_args_ns_comp.txt create mode 100644 cmd/helm/testdata/output/plugin_echo_bad_directive.txt create mode 100644 cmd/helm/testdata/output/plugin_echo_no_directive.txt diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index b8f34c19f..358679302 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -16,6 +16,7 @@ limitations under the License. package main import ( + "bytes" "fmt" "io" "io/ioutil" @@ -23,6 +24,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "syscall" @@ -30,10 +32,14 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/yaml" + "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/plugin" ) -const pluginStaticCompletionFile = "completion.yaml" +const ( + pluginStaticCompletionFile = "completion.yaml" + pluginDynamicCompletionExecutable = "plugin.complete" +) type pluginError struct { error @@ -81,6 +87,33 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { md.Usage = fmt.Sprintf("the %q plugin", md.Name) } + // This function is used to setup the environment for the plugin and then + // call the executable specified by the parameter 'main' + callPluginExecutable := func(cmd *cobra.Command, main string, argv []string, out io.Writer) error { + env := os.Environ() + for k, v := range settings.EnvVars() { + env = append(env, fmt.Sprintf("%s=%s", k, v)) + } + + prog := exec.Command(main, argv...) + prog.Env = env + prog.Stdin = os.Stdin + prog.Stdout = out + prog.Stderr = os.Stderr + if err := prog.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + os.Stderr.Write(eerr.Stderr) + status := eerr.Sys().(syscall.WaitStatus) + return pluginError{ + error: errors.Errorf("plugin %q exited with error", md.Name), + code: status.ExitStatus(), + } + } + return err + } + return nil + } + c := &cobra.Command{ Use: md.Name, Short: md.Usage, @@ -101,33 +134,59 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { return errors.Errorf("plugin %q exited with error", md.Name) } - env := os.Environ() - for k, v := range settings.EnvVars() { - env = append(env, fmt.Sprintf("%s=%s", k, v)) - } - - prog := exec.Command(main, argv...) - prog.Env = env - prog.Stdin = os.Stdin - prog.Stdout = out - prog.Stderr = os.Stderr - if err := prog.Run(); err != nil { - if eerr, ok := err.(*exec.ExitError); ok { - os.Stderr.Write(eerr.Stderr) - status := eerr.Sys().(syscall.WaitStatus) - return pluginError{ - error: errors.Errorf("plugin %q exited with error", md.Name), - code: status.ExitStatus(), - } - } - return err - } - return nil + return callPluginExecutable(cmd, main, argv, out) }, // This passes all the flags to the subcommand. DisableFlagParsing: true, } + // Setup dynamic completion for the plugin + completion.RegisterValidArgsFunc(c, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + u, err := processParent(cmd, args) + if err != nil { + return nil, completion.BashCompDirectiveError + } + + // We will call the dynamic completion script of the plugin + main := strings.Join([]string{plug.Dir, pluginDynamicCompletionExecutable}, string(filepath.Separator)) + + argv := []string{} + if !md.IgnoreFlags { + argv = append(argv, u...) + argv = append(argv, toComplete) + } + plugin.SetupPluginEnv(settings, md.Name, plug.Dir) + + completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv)) + buf := new(bytes.Buffer) + if err := callPluginExecutable(cmd, main, argv, buf); err != nil { + return nil, completion.BashCompDirectiveError + } + + var completions []string + for _, comp := range strings.Split(buf.String(), "\n") { + // Remove any empty lines + if len(comp) > 0 { + completions = append(completions, comp) + } + } + + // Check if the last line of output is of the form :, which + // indicates the BashCompletionDirective. + directive := completion.BashCompDirectiveDefault + if len(completions) > 0 { + lastLine := completions[len(completions)-1] + if len(lastLine) > 1 && lastLine[0] == ':' { + if strInt, err := strconv.Atoi(lastLine[1:]); err == nil { + directive = completion.BashCompDirective(strInt) + completions = completions[:len(completions)-1] + } + } + } + + return completions, directive + }) + // TODO: Make sure a command with this name does not already exist. baseCmd.AddCommand(c) } diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index 2d9a24ba9..e43f277a5 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -25,6 +25,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + + "helm.sh/helm/v3/pkg/release" ) func TestManuallyProcessArgs(t *testing.T) { @@ -242,6 +244,45 @@ func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompleti } } +func TestPluginDynamicCompletion(t *testing.T) { + + tests := []cmdTestCase{{ + name: "completion for plugin", + cmd: "__complete args ''", + golden: "output/plugin_args_comp.txt", + rels: []*release.Release{}, + }, { + name: "completion for plugin with flag", + cmd: "__complete args --myflag ''", + golden: "output/plugin_args_flag_comp.txt", + rels: []*release.Release{}, + }, { + name: "completion for plugin with global flag", + cmd: "__complete args --namespace mynamespace ''", + golden: "output/plugin_args_ns_comp.txt", + rels: []*release.Release{}, + }, { + name: "completion for plugin with multiple args", + cmd: "__complete args --myflag --namespace mynamespace start", + golden: "output/plugin_args_many_args_comp.txt", + rels: []*release.Release{}, + }, { + name: "completion for plugin no directive", + cmd: "__complete echo -n mynamespace ''", + golden: "output/plugin_echo_no_directive.txt", + rels: []*release.Release{}, + }, { + name: "completion for plugin bad directive", + cmd: "__complete echo ''", + golden: "output/plugin_echo_bad_directive.txt", + rels: []*release.Release{}, + }} + for _, test := range tests { + settings.PluginsDirectory = "testdata/helmhome/helm/plugins" + runTestCmd(t, []cmdTestCase{test}) + } +} + func TestLoadPlugins_HelmNoPlugins(t *testing.T) { settings.PluginsDirectory = "testdata/helmhome/helm/plugins" settings.RepositoryConfig = "testdata/helmhome/helm/repository" diff --git a/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete b/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete new file mode 100755 index 000000000..2b00c2281 --- /dev/null +++ b/cmd/helm/testdata/helmhome/helm/plugins/args/plugin.complete @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +echo "plugin.complete was called" +echo "Namespace: ${HELM_NAMESPACE:-NO_NS}" +echo "Num args received: ${#}" +echo "Args received: ${@}" + +# Final printout is the optional completion directive of the form : +if [ "$HELM_NAMESPACE" = "default" ]; then + echo ":4" +else + echo ":2" +fi diff --git a/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete b/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete new file mode 100755 index 000000000..6bc73d130 --- /dev/null +++ b/cmd/helm/testdata/helmhome/helm/plugins/echo/plugin.complete @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +echo "echo plugin.complete was called" +echo "Namespace: ${HELM_NAMESPACE:-NO_NS}" +echo "Num args received: ${#}" +echo "Args received: ${@}" + +# Final printout is the optional completion directive of the form : +if [ "$HELM_NAMESPACE" = "default" ]; then + # Output an invalid directive, which should be ignored + echo ":2222" +# else + # Don't include the directive, to test it is really optional +fi diff --git a/cmd/helm/testdata/output/plugin_args_comp.txt b/cmd/helm/testdata/output/plugin_args_comp.txt new file mode 100644 index 000000000..8fb01cc23 --- /dev/null +++ b/cmd/helm/testdata/output/plugin_args_comp.txt @@ -0,0 +1,5 @@ +plugin.complete was called +Namespace: default +Num args received: 1 +Args received: +:4 diff --git a/cmd/helm/testdata/output/plugin_args_flag_comp.txt b/cmd/helm/testdata/output/plugin_args_flag_comp.txt new file mode 100644 index 000000000..92f0e58a8 --- /dev/null +++ b/cmd/helm/testdata/output/plugin_args_flag_comp.txt @@ -0,0 +1,5 @@ +plugin.complete was called +Namespace: default +Num args received: 2 +Args received: --myflag +:4 diff --git a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt new file mode 100644 index 000000000..86fa768bb --- /dev/null +++ b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt @@ -0,0 +1,5 @@ +plugin.complete was called +Namespace: mynamespace +Num args received: 2 +Args received: --myflag start +:2 diff --git a/cmd/helm/testdata/output/plugin_args_ns_comp.txt b/cmd/helm/testdata/output/plugin_args_ns_comp.txt new file mode 100644 index 000000000..e12867daa --- /dev/null +++ b/cmd/helm/testdata/output/plugin_args_ns_comp.txt @@ -0,0 +1,5 @@ +plugin.complete was called +Namespace: mynamespace +Num args received: 1 +Args received: +:2 diff --git a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt new file mode 100644 index 000000000..f4b86cd47 --- /dev/null +++ b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt @@ -0,0 +1,5 @@ +echo plugin.complete was called +Namespace: default +Num args received: 1 +Args received: +:0 diff --git a/cmd/helm/testdata/output/plugin_echo_no_directive.txt b/cmd/helm/testdata/output/plugin_echo_no_directive.txt new file mode 100644 index 000000000..6266dd4d9 --- /dev/null +++ b/cmd/helm/testdata/output/plugin_echo_no_directive.txt @@ -0,0 +1,5 @@ +echo plugin.complete was called +Namespace: mynamespace +Num args received: 1 +Args received: +:0 diff --git a/internal/completion/complete.go b/internal/completion/complete.go index aa0d134b9..c8f78868a 100644 --- a/internal/completion/complete.go +++ b/internal/completion/complete.go @@ -188,29 +188,47 @@ func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args)) - flag, trimmedArgs, toComplete, err := checkIfFlagCompletion(cmd.Root(), args[:len(args)-1], args[len(args)-1]) - if err != nil { - // Error while attempting to parse flags - CompErrorln(err.Error()) - return - } + // The last argument, which is not complete, should not be part of the list of arguments + toComplete := args[len(args)-1] + trimmedArgs := args[:len(args)-1] + // Find the real command for which completion must be performed finalCmd, finalArgs, err := cmd.Root().Find(trimmedArgs) if err != nil { // Unable to find the real command. E.g., helm invalidCmd + CompDebugln(fmt.Sprintf("Unable to find a command for arguments: %v", trimmedArgs)) return } CompDebugln(fmt.Sprintf("Found final command '%s', with finalArgs %v", finalCmd.Name(), finalArgs)) + var flag *pflag.Flag + if !finalCmd.DisableFlagParsing { + // We only do flag completion if we are allowed to parse flags + // This is important for helm plugins which need to do their own flag completion. + flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) + if err != nil { + // Error while attempting to parse flags + CompErrorln(err.Error()) + return + } + } + // Parse the flags and extract the arguments to prepare for calling the completion function if err = finalCmd.ParseFlags(finalArgs); err != nil { CompErrorln(fmt.Sprintf("Error while parsing flags from args %v: %s", finalArgs, err.Error())) return } - argsWoFlags := finalCmd.Flags().Args() - CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", argsWoFlags, len(argsWoFlags))) + // We only remove the flags from the arguments if DisableFlagParsing is not set. + // This is important for helm plugins, which need to receive all flags. + // The plugin completion code will do its own flag parsing. + if !finalCmd.DisableFlagParsing { + finalArgs = finalCmd.Flags().Args() + CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", finalArgs, len(finalArgs))) + } + + // Find completion function for the flag or command var key interface{} var keyStr string if flag != nil { @@ -220,21 +238,23 @@ func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { key = finalCmd keyStr = finalCmd.Name() } - - // Find completion function for the flag or command completionFn, ok := validArgsFunctions[key] if !ok { CompErrorln(fmt.Sprintf("Dynamic completion not supported/needed for flag or command: %s", keyStr)) return } - CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), argsWoFlags, toComplete)) - completions, directive := completionFn(finalCmd, argsWoFlags, toComplete) + CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), finalArgs, toComplete)) + completions, directive := completionFn(finalCmd, finalArgs, toComplete) for _, comp := range completions { // Print each possible completion to stdout for the completion script to consume. fmt.Fprintln(out, comp) } + if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { + directive = BashCompDirectiveDefault + } + // As the last printout, print the completion directive for the // completion script to parse. // The directive integer must be that last character following a single : @@ -252,7 +272,7 @@ func isFlag(arg string) bool { return len(arg) > 0 && arg[0] == '-' } -func checkIfFlagCompletion(rootCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { +func checkIfFlagCompletion(finalCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { var flagName string trimmedArgs := args flagWithEqual := false @@ -287,19 +307,10 @@ func checkIfFlagCompletion(rootCmd *cobra.Command, args []string, lastArg string return nil, trimmedArgs, lastArg, nil } - // Find the real command for which completion must be performed - finalCmd, _, err := rootCmd.Find(trimmedArgs) - if err != nil { - // Unable to find the real command. E.g., helm invalidCmd - return nil, nil, "", errors.New("Unable to find final command for completion") - } - - CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found final command '%s'", finalCmd.Name())) - flag := findFlag(finalCmd, flagName) if flag == nil { // Flag not supported by this command, nothing to complete - err = fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName) + err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName) return nil, nil, "", err } From 7f3339cb4eec9800cb09a46512050fb509480f7e Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 14 Jan 2020 22:44:52 -0500 Subject: [PATCH 37/76] feat(tests): Allow to provision memory driver The memory driver is used for go tests. It can also be used from the command-line by setting the environment variable HELM_DRIVER=memory. In the latter case however, there was no way to pre-provision some releases. This commit introduces the HELM_MEMORY_DRIVER_DATA variable which can be used to provide a colon-separated list of yaml files specifying releases to provision automatically. For example: HELM_DRIVER=memory \ HELM_MEMORY_DRIVER_DATA=./testdata/releases.yaml \ helm list --all-namespaces Signed-off-by: Marc Khouzam --- cmd/helm/helm.go | 49 +++++++++++++++++++++++++++++++++++++++++- pkg/action/action.go | 13 ++++++++++- testdata/releases.yaml | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 testdata/releases.yaml diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 0ce40732b..257387547 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -19,6 +19,7 @@ package main // import "helm.sh/helm/v3/cmd/helm" import ( "flag" "fmt" + "io/ioutil" "log" "os" "strings" @@ -26,6 +27,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/klog" + "sigs.k8s.io/yaml" // Import to initialize client auth plugins. _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -34,6 +36,9 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/gates" + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" ) // FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work @@ -78,9 +83,13 @@ func main() { } } - if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), debug); err != nil { + 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) @@ -106,3 +115,41 @@ func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error { return nil } } + +// This function loads releases into the memory storage if the +// environment variable is properly set. +func loadReleasesInMemory(actionConfig *action.Configuration) { + filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":") + if len(filePaths) == 0 { + return + } + + store := actionConfig.Releases + mem, ok := store.Driver.(*driver.Memory) + if !ok { + // For an unexpected reason we are not dealing with the memory storage driver. + return + } + + actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} + + for _, path := range filePaths { + b, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal("Unable to read memory driver data", err) + } + + releases := []*release.Release{} + if err := yaml.Unmarshal(b, &releases); err != nil { + log.Fatal("Unable to unmarshal memory driver data: ", err) + } + + for _, rel := range releases { + if err := store.Create(rel); err != nil { + log.Fatal(err) + } + } + } + // Must reset namespace to the proper one + mem.SetNamespace(settings.Namespace()) +} diff --git a/pkg/action/action.go b/pkg/action/action.go index 1af5b3d9a..e4db942c8 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -241,7 +241,18 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac d.Log = log store = storage.Init(d) case "memory": - d := driver.NewMemory() + var d *driver.Memory + if c.Releases != nil { + if mem, ok := c.Releases.Driver.(*driver.Memory); ok { + // This function can be called more than once (e.g., helm list --all-namespaces). + // If a memory driver was already initialized, re-use it but set the possibly new namespace. + // We re-use it in case some releases where already created in the existing memory driver. + d = mem + } + } + if d == nil { + d = driver.NewMemory() + } d.SetNamespace(namespace) store = storage.Init(d) default: diff --git a/testdata/releases.yaml b/testdata/releases.yaml new file mode 100644 index 000000000..fef79f424 --- /dev/null +++ b/testdata/releases.yaml @@ -0,0 +1,43 @@ +# This file can be used as input to create test releases: +# HELM_MEMORY_DRIVER_DATA=./testdata/releases.yaml HELM_DRIVER=memory helm list --all-namespaces +- name: athos + version: 1 + namespace: default + info: + status: deployed + chart: + metadata: + name: athos-chart + version: 1.0.0 + appversion: 1.1.0 +- name: porthos + version: 2 + namespace: default + info: + status: deployed + chart: + metadata: + name: prothos-chart + version: 0.2.0 + appversion: 0.2.2 +- name: aramis + version: 3 + namespace: default + info: + status: deployed + chart: + metadata: + name: aramis-chart + version: 0.0.3 + appversion: 3.0.3 +- name: dartagnan + version: 4 + namespace: gascony + info: + status: deployed + chart: + metadata: + name: dartagnan-chart + version: 0.4.4 + appversion: 4.4.4 + From e92a258a9d7cc684589cb22c317eb7ddaeaf753e Mon Sep 17 00:00:00 2001 From: akash-gautam Date: Mon, 24 Feb 2020 00:23:14 +0530 Subject: [PATCH 38/76] fix(helm): add --skipCRDs flag to 'helm upgrade' When 'helm upgrade --install' is run, this will allow to skip installing CRDs Closes #7452 Signed-off-by: akash-gautam --- cmd/helm/upgrade.go | 2 ++ pkg/action/upgrade.go | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 119d79f8f..06aadb818 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -103,6 +103,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.ChartPathOptions = client.ChartPathOptions instClient.DryRun = client.DryRun instClient.DisableHooks = client.DisableHooks + instClient.SkipCRDs = client.SkipCRDs instClient.Timeout = client.Timeout instClient.Wait = client.Wait instClient.Devel = client.Devel @@ -167,6 +168,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema") + f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 08b638171..62e26adb4 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -42,9 +42,11 @@ type Upgrade struct { ChartPathOptions - Install bool - Devel bool - Namespace string + Install bool + Devel bool + Namespace string + // SkipCRDs skip installing CRDs when install flag is enabled during upgrade + SkipCRDs bool Timeout time.Duration Wait bool DisableHooks bool From 6b1eebd23a3e56714bb4f5d542acc4d087fa8073 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 24 Feb 2020 11:22:12 +0800 Subject: [PATCH 39/76] pkg/storage/records: add unit test for Get Signed-off-by: Zhou Hao --- pkg/storage/driver/records_test.go | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 79b60044f..030e32e9d 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -17,6 +17,7 @@ limitations under the License. package driver // import "helm.sh/helm/v3/pkg/storage/driver" import ( + "reflect" "testing" rspb "helm.sh/helm/v3/pkg/release" @@ -110,3 +111,34 @@ func TestRecordsRemoveAt(t *testing.T) { t.Fatalf("Expected length of rs to be 1, got %d", len(rs)) } } + +func TestRecordsGet(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), + }) + + var tests = []struct { + desc string + key string + rec *record + }{ + { + "get valid key", + "rls-a.v1", + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), + }, + { + "get invalid key", + "rls-a.v3", + nil, + }, + } + + for _, tt := range tests { + got := rs.Get(tt.key) + if !reflect.DeepEqual(tt.rec, got) { + t.Fatalf("Expected %v, got %v", tt.rec, got) + } + } +} From c96aff6a43c0da2163e267cc807b5a09a2713ccf Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 24 Feb 2020 11:42:04 +0800 Subject: [PATCH 40/76] pkg/storage/records: add unit test for Index Signed-off-by: Zhou Hao --- pkg/storage/driver/records_test.go | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 030e32e9d..c6c13f28b 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -142,3 +142,34 @@ func TestRecordsGet(t *testing.T) { } } } + +func TestRecordsIndex(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), + }) + + var tests = []struct { + desc string + key string + sort int + }{ + { + "get valid key", + "rls-a.v1", + 0, + }, + { + "get invalid key", + "rls-a.v3", + -1, + }, + } + + for _, tt := range tests { + got, _ := rs.Index(tt.key) + if got != tt.sort { + t.Fatalf("Expected %d, got %d", tt.sort, got) + } + } +} From f1f661d4ca28fbbfad75fe706e3eb7a4ce8d9478 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 24 Feb 2020 11:53:04 +0800 Subject: [PATCH 41/76] pkg/storage/records: add unit test for Exists Signed-off-by: Zhou Hao --- pkg/storage/driver/records_test.go | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index c6c13f28b..3ede92bcd 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -173,3 +173,34 @@ func TestRecordsIndex(t *testing.T) { } } } + +func TestRecordsExists(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), + }) + + var tests = []struct { + desc string + key string + ok bool + }{ + { + "get valid key", + "rls-a.v1", + true, + }, + { + "get invalid key", + "rls-a.v3", + false, + }, + } + + for _, tt := range tests { + got := rs.Exists(tt.key) + if got != tt.ok { + t.Fatalf("Expected %t, got %t", tt.ok, got) + } + } +} From b4f716413c150cb4e9db1f51dad820051896d459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kohout?= Date: Mon, 24 Feb 2020 11:57:18 +0100 Subject: [PATCH 42/76] Printing name of chart that do not have requested import value. Signed-off-by: Tomas Kohout --- pkg/chartutil/dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go index 4b389dc22..521e2abc4 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chartutil/dependencies.go @@ -233,7 +233,7 @@ func processImportValues(c *chart.Chart) error { // get child table vv, err := cvals.Table(r.Name + "." + child) if err != nil { - log.Printf("Warning: ImportValues missing table: %v", err) + log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err) continue } // create value map from child to be merged into parent From c235470e59fd4f17149339757940537f95605cef Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 25 Feb 2020 10:42:20 -0800 Subject: [PATCH 43/76] fix(cmd/helm): upgrade go-shellwords Removes workaround introduced in #7323 Signed-off-by: Adam Reese --- cmd/helm/helm_test.go | 29 ----------------------------- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 8c6c492f8..94646a5a3 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -18,7 +18,6 @@ package main import ( "bytes" - "fmt" "io/ioutil" "os" "os/exec" @@ -97,39 +96,11 @@ func storageFixture() *storage.Storage { return storage.Init(driver.NewMemory()) } -// go-shellwords does not handle empty arguments properly -// https://github.com/mattn/go-shellwords/issues/5#issuecomment-573431458 -// -// This method checks if the last argument was an empty one, -// and if go-shellwords missed it, we add it ourselves. -// -// This is important for completion tests as completion often -// uses an empty last parameter. -func checkLastEmpty(in string, out []string) []string { - lastIndex := len(in) - 1 - - if lastIndex >= 1 && (in[lastIndex] == '"' && in[lastIndex-1] == '"' || - in[lastIndex] == '\'' && in[lastIndex-1] == '\'') { - // The last parameter of 'in' was empty ("" or ''), let's make sure it was detected. - if len(out) > 0 && out[len(out)-1] != "" { - // Bug from go-shellwords: - // 'out' does not have the empty parameter. We add it ourselves as a workaround. - out = append(out, "") - } else { - fmt.Println("WARNING: go-shellwords seems to have been fixed. This workaround can be removed.") - } - } - return out -} - func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) { args, err := shellwords.Parse(cmd) if err != nil { return nil, "", err } - // Workaround the bug in shellwords - args = checkLastEmpty(cmd, args) - buf := new(bytes.Buffer) actionConfig := &action.Configuration{ diff --git a/go.mod b/go.mod index 9c22ea9e5..7ba7a5542 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.7.1 github.com/gosuri/uitable v0.0.4 - github.com/mattn/go-shellwords v1.0.9 + 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 diff --git a/go.sum b/go.sum index 89c7762d5..39b57b4f2 100644 --- a/go.sum +++ b/go.sum @@ -334,6 +334,8 @@ github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk= github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +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/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/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= From 13e2dcfde53c735dc313d0145bca063ba3a9d121 Mon Sep 17 00:00:00 2001 From: Song Shukun Date: Fri, 21 Feb 2020 16:10:11 +0900 Subject: [PATCH 44/76] Fix dep build to be compatiable with Helm 2 when requirements use repo alias Signed-off-by: Song Shukun --- pkg/downloader/manager.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index cb139f824..ff451a6e8 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -82,6 +82,19 @@ func (m *Manager) Build() error { // Check that all of the repos we're dependent on actually exist. req := c.Metadata.Dependencies + + // If using apiVersion v1, calculate the hash before resolve repo names + // because resolveRepoNames will change req if req uses repo alias + // and Helm 2 calculate the digest from the original req + // Fix for: https://github.com/helm/helm/issues/7619 + var v2Sum string + if c.Metadata.APIVersion == chart.APIVersionV1 { + v2Sum, err = resolver.HashV2Req(req) + if err != nil { + return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") + } + } + if _, err := m.resolveRepoNames(req); err != nil { return err } @@ -92,7 +105,7 @@ func (m *Manager) Build() error { // Fix for: https://github.com/helm/helm/issues/7233 if c.Metadata.APIVersion == chart.APIVersionV1 { log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") - if sum, err := resolver.HashV2Req(req); err != nil || sum != lock.Digest { + if v2Sum != lock.Digest { return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") } } else { From f05ffdd2da3bc6acf31747c42c292a8e34cd8697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Mac=C3=ADk?= Date: Wed, 26 Feb 2020 13:24:17 +0100 Subject: [PATCH 45/76] Fix golangci-lint errors. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pavel MacĂ­k --- cmd/helm/template.go | 5 ++--- internal/completion/complete.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 320718344..91a398429 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -24,8 +24,6 @@ import ( "regexp" "strings" - "helm.sh/helm/v3/pkg/releaseutil" - "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" @@ -33,6 +31,7 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/releaseutil" ) const templateDesc = ` @@ -53,7 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "template [NAME] [CHART]", - Short: fmt.Sprintf("locally render templates"), + Short: "locally render templates", Long: templateDesc, Args: require.MinimumNArgs(1), RunE: func(_ *cobra.Command, args []string) error { diff --git a/internal/completion/complete.go b/internal/completion/complete.go index c8f78868a..eaea4e914 100644 --- a/internal/completion/complete.go +++ b/internal/completion/complete.go @@ -259,7 +259,7 @@ func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { // completion script to parse. // The directive integer must be that last character following a single : // The completion script expects :directive - fmt.Fprintln(out, fmt.Sprintf(":%d", directive)) + fmt.Fprintf(out, ":%d\n", directive) // Print some helpful info to stderr for the user to understand. // Output from stderr should be ignored from the completion script. From 8f962a270c65e703b07fb749f80ed67855d92f23 Mon Sep 17 00:00:00 2001 From: Xiangxuan Liu Date: Thu, 21 Nov 2019 16:24:12 +0800 Subject: [PATCH 46/76] Return "unknown command" error for unknown subcommands Signed-off-by: Xiangxuan Liu --- cmd/helm/root.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 3c6a0d18e..3ebea3bae 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -26,7 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" - "helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/pkg/action" @@ -77,7 +76,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string Short: "The Helm package manager for Kubernetes.", Long: globalUsage, SilenceUsage: true, - Args: require.NoArgs, BashCompletionFunction: completion.GetBashCustomFunction(), } flags := cmd.PersistentFlags() From d5a2963cc95027951ea9654210786c639d5891ff Mon Sep 17 00:00:00 2001 From: Xiangxuan Liu Date: Fri, 22 Nov 2019 10:56:39 +0800 Subject: [PATCH 47/76] Add test for unknown subcommand Signed-off-by: Xiangxuan Liu --- cmd/helm/root_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go index df592a96d..e1fa1fc27 100644 --- a/cmd/helm/root_test.go +++ b/cmd/helm/root_test.go @@ -95,3 +95,11 @@ func TestRootCmd(t *testing.T) { }) } } + +func TestUnknownSubCmd(t *testing.T) { + _, _, err := executeActionCommand("foobar") + + if err == nil || err.Error() != `unknown command "foobar" for "helm"` { + t.Errorf("Expect unknown command error, got %q", err) + } +} From ebd48557b103f9da9faefb7ef085b2393f8183c5 Mon Sep 17 00:00:00 2001 From: Evgenii Iablokov Date: Thu, 27 Feb 2020 09:12:09 +0100 Subject: [PATCH 48/76] Update README.md Typo fix: Space missed in Markdown header. Signed-off-by: Evgeniy Yablokov --- cmd/helm/testdata/testcharts/alpine/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/testdata/testcharts/alpine/README.md b/cmd/helm/testdata/testcharts/alpine/README.md index fcf7ee017..05d39dbbc 100644 --- a/cmd/helm/testdata/testcharts/alpine/README.md +++ b/cmd/helm/testdata/testcharts/alpine/README.md @@ -1,4 +1,4 @@ -#Alpine: A simple Helm chart +# Alpine: A simple Helm chart Run a single pod of Alpine Linux. From a3f92f65e26323a3f91343c29ee0c4d1b6282d21 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 28 Feb 2020 12:32:45 -0500 Subject: [PATCH 49/76] Fixes verification output on pull command When using the --verify flag on the pull command the output was an internal Go object rather than useful detail. This is a bug. The output new displays who signed the chart along with the hash. Fixes #7624 Signed-off-by: Matt Farina --- cmd/helm/pull_test.go | 18 +++++++++++------- pkg/action/pull.go | 6 +++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 1aca66100..d4661f928 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" "testing" "helm.sh/helm/v3/pkg/repo/repotest" @@ -37,6 +36,10 @@ func TestPullCmd(t *testing.T) { t.Fatal(err) } + helmTestKeyOut := "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) \n" + + "Using Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\n" + + "Chart Hash Verified: " + // all flags will get "-d outdir" appended. tests := []struct { name string @@ -49,6 +52,7 @@ func TestPullCmd(t *testing.T) { expectFile string expectDir bool expectVerify bool + expectSha string }{ { name: "Basic chart fetch", @@ -77,6 +81,7 @@ func TestPullCmd(t *testing.T) { args: "test/signtest --verify --keyring testdata/helm-test-key.pub", expectFile: "./signtest-0.1.0.tgz", expectVerify: true, + expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", }, { name: "Fetch and fail verify", @@ -110,6 +115,7 @@ func TestPullCmd(t *testing.T) { expectFile: "./signtest2", expectDir: true, expectVerify: true, + expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", }, { name: "Chart fetch using repo URL", @@ -171,13 +177,11 @@ func TestPullCmd(t *testing.T) { } if tt.expectVerify { - pointerAddressPattern := "0[xX][A-Fa-f0-9]+" - sha256Pattern := "[A-Fa-f0-9]{64}" - verificationRegex := regexp.MustCompile( - fmt.Sprintf("Verification: &{%s sha256:%s signtest-0.1.0.tgz}\n", pointerAddressPattern, sha256Pattern)) - if !verificationRegex.MatchString(out) { - t.Errorf("%q: expected match for regex %s, got %s", tt.name, verificationRegex, out) + outString := helmTestKeyOut + tt.expectSha + "\n" + if out != outString { + t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out) } + } ef := filepath.Join(outdir, tt.expectFile) diff --git a/pkg/action/pull.go b/pkg/action/pull.go index 4ff5f5c3e..ee20bbe83 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -101,7 +101,11 @@ func (p *Pull) Run(chartRef string) (string, error) { } if p.Verify { - fmt.Fprintf(&out, "Verification: %v\n", v) + for name := range v.SignedBy.Identities { + fmt.Fprintf(&out, "Signed by: %v\n", name) + } + fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint) + fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash) } // After verification, untar the chart into the requested directory. From af35d61a98412ff56da98c11b603a9ec54f101c1 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 28 Feb 2020 12:52:21 -0500 Subject: [PATCH 50/76] Add verification output to the verify command This complements the verification output fixed in #7706. On verify there should be some detail about the verification rather than no information. Signed-off-by: Matt Farina --- cmd/helm/verify.go | 10 +++++++++- cmd/helm/verify_test.go | 2 +- pkg/action/verify.go | 24 ++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/cmd/helm/verify.go b/cmd/helm/verify.go index d3ae517c9..f26fb377f 100644 --- a/cmd/helm/verify.go +++ b/cmd/helm/verify.go @@ -16,6 +16,7 @@ limitations under the License. package main import ( + "fmt" "io" "github.com/spf13/cobra" @@ -44,7 +45,14 @@ func newVerifyCmd(out io.Writer) *cobra.Command { Long: verifyDesc, Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return client.Run(args[0]) + err := client.Run(args[0]) + if err != nil { + return err + } + + fmt.Fprint(out, client.Out) + + return nil }, } diff --git a/cmd/helm/verify_test.go b/cmd/helm/verify_test.go index a70051ff6..ccbcb3cf2 100644 --- a/cmd/helm/verify_test.go +++ b/cmd/helm/verify_test.go @@ -65,7 +65,7 @@ func TestVerifyCmd(t *testing.T) { { name: "verify validates a properly signed chart", cmd: "verify testdata/testcharts/signtest-0.1.0.tgz --keyring testdata/helm-test-key.pub", - expect: "", + expect: "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) \nUsing Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\nChart Hash Verified: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\n", wantError: false, }, } diff --git a/pkg/action/verify.go b/pkg/action/verify.go index c66b14b47..f36239496 100644 --- a/pkg/action/verify.go +++ b/pkg/action/verify.go @@ -17,6 +17,9 @@ limitations under the License. package action import ( + "fmt" + "strings" + "helm.sh/helm/v3/pkg/downloader" ) @@ -25,6 +28,7 @@ import ( // It provides the implementation of 'helm verify'. type Verify struct { Keyring string + Out string } // NewVerify creates a new Verify object with the given configuration. @@ -34,6 +38,22 @@ func NewVerify() *Verify { // Run executes 'helm verify'. func (v *Verify) Run(chartfile string) error { - _, err := downloader.VerifyChart(chartfile, v.Keyring) - return err + var out strings.Builder + p, err := downloader.VerifyChart(chartfile, v.Keyring) + if err != nil { + return err + } + + for name := range p.SignedBy.Identities { + fmt.Fprintf(&out, "Signed by: %v\n", name) + } + fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", p.SignedBy.PrimaryKey.Fingerprint) + fmt.Fprintf(&out, "Chart Hash Verified: %s\n", p.FileHash) + + // TODO(mattfarina): The output is set as a property rather than returned + // to maintain the Go API. In Helm v4 this function should return the out + // and the property on the struct can be removed. + v.Out = out.String() + + return nil } From 8edf86a7181c16fe4089c52f7b7fe58df5b08ce7 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Fri, 28 Feb 2020 12:35:01 -0800 Subject: [PATCH 51/76] fix(ADOPTERS): alphabetize org list (#7645) Signed-off-by: Matthew Fisher --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index a72f51e09..46b42b8a0 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -6,8 +6,8 @@ # Organizations Using Helm - [Blood Orange](https://bloodorange.io) -- [Microsoft](https://microsoft.com) - [IBM](https://www.ibm.com) +- [Microsoft](https://microsoft.com) - [Qovery](https://www.qovery.com/) - [Samsung SDS](https://www.samsungsds.com/) - [Ville de Montreal](https://montreal.ca) From 95d7f36d41d41f6e0efe1ec9839a7a3b85e76bc0 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 2 Mar 2020 13:39:58 +0800 Subject: [PATCH 52/76] add unit test for SecretDelete Signed-off-by: Zhou Hao --- pkg/storage/driver/secrets_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index 892482e5b..e4420704d 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -184,3 +184,28 @@ func TestSecretUpdate(t *testing.T) { t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) } } + +func TestSecretDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // perform the delete + rls, err := secrets.Delete(key) + if err != nil { + t.Fatalf("Failed to delete release with key %q: %s", key, err) + } + if !reflect.DeepEqual(rel, rls) { + t.Errorf("Expected {%v}, got {%v}", rel, rls) + } + + // fetch the deleted release + _, err = secrets.Get(key) + if !reflect.DeepEqual(ErrReleaseNotFound, err) { + t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) + } +} From ae508ebd1ca81a5d5ccba4f0729897872cb10409 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 2 Mar 2020 14:14:04 +0800 Subject: [PATCH 53/76] add unit test for ConfigMapDelete Signed-off-by: Zhou Hao --- pkg/storage/driver/cfgmaps_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 2aa38f284..a36cee1be 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -184,3 +184,28 @@ func TestConfigMapUpdate(t *testing.T) { t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) } } + +func TestConfigMapDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) + + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + + // perform the delete + rls, err := cfgmaps.Delete(key) + if err != nil { + t.Fatalf("Failed to delete release with key %q: %s", key, err) + } + if !reflect.DeepEqual(rel, rls) { + t.Errorf("Expected {%v}, got {%v}", rel, rls) + } + + // fetch the deleted release + _, err = cfgmaps.Get(key) + if !reflect.DeepEqual(ErrReleaseNotFound, err) { + t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) + } +} From 9744e9f619d3c1d8ddbe3af59e7d70d81c05dc5a Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Mon, 2 Mar 2020 14:23:35 +0800 Subject: [PATCH 54/76] fix(helm): respect resource policy on ungrade Don't delete a resource on upgrade if it is annotated with helm.io/resource-policy=keep. This can cause data loss for users if the annotation is ignored(e.g. for a PVC) Close #7677 Signed-off-by: Dong Gang --- pkg/kube/client.go | 16 ++++++++++++++++ pkg/kube/resource_policy.go | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 pkg/kube/resource_policy.go diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 31dabcc5d..04f247c93 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io" + "k8s.io/apimachinery/pkg/api/meta" "strings" "sync" "time" @@ -50,6 +51,8 @@ import ( // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. var ErrNoObjectsVisited = errors.New("no objects visited") +var metadataAccessor = meta.NewAccessor() + // Client represents a client capable of communicating with the Kubernetes API. type Client struct { Factory Factory @@ -210,6 +213,19 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err for _, info := range original.Difference(target) { c.Log("Deleting %q in %s...", info.Name, info.Namespace) + + if err := info.Get(); err != nil { + c.Log("Unable to get obj %q, err: %s", info.Name, err) + } + annotations, err := metadataAccessor.Annotations(info.Object) + if err != nil { + c.Log("Unable to get annotations on %q, err: %s", info.Name, err) + } + if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { + 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) { diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go new file mode 100644 index 000000000..5f391eb50 --- /dev/null +++ b/pkg/kube/resource_policy.go @@ -0,0 +1,26 @@ +/* +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 kube // import "helm.sh/helm/v3/pkg/kube" + +// ResourcePolicyAnno is the annotation name for a resource policy +const ResourcePolicyAnno = "helm.sh/resource-policy" + +// KeepPolicy is the resource policy type for keep +// +// This resource policy type allows resources to skip being deleted +// during an uninstallRelease action. +const KeepPolicy = "keep" From f5da6bd3d679e8da62cdf50c45503714d1510a73 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Mon, 2 Mar 2020 14:32:57 +0800 Subject: [PATCH 55/76] add unit test for RecordsReplace Signed-off-by: Zhou Hao --- pkg/storage/driver/records_test.go | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 3ede92bcd..0a27839cc 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -204,3 +204,37 @@ func TestRecordsExists(t *testing.T) { } } } + +func TestRecordsReplace(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), + }) + + var tests = []struct { + desc string + key string + rec *record + expected *record + }{ + { + "replace with existing key", + "rls-a.v2", + newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), + }, + { + "replace with non existing key", + "rls-a.v4", + newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusDeployed)), + nil, + }, + } + + for _, tt := range tests { + got := rs.Replace(tt.key, tt.rec) + if !reflect.DeepEqual(tt.expected, got) { + t.Fatalf("Expected %v, got %v", tt.expected, got) + } + } +} From c45869c4ad8f46140f6aea0d673aa7892f3eefad Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Mon, 2 Mar 2020 14:47:54 +0800 Subject: [PATCH 56/76] fix(helm): polish goimport Signed-off-by: Dong Gang --- 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 04f247c93..b761c6d12 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "io" - "k8s.io/apimachinery/pkg/api/meta" "strings" "sync" "time" @@ -34,6 +33,7 @@ import ( apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" From 69d9722edaea6f54194d6ab508142d3f4eb2be14 Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Mon, 2 Mar 2020 14:55:53 +0800 Subject: [PATCH 57/76] test(helm): fix client update error Signed-off-by: Dong Gang --- pkg/kube/client_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 9e7581d00..aa081423c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -147,6 +147,8 @@ func TestUpdate(t *testing.T) { return newResponse(200, &listB.Items[1]) case p == "/namespaces/default/pods/squid" && m == "DELETE": return newResponse(200, &listB.Items[1]) + case p == "/namespaces/default/pods/squid" && m == "GET": + return newResponse(200, &listB.Items[2]) default: t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) return nil, nil @@ -184,6 +186,7 @@ func TestUpdate(t *testing.T) { "/namespaces/default/pods/otter:GET", "/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods:POST", + "/namespaces/default/pods/squid:GET", "/namespaces/default/pods/squid:DELETE", } if len(expectedActions) != len(actions) { From a992464fa298e957ffd014496aca5fb97252cbdb Mon Sep 17 00:00:00 2001 From: Song Shukun Date: Tue, 25 Feb 2020 18:26:00 +0900 Subject: [PATCH 58/76] Save Chart.lock to helm package tar Signed-off-by: Song Shukun --- pkg/chartutil/save.go | 14 ++++++++++++++ pkg/chartutil/save_test.go | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index be0dfdc24..a2c6a9225 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -161,6 +161,20 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { return err } + // Save Chart.lock + // TODO: remove the APIVersion check when APIVersionV1 is not used anymore + if c.Metadata.APIVersion == chart.APIVersionV2 { + if c.Lock != nil { + ldata, err := yaml.Marshal(c.Lock) + if err != nil { + return err + } + if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil { + return err + } + } + } + // Save values.yaml for _, f := range c.Raw { if f.Name == ValuesfileName { diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index f367d42eb..306c13cee 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -49,6 +49,9 @@ func TestSave(t *testing.T) { Name: "ahab", Version: "1.2.3", }, + Lock: &chart.Lock{ + Digest: "testdigest", + }, Files: []*chart.File{ {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, }, @@ -77,6 +80,9 @@ func TestSave(t *testing.T) { if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { t.Fatal("Files data did not match") } + if c2.Lock != nil { + t.Fatal("Expected v1 chart archive not to contain Chart.lock file") + } if !bytes.Equal(c.Schema, c2.Schema) { indentation := 4 @@ -87,6 +93,22 @@ func TestSave(t *testing.T) { if _, err := Save(&chartWithInvalidJSON, dest); err == nil { t.Fatalf("Invalid JSON was not caught while saving chart") } + + c.Metadata.APIVersion = chart.APIVersionV2 + where, err = Save(c, dest) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + c2, err = loader.LoadFile(where) + if err != nil { + t.Fatal(err) + } + if c2.Lock == nil { + t.Fatal("Expected v2 chart archive to containe a Chart.lock file") + } + if c2.Lock.Digest != c.Lock.Digest { + t.Fatal("Chart.lock data did not match") + } }) } } From 14f6d1ea97eeef158adf7db0e9b42206905930bf Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Mon, 2 Mar 2020 12:09:41 -0800 Subject: [PATCH 59/76] ref(environment): use string checking instead It is more idiomatic to compare the string against the empty string than to check the string's length. Signed-off-by: Matthew Fisher --- pkg/cli/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 1e3b23617..e279331b0 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -135,10 +135,10 @@ func (s *EnvSettings) Namespace() string { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { s.configOnce.Do(func() { clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace) - if len(s.KubeToken) > 0 { + if s.KubeToken != "" { clientConfig.BearerToken = &s.KubeToken } - if len(s.KubeAPIServer) > 0 { + if s.KubeAPIServer != "" { clientConfig.APIServer = &s.KubeAPIServer } From dc26128fb4d55ecd1c0b600195b639e93aec02e2 Mon Sep 17 00:00:00 2001 From: Matthias Riegler Date: Wed, 4 Mar 2020 00:52:33 +0100 Subject: [PATCH 60/76] Add --insecure-skip-tls-verify for repositories (#7254) * added --insecure-skip-tls-verify for chart repos Signed-off-by: Matthias Riegler * fixed not passing the insecureSkipTLSverify option Signed-off-by: Matthias Riegler * fixed testcase Signed-off-by: Matthias Riegler * pass proxy when using insecureSkipVerify Signed-off-by: Matthias Riegler * Add testcases for insecureSkipVerifyTLS Signed-off-by: Matthias Riegler * added missing err check Signed-off-by: Matthias Riegler * panic after json marshal fails Signed-off-by: Matthias Riegler --- cmd/helm/repo_add.go | 23 ++++++++------ pkg/getter/getter.go | 22 +++++++++----- pkg/getter/httpgetter.go | 15 +++++++++ pkg/getter/httpgetter_test.go | 57 +++++++++++++++++++++++++++++++++++ pkg/repo/chartrepo.go | 26 +++++++++++----- 5 files changed, 119 insertions(+), 24 deletions(-) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index e6afce3d5..3d36fd0ed 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -43,9 +43,10 @@ type repoAddOptions struct { password string noUpdate bool - certFile string - keyFile string - caFile string + certFile string + keyFile string + caFile string + insecureSkipTLSverify bool repoFile string repoCache string @@ -75,6 +76,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") return cmd } @@ -113,13 +115,14 @@ func (o *repoAddOptions) run(out io.Writer) error { } c := repo.Entry{ - Name: o.name, - URL: o.url, - Username: o.username, - Password: o.password, - CertFile: o.certFile, - KeyFile: o.keyFile, - CAFile: o.caFile, + Name: o.name, + URL: o.url, + Username: o.username, + Password: o.password, + CertFile: o.certFile, + KeyFile: o.keyFile, + CAFile: o.caFile, + InsecureSkipTLSverify: o.insecureSkipTLSverify, } r, err := repo.NewChartRepository(&c, getter.All(settings)) diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 68638c2ca..4ccc74834 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -28,13 +28,14 @@ import ( // // Getters may or may not ignore these parameters as they are passed in. type options struct { - url string - certFile string - keyFile string - caFile string - username string - password string - userAgent string + url string + certFile string + keyFile string + caFile string + insecureSkipVerifyTLS bool + username string + password string + userAgent string } // Option allows specifying various settings configurable by the user for overriding the defaults @@ -64,6 +65,13 @@ func WithUserAgent(userAgent string) Option { } } +// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked +func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { + return func(opts *options) { + opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS + } +} + // WithTLSClientConfig sets the client auth with the provided credentials. func WithTLSClientConfig(certFile, keyFile, caFile string) Option { return func(opts *options) { diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 5b476ff2d..695a87743 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -17,6 +17,7 @@ package getter import ( "bytes" + "crypto/tls" "io" "net/http" @@ -111,5 +112,19 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { return client, nil } + + if g.opts.insecureSkipVerifyTLS { + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + Proxy: http.ProxyFromEnvironment, + }, + } + + return client, nil + } + return http.DefaultClient, nil } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index b20085574..a1288bf47 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -44,12 +44,14 @@ func TestHTTPGetter(t *testing.T) { cd := "../../testdata" join := filepath.Join ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem") + insecure := false // Test with options g, err = NewHTTPGetter( WithBasicAuth("I", "Am"), WithUserAgent("Groot"), WithTLSClientConfig(pub, priv, ca), + WithInsecureSkipVerifyTLS(insecure), ) if err != nil { t.Fatal(err) @@ -83,6 +85,29 @@ func TestHTTPGetter(t *testing.T) { if hg.opts.caFile != ca { t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) } + + if hg.opts.insecureSkipVerifyTLS != insecure { + t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS) + } + + // Test if setting insecureSkipVerifyTLS is being passed to the ops + insecure = true + + g, err = NewHTTPGetter( + WithInsecureSkipVerifyTLS(insecure), + ) + if err != nil { + t.Fatal(err) + } + + hg, ok = g.(*HTTPGetter) + if !ok { + t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") + } + + if hg.opts.insecureSkipVerifyTLS != insecure { + t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) + } } func TestDownload(t *testing.T) { @@ -191,3 +216,35 @@ func TestDownloadTLS(t *testing.T) { t.Error(err) } } + +func TestDownloadInsecureSkipTLSVerify(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer ts.Close() + + u, _ := url.ParseRequestURI(ts.URL) + + // Ensure the default behaviour did not change + g, err := NewHTTPGetter( + WithURL(u.String()), + ) + if err != nil { + t.Error(err) + } + + if _, err := g.Get(u.String()); err == nil { + t.Errorf("Expected Getter to throw an error, got %s", err) + } + + // Test certificate check skip + g, err = NewHTTPGetter( + WithURL(u.String()), + WithInsecureSkipVerifyTLS(true), + ) + if err != nil { + t.Error(err) + } + if _, err = g.Get(u.String()); err != nil { + t.Error(err) + } + +} diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 38b6b8fb0..c2c366a1e 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -19,8 +19,10 @@ package repo // import "helm.sh/helm/v3/pkg/repo" import ( "crypto/rand" "encoding/base64" + "encoding/json" "fmt" "io/ioutil" + "log" "net/url" "os" "path" @@ -38,13 +40,14 @@ import ( // Entry represents a collection of parameters for chart repository type Entry struct { - Name string `json:"name"` - URL string `json:"url"` - Username string `json:"username"` - Password string `json:"password"` - CertFile string `json:"certFile"` - KeyFile string `json:"keyFile"` - CAFile string `json:"caFile"` + Name string `json:"name"` + URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + CAFile string `json:"caFile"` + InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"` } // ChartRepository represents a chart repository @@ -121,6 +124,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { // TODO add user-agent resp, err := r.Client.Get(indexURL, getter.WithURL(r.Config.URL), + getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify), getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile), getter.WithBasicAuth(r.Config.Username, r.Config.Password), ) @@ -271,3 +275,11 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) { parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/" return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil } + +func (e *Entry) String() string { + buf, err := json.Marshal(e) + if err != nil { + log.Panic(err) + } + return string(buf) +} From 16024dc19a23e83f00a19742033031717a56be0e Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 3 Mar 2020 17:28:57 -0700 Subject: [PATCH 61/76] fix: add new static linter and fix issues it found (#7655) * fix: add new static linter and fix issues it found Signed-off-by: Matt Butcher * fixed two additional linter errors. Signed-off-by: Matt Butcher --- .golangci.yml | 1 + cmd/helm/lint.go | 2 +- go.mod | 1 + internal/completion/complete.go | 4 ++-- internal/experimental/registry/client_test.go | 4 ++-- pkg/lint/rules/template.go | 8 +++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 2c3b6234d..491e648a1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters: - structcheck - unused - varcheck + - staticcheck linters-settings: gofmt: diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index bc0d1852b..fe39a5741 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -106,7 +106,7 @@ func newLintCmd(out io.Writer) *cobra.Command { fmt.Fprint(&message, "\n") } - fmt.Fprintf(out, message.String()) + fmt.Fprint(out, message.String()) summary := fmt.Sprintf("%d chart(s) linted, %d chart(s) failed", len(paths), failed) if failed > 0 { diff --git a/go.mod b/go.mod index 7ba7a5542..4e3bcf9a1 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/stretchr/testify v1.4.0 github.com/xeipuuv/gojsonschema v1.1.0 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d + honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect k8s.io/api v0.17.3 k8s.io/apiextensions-apiserver v0.17.3 k8s.io/apimachinery v0.17.3 diff --git a/internal/completion/complete.go b/internal/completion/complete.go index eaea4e914..545f5b0dd 100644 --- a/internal/completion/complete.go +++ b/internal/completion/complete.go @@ -368,7 +368,7 @@ func CompDebug(msg string) { if debug { // Must print to stderr for this not to be read by the completion script. - fmt.Fprintf(os.Stderr, msg) + fmt.Fprintln(os.Stderr, msg) } } @@ -389,7 +389,7 @@ func CompError(msg string) { // If not already printed by the call to CompDebug(). if !debug { // Must print to stderr for this not to be read by the completion script. - fmt.Fprintf(os.Stderr, msg) + fmt.Fprintln(os.Stderr, msg) } } diff --git a/internal/experimental/registry/client_test.go b/internal/experimental/registry/client_test.go index 33799f5fa..6e9d5db36 100644 --- a/internal/experimental/registry/client_test.go +++ b/internal/experimental/registry/client_test.go @@ -162,13 +162,13 @@ func (suite *RegistryClientTestSuite) Test_2_LoadChart() { // non-existent ref ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) suite.Nil(err) - ch, err := suite.RegistryClient.LoadChart(ref) + _, err = suite.RegistryClient.LoadChart(ref) suite.NotNil(err) // existing ref ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)) suite.Nil(err) - ch, err = suite.RegistryClient.LoadChart(ref) + ch, err := suite.RegistryClient.LoadChart(ref) suite.Nil(err) suite.Equal("testchart", ch.Metadata.Name) suite.Equal("1.2.3", ch.Metadata.Version) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 5c6cd7336..3d388f81b 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -116,11 +116,9 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // key will be raised as well err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) - validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) - - if !validYaml { - continue - } + // 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)) } } From 187526eb13f03bcd04ed2762eae507ec63baab02 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Thu, 5 Mar 2020 11:02:25 -0800 Subject: [PATCH 62/76] chore(go.mod): run `go mod tidy` Signed-off-by: Matthew Fisher --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 4e3bcf9a1..7ba7a5542 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/stretchr/testify v1.4.0 github.com/xeipuuv/gojsonschema v1.1.0 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d - honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect k8s.io/api v0.17.3 k8s.io/apiextensions-apiserver v0.17.3 k8s.io/apimachinery v0.17.3 diff --git a/go.sum b/go.sum index 39b57b4f2..4d8cc83a2 100644 --- a/go.sum +++ b/go.sum @@ -332,8 +332,6 @@ 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-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk= -github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= From d829343c1514db17bee7a90624d06cdfbffde963 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Thu, 5 Mar 2020 21:41:30 -0500 Subject: [PATCH 63/76] Use Create method if no resources need to be adopted Signed-off-by: Jacob LeGrone --- pkg/action/install.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index cab55309c..d6fd829e5 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -243,7 +243,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // Mark this release as in-progress rel.SetStatus(release.StatusPendingInstall, "Initial install underway") - var adoptedResources kube.ResourceList + var toBeAdopted kube.ResourceList resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") @@ -261,11 +261,10 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // deleting the release because the manifest will be pointing at that // resource if !i.ClientOnly && !isUpgrade { - toBeUpdated, err := existingResourceConflict(resources, rel.Name, rel.Namespace) + toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } - adoptedResources = toBeUpdated } // Bail out here if it is a dry run @@ -300,8 +299,14 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // At this point, we can do the install. Note that before we were detecting whether to // do an update, but it's not clear whether we WANT to do an update if the re-use is set // to true, since that is basically an upgrade operation. - if _, err := i.cfg.KubeClient.Update(adoptedResources, resources, false); err != nil { - return i.failRelease(rel, err) + if len(toBeAdopted) == 0 { + if _, err := i.cfg.KubeClient.Create(resources); err != nil { + return i.failRelease(rel, err) + } + } else { + if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { + return i.failRelease(rel, err) + } } if i.Wait { From a6994f6659d9801cca60ebfb1a12366fb7e2617f Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Fri, 6 Mar 2020 16:57:25 +0000 Subject: [PATCH 64/76] Port --devel flag from v2 to v3 Helm v2 PR #5141 Signed-off-by: Martin Hickey --- cmd/helm/show.go | 49 +++++++++++++++++++++++++--------------------- go.mod | 2 ++ go.sum | 4 ++++ pkg/action/show.go | 3 ++- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/cmd/helm/show.go b/cmd/helm/show.go index a82ad2777..ac38ed5af 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -77,11 +77,7 @@ func newShowCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll - cp, err := client.ChartPathOptions.LocateChart(args[0], settings) - if err != nil { - return err - } - output, err := client.Run(cp) + output, err := runShow(args, client) if err != nil { return err } @@ -97,11 +93,7 @@ func newShowCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues - cp, err := client.ChartPathOptions.LocateChart(args[0], settings) - if err != nil { - return err - } - output, err := client.Run(cp) + output, err := runShow(args, client) if err != nil { return err } @@ -117,11 +109,7 @@ func newShowCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart - cp, err := client.ChartPathOptions.LocateChart(args[0], settings) - if err != nil { - return err - } - output, err := client.Run(cp) + output, err := runShow(args, client) if err != nil { return err } @@ -137,11 +125,7 @@ func newShowCmd(out io.Writer) *cobra.Command { Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme - cp, err := client.ChartPathOptions.LocateChart(args[0], settings) - if err != nil { - return err - } - output, err := client.Run(cp) + output, err := runShow(args, client) if err != nil { return err } @@ -152,8 +136,7 @@ func newShowCmd(out io.Writer) *cobra.Command { cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd} for _, subCmd := range cmds { - addChartPathOptionsFlags(subCmd.Flags(), &client.ChartPathOptions) - showCommand.AddCommand(subCmd) + addShowFlags(showCommand, subCmd, client) // Register the completion function for each subcommand completion.RegisterValidArgsFunc(subCmd, validArgsFunc) @@ -161,3 +144,25 @@ func newShowCmd(out io.Writer) *cobra.Command { return showCommand } + +func addShowFlags(showCmd *cobra.Command, subCmd *cobra.Command, client *action.Show) { + f := subCmd.Flags() + + f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") + addChartPathOptionsFlags(f, &client.ChartPathOptions) + showCmd.AddCommand(subCmd) +} + +func runShow(args []string, client *action.Show) (string, error) { + debug("Original chart version: %q", client.Version) + if client.Version == "" && client.Devel { + debug("setting version to >0.0.0-0") + client.Version = ">0.0.0-0" + } + + cp, err := client.ChartPathOptions.LocateChart(args[0], settings) + if err != nil { + return "", err + } + return client.Run(cp) +} diff --git a/go.mod b/go.mod index 7ba7a5542..bfd557ff0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 + github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.0.3 github.com/Masterminds/sprig/v3 v3.0.2 github.com/Masterminds/vcs v1.13.1 @@ -34,6 +35,7 @@ require ( k8s.io/apimachinery v0.17.3 k8s.io/cli-runtime v0.17.3 k8s.io/client-go v0.17.3 + k8s.io/helm v2.16.3+incompatible k8s.io/klog v1.0.0 k8s.io/kubectl v0.17.3 sigs.k8s.io/yaml v1.1.0 diff --git a/go.sum b/go.sum index 4d8cc83a2..7495b2263 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 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/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= @@ -653,6 +655,8 @@ 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/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/helm v2.16.3+incompatible h1:MGUcXcG1uAXWZmxu4vzzgRjZOnfFUsSJbHgqM+kyqzM= +k8s.io/helm v2.16.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= 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= diff --git a/pkg/action/show.go b/pkg/action/show.go index 14b59a5ea..cc85477cd 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -51,8 +51,9 @@ func (o ShowOutputFormat) String() string { // // It provides the implementation of 'helm show' and its respective subcommands. type Show struct { - OutputFormat ShowOutputFormat ChartPathOptions + Devel bool + OutputFormat ShowOutputFormat } // NewShow creates a new Show object with the given configuration. From c8d8007c7aba5b66d185f4c82cc19a16c8246dd7 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Fri, 6 Mar 2020 17:06:52 +0000 Subject: [PATCH 65/76] Fix stray modules Signed-off-by: Martin Hickey --- go.mod | 2 -- go.sum | 4 ---- 2 files changed, 6 deletions(-) diff --git a/go.mod b/go.mod index bfd557ff0..7ba7a5542 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 - github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.0.3 github.com/Masterminds/sprig/v3 v3.0.2 github.com/Masterminds/vcs v1.13.1 @@ -35,7 +34,6 @@ require ( k8s.io/apimachinery v0.17.3 k8s.io/cli-runtime v0.17.3 k8s.io/client-go v0.17.3 - k8s.io/helm v2.16.3+incompatible k8s.io/klog v1.0.0 k8s.io/kubectl v0.17.3 sigs.k8s.io/yaml v1.1.0 diff --git a/go.sum b/go.sum index 7495b2263..4d8cc83a2 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,6 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 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/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= @@ -655,8 +653,6 @@ 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/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/helm v2.16.3+incompatible h1:MGUcXcG1uAXWZmxu4vzzgRjZOnfFUsSJbHgqM+kyqzM= -k8s.io/helm v2.16.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= 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= From d59493adffb2fcafb6ed17aaa21ee37498e3a48b Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Mon, 9 Mar 2020 11:46:36 +0000 Subject: [PATCH 66/76] Add unit test Signed-off-by: Martin Hickey --- cmd/helm/show_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 cmd/helm/show_test.go diff --git a/cmd/helm/show_test.go b/cmd/helm/show_test.go new file mode 100644 index 000000000..00d7c8145 --- /dev/null +++ b/cmd/helm/show_test.go @@ -0,0 +1,82 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "helm.sh/helm/v3/pkg/repo/repotest" +) + +func TestShowPreReleaseChart(t *testing.T) { + srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*") + if err != nil { + t.Fatal(err) + } + defer srv.Stop() + + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + args string + flags string + fail bool + expectedErr string + }{ + { + name: "show pre-release chart", + args: "test/pre-release-chart", + fail: true, + expectedErr: "failed to download \"test/pre-release-chart\"", + }, + { + name: "show pre-release chart with 'devel' flag", + args: "test/pre-release-chart", + flags: "--devel", + fail: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outdir := srv.Root() + cmd := fmt.Sprintf("show all '%s' %s --repository-config %s --repository-cache %s", + tt.args, + tt.flags, + filepath.Join(outdir, "repositories.yaml"), + outdir, + ) + //_, out, err := executeActionCommand(cmd) + _, _, err := executeActionCommand(cmd) + if err != nil { + if tt.fail { + if !strings.Contains(err.Error(), tt.expectedErr) { + t.Errorf("%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) + } + return + } + t.Errorf("%q reported error: %s", tt.name, err) + } + }) + } +} From bf5c0ae7f46dce9f44633e0d7e87b933c375bf5a Mon Sep 17 00:00:00 2001 From: James McElwain Date: Mon, 9 Mar 2020 18:36:42 -0700 Subject: [PATCH 67/76] fix(install): correct append tls config. Signed-off-by: James McElwain --- pkg/downloader/chart_downloader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 039661de4..340a65472 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -183,7 +183,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er getter.WithURL(rc.URL), ) if rc.CertFile != "" || rc.KeyFile != "" || rc.CAFile != "" { - getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile) + c.Options = append(c.Options, getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile)) } if rc.Username != "" && rc.Password != "" { c.Options = append( From 28b085bed5d35d6495e06d4fc7763f4028142560 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 9 Mar 2020 16:26:51 -0400 Subject: [PATCH 68/76] Fixing issue where archives created on windows have broken paths When archives are created on windows the path spearator in the archive file is \\. This causes issues when the file is unpacked. For example, on Linux the files are unpacked in a flat structure and \ is part of the file name. This causes comp issues. In Helm v2 the path was set as / when the archive was written. This works on both Windows and POSIX systems. The fix being implemented is to use the ToSlash function to ensure / is used as the separator. Fixes #7748 Signed-off-by: Matt Farina --- pkg/chartutil/save.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index a2c6a9225..be5d151d7 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -223,7 +223,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { func writeToTar(out *tar.Writer, name string, body []byte) error { // TODO: Do we need to create dummy parent directory names if none exist? h := &tar.Header{ - Name: name, + Name: filepath.ToSlash(name), Mode: 0644, Size: int64(len(body)), ModTime: time.Now(), From 8d1de39fe3234c8925dac6d3efca6aba687ebf87 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 10 Mar 2020 22:22:15 -0400 Subject: [PATCH 69/76] Add more detail to error messages and support a non-force mode in metadata visitor Signed-off-by: Jacob LeGrone --- pkg/action/install.go | 3 +- pkg/action/upgrade.go | 3 +- pkg/action/validate.go | 136 +++++++++++++++++++++++++++++++++++------ 3 files changed, 121 insertions(+), 21 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index d6fd829e5..f7e8f77d1 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -249,7 +249,8 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } - err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace)) + // It is safe to use "force" here because these are resources currently rendered by the chart. + err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) if err != nil { return nil, err } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index b42192cda..0741ef19f 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -203,7 +203,8 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } - err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace)) + // It is safe to use force only on target because these are resources currently rendered by the chart. + err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true)) if err != nil { return upgradedRelease, err } diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 17a61863e..0c40a9c3c 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/resource" "helm.sh/helm/v3/pkg/kube" @@ -30,12 +31,14 @@ import ( var accessor = meta.NewAccessor() const ( + appManagedByLabel = "app.kubernetes.io/managed-by" + appManagedByHelm = "Helm" helmReleaseNameAnnotation = "meta.helm.sh/release-name" helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" ) func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { - requireUpdate := kube.ResourceList{} + var requireUpdate kube.ResourceList err := resources.Visit(func(info *resource.Info, err error) error { if err != nil { @@ -48,39 +51,134 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN if apierrors.IsNotFound(err) { return nil } - return errors.Wrap(err, "could not get information about the resource") } - // Allow adoption of the resource if it already has app.kubernetes.io/instance and app.kubernetes.io/managed-by labels. - anno, err := accessor.Annotations(existing) - if err == nil && anno != nil && anno[helmReleaseNameAnnotation] == releaseName && anno[helmReleaseNamespaceAnnotation] == releaseNamespace { - requireUpdate = append(requireUpdate, info) - return nil + // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. + if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { + return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) } - return fmt.Errorf( - "existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", - info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind) + requireUpdate.Append(info) + return nil }) return requireUpdate, err } -func setMetadataVisitor(releaseName, releaseNamespace string) resource.VisitorFunc { +func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { + lbls, err := accessor.Labels(obj) + if err != nil { + return err + } + annos, err := accessor.Annotations(obj) + if err != nil { + return err + } + + var errs []error + if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { + errs = append(errs, fmt.Errorf("label validation error: %s", err)) + } + if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + } + if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + } + + if len(errs) > 0 { + err := errors.New("invalid ownership metadata") + for _, e := range errs { + err = fmt.Errorf("%w; %s", err, e) + } + return err + } + + return nil +} + +func requireValue(meta map[string]string, k, v string) error { + actual, ok := meta[k] + if !ok { + return fmt.Errorf("missing key %q: must be set to %q", k, v) + } + if actual != v { + return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual) + } + return nil +} + +// setMetadataVisitor adds release tracking metadata to all resources. If force is enabled, existing +// ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an +// existing and conflicting value for the managed by label or Helm release/namespace annotations. +func setMetadataVisitor(releaseName, releaseNamespace string, force bool) resource.VisitorFunc { return func(info *resource.Info, err error) error { if err != nil { return err } - anno, err := accessor.Annotations(info.Object) - if err != nil { - return err + + if !force { + if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { + return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) + } } - if anno == nil { - anno = make(map[string]string) + + if err := mergeLabels(info.Object, map[string]string{ + appManagedByLabel: appManagedByHelm, + }); err != nil { + return fmt.Errorf( + "%s labels could not be updated: %s", + resourceString(info), err, + ) + } + + if err := mergeAnnotations(info.Object, map[string]string{ + helmReleaseNameAnnotation: releaseName, + helmReleaseNamespaceAnnotation: releaseNamespace, + }); err != nil { + return fmt.Errorf( + "%s annotations could not be updated: %s", + resourceString(info), err, + ) } - anno[helmReleaseNameAnnotation] = releaseName - anno[helmReleaseNamespaceAnnotation] = releaseNamespace - return accessor.SetAnnotations(info.Object, anno) + + return nil + } +} + +func resourceString(info *resource.Info) string { + _, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind() + return fmt.Sprintf( + "%s %q in namespace %q", + k, info.Name, info.Namespace, + ) +} + +func mergeLabels(obj runtime.Object, labels map[string]string) error { + current, err := accessor.Labels(obj) + if err != nil { + return err + } + return accessor.SetLabels(obj, mergeStrStrMaps(current, labels)) +} + +func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { + current, err := accessor.Annotations(obj) + if err != nil { + return err + } + return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations)) +} + +// merge two maps, always taking the value on the right +func mergeStrStrMaps(current, desired map[string]string) map[string]string { + result := make(map[string]string) + for k, v := range current { + result[k] = v + } + for k, desiredVal := range desired { + result[k] = desiredVal } + return result } From 9496a7474bf3bcf8bb0b7efc953b85174ae8d8da Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 10 Mar 2020 22:22:33 -0400 Subject: [PATCH 70/76] Add tests Signed-off-by: Jacob LeGrone --- pkg/action/validate_test.go | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 pkg/action/validate_test.go diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go new file mode 100644 index 000000000..a9c1cb49c --- /dev/null +++ b/pkg/action/validate_test.go @@ -0,0 +1,123 @@ +/* +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 action + +import ( + "testing" + + "helm.sh/helm/v3/pkg/kube" + + appsv1 "k8s.io/api/apps/v1" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/meta" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" +) + +func newDeploymentResource(name, namespace string) *resource.Info { + return &resource.Info{ + Name: name, + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"}, + GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + }, + Object: &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + } +} + +func TestCheckOwnership(t *testing.T) { + deployFoo := newDeploymentResource("foo", "ns-a") + + // Verify that a resource that lacks labels/annotations is not owned + err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") + assert.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + + // Set managed by label and verify annotation error message + _ = accessor.SetLabels(deployFoo.Object, map[string]string{ + appManagedByLabel: appManagedByHelm, + }) + err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") + assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + + // Set only the release name annotation and verify missing release namespace error message + _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ + helmReleaseNameAnnotation: "rel-a", + }) + err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") + assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + + // Set both release name and namespace annotations and verify no ownership errors + _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ + helmReleaseNameAnnotation: "rel-a", + helmReleaseNamespaceAnnotation: "ns-a", + }) + err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") + assert.NoError(t, err) + + // Verify ownership error for wrong release name + err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") + assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) + + // Verify ownership error for wrong release namespace + err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") + assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) + + // Verify ownership error for wrong manager label + _ = accessor.SetLabels(deployFoo.Object, map[string]string{ + appManagedByLabel: "helm", + }) + err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") + assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) +} + +func TestSetMetadataVisitor(t *testing.T) { + var ( + err error + deployFoo = newDeploymentResource("foo", "ns-a") + deployBar = newDeploymentResource("bar", "ns-a-system") + resources = kube.ResourceList{deployFoo, deployBar} + ) + + // Set release tracking metadata and verify no error + err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) + assert.NoError(t, err) + + // Verify that release "b" cannot take ownership of "a" + err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) + assert.Error(t, err) + + // Force release "b" to take ownership + err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) + assert.NoError(t, err) + + // Check that there is now no ownership error when setting metadata without force + err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) + assert.NoError(t, err) + + // Add a new resource that is missing ownership metadata and verify error + resources.Append(newDeploymentResource("baz", "default")) + err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) + assert.Error(t, err) + assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) +} From 4feed2de1b7e031c5a560dded8f69cfbea17d93e Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Thu, 12 Mar 2020 14:15:05 -0500 Subject: [PATCH 71/76] Correcting links for release notes Signed-off-by: Bridget Kromhout --- scripts/release-notes.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index 3299eeddc..dd48d4a17 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -81,14 +81,14 @@ The community keeps growing, and we'd love to see you there! Download Helm ${RELEASE}. The common platform binaries are here: -- [MacOS amd64](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) -- [Linux amd64](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-amd64.tar.gz.sha256)) -- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256)) -- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256)) -- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256)) -- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256)) -- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) -- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) +- [MacOS amd64](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) +- [Linux amd64](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-amd64.tar.gz.sha256)) +- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256)) +- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256)) +- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256)) +- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256)) +- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256)) +- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`. From 22b7562c62c2fc0cc89f568755f0c2e09106c0ab Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 12 Mar 2020 12:16:21 -0400 Subject: [PATCH 72/76] fix(cli): Make upgrade check if cluster reachable The 'helm upgrade' command was not checking if the cluster was reachable. Also, 'helm upgrade --install' first checks if the release exists already. If that check fails there is no point in continuing the upgrade. This optimization avoids a second timeout of 30 seconds when trying to do the upgrade. Signed-off-by: Marc Khouzam --- cmd/helm/upgrade.go | 5 +++-- pkg/action/upgrade.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 53aef7af7..af8ff68e3 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -91,8 +91,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } 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. + // If a release does not exist, install it. histClient := action.NewHistory(cfg) histClient.Max = 1 if _, err := histClient.Run(args[0]); err == driver.ErrReleaseNotFound { @@ -119,6 +118,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } return outfmt.Write(out, &statusPrinter{rel, settings.Debug}) + } else if err != nil { + return err } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 62e26adb4..fabba9af4 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -75,6 +75,10 @@ func NewUpgrade(cfg *Configuration) *Upgrade { // Run executes the upgrade on the given release. func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { + if err := u.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both u.Wait = u.Wait || u.Atomic From 06bc18c624c3ce264c926632ce8bc9fe471ce6e6 Mon Sep 17 00:00:00 2001 From: tiendc Date: Sat, 14 Mar 2020 01:35:39 +0700 Subject: [PATCH 73/76] Fix a bug in storage/driver/secrets.go Delete() (#7348) * Fix a bug in storage/driver/secrets.go --- pkg/storage/driver/secrets.go | 6 +----- pkg/storage/driver/secrets_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index dcb2ecfcf..1f1931aed 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -185,11 +185,7 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error { func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) { // fetch the release to check existence if rls, err = secrets.Get(key); err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseExists - } - - return nil, errors.Wrapf(err, "delete: failed to get release %q", key) + return nil, err } // delete the release err = secrets.impl.Delete(key, &metav1.DeleteOptions{}) diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index e4420704d..5f0ecc8bb 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -194,6 +194,12 @@ func TestSecretDelete(t *testing.T) { secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + // perform the delete on a non-existing release + _, err := secrets.Delete("nonexistent") + if err != ErrReleaseNotFound { + t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err) + } + // perform the delete rls, err := secrets.Delete(key) if err != nil { From 26830942d275b3a70edfdc32474230f3499a18e4 Mon Sep 17 00:00:00 2001 From: tiendc Date: Sat, 14 Mar 2020 01:36:14 +0700 Subject: [PATCH 74/76] Fix a bug in Delete() in storage/driver/cfgmaps.go (#7367) --- pkg/storage/driver/cfgmaps.go | 5 ----- pkg/storage/driver/cfgmaps_test.go | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index cc2e2416a..f9d4da8c3 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -201,11 +201,6 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { // fetch the release to check existence if rls, err = cfgmaps.Get(key); err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseExists - } - - cfgmaps.Log("delete: failed to get release %q: %s", key, err) return nil, err } // delete the release diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index a36cee1be..e40247d3c 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -194,6 +194,12 @@ func TestConfigMapDelete(t *testing.T) { cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + // perform the delete on a non-existent release + _, err := cfgmaps.Delete("nonexistent") + if err != ErrReleaseNotFound { + t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) + } + // perform the delete rls, err := cfgmaps.Delete(key) if err != nil { From 9d20e44ad15c0a3e69b7ee2fbf3e50968c00befb Mon Sep 17 00:00:00 2001 From: Kim Bao Long Date: Thu, 27 Feb 2020 14:49:41 +0700 Subject: [PATCH 75/76] Add unit test for lint/values.go Signed-off-by: Kim Bao Long --- pkg/lint/rules/values_test.go | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pkg/lint/rules/values_test.go diff --git a/pkg/lint/rules/values_test.go b/pkg/lint/rules/values_test.go new file mode 100644 index 000000000..901a739fd --- /dev/null +++ b/pkg/lint/rules/values_test.go @@ -0,0 +1,37 @@ +/* +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 ( + "os" + "path/filepath" + "testing" +) + +var ( + nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") +) + +func TestValidateValuesYamlNotDirectory(t *testing.T) { + _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) + defer os.Remove(nonExistingValuesFilePath) + + err := validateValuesFileExistence(nonExistingValuesFilePath) + if err == nil { + t.Errorf("validateValuesFileExistence to return a linter error, got no error") + } +} From 9a0e7d8a317947afcaa19f1e6fcf8df3df31fa77 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Wed, 18 Mar 2020 12:22:06 +0000 Subject: [PATCH 76/76] Improve error message to check in unit test Signed-off-by: Martin Hickey --- pkg/lint/lint_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 2a982d088..b51939d76 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -93,7 +93,7 @@ func TestBadValues(t *testing.T) { if len(m) < 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } - if !strings.Contains(m[0].Err.Error(), "cannot unmarshal") { + if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") { t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) } }