From 36c9c0e5202789f0f2c87fabf816900259de0fb8 Mon Sep 17 00:00:00 2001 From: Simon Alling Date: Tue, 19 Nov 2019 08:41:15 +0100 Subject: [PATCH 01/76] Simplify chart installable check The error conveys at least as much information as the boolean. Signed-off-by: Simon Alling --- cmd/helm/install.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 40935db17..4255125ae 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -176,8 +176,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, err } - validInstallableChart, err := isChartInstallable(chartRequested) - if !validInstallableChart { + if err := checkIfInstallable(chartRequested); err != nil { return nil, err } @@ -209,13 +208,13 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return client.Run(chartRequested, vals) } -// isChartInstallable validates if a chart can be installed +// checkIfInstallable validates if a chart can be installed // // Application chart type is only installable -func isChartInstallable(ch *chart.Chart) (bool, error) { +func checkIfInstallable(ch *chart.Chart) error { switch ch.Metadata.Type { case "", "application": - return true, nil + return nil } - return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type) + return errors.Errorf("%s charts are not installable", ch.Metadata.Type) } From 70f89e5f26015a92432bdeb94ddb6d6e52532b19 Mon Sep 17 00:00:00 2001 From: Andrew Melis Date: Sat, 6 Jun 2020 03:32:49 -0400 Subject: [PATCH 02/76] Make helm ls return only current releases if providing state filter Previously, the `helm ls --$state` operation would display outdated releases under certain conditions. Given the following set of releases: ``` NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE bar 1 Wed Apr 8 16:54:39 2020 DEPLOYED bar-4.0.0 1.0 default foo 1 Fri Feb 7 06:16:56 2020 DEPLOYED foo-0.1.0 1.0 default foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default foo 4 Tue May 5 08:16:56 2020 DEPLOYED foo-0.2.0 1.0 default qux 1 Tue Jun 9 10:32:00 2020 DEPLOYED qux-4.0.3 1.0 default qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.3 1.0 default ``` `helm ls --failed` produced the following output: ``` NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default ``` Including the `qux` release in that `helm ls --failed` output is not controversial; the most recent revision of `qux` was not successful and an operator should investigate. Including the `foo` release in the output, however, is questionable. Revision 3 of `foo` is _not_ the most recent release of `foo`, and that FAILED release was fixed in a susubsequent upgrade. A user may see that FAILED deploy and start taking inappropriate action. Further, that issue was fixed months ago in this example -- troubleshooting an old deploy may not be safe if significant changes have occurred. Concern over this behavior was raised in https://github.com/helm/helm/issues/7495. This behavior applied to all the state filter flags (--deployed, --failed, --pending, etc.), and a user could pass multiple state filter flags to a single command. The previous behavior can be summarized as follows: For each release name, all release revisions having any of the supplied state flags were retrieved, and the most recent revision among these was returned (regardless of whether a newer revision of an unspecified state exists). This change request alters the helm list action to match user expectations such that only "current" releases are shown when filtering on release state. After this change, the following output would be produced by `helm ls --failed`: ``` NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default ``` The command now returns only `qux` because it is the only "current" FAILED release. This behavior change applies to all the state filters _except_ `superseded`, which now becomes a special case. By definition, at least one newer release exists ahead of each superseded release. A conditional is included in this change request to maintain the preexisting behavior (return "most recent" superseded revison for each release name) if the superseded state filter is requested. --- Note that there is an alternate perspective that a state filter flag should return all releases of a given state rather than only the "current" releases. In the above example, `helm ls --failed` with this approach would return the following: ``` NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE foo 2 Mon May 4 07:16:56 2020 FAILED foo-0.1.0 1.0 default foo 3 Mon May 4 07:20:00 2020 FAILED foo-0.1.0 1.0 default qux 2 Tue Jun 9 10:57:00 2020 FAILED qux-4.0.0 1.0 default ``` Multiple FAILED `foo` revisions are included in the output, unlike the current behavior. This approach is logical and achievable. It allows a user to find exactly what is requested: all historical releases of a given state. In order to achieve continuity with helm behavior, however, a new filter (something like "current") would probably need to be implemented and become the new default. Given current helm behavior as well as the comments in the #7495, I did not pursue this approach. --- Technical details: - Moved list action state mask filter after latest release filter Previously, the list operation in helm/pkg/action/list.go skipped releases that were not covered by the state mask on _retrieval_ from the Releases store: ``` results, err := l.cfg.Releases.List(func(rel *release.Release) bool { // Skip anything that the mask doesn't cover currentStatus := l.StateMask.FromName(rel.Info.Status.String()) if l.StateMask¤tStatus == 0 { return false } ... ``` https://github.com/helm/helm/blob/8ea6b970ecd02365a230420692350057d48278e5/pkg/action/list.go#L154-L159 While filtering on retrieval in this manner avoided an extra iteration through the entire list to check on the supplied condition later, it introduced the possibility of returning an outdated release to the user because newer releases (that would have otherwise squashed outdated releases in the `filterList` function) are simply not included in the set of working records. This change moves the state mask filtering process to _after_ the set of current releases is built. Outdated, potentially misleading releases are scrubbed out prior to the application of the state mask filter. As written, this state mask filtration (in the new `filterStateMask` method on `*List`) incurs an additional, potentially expensive iteration over the set of releases to return to the user. An alternative approach could avoid that extra iteration and fit this logic into the existing `filterList` function at the cost of making `filterList` function a little harder to understand. - Rename filterList to filterLatestReleases for clarity Another function that filters the list is added, so update to the more descriptive name here. - List superseded releases without filtering for latest This change makes superseded releases a special case, as they would _never_ be displayed otherwise (by definition, as superseded releases have been replaced by a newer release), so a conditional maintains current behavior ("return newest superseded revision for each release name") Fixes #7495. Signed-off-by: Andrew Melis --- pkg/action/list.go | 35 +++++++++++++++++++------- pkg/action/list_test.go | 56 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/pkg/action/list.go b/pkg/action/list.go index ac6fd1b75..3d5e6d7a6 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -152,12 +152,6 @@ func (l *List) Run() ([]*release.Release, error) { } results, err := l.cfg.Releases.List(func(rel *release.Release) bool { - // Skip anything that the mask doesn't cover - currentStatus := l.StateMask.FromName(rel.Info.Status.String()) - if l.StateMask¤tStatus == 0 { - return false - } - // Skip anything that doesn't match the filter. if filter != nil && !filter.MatchString(rel.Name) { return false @@ -173,7 +167,16 @@ func (l *List) Run() ([]*release.Release, error) { return results, nil } - results = filterList(results) + // by definition, superseded releases are never shown if + // only the latest releases are returned. so if requested statemask + // is _only_ ListSuperseded, skip the latest release filter + if l.StateMask != ListSuperseded { + results = filterLatestReleases(results) + } + + // State mask application must occur after filtering to + // latest releases, otherwise outdated entries can be returned + results = l.filterStateMask(results) // Unfortunately, we have to sort before truncating, which can incur substantial overhead l.sort(results) @@ -222,8 +225,8 @@ func (l *List) sort(rels []*release.Release) { } } -// filterList returns a list scrubbed of old releases. -func filterList(releases []*release.Release) []*release.Release { +// filterLatestReleases returns a list scrubbed of old releases. +func filterLatestReleases(releases []*release.Release) []*release.Release { latestReleases := make(map[string]*release.Release) for _, rls := range releases { @@ -242,6 +245,20 @@ func filterList(releases []*release.Release) []*release.Release { return list } +func (l *List) filterStateMask(releases []*release.Release) []*release.Release { + desiredStateReleases := make([]*release.Release, 0) + + for _, rls := range releases { + currentStatus := l.StateMask.FromName(rls.Info.Status.String()) + if l.StateMask¤tStatus == 0 { + continue + } + desiredStateReleases = append(desiredStateReleases, rls) + } + + return desiredStateReleases +} + // SetStateMask calculates the state mask based on parameters. func (l *List) SetStateMask() { if l.All { diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index 378a747b0..b8e2ece68 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -188,6 +188,56 @@ func TestList_StateMask(t *testing.T) { is.Len(res, 3) } +func TestList_StateMaskWithStaleRevisions(t *testing.T) { + is := assert.New(t) + lister := newListFixture(t) + lister.StateMask = ListFailed + + makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t) + + res, err := lister.Run() + + is.NoError(err) + is.Len(res, 1) + + // "dirty" release should _not_ be present as most recent + // release is deployed despite failed release in past + is.Equal("failed", res[0].Name) +} + +func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) { + t.Helper() + one := namedReleaseStub("clean", release.StatusDeployed) + one.Namespace = "default" + one.Version = 1 + + two := namedReleaseStub("dirty", release.StatusDeployed) + two.Namespace = "default" + two.Version = 1 + + three := namedReleaseStub("dirty", release.StatusFailed) + three.Namespace = "default" + three.Version = 2 + + four := namedReleaseStub("dirty", release.StatusDeployed) + four.Namespace = "default" + four.Version = 3 + + five := namedReleaseStub("failed", release.StatusFailed) + five.Namespace = "default" + five.Version = 1 + + for _, rel := range []*release.Release{one, two, three, four, five} { + if err := store.Create(rel); err != nil { + t.Fatal(err) + } + } + + all, err := store.ListReleases() + assert.NoError(t, err) + assert.Len(t, all, 5, "sanity test: five items added") +} + func TestList_Filter(t *testing.T) { is := assert.New(t) lister := newListFixture(t) @@ -236,7 +286,7 @@ func makeMeSomeReleases(store *storage.Storage, t *testing.T) { assert.Len(t, all, 3, "sanity test: three items added") } -func TestFilterList(t *testing.T) { +func TestFilterLatestReleases(t *testing.T) { t.Run("should filter old versions of the same release", func(t *testing.T) { r1 := releaseStub() r1.Name = "r" @@ -248,7 +298,7 @@ func TestFilterList(t *testing.T) { another.Name = "another" another.Version = 1 - filteredList := filterList([]*release.Release{r1, r2, another}) + filteredList := filterLatestReleases([]*release.Release{r1, r2, another}) expectedFilteredList := []*release.Release{r2, another} assert.ElementsMatch(t, expectedFilteredList, filteredList) @@ -264,7 +314,7 @@ func TestFilterList(t *testing.T) { r2.Namespace = "testing" r2.Version = 2 - filteredList := filterList([]*release.Release{r1, r2}) + filteredList := filterLatestReleases([]*release.Release{r1, r2}) expectedFilteredList := []*release.Release{r1, r2} assert.ElementsMatch(t, expectedFilteredList, filteredList) From 5396df2e282c61ffb1fc8fa65240c36a0216055f Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Sun, 8 Dec 2019 00:30:16 +0800 Subject: [PATCH 03/76] fix #6116 Signed-off-by: zwwhdls --- pkg/strvals/parser.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index c735412e9..3c585cbea 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -305,8 +305,15 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { if err != nil { return list, errors.Wrap(err, "error parsing index") } + var crtList []interface{} + if len(list) > i { + // If nested list already exists, take the value of list to next cycle. + crtList = list[i].([]interface{}) + } else { + crtList = list + } // Now we need to get the value after the ]. - list2, err := t.listItem(list, i) + list2, err := t.listItem(crtList, i) if err != nil { return list, err } From c41c72cee980b8763a3701450b4b5da8ebf343a2 Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Sun, 8 Dec 2019 00:41:35 +0800 Subject: [PATCH 04/76] add test case Signed-off-by: zwwhdls --- pkg/strvals/parser_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 742256153..7c6e6f73b 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -427,7 +427,7 @@ func TestParseInto(t *testing.T) { "inner2": "value2", }, } - input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4" + input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4,listOuter[0][0].type=listValue" expect := map[string]interface{}{ "outer": map[string]interface{}{ "inner1": "value1", @@ -435,12 +435,22 @@ func TestParseInto(t *testing.T) { "inner3": "value3", "inner4": 4, }, + "listOuter": [][]interface{}{{map[string]string{ + "type": "listValue", + "status": "alive", + }}, + }, } if err := ParseInto(input, got); err != nil { t.Fatal(err) } + input2 := "listOuter[0][0].status=alive" + if err := ParseInto(input2, got); err != nil { + t.Fatal(err) + } + y1, err := yaml.Marshal(expect) if err != nil { t.Fatal(err) From 4532485fd03a8cb56186c93ffeba285431073429 Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Sun, 8 Dec 2019 10:37:46 +0800 Subject: [PATCH 05/76] fix another extreme case Signed-off-by: zwwhdls --- pkg/strvals/parser.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 3c585cbea..cc5c509da 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -301,7 +301,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { } case last == '[': // now we have a nested list. Read the index and handle. - i, err := t.keyIndex() + nextI, err := t.keyIndex() if err != nil { return list, errors.Wrap(err, "error parsing index") } @@ -309,15 +309,18 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { if len(list) > i { // If nested list already exists, take the value of list to next cycle. crtList = list[i].([]interface{}) - } else { - crtList = list } // Now we need to get the value after the ]. +<<<<<<< HEAD list2, err := t.listItem(crtList, i) if err != nil { return list, err } return setIndex(list, i, list2) +======= + list2, err := t.listItem(crtList, nextI) + return setIndex(list, i, list2), err +>>>>>>> fix another extreme case case last == '.': // We have a nested object. Send to t.key inner := map[string]interface{}{} From 1b39857ac32165db1ad64f66a03d74f1fa09a3f7 Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Sun, 8 Dec 2019 11:30:11 +0800 Subject: [PATCH 06/76] add test case Signed-off-by: zwwhdls --- pkg/strvals/parser.go | 5 +- pkg/strvals/parser_test.go | 137 ++++++++++++++++++++++++++++--------- 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index cc5c509da..45aa65eac 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -308,7 +308,10 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { var crtList []interface{} if len(list) > i { // If nested list already exists, take the value of list to next cycle. - crtList = list[i].([]interface{}) + existed := list[i] + if existed != nil { + crtList = list[i].([]interface{}) + } } // Now we need to get the value after the ]. <<<<<<< HEAD diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 7c6e6f73b..cef98ba0a 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -421,49 +421,118 @@ func TestParseSet(t *testing.T) { } func TestParseInto(t *testing.T) { - got := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", + tests := []struct { + input string + input2 string + got map[string]interface{} + expect map[string]interface{} + err bool + }{ + { + input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", + got: map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "overwrite", + "inner2": "value2", + }, + }, + expect: map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "value1", + "inner2": "value2", + "inner3": "value3", + "inner4": 4, + }}, + err: false, }, - } - input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4,listOuter[0][0].type=listValue" - expect := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "value1", - "inner2": "value2", - "inner3": "value3", - "inner4": 4, + { + input: "listOuter[0][0].type=listValue", + input2: "listOuter[0][0].status=alive", + got: map[string]interface{}{}, + expect: map[string]interface{}{ + "listOuter": [][]interface{}{{map[string]string{ + "type": "listValue", + "status": "alive", + }}}, + }, + err: false, }, - "listOuter": [][]interface{}{{map[string]string{ - "type": "listValue", - "status": "alive", - }}, + { + input: "listOuter[0][0].type=listValue", + input2: "listOuter[1][0].status=alive", + got: map[string]interface{}{}, + expect: map[string]interface{}{ + "listOuter": [][]interface{}{ + { + map[string]string{"type": "listValue"}, + }, + { + map[string]string{"status": "alive"}, + }, + }, + }, + err: false, + }, + { + input: "listOuter[0][1][0].type=listValue", + input2: "listOuter[0][0][1].status=alive", + got: map[string]interface{}{ + "listOuter": []interface{}{ + []interface{}{ + []interface{}{ + map[string]string{"exited": "old"}, + }, + }, + }, + }, + expect: map[string]interface{}{ + "listOuter": [][][]interface{}{ + { + { + map[string]string{"exited": "old"}, + map[string]string{"status": "alive"}, + }, + { + map[string]string{"type": "listValue"}, + }, + }, + }, + }, + err: false, }, } + for _, tt := range tests { + if err := ParseInto(tt.input, tt.got); err != nil { + t.Fatal(err) + } + if tt.err { + t.Errorf("%s: Expected error. Got nil", tt.input) + } - if err := ParseInto(input, got); err != nil { - t.Fatal(err) - } - - input2 := "listOuter[0][0].status=alive" - if err := ParseInto(input2, got); err != nil { - t.Fatal(err) - } + if tt.input2 != "" { + if err := ParseInto(tt.input2, tt.got); err != nil { + t.Fatal(err) + } + if tt.err { + t.Errorf("%s: Expected error. Got nil", tt.input2) + } + } - 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) - } + y1, err := yaml.Marshal(tt.expect) + if err != nil { + t.Fatal(err) + } + y2, err := yaml.Marshal(tt.got) + if err != nil { + t.Fatalf("Error serializing parsed value: %s", err) + } - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + if string(y1) != string(y2) { + t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) + } } } + func TestParseIntoString(t *testing.T) { got := map[string]interface{}{ "outer": map[string]interface{}{ From d58a984878ea290e04a135ef037b75e13b7b8df5 Mon Sep 17 00:00:00 2001 From: zwwhdls Date: Wed, 1 Jul 2020 21:29:20 +0800 Subject: [PATCH 07/76] fix conflict Signed-off-by: zwwhdls --- pkg/strvals/parser.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 45aa65eac..457b99f94 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -314,16 +314,11 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { } } // Now we need to get the value after the ]. -<<<<<<< HEAD - list2, err := t.listItem(crtList, i) + list2, err := t.listItem(crtList, nextI) if err != nil { return list, err } return setIndex(list, i, list2) -======= - list2, err := t.listItem(crtList, nextI) - return setIndex(list, i, list2), err ->>>>>>> fix another extreme case case last == '.': // We have a nested object. Send to t.key inner := map[string]interface{}{} From fc4a11c131822df4dcb4c8a1e5a1448cc109c39d Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Tue, 7 Jul 2020 10:39:45 -0700 Subject: [PATCH 08/76] bump version to v3.3 Signed-off-by: Matthew Fisher (cherry picked from commit 5c2dfaad847df2ac8f289d278186d048f446c70c) --- cmd/helm/testdata/output/version-client-shorthand.txt | 2 +- cmd/helm/testdata/output/version-client.txt | 2 +- cmd/helm/testdata/output/version-short.txt | 2 +- cmd/helm/testdata/output/version-template.txt | 2 +- cmd/helm/testdata/output/version.txt | 2 +- internal/version/version.go | 2 +- pkg/chartutil/capabilities_test.go | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/helm/testdata/output/version-client-shorthand.txt b/cmd/helm/testdata/output/version-client-shorthand.txt index d613309fe..910493bc4 100644 --- a/cmd/helm/testdata/output/version-client-shorthand.txt +++ b/cmd/helm/testdata/output/version-client-shorthand.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-client.txt b/cmd/helm/testdata/output/version-client.txt index d613309fe..910493bc4 100644 --- a/cmd/helm/testdata/output/version-client.txt +++ b/cmd/helm/testdata/output/version-client.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-short.txt b/cmd/helm/testdata/output/version-short.txt index 4d5034cea..a6c626024 100644 --- a/cmd/helm/testdata/output/version-short.txt +++ b/cmd/helm/testdata/output/version-short.txt @@ -1 +1 @@ -v3.2 +v3.3 diff --git a/cmd/helm/testdata/output/version-template.txt b/cmd/helm/testdata/output/version-template.txt index 7c09e8d57..48c6d2b04 100644 --- a/cmd/helm/testdata/output/version-template.txt +++ b/cmd/helm/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v3.2 \ No newline at end of file +Version: v3.3 \ No newline at end of file diff --git a/cmd/helm/testdata/output/version.txt b/cmd/helm/testdata/output/version.txt index d613309fe..910493bc4 100644 --- a/cmd/helm/testdata/output/version.txt +++ b/cmd/helm/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.3", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/internal/version/version.go b/internal/version/version.go index baa65a028..712aae640 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -30,7 +30,7 @@ var ( // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. // Increment patch number for critical fixes to existing releases. - version = "v3.2" + version = "v3.3" // metadata is extra build time data metadata = "" diff --git a/pkg/chartutil/capabilities_test.go b/pkg/chartutil/capabilities_test.go index 489a472be..ae9624bca 100644 --- a/pkg/chartutil/capabilities_test.go +++ b/pkg/chartutil/capabilities_test.go @@ -62,7 +62,7 @@ func TestDefaultCapabilities(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) { hv := DefaultCapabilities.HelmVersion - if hv.Version != "v3.2" { - t.Errorf("Expected default HelmVerison to be v3.2, got %q", hv.Version) + if hv.Version != "v3.3" { + t.Errorf("Expected default HelmVerison to be v3.3, got %q", hv.Version) } } From ebe6abd660eda59b497d683d418e658b323ca4ea Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 7 Jul 2020 19:18:00 -0400 Subject: [PATCH 09/76] chore(OWNERS): move michelleN to emeritus Thank you. Signed-off-by: Michelle Noorali --- OWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OWNERS b/OWNERS index d7dac5514..e7c953077 100644 --- a/OWNERS +++ b/OWNERS @@ -6,7 +6,6 @@ maintainers: - jdolitsky - marckhouzam - mattfarina - - michelleN - prydonius - SlickNik - technosophos @@ -14,6 +13,7 @@ maintainers: - viglesiasce emeritus: - jascott1 + - michelleN - migmartri - nebril - seh From 637a5c649469935e9d0e69873190dc014ce4d9cf Mon Sep 17 00:00:00 2001 From: wawa0210 Date: Mon, 29 Jun 2020 23:51:57 +0800 Subject: [PATCH 10/76] Environment variable for setting the max history for an environment Signed-off-by: wawa0210 --- cmd/helm/root.go | 1 + cmd/helm/upgrade.go | 2 +- pkg/cli/environment.go | 19 +++++++++++++++++++ pkg/cli/environment_test.go | 38 ++++++++++++++++++++++--------------- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 20dd1d93b..449009cb5 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -50,6 +50,7 @@ Environment variables: | $HELM_DATA_HOME | set an alternative location for storing Helm data. | | $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres | | $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | +| $HELM_MAX_HISTORY | set the maximum number of helm release history. | | $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index c992e2f6b..dbddaa368 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -180,7 +180,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { 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") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") - f.IntVar(&client.MaxHistory, "history-max", 10, "limit the maximum number of revisions saved per release. Use 0 for no limit") + f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.StringVar(&client.Description, "description", "", "add a custom description") diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index d62f57a55..a9994f03d 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -33,6 +33,9 @@ import ( "helm.sh/helm/v3/pkg/helmpath" ) +// defaultMaxHistory sets the maximum number of releases to 0: unlimited +const defaultMaxHistory = 10 + // EnvSettings describes all of the environment settings. type EnvSettings struct { namespace string @@ -56,11 +59,14 @@ type EnvSettings struct { RepositoryCache string // PluginsDirectory is the path to the plugins directory. PluginsDirectory string + // MaxHistory is the max release history maintained. + MaxHistory int } func New() *EnvSettings { env := &EnvSettings{ namespace: os.Getenv("HELM_NAMESPACE"), + MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeToken: os.Getenv("HELM_KUBETOKEN"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), @@ -102,6 +108,18 @@ func envOr(name, def string) string { return def } +func envIntOr(name string, def int) int { + if name == "" { + return def + } + envVal := envOr(name, strconv.Itoa(def)) + ret, err := strconv.Atoi(envVal) + if err != nil { + return def + } + return ret +} + func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ "HELM_BIN": os.Args[0], @@ -114,6 +132,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_REPOSITORY_CACHE": s.RepositoryCache, "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_NAMESPACE": s.Namespace(), + "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), // broken, these are populated from helm flags and not kubeconfig. "HELM_KUBECONTEXT": s.KubeContext, diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index fadc2981e..3234a133b 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -35,29 +35,34 @@ func TestEnvSettings(t *testing.T) { // expected values ns, kcontext string debug bool + maxhistory int }{ { - name: "defaults", - ns: "default", + name: "defaults", + ns: "default", + maxhistory: defaultMaxHistory, }, { - name: "with flags set", - args: "--debug --namespace=myns", - ns: "myns", - debug: true, + name: "with flags set", + args: "--debug --namespace=myns", + ns: "myns", + debug: true, + maxhistory: defaultMaxHistory, }, { - name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, - ns: "yourns", - debug: true, + name: "with envvars set", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_MAX_HISTORY": "5"}, + ns: "yourns", + maxhistory: 5, + debug: true, }, { - name: "with flags and envvars set", - args: "--debug --namespace=myns", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, - ns: "myns", - debug: true, + name: "with flags and envvars set", + args: "--debug --namespace=myns", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"}, + ns: "myns", + debug: true, + maxhistory: defaultMaxHistory, }, } @@ -84,6 +89,9 @@ func TestEnvSettings(t *testing.T) { if settings.KubeContext != tt.kcontext { t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) } + if settings.MaxHistory != tt.maxhistory { + t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) + } }) } } From 2750e4d78181b614776d82031f6ddfece8d020e2 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 9 Jul 2020 14:31:51 -0600 Subject: [PATCH 11/76] Lint dependencies (#7970) * feat: add dependency tests Signed-off-by: Matt Butcher * replaced on-disk fixtures with in-memory fixtures Signed-off-by: Matt Butcher --- cmd/helm/lint_test.go | 2 +- ...hart-with-bad-subcharts-with-subcharts.txt | 6 +- .../output/lint-chart-with-bad-subcharts.txt | 4 +- .../charts/bad-subchart/Chart.yaml | 2 +- pkg/chartutil/save_test.go | 6 +- pkg/lint/lint.go | 1 + pkg/lint/lint_test.go | 12 ++- pkg/lint/rules/dependencies.go | 82 +++++++++++++++ pkg/lint/rules/dependencies_test.go | 99 +++++++++++++++++++ 9 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 pkg/lint/rules/dependencies.go create mode 100644 pkg/lint/rules/dependencies_test.go diff --git a/cmd/helm/lint_test.go b/cmd/helm/lint_test.go index 7638acb84..9079935d4 100644 --- a/cmd/helm/lint_test.go +++ b/cmd/helm/lint_test.go @@ -27,7 +27,7 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) { name: "lint good chart with bad subcharts", cmd: fmt.Sprintf("lint %s", testChart), golden: "output/lint-chart-with-bad-subcharts.txt", - wantError: false, + wantError: true, }, { name: "lint good chart with bad subcharts using --with-subcharts flag", cmd: fmt.Sprintf("lint --with-subcharts %s", testChart), diff --git a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt b/cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt index cda011b57..e77aa387f 100644 --- a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt +++ b/cmd/helm/testdata/output/lint-chart-with-bad-subcharts-with-subcharts.txt @@ -1,6 +1,8 @@ ==> Linting testdata/testcharts/chart-with-bad-subcharts [INFO] Chart.yaml: icon is recommended [WARNING] templates/: directory not found +[ERROR] : unable to load chart + error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required ==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart [ERROR] Chart.yaml: name is required @@ -8,9 +10,11 @@ [ERROR] Chart.yaml: version is required [INFO] Chart.yaml: icon is recommended [WARNING] templates/: directory not found +[ERROR] : unable to load chart + validation: chart.metadata.name is required ==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart [INFO] Chart.yaml: icon is recommended [WARNING] templates/: directory not found -Error: 3 chart(s) linted, 1 chart(s) failed +Error: 3 chart(s) linted, 2 chart(s) failed diff --git a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt b/cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt index 51f3f3718..265e555f7 100644 --- a/cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt +++ b/cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt @@ -1,5 +1,7 @@ ==> Linting testdata/testcharts/chart-with-bad-subcharts [INFO] Chart.yaml: icon is recommended [WARNING] templates/: directory not found +[ERROR] : unable to load chart + error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required -1 chart(s) linted, 0 chart(s) failed +Error: 1 chart(s) linted, 1 chart(s) failed diff --git a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml index 0daa5c188..a6754b24f 100644 --- a/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml +++ b/cmd/helm/testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart/Chart.yaml @@ -1 +1 @@ -description: Bad subchart \ No newline at end of file +description: Bad subchart diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index 306c13cee..3a45b2992 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -30,15 +30,13 @@ import ( "testing" "time" + "helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" ) func TestSave(t *testing.T) { - tmp, err := ioutil.TempDir("", "helm-") - if err != nil { - t.Fatal(err) - } + tmp := ensure.TempDir(t) defer os.RemoveAll(tmp) for _, dest := range []string{tmp, path.Join(tmp, "newdir")} { diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index 223ead75a..67e76bd3d 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -32,5 +32,6 @@ func All(basedir string, values map[string]interface{}, namespace string, strict rules.Chartfile(&linter) rules.ValuesWithOverrides(&linter, values) rules.Templates(&linter, values, namespace, strict) + rules.Dependencies(&linter) return linter } diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index e7ff4cd7a..29ed67026 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -38,12 +38,12 @@ const goodChartDir = "rules/testdata/goodone" func TestBadChart(t *testing.T) { m := All(badChartDir, values, namespace, strict).Messages - if len(m) != 7 { + if len(m) != 8 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) } - // There should be one INFO, 2 WARNINGs and one ERROR messages, check for them - var i, w, e, e2, e3, e4, e5 bool + // There should be one INFO, 2 WARNINGs and 2 ERROR messages, check for them + var i, w, e, e2, e3, e4, e5, e6 bool for _, msg := range m { if msg.Severity == support.InfoSev { if strings.Contains(msg.Err.Error(), "icon is recommended") { @@ -74,9 +74,13 @@ func TestBadChart(t *testing.T) { if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { e5 = true } + // This comes from the dependency check, which loads dependency info from the Chart.yaml + if strings.Contains(msg.Err.Error(), "unable to load chart") { + e6 = true + } } } - if !e || !e2 || !e3 || !e4 || !e5 || !w || !i { + if !e || !e2 || !e3 || !e4 || !e5 || !w || !i || !e6 { t.Errorf("Didn't find all the expected errors, got %#v", m) } } diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go new file mode 100644 index 000000000..abecd1feb --- /dev/null +++ b/pkg/lint/rules/dependencies.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 rules // import "helm.sh/helm/v3/pkg/lint/rules" + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/lint/support" +) + +// Dependencies runs lints against a chart's dependencies +// +// See https://github.com/helm/helm/issues/7910 +func Dependencies(linter *support.Linter) { + c, err := loader.LoadDir(linter.ChartDir) + if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) { + return + } + + linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c)) + linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c)) +} + +func validateChartFormat(chartError error) error { + if chartError != nil { + return errors.Errorf("unable to load chart\n\t%s", chartError) + } + return nil +} + +func validateDependencyInChartsDir(c *chart.Chart) (err error) { + dependencies := map[string]struct{}{} + missing := []string{} + for _, dep := range c.Dependencies() { + dependencies[dep.Metadata.Name] = struct{}{} + } + for _, dep := range c.Metadata.Dependencies { + if _, ok := dependencies[dep.Name]; !ok { + missing = append(missing, dep.Name) + } + } + if len(missing) > 0 { + err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ",")) + } + return err +} + +func validateDependencyInMetadata(c *chart.Chart) (err error) { + dependencies := map[string]struct{}{} + missing := []string{} + for _, dep := range c.Metadata.Dependencies { + dependencies[dep.Name] = struct{}{} + } + for _, dep := range c.Dependencies() { + if _, ok := dependencies[dep.Metadata.Name]; !ok { + missing = append(missing, dep.Metadata.Name) + } + } + if len(missing) > 0 { + err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ",")) + } + return err +} diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go new file mode 100644 index 000000000..075190eac --- /dev/null +++ b/pkg/lint/rules/dependencies_test.go @@ -0,0 +1,99 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package rules + +import ( + "os" + "path/filepath" + "testing" + + "helm.sh/helm/v3/internal/test/ensure" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/lint/support" +) + +func chartWithBadDependencies() chart.Chart { + badChartDeps := chart.Chart{ + Metadata: &chart.Metadata{ + Name: "badchart", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{ + { + Name: "sub2", + }, + { + Name: "sub3", + }, + }, + }, + } + + badChartDeps.SetDependencies( + &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "sub1", + Version: "0.1.0", + APIVersion: "v2", + }, + }, + &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "sub2", + Version: "0.1.0", + APIVersion: "v2", + }, + }, + ) + return badChartDeps +} + +func TestValidateDependencyInChartsDir(t *testing.T) { + c := chartWithBadDependencies() + + if err := validateDependencyInChartsDir(&c); err == nil { + t.Error("chart should have been flagged for missing deps in chart directory") + } +} + +func TestValidateDependencyInMetadata(t *testing.T) { + c := chartWithBadDependencies() + + if err := validateDependencyInMetadata(&c); err == nil { + t.Errorf("chart should have been flagged for missing deps in chart metadata") + } +} + +func TestDependencies(t *testing.T) { + tmp := ensure.TempDir(t) + defer os.RemoveAll(tmp) + + c := chartWithBadDependencies() + err := chartutil.SaveDir(&c, tmp) + if err != nil { + t.Fatal(err) + } + linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} + + Dependencies(&linter) + if l := len(linter.Messages); l != 2 { + t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l) + for i, msg := range linter.Messages { + t.Logf("Message: %d, Error: %#v", i, msg) + } + } +} From 971c9215d19ea5ab63f6e3eca971e186442564d6 Mon Sep 17 00:00:00 2001 From: Jack Weldon Date: Fri, 10 Jul 2020 15:44:39 -0400 Subject: [PATCH 12/76] feat(helm): prompt for password with helm repo add Previously in Helm 2, 'helm repo add' would prompt for your password if you only provided the --username flag. This helps prevent someone's credentials from being logged in their shell's history, from showing up in process lists, and from being seen by others nearby. Closes #7174 Signed-off-by: Jack Weldon --- cmd/helm/repo_add.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 9c67641e5..3abb58334 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -24,11 +24,13 @@ import ( "os" "path/filepath" "strings" + "syscall" "time" "github.com/gofrs/flock" "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" "sigs.k8s.io/yaml" "helm.sh/helm/v3/cmd/helm/require" @@ -114,6 +116,16 @@ func (o *repoAddOptions) run(out io.Writer) error { return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name) } + if o.username != "" && o.password == "" { + fmt.Fprint(out, "Password:") + password, err := terminal.ReadPassword(syscall.Stdin) + fmt.Fprintln(out) + if err != nil { + return err + } + o.password = string(password) + } + c := repo.Entry{ Name: o.name, URL: o.url, From ac1a25517b5ee5c13556a2d4eff313a2909ee497 Mon Sep 17 00:00:00 2001 From: Jack Weldon Date: Fri, 10 Jul 2020 17:59:20 -0400 Subject: [PATCH 13/76] address PR comment, adding whitespace for formatting Signed-off-by: Jack Weldon --- cmd/helm/repo_add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 3abb58334..2fc697ff0 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -117,7 +117,7 @@ func (o *repoAddOptions) run(out io.Writer) error { } if o.username != "" && o.password == "" { - fmt.Fprint(out, "Password:") + fmt.Fprint(out, "Password: ") password, err := terminal.ReadPassword(syscall.Stdin) fmt.Fprintln(out) if err != nil { From 7ba8343b8d0edc1a3de96ee15003b072c7e72627 Mon Sep 17 00:00:00 2001 From: Jack Weldon Date: Fri, 10 Jul 2020 19:51:38 -0400 Subject: [PATCH 14/76] fix windows build failure caused by #8431 No longer using the 'syscall' package, as further reading into this issue has shown that 'syscall' is deprecated/locked down. Additional issues posted on Golang's github indicates that the newer preferred mechanism to get the file descriptor for stdin is: int(os.Stdin.Fd()) Signed-off-by: Jack Weldon --- cmd/helm/repo_add.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 2fc697ff0..418a549c6 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -24,7 +24,6 @@ import ( "os" "path/filepath" "strings" - "syscall" "time" "github.com/gofrs/flock" @@ -117,8 +116,9 @@ func (o *repoAddOptions) run(out io.Writer) error { } if o.username != "" && o.password == "" { + fd := int(os.Stdin.Fd()) fmt.Fprint(out, "Password: ") - password, err := terminal.ReadPassword(syscall.Stdin) + password, err := terminal.ReadPassword(fd) fmt.Fprintln(out) if err != nil { return err From 8217aba4a6cf04bac538ba2178603ec1378dbaef Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 13 Jul 2020 10:53:26 +0200 Subject: [PATCH 15/76] fix(kube): use logger instead of fmt.Printf Signed-off-by: Hidde Beydals --- pkg/kube/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index f908611db..62e1b104b 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -542,14 +542,14 @@ func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error switch o.Status.Phase { case v1.PodSucceeded: - fmt.Printf("Pod %s succeeded\n", o.Name) + c.Log("Pod %s succeeded", o.Name) return true, nil case v1.PodFailed: return true, errors.Errorf("pod %s failed", o.Name) case v1.PodPending: - fmt.Printf("Pod %s pending\n", o.Name) + c.Log("Pod %s pending", o.Name) case v1.PodRunning: - fmt.Printf("Pod %s running\n", o.Name) + c.Log("Pod %s running", o.Name) } return false, nil From 57bd8a71b0002b00a13b6a4bfa17e45839b73795 Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Mon, 13 Jul 2020 18:59:09 +0800 Subject: [PATCH 16/76] feature(show): support jsonpath output for `helm show value` Signed-off-by: Dong Gang --- cmd/helm/show.go | 3 +++ pkg/action/show.go | 24 +++++++++++++++++------- pkg/action/show_test.go | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/cmd/helm/show.go b/cmd/helm/show.go index c122d8476..b374a3594 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -151,6 +151,9 @@ func addShowFlags(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") + if subCmd.Name() == "values" { + f.StringVar(&client.JsonPathTemplate, "jsonpath", "", "use jsonpath output format") + } addChartPathOptionsFlags(f, &client.ChartPathOptions) err := subCmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/pkg/action/show.go b/pkg/action/show.go index 9baa9cf43..a5544492b 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -20,11 +20,12 @@ import ( "fmt" "strings" + "helm.sh/helm/v3/pkg/chartutil" + "k8s.io/cli-runtime/pkg/printers" "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" ) // ShowOutputFormat is the format of the output of `helm show` @@ -52,9 +53,10 @@ func (o ShowOutputFormat) String() string { // It provides the implementation of 'helm show' and its respective subcommands. type Show struct { ChartPathOptions - Devel bool - OutputFormat ShowOutputFormat - chart *chart.Chart // for testing + Devel bool + OutputFormat ShowOutputFormat + JsonPathTemplate string + chart *chart.Chart // for testing } // NewShow creates a new Show object with the given configuration. @@ -87,9 +89,17 @@ func (s *Show) Run(chartpath string) (string, error) { if s.OutputFormat == ShowAll { fmt.Fprintln(&out, "---") } - for _, f := range s.chart.Raw { - if f.Name == chartutil.ValuesfileName { - fmt.Fprintln(&out, string(f.Data)) + if s.JsonPathTemplate != "" { + printer, err := printers.NewJSONPathPrinter(s.JsonPathTemplate) + if err != nil { + return "", fmt.Errorf("error parsing jsonpath %s, %v\n", s.JsonPathTemplate, err) + } + printer.Execute(&out, s.chart.Values) + } else { + for _, f := range s.chart.Raw { + if f.Name == chartutil.ValuesfileName { + fmt.Fprintln(&out, string(f.Data)) + } } } } diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 7be9daaa5..0251a528d 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -69,3 +69,17 @@ func TestShowNoValues(t *testing.T) { t.Errorf("expected empty values buffer, got %s", output) } } + +func TestShowValuesByJsonPathFormat(t *testing.T) { + client := NewShow(ShowValues) + client.JsonPathTemplate = "{$.nestedKey.simpleKey}" + client.chart = buildChart(withSampleValues()) + output, err := client.Run("") + if err != nil { + t.Fatal(err) + } + expect := "simpleValue" + if output != expect { + t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) + } +} From 1dfe66aa85bf09cd1f09271c2810c5deb9f22454 Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Mon, 13 Jul 2020 19:11:40 +0800 Subject: [PATCH 17/76] fix the code style error Signed-off-by: Dong Gang --- cmd/helm/show.go | 2 +- pkg/action/show.go | 10 +++++----- pkg/action/show_test.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/helm/show.go b/cmd/helm/show.go index b374a3594..60ed0ab8e 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -152,7 +152,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") if subCmd.Name() == "values" { - f.StringVar(&client.JsonPathTemplate, "jsonpath", "", "use jsonpath output format") + f.StringVar(&client.JSONPathTemplate, "jsonpath", "", "use jsonpath output format") } addChartPathOptionsFlags(f, &client.ChartPathOptions) diff --git a/pkg/action/show.go b/pkg/action/show.go index a5544492b..73d989884 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -20,12 +20,12 @@ import ( "fmt" "strings" - "helm.sh/helm/v3/pkg/chartutil" "k8s.io/cli-runtime/pkg/printers" "sigs.k8s.io/yaml" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" ) // ShowOutputFormat is the format of the output of `helm show` @@ -55,7 +55,7 @@ type Show struct { ChartPathOptions Devel bool OutputFormat ShowOutputFormat - JsonPathTemplate string + JSONPathTemplate string chart *chart.Chart // for testing } @@ -89,10 +89,10 @@ func (s *Show) Run(chartpath string) (string, error) { if s.OutputFormat == ShowAll { fmt.Fprintln(&out, "---") } - if s.JsonPathTemplate != "" { - printer, err := printers.NewJSONPathPrinter(s.JsonPathTemplate) + if s.JSONPathTemplate != "" { + printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) if err != nil { - return "", fmt.Errorf("error parsing jsonpath %s, %v\n", s.JsonPathTemplate, err) + return "", fmt.Errorf("error parsing jsonpath %s, %v", s.JSONPathTemplate, err) } printer.Execute(&out, s.chart.Values) } else { diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 0251a528d..a2efdc8ac 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -72,7 +72,7 @@ func TestShowNoValues(t *testing.T) { func TestShowValuesByJsonPathFormat(t *testing.T) { client := NewShow(ShowValues) - client.JsonPathTemplate = "{$.nestedKey.simpleKey}" + client.JSONPathTemplate = "{$.nestedKey.simpleKey}" client.chart = buildChart(withSampleValues()) output, err := client.Run("") if err != nil { From fe40c4bf844feee87c3b3c1163b7f5f40dc9a793 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Mon, 13 Jul 2020 15:31:26 -0500 Subject: [PATCH 18/76] Clarify comments to match practice Signed-off-by: Bridget Kromhout --- internal/version/version.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/version/version.go b/internal/version/version.go index 712aae640..6154ff3a4 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -25,11 +25,9 @@ import ( var ( // version is the current version of Helm. // Update this whenever making a new release. - // The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata] // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - // Increment patch number for critical fixes to existing releases. version = "v3.3" // metadata is extra build time data From 5684c864908819ad43dfa6efb6f474611d721e7c Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Mon, 13 Jul 2020 15:37:12 -0500 Subject: [PATCH 19/76] Fixing version and spelling errors Signed-off-by: Bridget Kromhout --- pkg/chartutil/capabilities_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/chartutil/capabilities_test.go b/pkg/chartutil/capabilities_test.go index ae9624bca..66eeee755 100644 --- a/pkg/chartutil/capabilities_test.go +++ b/pkg/chartutil/capabilities_test.go @@ -55,7 +55,7 @@ func TestDefaultCapabilities(t *testing.T) { t.Errorf("Expected default KubeVersion.Major to be 1, got %q", kv.Major) } if kv.Minor != "18" { - t.Errorf("Expected default KubeVersion.Minor to be 16, got %q", kv.Minor) + t.Errorf("Expected default KubeVersion.Minor to be 18, got %q", kv.Minor) } } @@ -63,6 +63,6 @@ func TestDefaultCapabilitiesHelmVersion(t *testing.T) { hv := DefaultCapabilities.HelmVersion if hv.Version != "v3.3" { - t.Errorf("Expected default HelmVerison to be v3.3, got %q", hv.Version) + t.Errorf("Expected default HelmVersion to be v3.3, got %q", hv.Version) } } From 9a4f4ec64b8092d2ba3d7493001837df4737e36c Mon Sep 17 00:00:00 2001 From: Cristian Klein Date: Thu, 2 Jan 2020 00:23:10 +0100 Subject: [PATCH 20/76] fix(helm): Avoid corrupting storage via a lock If two `helm upgrade`s are executed at the exact same time, then one of the invocations will fail with "already exists". If one `helm upgrade` is executed and a second one is started while the first is in `pending-upgrade`, then the second invocation will create a new release. Effectively, two helm invocations will simultaneously change the state of Kubernetes resources -- which is scary -- then two releases will be in `deployed` state -- which can cause other issues. This commit fixes the corrupted storage problem, by introducting a poor person's lock. If the last release is in a pending state, then helm will abort. If the last release is in a pending state, due to a previously killed helm, then the user is expected to do `helm rollback`. Closes #7274 Signed-off-by: Cristian Klein --- pkg/action/action.go | 2 ++ pkg/action/upgrade.go | 5 +++++ pkg/release/status.go | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/pkg/action/action.go b/pkg/action/action.go index 698ebd23f..fec86cd42 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -60,6 +60,8 @@ var ( errInvalidRevision = errors.New("invalid release revision") // errInvalidName indicates that an invalid release name was provided errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") + // errPending indicates that another instance of Helm is already applying an operation on a release. + errPending = errors.New("another operation (install/upgrade/rollback) is in progress") ) // ValidName is a regular expression for resource names. diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 921a0eba5..949aebcae 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -170,6 +170,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } + // Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock. + if lastRelease.Info.Status.IsPending() { + return nil, nil, errPending + } + var currentRelease *release.Release if lastRelease.Info.Status == release.StatusDeployed { // no need to retrieve the last deployed release from storage as the last release is deployed diff --git a/pkg/release/status.go b/pkg/release/status.go index 49b0f1544..e0e3ed62a 100644 --- a/pkg/release/status.go +++ b/pkg/release/status.go @@ -42,3 +42,8 @@ const ( ) func (x Status) String() string { return string(x) } + +// IsPending determines if this status is a state or a transition. +func (x Status) IsPending() bool { + return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback +} From 20fb7bac4e9001751a0b04c71006b4bb506378cf Mon Sep 17 00:00:00 2001 From: Cristian Klein Date: Thu, 9 Jan 2020 00:38:47 +0100 Subject: [PATCH 21/76] fix(helm): Added test for concurrent upgrades Signed-off-by: Cristian Klein --- pkg/action/upgrade_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index f25d115c4..f16de6479 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -253,3 +253,23 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { is.Equal(expectedValues, updatedRes.Config) }) } + +func TestUpgradeRelease_Pending(t *testing.T) { + req := require.New(t) + + upAction := upgradeAction(t) + rel := releaseStub() + rel.Name = "come-fail-away" + rel.Info.Status = release.StatusDeployed + upAction.cfg.Releases.Create(rel) + rel2 := releaseStub() + rel2.Name = "come-fail-away" + rel2.Info.Status = release.StatusPendingUpgrade + rel2.Version = 2 + upAction.cfg.Releases.Create(rel2) + + vals := map[string]interface{}{} + + _, err := upAction.Run(rel.Name, buildChart(), vals) + req.Contains(err.Error(), "progress", err) +} From 08517a9ec0abbf238d56ec97de80e6ecc8946ab6 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Mon, 13 Jul 2020 16:04:15 -0500 Subject: [PATCH 22/76] Correct make target in Makefile Signed-off-by: Bridget Kromhout --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 524421279..f8325480a 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,7 @@ clean: .PHONY: release-notes release-notes: @if [ ! -d "./_dist" ]; then \ - echo "please run 'make fetch-release' first" && \ + echo "please run 'make fetch-dist' first" && \ exit 1; \ fi @if [ -z "${PREVIOUS_RELEASE}" ]; then \ From 0d70c63396083f868963797632715f06c9b90ca9 Mon Sep 17 00:00:00 2001 From: Cristian Klein Date: Mon, 13 Jul 2020 23:41:25 +0200 Subject: [PATCH 23/76] fix(helm): Update test during pending install Signed-off-by: Cristian Klein --- cmd/helm/testdata/output/upgrade-with-pending-install.txt | 1 + cmd/helm/upgrade_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 cmd/helm/testdata/output/upgrade-with-pending-install.txt diff --git a/cmd/helm/testdata/output/upgrade-with-pending-install.txt b/cmd/helm/testdata/output/upgrade-with-pending-install.txt new file mode 100644 index 000000000..57a8e7873 --- /dev/null +++ b/cmd/helm/testdata/output/upgrade-with-pending-install.txt @@ -0,0 +1 @@ +Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 7e88bc0df..64daf2934 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -158,7 +158,7 @@ func TestUpgradeCmd(t *testing.T) { { name: "upgrade a pending install release", cmd: fmt.Sprintf("upgrade funny-bunny '%s'", chartPath), - golden: "output/upgrade-with-bad-or-missing-existing-release.txt", + golden: "output/upgrade-with-pending-install.txt", wantError: true, rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusPendingInstall)}, }, From 4be3804c50e4a4260e8e49680e2826c66e32d898 Mon Sep 17 00:00:00 2001 From: wawa0210 Date: Thu, 9 Jul 2020 17:40:36 +0800 Subject: [PATCH 24/76] Rollback command support for max history Signed-off-by: wawa0210 --- cmd/helm/rollback.go | 1 + pkg/action/rollback.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 4849913a1..2cd6fa2cb 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -83,6 +83,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") + f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") return cmd } diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 81812983f..8773b6271 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -42,6 +42,7 @@ type Rollback struct { Recreate bool // will (if true) recreate pods after a rollback. Force bool // will (if true) force resource upgrade through uninstall/recreate if needed CleanupOnFail bool + MaxHistory int // MaxHistory limits the maximum number of revisions saved per release } // NewRollback creates a new Rollback object with the given configuration. @@ -57,6 +58,8 @@ func (r *Rollback) Run(name string) error { return err } + r.cfg.Releases.MaxHistory = r.MaxHistory + r.cfg.Log("preparing rollback of %s", name) currentRelease, targetRelease, err := r.prepareRollback(name) if err != nil { From c709e10b3204b823f7e00bae8ca59b6db65e9865 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Tue, 14 Jul 2020 11:05:37 -0500 Subject: [PATCH 25/76] Reinstating comment that is still accurate Signed-off-by: Bridget Kromhout --- internal/version/version.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/version/version.go b/internal/version/version.go index 6154ff3a4..2941bb489 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -25,6 +25,7 @@ import ( var ( // version is the current version of Helm. // Update this whenever making a new release. + // The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata] // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. From 3ee7047827b0fc5b87fb2d8bde6bb64fa93fcf73 Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Wed, 15 Jul 2020 11:09:01 +0800 Subject: [PATCH 26/76] polish the help text of flag Signed-off-by: Dong Gang --- cmd/helm/show.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/show.go b/cmd/helm/show.go index 60ed0ab8e..fe5eaee26 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -152,7 +152,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") if subCmd.Name() == "values" { - f.StringVar(&client.JSONPathTemplate, "jsonpath", "", "use jsonpath output format") + f.StringVar(&client.JSONPathTemplate, "jsonpath", "", "supply a JSONPath expression to filter the output") } addChartPathOptionsFlags(f, &client.ChartPathOptions) From 9777925a2ae1e98a3e7ec3923d0b56b7199c7806 Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Thu, 16 Jul 2020 09:10:03 +0800 Subject: [PATCH 27/76] fix(create): update the hook name of test-connection pod Signed-off-by: Dong Gang --- pkg/chartutil/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index e74761b3f..c036ce37d 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -457,7 +457,7 @@ metadata: labels: {{- include ".labels" . | nindent 4 }} annotations: - "helm.sh/hook": test-success + "helm.sh/hook": test spec: containers: - name: wget From 844e575d2a7090903c26e658037e3298ecff9479 Mon Sep 17 00:00:00 2001 From: Simon Shine Date: Fri, 17 Jul 2020 06:33:27 +0200 Subject: [PATCH 28/76] Alter whitespace in "Update Complete" output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most places say "Happy Helming!". But pkg/downloader/manager.go also says "⎈Happy Helming!⎈" Signed-off-by: Simon Shine --- cmd/helm/repo_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index 5c1086e81..cbfc05b00 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -96,5 +96,5 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) { }(re) } wg.Wait() - fmt.Fprintln(out, "Update Complete. ⎈ Happy Helming!⎈ ") + fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") } From 9a13385022b0976a704c24c8d11e7b0f3561b931 Mon Sep 17 00:00:00 2001 From: Chris Wells Date: Sun, 19 Jul 2020 14:42:04 -0400 Subject: [PATCH 29/76] fix: Allow building in a path containing spaces Signed-off-by: Chris Wells --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f8325480a..97f99fd86 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ all: build build: $(BINDIR)/$(BINNAME) $(BINDIR)/$(BINNAME): $(SRC) - GO111MODULE=on go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(BINNAME) ./cmd/helm + GO111MODULE=on go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm # ------------------------------------------------------------------------------ # test @@ -97,7 +97,7 @@ test-acceptance: TARGETS = linux/amd64 test-acceptance: build build-cross @if [ -d "${ACCEPTANCE_DIR}" ]; then \ cd ${ACCEPTANCE_DIR} && \ - ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH=$(BINDIR) make acceptance; \ + ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH='$(BINDIR)' make acceptance; \ else \ echo "You must clone the acceptance_testing repo under $(ACCEPTANCE_DIR)"; \ echo "You can find the acceptance_testing repo at https://github.com/helm/acceptance-testing"; \ @@ -178,7 +178,7 @@ checksum: .PHONY: clean clean: - @rm -rf $(BINDIR) ./_dist + @rm -rf '$(BINDIR)' ./_dist .PHONY: release-notes release-notes: From ffc3d42f87f8334684f12372bbdda8147702a12d Mon Sep 17 00:00:00 2001 From: Holder Date: Wed, 22 Jul 2020 23:03:25 +0800 Subject: [PATCH 30/76] fix(sdk): Polish the downloader/manager package error return (#8491) * fix(sdk): Polish the downloader/manager package error return Close #8471 Signed-off-by: Dong Gang * Modify the repositories validation function `resloveRepoNames` and add a unit test. Signed-off-by: Dong Gang * Remove wrong commit Signed-off-by: Dong Gang --- cmd/helm/dependency_build.go | 7 ++++++- pkg/downloader/manager.go | 13 ++++++++++++- pkg/downloader/manager_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/cmd/helm/dependency_build.go b/cmd/helm/dependency_build.go index 478b49479..4e87684ce 100644 --- a/cmd/helm/dependency_build.go +++ b/cmd/helm/dependency_build.go @@ -16,6 +16,7 @@ limitations under the License. package main import ( + "fmt" "io" "os" "path/filepath" @@ -65,7 +66,11 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { if client.Verify { man.Verify = downloader.VerifyIfPossible } - return man.Build() + err := man.Build() + if e, ok := err.(downloader.ErrRepoNotFound); ok { + return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) + } + return err }, } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 00198de0c..2d6918739 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -42,6 +42,17 @@ import ( "helm.sh/helm/v3/pkg/repo" ) +// ErrRepoNotFound indicates that chart repositories can't be found in local repo cache. +// The value of Repos is missing repos. +type ErrRepoNotFound struct { + Repos []string +} + +// Error implements the error interface. +func (e ErrRepoNotFound) Error() string { + return fmt.Sprintf("no repository definition for %s", strings.Join(e.Repos, ", ")) +} + // Manager handles the lifecycle of fetching, resolving, and storing dependencies. type Manager struct { // Out is used to print warnings and notifications. @@ -411,7 +422,7 @@ Loop: missing = append(missing, dd.Repository) } if len(missing) > 0 { - return errors.Errorf("no repository definition for %s. Please add the missing repos via 'helm repo add'", strings.Join(missing, ", ")) + return ErrRepoNotFound{missing} } return nil } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index ea235c13f..dd3cbbd7b 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -360,3 +360,32 @@ func TestBuild_WithRepositoryAlias(t *testing.T) { Repository: "@test", }) } + +func TestErrRepoNotFound_Error(t *testing.T) { + type fields struct { + Repos []string + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "OK", + fields: fields{ + Repos: []string{"https://charts1.example.com", "https://charts2.example.com"}, + }, + want: "no repository definition for https://charts1.example.com, https://charts2.example.com", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := ErrRepoNotFound{ + Repos: tt.fields.Repos, + } + if got := e.Error(); got != tt.want { + t.Errorf("Error() = %v, want %v", got, tt.want) + } + }) + } +} From f9d35242d0cc3774e4c4d937e6c00c49aa8c6c7f Mon Sep 17 00:00:00 2001 From: Andrew Melis Date: Wed, 22 Jul 2020 12:21:30 -0400 Subject: [PATCH 31/76] Enhance readability by extracting bitwise operation Fixes https://github.com/helm/helm/pull/8282#discussion_r458368780 Signed-off-by: Andrew Melis --- pkg/action/list.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/action/list.go b/pkg/action/list.go index 3d5e6d7a6..0f85de519 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -250,7 +250,8 @@ func (l *List) filterStateMask(releases []*release.Release) []*release.Release { for _, rls := range releases { currentStatus := l.StateMask.FromName(rls.Info.Status.String()) - if l.StateMask¤tStatus == 0 { + mask := l.StateMask & currentStatus + if mask == 0 { continue } desiredStateReleases = append(desiredStateReleases, rls) From ff147e9ed7463f6349ee416da723949ae2d33330 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 16 Jul 2020 11:03:08 -0400 Subject: [PATCH 32/76] Locking file URIs to a version in lockfile Previously, if a range was specified for a file:// url as a dependency the range would be put in the lockfile. Lockfiles are designed to pin to a specific version and not support ranges. This is for reproducibility. The change here pins to a the specific version of the chart specified using the file:// when update is run. Signed-off-by: Matt Farina --- internal/resolver/resolver.go | 13 +++++++++++-- internal/resolver/resolver_test.go | 11 +++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index cec29c947..c72a39e82 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/repo" @@ -68,14 +69,22 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string } if strings.HasPrefix(d.Repository, "file://") { - if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil { + chartpath, err := GetLocalPath(d.Repository, r.chartpath) + if err != nil { + return nil, err + } + + // The version of the chart locked will be the version of the chart + // currently listed in the file system within the chart. + ch, err := loader.LoadDir(chartpath) + if err != nil { return nil, err } locked[i] = &chart.Dependency{ Name: d.Name, Repository: d.Repository, - Version: d.Version, + Version: ch.Metadata.Version, } continue } diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index 0b0c3a6f8..f59188508 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -82,6 +82,17 @@ func TestResolve(t *testing.T) { }, }, }, + { + name: "repo from valid local path with range resolution", + req: []*chart.Dependency{ + {Name: "base", Repository: "file://base", Version: "^0.1.0"}, + }, + expect: &chart.Lock{ + Dependencies: []*chart.Dependency{ + {Name: "base", Repository: "file://base", Version: "0.1.0"}, + }, + }, + }, { name: "repo from invalid local path", req: []*chart.Dependency{ From 9cb90a8b234f98c16aef0918cba92d83061d7c97 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 17 Jul 2020 11:54:44 -0400 Subject: [PATCH 33/76] Make unmanaged repositories version resolvable If a repository was not know to helm (e.g. added using helm repo add) then Helm would use the range set in the depenencies as the version in the lock file. Lock files should not have ranges since they are locked to versions. Helm did this because the version information for repositories was not know to Helm. This change fixes that by making the repository and chart information known to Helm so it can resolve the versions. Closes #8449 Signed-off-by: Matt Farina --- pkg/downloader/manager.go | 132 ++++++++++++++++++++++++--------- pkg/downloader/manager_test.go | 30 ++++++++ 2 files changed, 126 insertions(+), 36 deletions(-) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 2d6918739..9db5ed928 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -16,6 +16,8 @@ limitations under the License. package downloader import ( + "crypto" + "encoding/hex" "fmt" "io" "io/ioutil" @@ -106,7 +108,13 @@ func (m *Manager) Build() error { } } - if _, err := m.resolveRepoNames(req); err != nil { + repoNames, err := m.resolveRepoNames(req) + if err != nil { + return err + } + + _, err = m.ensureMissingRepos(repoNames, req) + if err != nil { return err } @@ -124,11 +132,6 @@ func (m *Manager) Build() error { } } - // Check that all of the repos we're dependent on actually exist. - if err := m.hasAllRepos(lock.Dependencies); err != nil { - return err - } - if !m.SkipUpdate { // For each repo in the file, update the cached copy of that repo if err := m.UpdateRepositories(); err != nil { @@ -158,14 +161,23 @@ func (m *Manager) Update() error { return nil } - // Check that all of the repos we're dependent on actually exist and - // the repo index names. + // Get the names of the repositories the dependencies need that Helm is + // configured to know about. repoNames, err := m.resolveRepoNames(req) if err != nil { return err } - // For each repo in the file, update the cached copy of that repo + // For the repositories Helm is not configured to know about, ensure Helm + // has some information about them and, when possible, the index files + // locally. + repoNames, err = m.ensureMissingRepos(repoNames, req) + if err != nil { + return err + } + + // For each of the repositories Helm is configured to know about, update + // the index information locally. if !m.SkipUpdate { if err := m.UpdateRepositories(); err != nil { return err @@ -393,38 +405,60 @@ func (m *Manager) safeDeleteDep(name, dir string) error { return nil } -// hasAllRepos ensures that all of the referenced deps are in the local repo cache. -func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return err - } - repos := rf.Repositories +// ensureMissingRepos attempts to ensure the repository information for repos +// not managed by Helm is present. This takes in the repoNames Helm is configured +// to work with along with the chart dependencies. It will find the deps not +// in a known repo and attempt to ensure the data is present for steps like +// version resolution. +func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { + + var ru []*repo.Entry - // Verify that all repositories referenced in the deps are actually known - // by Helm. - missing := []string{} -Loop: for _, dd := range deps { - // If repo is from local path, continue - if strings.HasPrefix(dd.Repository, "file://") { + + // When the repoName for a dependency is known we can skip ensuring + if _, ok := repoNames[dd.Name]; ok { continue } - if dd.Repository == "" { - continue + // The generated repository name, which will result in an index being + // locally cached, has a name pattern of "helm-manager-" followed by a + // sha256 of the repo name. This assumes end users will never create + // repositories with these names pointing to other repositories. Using + // this method of naming allows the existing repository pulling and + // resolution code to do most of the work. + rn, err := key(dd.Repository) + if err != nil { + return repoNames, err } - for _, repo := range repos { - if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { - continue Loop - } + rn = "helm-manager-" + rn + + repoNames[dd.Name] = rn + + // Assuming the repository is generally available. For Helm managed + // access controls the repository needs to be added through the user + // managed system. This path will work for public charts, like those + // supplied by Bitnami, but not for protected charts, like corp ones + // behind a username and pass. + ri := &repo.Entry{ + Name: rn, + URL: dd.Repository, } - missing = append(missing, dd.Repository) + ru = append(ru, ri) } - if len(missing) > 0 { - return ErrRepoNotFound{missing} + + // Calls to UpdateRepositories (a public function) will only update + // repositories configured by the user. Here we update repos found in + // the dependencies that are not known to the user if update skipping + // is not configured. + if !m.SkipUpdate && len(ru) > 0 { + fmt.Fprintln(m.Out, "Getting updates for unmanaged Helm repositories...") + if err := m.parallelRepoUpdate(ru); err != nil { + return repoNames, err + } } - return nil + + return repoNames, nil } // resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file @@ -517,16 +551,18 @@ func (m *Manager) UpdateRepositories() error { } repos := rf.Repositories if len(repos) > 0 { + fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") // This prints warnings straight to out. if err := m.parallelRepoUpdate(repos); err != nil { return err } + fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") } return nil } func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { - fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") + var wg sync.WaitGroup for _, c := range repos { r, err := repo.NewChartRepository(c, m.Getters) @@ -536,15 +572,27 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { wg.Add(1) go func(r *repo.ChartRepository) { if _, err := r.DownloadIndexFile(); err != nil { - fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) + // For those dependencies that are not known to helm and using a + // generated key name we display the repo url. + if strings.HasPrefix(r.Config.Name, "helm-manager-") { + fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err) + } else { + fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) + } } else { - fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) + // For those dependencies that are not known to helm and using a + // generated key name we display the repo url. + if strings.HasPrefix(r.Config.Name, "helm-manager-") { + fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL) + } else { + fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) + } } wg.Done() }(r) } wg.Wait() - fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") + return nil } @@ -739,3 +787,15 @@ func move(tmpPath, destPath string) error { } return nil } + +// key is used to turn a name, such as a repository url, into a filesystem +// safe name that is unique for querying. To accomplish this a unique hash of +// the string is used. +func key(name string) (string, error) { + in := strings.NewReader(name) + hash := crypto.SHA256.New() + if _, err := io.Copy(hash, in); err != nil { + return "", nil + } + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index dd3cbbd7b..e60cf7624 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -389,3 +389,33 @@ func TestErrRepoNotFound_Error(t *testing.T) { }) } } + +func TestKey(t *testing.T) { + tests := []struct { + name string + expect string + }{ + { + name: "file:////tmp", + expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3", + }, + { + name: "https://example.com/charts", + expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d", + }, + { + name: "foo/bar/baz", + expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82", + }, + } + + for _, tt := range tests { + o, err := key(tt.name) + if err != nil { + t.Fatalf("unable to generate key for %q with error: %s", tt.name, err) + } + if o != tt.expect { + t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) + } + } +} From 1458113696297a7d0426f275c9d0689d4d0ff47e Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 17 Jul 2020 15:44:50 -0400 Subject: [PATCH 34/76] Adding helm v4 todo Signed-off-by: Matt Farina --- pkg/downloader/manager.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 9db5ed928..6a4058c2c 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -171,6 +171,10 @@ func (m *Manager) Update() error { // For the repositories Helm is not configured to know about, ensure Helm // has some information about them and, when possible, the index files // locally. + // TODO(mattfarina): Repositories should be explicitly added by end users + // rather than automattic. In Helm v4 require users to add repositories. They + // should have to add them in order to make sure they are aware of the + // respoitories and opt-in to any locations. For security. repoNames, err = m.ensureMissingRepos(repoNames, req) if err != nil { return err From 25f67f74eeee861e132d1a0220144c87631dbe38 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Wed, 22 Jul 2020 15:51:27 -0400 Subject: [PATCH 35/76] Restoring Build behavior Two things changed in this commit... 1. The Build behavior was restored and the change only impacts Update. This is a more minimal functionality change thats a more secure behavior 2. Cleanup from Josh's feedback on the PR to create a const and comment changes Signed-off-by: Matt Farina --- pkg/downloader/manager.go | 58 +++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 6a4058c2c..bcd5dcec4 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -108,13 +108,7 @@ func (m *Manager) Build() error { } } - repoNames, err := m.resolveRepoNames(req) - if err != nil { - return err - } - - _, err = m.ensureMissingRepos(repoNames, req) - if err != nil { + if _, err := m.resolveRepoNames(req); err != nil { return err } @@ -132,6 +126,11 @@ func (m *Manager) Build() error { } } + // Check that all of the repos we're dependent on actually exist. + if err := m.hasAllRepos(lock.Dependencies); err != nil { + return err + } + if !m.SkipUpdate { // For each repo in the file, update the cached copy of that repo if err := m.UpdateRepositories(); err != nil { @@ -174,7 +173,7 @@ func (m *Manager) Update() error { // TODO(mattfarina): Repositories should be explicitly added by end users // rather than automattic. In Helm v4 require users to add repositories. They // should have to add them in order to make sure they are aware of the - // respoitories and opt-in to any locations. For security. + // respoitories and opt-in to any locations, for security. repoNames, err = m.ensureMissingRepos(repoNames, req) if err != nil { return err @@ -409,6 +408,40 @@ func (m *Manager) safeDeleteDep(name, dir string) error { return nil } +// hasAllRepos ensures that all of the referenced deps are in the local repo cache. +func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + return err + } + repos := rf.Repositories + + // Verify that all repositories referenced in the deps are actually known + // by Helm. + missing := []string{} +Loop: + for _, dd := range deps { + // If repo is from local path, continue + if strings.HasPrefix(dd.Repository, "file://") { + continue + } + + if dd.Repository == "" { + continue + } + for _, repo := range repos { + if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { + continue Loop + } + } + missing = append(missing, dd.Repository) + } + if len(missing) > 0 { + return ErrRepoNotFound{missing} + } + return nil +} + // ensureMissingRepos attempts to ensure the repository information for repos // not managed by Helm is present. This takes in the repoNames Helm is configured // to work with along with the chart dependencies. It will find the deps not @@ -435,7 +468,7 @@ func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart. if err != nil { return repoNames, err } - rn = "helm-manager-" + rn + rn = managerKeyPrefix + rn repoNames[dd.Name] = rn @@ -578,7 +611,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { if _, err := r.DownloadIndexFile(); err != nil { // For those dependencies that are not known to helm and using a // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, "helm-manager-") { + if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err) } else { fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) @@ -586,7 +619,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { } else { // For those dependencies that are not known to helm and using a // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, "helm-manager-") { + if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL) } else { fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) @@ -792,6 +825,9 @@ func move(tmpPath, destPath string) error { return nil } +// The prefix to use for cache keys created by the manager for repo names +const managerKeyPrefix = "helm-manager-" + // key is used to turn a name, such as a repository url, into a filesystem // safe name that is unique for querying. To accomplish this a unique hash of // the string is used. From d141593d834f99822c08b9f3ca768258483afbf1 Mon Sep 17 00:00:00 2001 From: She Jiayu Date: Thu, 23 Jul 2020 12:28:43 +0800 Subject: [PATCH 36/76] Avoid hardcoded container port in default notes Signed-off-by: She Jiayu --- pkg/chartutil/create.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index c036ce37d..6e382b961 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -381,8 +381,9 @@ const defaultNotes = `1. Get the application URL by running these commands: echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include ".name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT {{- end }} ` From 4faeedd98b03e5af7733317a84e77ebff28c55f7 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Thu, 23 Jul 2020 11:51:17 +0530 Subject: [PATCH 37/76] fix watch error due to elb/proxy timeout Signed-off-by: Rajat Jindal --- pkg/kube/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 62e1b104b..decfc2e6e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -35,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -478,7 +479,7 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) defer cancel() - _, err = watchtools.ListWatchUntil(ctx, lw, func(e watch.Event) (bool, error) { + _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { // Make sure the incoming object is versioned as we use unstructured // objects when we build manifests obj := convertWithMapper(e.Object, info.Mapping) From b6bbf34097312fefec8120fe066780340f2d983f Mon Sep 17 00:00:00 2001 From: Haoming Zhang Date: Thu, 23 Jul 2020 21:04:18 -0700 Subject: [PATCH 38/76] Close gzip reader when done. Signed-off-by: Haoming Zhang --- pkg/storage/driver/util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go index a87002ab4..e5b846163 100644 --- a/pkg/storage/driver/util.go +++ b/pkg/storage/driver/util.go @@ -68,6 +68,7 @@ func decodeRelease(data string) (*rspb.Release, error) { if err != nil { return nil, err } + defer r.Close() b2, err := ioutil.ReadAll(r) if err != nil { return nil, err From b13247c333339bcd8bf963e62df2e03f37383984 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Mon, 27 Jul 2020 08:57:57 -0700 Subject: [PATCH 39/76] introduce stale issue bot Signed-off-by: Matthew Fisher --- .github/workflows/stale-issue-bot.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/stale-issue-bot.yaml diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml new file mode 100644 index 000000000..e21e6d5ca --- /dev/null +++ b/.github/workflows/stale-issue-bot.yaml @@ -0,0 +1,15 @@ +name: "Close stale issues" +on: + schedule: + - cron: "0 0 * * *" +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' + exempt-issue-labels: 'keep open' + days-before-stale: 90 + days-before-close: 30 From 44212f83dc9c893d962fe48648320e7b7b66950c Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 28 Jul 2020 09:52:39 -0400 Subject: [PATCH 40/76] Fix issue with install and upgrade running all hooks When #8156 was merged it had the side effect that all hooks were run all the time. All the hooks were put in the flow of the content rendered and sent to Kubernetes on every command. For example, if you ran the following 2 commands the test hooks would run: helm create foo helm install foo ./foo This should not run any hooks. But, the generated test hook is run. The change in this commit moves the writing of the hooks to output or disk back into the template command rather than in a private function within the actions. This is where it was for v3.2. One side effect is that post renderers will not work on hooks. This was the case in v3.2. Since this bug is blocking the release of v3.3.0 it is being rolled back. A refactor effort is underway for this section of code. post renderer for hooks should be added back as part of that work. Since post renderer hooks did not make it into a release it is ok to roll it back for now. There is code in the cmd/helm package that has been duplicated from pkg/action. This is a temporary measure to fix the immediate bug with plans to correct the situation as part of a refactor of renderResources. Signed-off-by: Matt Farina --- cmd/helm/template.go | 68 ++++++++++++++++++++++++++++++++++++++ pkg/action/action.go | 22 ++---------- pkg/action/install.go | 2 +- pkg/action/install_test.go | 6 ---- pkg/action/upgrade.go | 2 +- 5 files changed, 73 insertions(+), 27 deletions(-) diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 5a8a25102..6123d29d4 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -20,6 +20,8 @@ import ( "bytes" "fmt" "io" + "os" + "path" "path/filepath" "regexp" "sort" @@ -79,6 +81,25 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if rel != nil { var manifests bytes.Buffer fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) + if !client.DisableHooks { + fileWritten := make(map[string]bool) + for _, m := range rel.Hooks { + if client.OutputDir == "" { + fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) + } else { + newDir := client.OutputDir + if client.UseReleaseName { + newDir = filepath.Join(client.OutputDir, client.ReleaseName) + } + err = writeToFile(newDir, m.Path, m.Manifest, fileWritten[m.Path]) + if err != nil { + return err + } + fileWritten[m.Path] = true + } + + } + } // if we have a list of files to render, then check that each of the // provided files exists in the chart. @@ -149,3 +170,50 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } + +// The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile) +// are coppied from the actions package. This is part of a change to correct a +// bug introduced by #8156. As part of the todo to refactor renderResources +// this duplicate code should be removed. It is added here so that the API +// surface area is as minimally impacted as possible in fixing the issue. +func writeToFile(outputDir string, name string, data string, append bool) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := createOrOpenFile(outfileName, append) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +func createOrOpenFile(filename string, append bool) (*os.File, error) { + if append { + return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) + } + return os.Create(filename) +} + +func ensureDirectoryForFile(file string) error { + baseDir := path.Dir(file) + _, err := os.Stat(baseDir) + if err != nil && !os.IsNotExist(err) { + return err + } + + return os.MkdirAll(baseDir, 0755) +} diff --git a/pkg/action/action.go b/pkg/action/action.go index fec86cd42..3c47cc6df 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -99,7 +99,9 @@ type Configuration struct { // renderResources renders the templates in a chart // // TODO: This function is badly in need of a refactor. -func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, disableHooks bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { +// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed +// This code has to do with writing files to dick. +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -212,24 +214,6 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values } } - if !disableHooks && len(hs) > 0 { - for _, h := range hs { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", h.Path, h.Manifest) - } else { - newDir := outputDir - if useReleaseName { - newDir = filepath.Join(outputDir, releaseName) - } - err = writeToFile(newDir, h.Path, h.Manifest, fileWritten[h.Path]) - if err != nil { - return hs, b, "", err - } - fileWritten[h.Path] = true - } - } - } - if pr != nil { b, err = pr.Run(b) if err != nil { diff --git a/pkg/action/install.go b/pkg/action/install.go index 48a3aeeca..00fb208b0 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -236,7 +236,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.DisableHooks, i.PostRenderer, i.DryRun) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 4366889ce..6c4012cfd 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -499,9 +499,6 @@ func TestInstallReleaseOutputDir(t *testing.T) { _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) is.NoError(err) - _, err = os.Stat(filepath.Join(dir, "hello/templates/hooks")) - is.NoError(err) - _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) is.NoError(err) @@ -542,9 +539,6 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) is.NoError(err) - _, err = os.Stat(filepath.Join(newDir, "hello/templates/hooks")) - is.NoError(err) - _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) is.NoError(err) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 949aebcae..e7c2aec25 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -222,7 +222,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, false, u.PostRenderer, u.DryRun) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) if err != nil { return nil, nil, err } From 8bb0413552ca5b2de39d9cb2ec06089c6569908c Mon Sep 17 00:00:00 2001 From: Ma Xinjian Date: Fri, 31 Jul 2020 14:15:21 +0800 Subject: [PATCH 41/76] darwin-386 and windows-386 are not supported now $ curl https://get.helm.sh/helm-v3.2.4-windows-386.tar.gz BlobNotFoundThe specified does not exist. RequestId:6d53f961-d01e-0032-025f-5e4d79000000 $ curl https://get.helm.sh/helm-v2.16.9-darwin-386.tar.gz BlobNotFoundThe specified does not exist. RequestId:81020fad-c01e-0001-0e60-5e12d2000000 Signed-off-by: Ma Xinjian --- scripts/get | 2 +- scripts/get-helm-3 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/get b/scripts/get index 094892346..777a53bbc 100755 --- a/scripts/get +++ b/scripts/get @@ -62,7 +62,7 @@ runAsRoot() { # verifySupported checks that the os/arch combination is supported for # binary builds. verifySupported() { - local supported="darwin-386\ndarwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nwindows-386\nwindows-amd64" + local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuilt binary for ${OS}-${ARCH}." echo "To build from source, go to https://github.com/helm/helm" diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index ce9411edc..f2495e444 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -60,7 +60,7 @@ runAsRoot() { # verifySupported checks that the os/arch combination is supported for # binary builds. verifySupported() { - local supported="darwin-386\ndarwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-386\nwindows-amd64" + local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuilt binary for ${OS}-${ARCH}." echo "To build from source, go to https://github.com/helm/helm" From 4cc19d1d82c4f4ee4bb9af5739e1184f5800f588 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Fri, 31 Jul 2020 07:57:17 +0000 Subject: [PATCH 42/76] Fix typo Signed-off-by: Martin Hickey --- pkg/action/action.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index 3c47cc6df..071db709b 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -100,7 +100,7 @@ type Configuration struct { // // TODO: This function is badly in need of a refactor. // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed -// This code has to do with writing files to dick. +// This code has to do with writing files to disk. func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) From fbc32aea3d43853f94768e4bc7bc4045fe9fb749 Mon Sep 17 00:00:00 2001 From: bellkeyang Date: Fri, 31 Jul 2020 20:39:38 +0800 Subject: [PATCH 43/76] bufix: fix validateNumColons docs Signed-off-by: bellkeyang --- internal/experimental/registry/reference.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/experimental/registry/reference.go b/internal/experimental/registry/reference.go index ebab29a7e..f0e91d4ba 100644 --- a/internal/experimental/registry/reference.go +++ b/internal/experimental/registry/reference.go @@ -105,7 +105,7 @@ func (ref *Reference) validateRepo() error { return err } -// validateNumColon ensures the ref only contains a single colon character (:) +// validateNumColons ensures the ref only contains a single colon character (:) // (or potentially two, there might be a port number specified i.e. :5000) func (ref *Reference) validateNumColons() error { if strings.Contains(ref.Tag, ":") { From 131510aa94faaa66ea4b3c3b7e4156206902ffc5 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 31 Jul 2020 17:17:54 -0600 Subject: [PATCH 44/76] fix test that modifies the wrong cache data Signed-off-by: Matt Butcher --- cmd/helm/repo_add_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/helm/repo_add_test.go b/cmd/helm/repo_add_test.go index d1b9bc0fc..7cb669a1a 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -40,11 +40,12 @@ func TestRepoAddCmd(t *testing.T) { } defer srv.Stop() - repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml") + tmpdir := ensure.TempDir(t) + repoFile := filepath.Join(tmpdir, "repositories.yaml") tests := []cmdTestCase{{ name: "add a repository", - cmd: fmt.Sprintf("repo add test-name %s --repository-config %s", srv.URL(), repoFile), + cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir), golden: "output/repo-add.txt", }} From edc7d8ea320c4f32fa86a4278c175b96a8da9f2a Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Wed, 29 Jul 2020 20:30:11 +0300 Subject: [PATCH 45/76] Added selector option to list command Signed-off-by: Dmitry Chepurovskiy --- cmd/helm/list.go | 1 + pkg/action/list.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 18e5ffe45..dfa17d9bc 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -126,6 +126,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch") f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value") f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results") + f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") bindOutputFlag(cmd, &outfmt) return cmd diff --git a/pkg/action/list.go b/pkg/action/list.go index 0f85de519..854a8942b 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -126,6 +126,7 @@ type List struct { Deployed bool Failed bool Pending bool + Selector string } // NewList constructs a new *List From 99bd709530bbcbee4fc21822164ad44aa1770b24 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Wed, 29 Jul 2020 22:24:07 +0300 Subject: [PATCH 46/76] Pass labels from secret/configmap to release object Signed-off-by: Dmitry Chepurovskiy --- pkg/release/release.go | 2 ++ pkg/storage/driver/cfgmaps.go | 1 + pkg/storage/driver/secrets.go | 1 + 3 files changed, 4 insertions(+) diff --git a/pkg/release/release.go b/pkg/release/release.go index 8582a86f3..1245d8129 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -37,6 +37,8 @@ type Release struct { Version int `json:"version,omitempty"` // Namespace is the kubernetes namespace of the release. Namespace string `json:"namespace,omitempty"` + // Labels of the release + Labels map[string]string `json:"labels,omitempty"` } // SetStatus is a helper for setting the status on a release. diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 71e635975..05213bbdd 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -106,6 +106,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas continue } if filter(rls) { + rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } } diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 44280f70f..1503bc054 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -98,6 +98,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, continue } if filter(rls) { + rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } } From 357a0785bcd09e068c240595f12f14ab29fae066 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Wed, 29 Jul 2020 23:30:16 +0300 Subject: [PATCH 47/76] Added selector filtering Signed-off-by: Dmitry Chepurovskiy --- pkg/action/list.go | 13 +++++++++++++ pkg/storage/driver/cfgmaps.go | 4 +++- pkg/storage/driver/secrets.go | 4 +++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/action/list.go b/pkg/action/list.go index 854a8942b..9cf7ec138 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -20,6 +20,8 @@ import ( "path" "regexp" + "k8s.io/apimachinery/pkg/labels" + "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" ) @@ -152,11 +154,22 @@ func (l *List) Run() ([]*release.Release, error) { } } + selectorObj, err := labels.Parse(l.Selector) + if err != nil { + return nil, err + } + results, err := l.cfg.Releases.List(func(rel *release.Release) bool { // Skip anything that doesn't match the filter. if filter != nil && !filter.MatchString(rel.Name) { return false } + + // Skip anything that doesn't match the selector + if ! selectorObj.Matches(labels.Set(rel.Labels)) { + return false + } + return true }) diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 05213bbdd..94c278875 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -105,8 +105,10 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas cfgmaps.Log("list: failed to decode release: %v: %s", item, err) continue } + + rls.Labels = item.ObjectMeta.Labels + if filter(rls) { - rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } } diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 1503bc054..64dcf8216 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -97,8 +97,10 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, secrets.Log("list: failed to decode release: %v: %s", item, err) continue } + + rls.Labels = item.ObjectMeta.Labels + if filter(rls) { - rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } } From 09172b468a7c8278c566880c26efd1078e43e5c8 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Wed, 29 Jul 2020 23:42:43 +0300 Subject: [PATCH 48/76] Fix linting issues Signed-off-by: Dmitry Chepurovskiy --- pkg/action/list.go | 4 ++-- pkg/storage/driver/secrets.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/list.go b/pkg/action/list.go index 9cf7ec138..56008c6fa 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -166,10 +166,10 @@ func (l *List) Run() ([]*release.Release, error) { } // Skip anything that doesn't match the selector - if ! selectorObj.Matches(labels.Set(rel.Labels)) { + if !selectorObj.Matches(labels.Set(rel.Labels)) { return false } - + return true }) diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 64dcf8216..2e8530d0c 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -97,7 +97,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, secrets.Log("list: failed to decode release: %v: %s", item, err) continue } - + rls.Labels = item.ObjectMeta.Labels if filter(rls) { From 6d60d26913ef81180eb6cfc13cdc2106e3ae24ef Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Thu, 30 Jul 2020 20:52:57 +0300 Subject: [PATCH 49/76] Added notice about supported backends Signed-off-by: Dmitry Chepurovskiy --- cmd/helm/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index dfa17d9bc..2f40707b3 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -126,7 +126,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch") f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value") f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results") - f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.") bindOutputFlag(cmd, &outfmt) return cmd From 2ea8f805b9348201f95d6eacd893cccfdaeb8624 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Thu, 30 Jul 2020 22:40:46 +0300 Subject: [PATCH 50/76] Added testing for list action with selector Signed-off-by: Dmitry Chepurovskiy --- pkg/action/list_test.go | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index b8e2ece68..73009d523 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -320,3 +320,49 @@ func TestFilterLatestReleases(t *testing.T) { assert.ElementsMatch(t, expectedFilteredList, filteredList) }) } + +func TestSelectorList(t *testing.T) { + r1 := releaseStub() + r1.Name = "r1" + r1.Version = 1 + r1.Labels = map[string]string{"key": "value1"} + r2 := releaseStub() + r2.Name = "r2" + r2.Version = 1 + r2.Labels = map[string]string{"key": "value2"} + r3 := releaseStub() + r3.Name = "r3" + r3.Version = 1 + r3.Labels = map[string]string{} + + lister := newListFixture(t) + for _, rel := range []*release.Release{r1, r2, r3} { + if err := lister.cfg.Releases.Create(rel); err != nil { + t.Fatal(err) + } + } + + t.Run("should fail selector parsing", func(t *testing.T) { + is := assert.New(t) + lister.Selector = "a?=b" + + _, err := lister.Run() + is.Error(err) + }) + + t.Run("should select one release with matching label", func(t *testing.T) { + lister.Selector = "key==value1" + res, _ := lister.Run() + + expectedFilteredList := []*release.Release{r1} + assert.ElementsMatch(t, expectedFilteredList, res) + }) + + t.Run("should select two releases with non matching label", func(t *testing.T) { + lister.Selector = "key!=value1" + res, _ := lister.Run() + + expectedFilteredList := []*release.Release{r2, r3} + assert.ElementsMatch(t, expectedFilteredList, res) + }) +} From 266c74f390c6dcd08ffbbe942df99d5557632806 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Tue, 4 Aug 2020 18:17:56 +0300 Subject: [PATCH 51/76] Move selector filtering after latest release version filtering Signed-off-by: Dmitry Chepurovskiy --- pkg/action/list.go | 29 +++++++++++++++++++---------- pkg/release/release.go | 5 +++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pkg/action/list.go b/pkg/action/list.go index 56008c6fa..5ba0c4770 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -154,22 +154,12 @@ func (l *List) Run() ([]*release.Release, error) { } } - selectorObj, err := labels.Parse(l.Selector) - if err != nil { - return nil, err - } - results, err := l.cfg.Releases.List(func(rel *release.Release) bool { // Skip anything that doesn't match the filter. if filter != nil && !filter.MatchString(rel.Name) { return false } - // Skip anything that doesn't match the selector - if !selectorObj.Matches(labels.Set(rel.Labels)) { - return false - } - return true }) @@ -192,6 +182,13 @@ func (l *List) Run() ([]*release.Release, error) { // latest releases, otherwise outdated entries can be returned results = l.filterStateMask(results) + // Skip anything that doesn't match the selector + selectorObj, err := labels.Parse(l.Selector) + if err != nil { + return nil, err + } + results = l.filterSelector(results, selectorObj) + // Unfortunately, we have to sort before truncating, which can incur substantial overhead l.sort(results) @@ -274,6 +271,18 @@ func (l *List) filterStateMask(releases []*release.Release) []*release.Release { return desiredStateReleases } +func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release { + desiredStateReleases := make([]*release.Release, 0) + + for _, rls := range releases { + if selector.Matches(labels.Set(rls.Labels)) { + desiredStateReleases = append(desiredStateReleases, rls) + } + } + + return desiredStateReleases +} + // SetStateMask calculates the state mask based on parameters. func (l *List) SetStateMask() { if l.All { diff --git a/pkg/release/release.go b/pkg/release/release.go index 1245d8129..b90612873 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -37,8 +37,9 @@ type Release struct { Version int `json:"version,omitempty"` // Namespace is the kubernetes namespace of the release. Namespace string `json:"namespace,omitempty"` - // Labels of the release - Labels map[string]string `json:"labels,omitempty"` + // Labels of the release. + // Disabled encoding into Json cause labels are stored in storage driver metadata field. + Labels map[string]string `json:"-"` } // SetStatus is a helper for setting the status on a release. From df4708a9de8a6f4c89dca7555a5b338c8298128d Mon Sep 17 00:00:00 2001 From: Dong Gang Date: Wed, 5 Aug 2020 10:37:00 +0800 Subject: [PATCH 52/76] polish the error handler Signed-off-by: Dong Gang --- pkg/action/show.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/action/show.go b/pkg/action/show.go index 73d989884..4eab2b4fd 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "k8s.io/cli-runtime/pkg/printers" "sigs.k8s.io/yaml" @@ -92,7 +93,7 @@ func (s *Show) Run(chartpath string) (string, error) { if s.JSONPathTemplate != "" { printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) if err != nil { - return "", fmt.Errorf("error parsing jsonpath %s, %v", s.JSONPathTemplate, err) + return "", errors.Wrapf(err, "error parsing jsonpath %s", s.JSONPathTemplate) } printer.Execute(&out, s.chart.Values) } else { From 5421c7e99526af9683491fe5e069ed001fb5fd58 Mon Sep 17 00:00:00 2001 From: Tero <31454692+tero-dev@users.noreply.github.com> Date: Thu, 6 Aug 2020 17:00:14 +0300 Subject: [PATCH 53/76] Fix Quick Start Guide Link in README.md The previous link https://docs.helm.sh/using_helm/#quickstart-guide was redirecting into https://helm.sh/ja/docs/intro/ which is a Japanese version of the docs. I've fixed the link so it goes into the English version instead. Signed-off-by: Tero <31454692+tero-dev@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb6908fdb..9c39175bc 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If you want to use a package manager: - [GoFish](https://gofi.sh/) users can use `gofish install helm`. - [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic` -To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide). +To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/). See the [installation guide](https://helm.sh/docs/intro/install/) for more options, including installing pre-releases. From 4bd68b75cf0fdf2355285ad0e386fbce806fce5f Mon Sep 17 00:00:00 2001 From: Jinesi Yelizati Date: Sat, 8 Aug 2020 11:17:10 +0800 Subject: [PATCH 54/76] chore(deps): add dependabot.yml Signed-off-by: Jinesi Yelizati --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..68334cf33 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 + +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file From 4b1fa60d5883d53b4e4f09d3420b108e66bbd737 Mon Sep 17 00:00:00 2001 From: Thomas O'Donnell Date: Wed, 5 Aug 2020 19:02:42 +0200 Subject: [PATCH 55/76] Update Common Lables template in starter chart Have update the Common Labels template in the starter chart so that the value for the `app.kubernetes.io/version` is set to the same value as the image tag used in the deployment. Signed-off-by: Thomas O'Donnell --- pkg/chartutil/create.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index c036ce37d..cf10f0875 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -424,9 +424,7 @@ Common labels {{- define ".labels" -}} helm.sh/chart: {{ include ".chart" . }} {{ include ".selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} +app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} From 83a5e620d0acde77502b1f814f749268e8d8ef6e Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 12 Aug 2020 23:00:16 +0200 Subject: [PATCH 56/76] release/mock: Ensure conversion from int to string yields a string With the release of go 1.15, the test-suite doesn't pass as `go test` got a new warning for improper `string(x)` usage. https://golang.org/doc/go1.15#vet $ make test-unit # helm.sh/helm/v3/pkg/release pkg/release/mock.go:56:27: conversion from int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) [snip] make: *** [Makefile:82: test-unit] Error 2 This patch changes ensures we are utilizing `fmt.Sprint` instead as recommended. Signed-off-by: Morten Linderud --- pkg/release/mock.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/release/mock.go b/pkg/release/mock.go index 3abbd574b..a28e1dc16 100644 --- a/pkg/release/mock.go +++ b/pkg/release/mock.go @@ -17,6 +17,7 @@ limitations under the License. package release import ( + "fmt" "math/rand" "helm.sh/helm/v3/pkg/chart" @@ -53,7 +54,7 @@ func Mock(opts *MockReleaseOptions) *Release { name := opts.Name if name == "" { - name = "testrelease-" + string(rand.Intn(100)) + name = "testrelease-" + fmt.Sprint(rand.Intn(100)) } version := 1 From 065b7f6e257f7ee47ca0194f8c3b804e14173514 Mon Sep 17 00:00:00 2001 From: Maartje Eyskens Date: Fri, 14 Aug 2020 09:43:56 +0200 Subject: [PATCH 57/76] Bump Kubernetes to v0.18.8 + Bump jsonpatch jsonpatch now is the same version as used in Kubernetes v0.18.8 Signed-off-by: Maartje Eyskens --- go.mod | 14 +++++++------- go.sum | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 08eae92fa..66447ee3d 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce github.com/docker/go-units v0.4.0 - github.com/evanphx/json-patch v4.5.0+incompatible + github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.7.1 github.com/gosuri/uitable v0.0.4 @@ -34,13 +34,13 @@ require ( github.com/stretchr/testify v1.6.1 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - k8s.io/api v0.18.4 - k8s.io/apiextensions-apiserver v0.18.4 - k8s.io/apimachinery v0.18.4 - k8s.io/cli-runtime v0.18.4 - k8s.io/client-go v0.18.4 + k8s.io/api v0.18.8 + k8s.io/apiextensions-apiserver v0.18.8 + k8s.io/apimachinery v0.18.8 + k8s.io/cli-runtime v0.18.8 + k8s.io/client-go v0.18.8 k8s.io/klog v1.0.0 - k8s.io/kubectl v0.18.4 + k8s.io/kubectl v0.18.8 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 0d0ef8c83..94f5fee82 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b h1:vCplRbYcTTeBVLjIU0KvipEeVBSxl6sakUBRmeLBTkw= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= @@ -395,6 +397,7 @@ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -915,18 +918,32 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= +k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= +k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= k8s.io/apiextensions-apiserver v0.18.4 h1:Y3HGERmS8t9u12YNUFoOISqefaoGRuTc43AYCLzWmWE= k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= +k8s.io/apiextensions-apiserver v0.18.8 h1:pkqYPKTHa0/3lYwH7201RpF9eFm0lmZDFBNzhN+k/sA= +k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM= k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA= k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= k8s.io/cli-runtime v0.18.4 h1:IUx7quIOb4gbQ4M+B1ksF/PTBovQuL5tXWzplX3t+FM= k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g= +k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k= +k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M= k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc= k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= +k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= +k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.4 h1:Kr53Fp1iCGNsl9Uv4VcRvLy7YyIqi9oaJOQ7SXtKI98= k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= +k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8= +k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -937,8 +954,11 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOEC k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kubectl v0.18.4 h1:l9DUYPTEMs1+qNtoqPpTyaJOosvj7l7tQqphCO1K52s= k8s.io/kubectl v0.18.4/go.mod h1:EzB+nfeUWk6fm6giXQ8P4Fayw3dsN+M7Wjy23mTRtB0= +k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA= +k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/metrics v0.18.4/go.mod h1:luze4fyI9JG4eLDZy0kFdYEebqNfi0QrG4xNEbPkHOs= +k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= From d6708667da5c79ab4a093ea7473e96b93be5f1f7 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 8 Aug 2020 03:43:34 -0400 Subject: [PATCH 58/76] feat(comp): Disable file completion when not valid With Cobra 1.0, it is now possible to control when file completion should or should not be done. For example: helm list should not trigger file completion since 'helm list' does not accept any arguments. This commit disables file completion when appropriate and adds tests to verify that file completion is properly disabled. Signed-off-by: Marc Khouzam --- cmd/helm/completion.go | 16 ++++++--- cmd/helm/completion_test.go | 58 ++++++++++++++++++++++++++++++++ cmd/helm/create.go | 9 +++++ cmd/helm/create_test.go | 5 +++ cmd/helm/dependency.go | 11 +++--- cmd/helm/dependency_test.go | 4 +++ cmd/helm/docs.go | 11 +++--- cmd/helm/docs_test.go | 25 ++++++++++++++ cmd/helm/env.go | 9 ++--- cmd/helm/env_test.go | 25 ++++++++++++++ cmd/helm/get.go | 9 ++--- cmd/helm/get_all_test.go | 5 +++ cmd/helm/get_hooks_test.go | 5 +++ cmd/helm/get_manifest_test.go | 5 +++ cmd/helm/get_notes_test.go | 5 +++ cmd/helm/get_test.go | 25 ++++++++++++++ cmd/helm/get_values_test.go | 5 +++ cmd/helm/history_test.go | 5 +++ cmd/helm/install_test.go | 7 ++++ cmd/helm/lint_test.go | 5 +++ cmd/helm/list.go | 11 +++--- cmd/helm/list_test.go | 4 +++ cmd/helm/package_test.go | 5 +++ cmd/helm/plugin.go | 7 ++-- cmd/helm/plugin_install.go | 8 +++++ cmd/helm/plugin_list.go | 7 ++-- cmd/helm/plugin_test.go | 23 +++++++++++++ cmd/helm/pull_test.go | 5 +++ cmd/helm/release_testing_test.go | 26 ++++++++++++++ cmd/helm/repo.go | 9 ++--- cmd/helm/repo_add.go | 7 ++-- cmd/helm/repo_add_test.go | 6 ++++ cmd/helm/repo_index.go | 8 +++++ cmd/helm/repo_index_test.go | 5 +++ cmd/helm/repo_list.go | 9 ++--- cmd/helm/repo_list_test.go | 4 +++ cmd/helm/repo_remove_test.go | 5 +++ cmd/helm/repo_test.go | 25 ++++++++++++++ cmd/helm/repo_update.go | 11 +++--- cmd/helm/repo_update_test.go | 4 +++ cmd/helm/rollback_test.go | 6 ++++ cmd/helm/root.go | 3 ++ cmd/helm/root_test.go | 8 +++++ cmd/helm/search.go | 7 ++-- cmd/helm/search_hub_test.go | 4 +++ cmd/helm/search_repo.go | 5 +++ cmd/helm/search_repo_test.go | 4 +++ cmd/helm/search_test.go | 23 +++++++++++++ cmd/helm/show.go | 11 +++--- cmd/helm/show_test.go | 20 +++++++++++ cmd/helm/status_test.go | 5 +++ cmd/helm/template_test.go | 7 ++++ cmd/helm/uninstall_test.go | 5 +++ cmd/helm/upgrade_test.go | 6 ++++ cmd/helm/verify.go | 8 +++++ cmd/helm/verify_test.go | 5 +++ cmd/helm/version.go | 9 ++--- cmd/helm/version_test.go | 4 +++ 58 files changed, 517 insertions(+), 61 deletions(-) create mode 100644 cmd/helm/completion_test.go create mode 100644 cmd/helm/docs_test.go create mode 100644 cmd/helm/env_test.go create mode 100644 cmd/helm/get_test.go create mode 100644 cmd/helm/release_testing_test.go create mode 100644 cmd/helm/repo_test.go create mode 100644 cmd/helm/search_test.go diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index 696021363..c7e763c3d 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -54,10 +54,11 @@ $ helm completion zsh > "${fpath[1]}/_helm" func newCompletionCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "completion", - Short: "generate autocompletions script for the specified shell", - Long: completionDesc, - Args: require.NoArgs, + Use: "completion", + Short: "generate autocompletions script for the specified shell", + Long: completionDesc, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, // Disable file completion } bash := &cobra.Command{ @@ -66,6 +67,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: bashCompDesc, Args: require.NoArgs, DisableFlagsInUseLine: true, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { return runCompletionBash(out, cmd) }, @@ -77,6 +79,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: zshCompDesc, Args: require.NoArgs, DisableFlagsInUseLine: true, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { return runCompletionZsh(out, cmd) }, @@ -253,3 +256,8 @@ __helm_bash_source <(__helm_convert_bash_to_zsh) out.Write([]byte(zshTail)) return nil } + +// Function to disable file completion +func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/helm/completion_test.go b/cmd/helm/completion_test.go new file mode 100644 index 000000000..44c74f85a --- /dev/null +++ b/cmd/helm/completion_test.go @@ -0,0 +1,58 @@ +/* +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" + "strings" + "testing" + + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" +) + +// Check if file completion should be performed according to parameter 'shouldBePerformed' +func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { + storage := storageFixture() + storage.Create(&release.Release{ + Name: "myrelease", + Info: &release.Info{Status: release.StatusDeployed}, + Chart: &chart.Chart{}, + Version: 1, + }) + + testcmd := fmt.Sprintf("__complete %s ''", cmdName) + _, out, err := executeActionCommandC(storage, testcmd) + if err != nil { + t.Errorf("unexpected error, %s", err) + } + if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed { + if shouldBePerformed { + t.Error(fmt.Sprintf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)) + } else { + + t.Error(fmt.Sprintf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)) + } + t.Log(out) + } +} + +func TestCompletionFileCompletion(t *testing.T) { + checkFileCompletion(t, "completion", false) + checkFileCompletion(t, "completion bash", false) + checkFileCompletion(t, "completion zsh", false) +} diff --git a/cmd/helm/create.go b/cmd/helm/create.go index 5bdda05cb..21a7e026c 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -64,6 +64,15 @@ func newCreateCmd(out io.Writer) *cobra.Command { Short: "create a new chart with the given name", Long: createDesc, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + // Allow file completion when completing the argument for the name + // which could be a path + return nil, cobra.ShellCompDirectiveDefault + } + // No more completions, so disable file completion + return nil, cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { o.name = args[0] o.starterDir = helmpath.DataPath("starters") diff --git a/cmd/helm/create_test.go b/cmd/helm/create_test.go index bbb8d394a..1db6bed52 100644 --- a/cmd/helm/create_test.go +++ b/cmd/helm/create_test.go @@ -192,3 +192,8 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) { t.Error("Did not find foo.tpl") } } + +func TestCreateFileCompletion(t *testing.T) { + checkFileCompletion(t, "create", true) + checkFileCompletion(t, "create myname", false) +} diff --git a/cmd/helm/dependency.go b/cmd/helm/dependency.go index 2cc4c5045..39dfd98ce 100644 --- a/cmd/helm/dependency.go +++ b/cmd/helm/dependency.go @@ -84,11 +84,12 @@ This will produce an error if the chart cannot be loaded. func newDependencyCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "dependency update|build|list", - Aliases: []string{"dep", "dependencies"}, - Short: "manage a chart's dependencies", - Long: dependencyDesc, - Args: require.NoArgs, + Use: "dependency update|build|list", + Aliases: []string{"dep", "dependencies"}, + Short: "manage a chart's dependencies", + Long: dependencyDesc, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, // Disable file completion } cmd.AddCommand(newDependencyListCmd(out)) diff --git a/cmd/helm/dependency_test.go b/cmd/helm/dependency_test.go index 80b357a77..34c6a25e1 100644 --- a/cmd/helm/dependency_test.go +++ b/cmd/helm/dependency_test.go @@ -51,3 +51,7 @@ func TestDependencyListCmd(t *testing.T) { }} runTestCmd(t, tests) } + +func TestDependencyFileCompletion(t *testing.T) { + checkFileCompletion(t, "dependency", false) +} diff --git a/cmd/helm/docs.go b/cmd/helm/docs.go index c974d4014..621b17ca1 100644 --- a/cmd/helm/docs.go +++ b/cmd/helm/docs.go @@ -47,11 +47,12 @@ func newDocsCmd(out io.Writer) *cobra.Command { o := &docsOptions{} cmd := &cobra.Command{ - Use: "docs", - Short: "generate documentation as markdown or man pages", - Long: docsDesc, - Hidden: true, - Args: require.NoArgs, + Use: "docs", + Short: "generate documentation as markdown or man pages", + Long: docsDesc, + Hidden: true, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { o.topCmd = cmd.Root() return o.run(out) diff --git a/cmd/helm/docs_test.go b/cmd/helm/docs_test.go new file mode 100644 index 000000000..cda299b0a --- /dev/null +++ b/cmd/helm/docs_test.go @@ -0,0 +1,25 @@ +/* +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 ( + "testing" +) + +func TestDocsFileCompletion(t *testing.T) { + checkFileCompletion(t, "docs", false) +} diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 0fbfb9da4..4db3d80de 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -32,10 +32,11 @@ Env prints out all the environment information in use by Helm. func newEnvCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "env", - Short: "helm client environment information", - Long: envHelp, - Args: require.NoArgs, + Use: "env", + Short: "helm client environment information", + Long: envHelp, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, Run: func(cmd *cobra.Command, args []string) { envVars := settings.EnvVars() diff --git a/cmd/helm/env_test.go b/cmd/helm/env_test.go new file mode 100644 index 000000000..a7170a8cc --- /dev/null +++ b/cmd/helm/env_test.go @@ -0,0 +1,25 @@ +/* +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 ( + "testing" +) + +func TestEnvFileCompletion(t *testing.T) { + checkFileCompletion(t, "env", false) +} diff --git a/cmd/helm/get.go b/cmd/helm/get.go index 7c4854b59..e94871c7b 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -37,10 +37,11 @@ get extended information about the release, including: func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "get", - Short: "download extended information of a named release", - Long: getHelp, - Args: require.NoArgs, + Use: "get", + Short: "download extended information of a named release", + Long: getHelp, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, // Disable file completion } cmd.AddCommand(newGetAllCmd(cfg, out)) diff --git a/cmd/helm/get_all_test.go b/cmd/helm/get_all_test.go index a213ac583..0c140faf8 100644 --- a/cmd/helm/get_all_test.go +++ b/cmd/helm/get_all_test.go @@ -45,3 +45,8 @@ func TestGetCmd(t *testing.T) { func TestGetAllRevisionCompletion(t *testing.T) { revisionFlagCompletionTest(t, "get all") } + +func TestGetAllFileCompletion(t *testing.T) { + checkFileCompletion(t, "get all", false) + checkFileCompletion(t, "get all myrelease", false) +} diff --git a/cmd/helm/get_hooks_test.go b/cmd/helm/get_hooks_test.go index 7b9142d12..6c010d1bd 100644 --- a/cmd/helm/get_hooks_test.go +++ b/cmd/helm/get_hooks_test.go @@ -40,3 +40,8 @@ func TestGetHooks(t *testing.T) { func TestGetHooksRevisionCompletion(t *testing.T) { revisionFlagCompletionTest(t, "get hooks") } + +func TestGetHooksFileCompletion(t *testing.T) { + checkFileCompletion(t, "get hooks", false) + checkFileCompletion(t, "get hooks myrelease", false) +} diff --git a/cmd/helm/get_manifest_test.go b/cmd/helm/get_manifest_test.go index bd0ffc5d6..f3f572e80 100644 --- a/cmd/helm/get_manifest_test.go +++ b/cmd/helm/get_manifest_test.go @@ -40,3 +40,8 @@ func TestGetManifest(t *testing.T) { func TestGetManifestRevisionCompletion(t *testing.T) { revisionFlagCompletionTest(t, "get manifest") } + +func TestGetManifestFileCompletion(t *testing.T) { + checkFileCompletion(t, "get manifest", false) + checkFileCompletion(t, "get manifest myrelease", false) +} diff --git a/cmd/helm/get_notes_test.go b/cmd/helm/get_notes_test.go index a59120b77..7d43c87e7 100644 --- a/cmd/helm/get_notes_test.go +++ b/cmd/helm/get_notes_test.go @@ -40,3 +40,8 @@ func TestGetNotesCmd(t *testing.T) { func TestGetNotesRevisionCompletion(t *testing.T) { revisionFlagCompletionTest(t, "get notes") } + +func TestGetNotesFileCompletion(t *testing.T) { + checkFileCompletion(t, "get notes", false) + checkFileCompletion(t, "get notes myrelease", false) +} diff --git a/cmd/helm/get_test.go b/cmd/helm/get_test.go new file mode 100644 index 000000000..79f914bea --- /dev/null +++ b/cmd/helm/get_test.go @@ -0,0 +1,25 @@ +/* +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 ( + "testing" +) + +func TestGetFileCompletion(t *testing.T) { + checkFileCompletion(t, "get", false) +} diff --git a/cmd/helm/get_values_test.go b/cmd/helm/get_values_test.go index ecd92d354..2a71b1e4d 100644 --- a/cmd/helm/get_values_test.go +++ b/cmd/helm/get_values_test.go @@ -60,3 +60,8 @@ func TestGetValuesRevisionCompletion(t *testing.T) { func TestGetValuesOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "get values") } + +func TestGetValuesFileCompletion(t *testing.T) { + checkFileCompletion(t, "get values", false) + checkFileCompletion(t, "get values myrelease", false) +} diff --git a/cmd/helm/history_test.go b/cmd/helm/history_test.go index c9bfb648e..fffd983da 100644 --- a/cmd/helm/history_test.go +++ b/cmd/helm/history_test.go @@ -108,3 +108,8 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) { }} runTestCmd(t, tests) } + +func TestHistoryFileCompletion(t *testing.T) { + checkFileCompletion(t, "history", false) + checkFileCompletion(t, "history myrelease", false) +} diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 36de648f9..6892fcd86 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -239,3 +239,10 @@ func TestInstallVersionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestInstallFileCompletion(t *testing.T) { + checkFileCompletion(t, "install", false) + checkFileCompletion(t, "install --generate-name", true) + checkFileCompletion(t, "install myname", true) + checkFileCompletion(t, "install myname mychart", false) +} diff --git a/cmd/helm/lint_test.go b/cmd/helm/lint_test.go index 9079935d4..3501ccf87 100644 --- a/cmd/helm/lint_test.go +++ b/cmd/helm/lint_test.go @@ -36,3 +36,8 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) { }} runTestCmd(t, tests) } + +func TestLintFileCompletion(t *testing.T) { + checkFileCompletion(t, "lint", true) + checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given +} diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 2f40707b3..08a26be04 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -63,11 +63,12 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var outfmt output.Format cmd := &cobra.Command{ - Use: "list", - Short: "list releases", - Long: listHelp, - Aliases: []string{"ls"}, - Args: require.NoArgs, + Use: "list", + Short: "list releases", + Long: listHelp, + Aliases: []string{"ls"}, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { if client.AllNamespaces { if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil { diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index dadb57b94..b3b29356e 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -235,3 +235,7 @@ func TestListCmd(t *testing.T) { func TestListOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "list") } + +func TestListFileCompletion(t *testing.T) { + checkFileCompletion(t, "list", false) +} diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index e0a5fabd6..ecb3ee36c 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -202,3 +202,8 @@ func setFlags(cmd *cobra.Command, flags map[string]string) { dest.Set(f, v) } } + +func TestPackageFileCompletion(t *testing.T) { + checkFileCompletion(t, "package", true) + checkFileCompletion(t, "package mypath", true) // Multiple paths can be given +} diff --git a/cmd/helm/plugin.go b/cmd/helm/plugin.go index 8e1044f54..a118a03eb 100644 --- a/cmd/helm/plugin.go +++ b/cmd/helm/plugin.go @@ -32,9 +32,10 @@ Manage client-side Helm plugins. func newPluginCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "plugin", - Short: "install, list, or uninstall Helm plugins", - Long: pluginHelp, + Use: "plugin", + Short: "install, list, or uninstall Helm plugins", + Long: pluginHelp, + ValidArgsFunction: noCompletions, // Disable file completion } cmd.AddCommand( newPluginInstallCmd(out), diff --git a/cmd/helm/plugin_install.go b/cmd/helm/plugin_install.go index 8deb1f4f9..183d3dc57 100644 --- a/cmd/helm/plugin_install.go +++ b/cmd/helm/plugin_install.go @@ -43,6 +43,14 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command { Long: pluginInstallDesc, Aliases: []string{"add"}, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + // We do file completion, in case the plugin is local + return nil, cobra.ShellCompDirectiveDefault + } + // No more completion once the plugin path has been specified + return nil, cobra.ShellCompDirectiveNoFileComp + }, PreRunE: func(cmd *cobra.Command, args []string) error { return o.complete(args) }, diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go index 49a454963..6503161e8 100644 --- a/cmd/helm/plugin_list.go +++ b/cmd/helm/plugin_list.go @@ -28,9 +28,10 @@ import ( func newPluginListCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "list installed Helm plugins", + Use: "list", + Aliases: []string{"ls"}, + Short: "list installed Helm plugins", + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { debug("pluginDirs: %s", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index e43f277a5..cf21d8460 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -298,3 +298,26 @@ func TestLoadPlugins_HelmNoPlugins(t *testing.T) { t.Fatalf("Expected 0 plugins, got %d", len(plugins)) } } + +func TestPluginFileCompletion(t *testing.T) { + checkFileCompletion(t, "plugin", false) +} + +func TestPluginInstallFileCompletion(t *testing.T) { + checkFileCompletion(t, "plugin install", true) + checkFileCompletion(t, "plugin install mypath", false) +} + +func TestPluginListFileCompletion(t *testing.T) { + checkFileCompletion(t, "plugin list", false) +} + +func TestPluginUninstallFileCompletion(t *testing.T) { + checkFileCompletion(t, "plugin uninstall", false) + checkFileCompletion(t, "plugin uninstall myplugin", false) +} + +func TestPluginUpdateFileCompletion(t *testing.T) { + checkFileCompletion(t, "plugin update", false) + checkFileCompletion(t, "plugin update myplugin", false) +} diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 435df51f1..3f769a1bc 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -221,3 +221,8 @@ func TestPullVersionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestPullFileCompletion(t *testing.T) { + checkFileCompletion(t, "pull", false) + checkFileCompletion(t, "pull repo/chart", false) +} diff --git a/cmd/helm/release_testing_test.go b/cmd/helm/release_testing_test.go new file mode 100644 index 000000000..257e95721 --- /dev/null +++ b/cmd/helm/release_testing_test.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 main + +import ( + "testing" +) + +func TestReleaseTestingFileCompletion(t *testing.T) { + checkFileCompletion(t, "test", false) + checkFileCompletion(t, "test myrelease", false) +} diff --git a/cmd/helm/repo.go b/cmd/helm/repo.go index ad6ceaa8f..5aac38819 100644 --- a/cmd/helm/repo.go +++ b/cmd/helm/repo.go @@ -34,10 +34,11 @@ It can be used to add, remove, list, and index chart repositories. func newRepoCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "repo add|remove|list|index|update [ARGS]", - Short: "add, list, remove, update, and index chart repositories", - Long: repoHelm, - Args: require.NoArgs, + Use: "repo add|remove|list|index|update [ARGS]", + Short: "add, list, remove, update, and index chart repositories", + Long: repoHelm, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, // Disable file completion } cmd.AddCommand(newRepoAddCmd(out)) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 418a549c6..3eeb342f5 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -57,9 +57,10 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { o := &repoAddOptions{} cmd := &cobra.Command{ - Use: "add [NAME] [URL]", - Short: "add a chart repository", - Args: require.ExactArgs(2), + Use: "add [NAME] [URL]", + Short: "add a chart repository", + Args: require.ExactArgs(2), + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { o.name = args[0] o.url = args[1] diff --git a/cmd/helm/repo_add_test.go b/cmd/helm/repo_add_test.go index 7cb669a1a..9ef64390b 100644 --- a/cmd/helm/repo_add_test.go +++ b/cmd/helm/repo_add_test.go @@ -160,3 +160,9 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { } } } + +func TestRepoAddFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo add", false) + checkFileCompletion(t, "repo add reponame", false) + checkFileCompletion(t, "repo add reponame https://example.com", false) +} diff --git a/cmd/helm/repo_index.go b/cmd/helm/repo_index.go index 63afaf37b..917acd442 100644 --- a/cmd/helm/repo_index.go +++ b/cmd/helm/repo_index.go @@ -53,6 +53,14 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command { Short: "generate an index file given a directory containing packaged charts", Long: repoIndexDesc, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + // Allow file completion when completing the argument for the directory + return nil, cobra.ShellCompDirectiveDefault + } + // No more completions, so disable file completion + return nil, cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { o.dir = args[0] return o.run(out) diff --git a/cmd/helm/repo_index_test.go b/cmd/helm/repo_index_test.go index e04ae1b59..ae3390154 100644 --- a/cmd/helm/repo_index_test.go +++ b/cmd/helm/repo_index_test.go @@ -165,3 +165,8 @@ func copyFile(dst, src string) error { return err } + +func TestRepoIndexFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo index", true) + checkFileCompletion(t, "repo index mydir", false) +} diff --git a/cmd/helm/repo_list.go b/cmd/helm/repo_list.go index ed1c9573c..fc53ba75a 100644 --- a/cmd/helm/repo_list.go +++ b/cmd/helm/repo_list.go @@ -32,10 +32,11 @@ import ( func newRepoListCmd(out io.Writer) *cobra.Command { var outfmt output.Format cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "list chart repositories", - Args: require.NoArgs, + Use: "list", + Aliases: []string{"ls"}, + Short: "list chart repositories", + Args: require.NoArgs, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { f, err := repo.LoadFile(settings.RepositoryConfig) if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) { diff --git a/cmd/helm/repo_list_test.go b/cmd/helm/repo_list_test.go index f371452f2..90149ebda 100644 --- a/cmd/helm/repo_list_test.go +++ b/cmd/helm/repo_list_test.go @@ -23,3 +23,7 @@ import ( func TestRepoListOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "repo list") } + +func TestRepoListFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo list", false) +} diff --git a/cmd/helm/repo_remove_test.go b/cmd/helm/repo_remove_test.go index f7d50140e..0ea1d63d2 100644 --- a/cmd/helm/repo_remove_test.go +++ b/cmd/helm/repo_remove_test.go @@ -160,3 +160,8 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, t.Errorf("Error cache chart file was not removed for repository %s", repoName) } } + +func TestRepoRemoveFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo remove", false) + checkFileCompletion(t, "repo remove repo1", false) +} diff --git a/cmd/helm/repo_test.go b/cmd/helm/repo_test.go new file mode 100644 index 000000000..2b0df7c4c --- /dev/null +++ b/cmd/helm/repo_test.go @@ -0,0 +1,25 @@ +/* +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 ( + "testing" +) + +func TestRepoFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo", false) +} diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index cbfc05b00..e845751c1 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -46,11 +46,12 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { o := &repoUpdateOptions{update: updateCharts} cmd := &cobra.Command{ - Use: "update", - Aliases: []string{"up"}, - Short: "update information of available charts locally from chart repositories", - Long: updateDesc, - Args: require.NoArgs, + Use: "update", + Aliases: []string{"up"}, + Short: "update information of available charts locally from chart repositories", + Long: updateDesc, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index 981f0bba3..e5e4eb337 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -100,3 +100,7 @@ func TestUpdateCharts(t *testing.T) { t.Error("Update was not successful") } } + +func TestRepoUpdateFileCompletion(t *testing.T) { + checkFileCompletion(t, "repo update", false) +} diff --git a/cmd/helm/rollback_test.go b/cmd/helm/rollback_test.go index c11a7fca6..b39378f92 100644 --- a/cmd/helm/rollback_test.go +++ b/cmd/helm/rollback_test.go @@ -104,3 +104,9 @@ func TestRollbackRevisionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestRollbackFileCompletion(t *testing.T) { + checkFileCompletion(t, "rollback", false) + checkFileCompletion(t, "rollback myrelease", false) + checkFileCompletion(t, "rollback myrelease 1", false) +} diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 449009cb5..904f11a21 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -75,6 +75,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string Short: "The Helm package manager for Kubernetes.", Long: globalUsage, SilenceUsage: true, + // This breaks completion for 'helm help ' + // The Cobra release following 1.0 will fix this + //ValidArgsFunction: noCompletions, // Disable file completion } flags := cmd.PersistentFlags() diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go index 891bb698e..075544971 100644 --- a/cmd/helm/root_test.go +++ b/cmd/helm/root_test.go @@ -121,3 +121,11 @@ func TestUnknownSubCmd(t *testing.T) { t.Errorf("Expect unknown command error, got %q", err) } } + +// Need the release of Cobra following 1.0 to be able to disable +// file completion on the root command. Until then, we cannot +// because it would break 'helm help ' +// +// func TestRootFileCompletion(t *testing.T) { +// checkFileCompletion(t, "", false) +// } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index 240d5e7c7..44c8d64e3 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -31,9 +31,10 @@ search subcommands to search different locations for charts. func newSearchCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "search [keyword]", - Short: "search for a keyword in charts", - Long: searchDesc, + Use: "search [keyword]", + Short: "search for a keyword in charts", + Long: searchDesc, + ValidArgsFunction: noCompletions, // Disable file completion } cmd.AddCommand(newSearchHubCmd(out)) diff --git a/cmd/helm/search_hub_test.go b/cmd/helm/search_hub_test.go index 7b0f3a389..4f62eed74 100644 --- a/cmd/helm/search_hub_test.go +++ b/cmd/helm/search_hub_test.go @@ -54,3 +54,7 @@ func TestSearchHubCmd(t *testing.T) { func TestSearchHubOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "search hub") } + +func TestSearchHubFileCompletion(t *testing.T) { + checkFileCompletion(t, "search hub", true) // File completion may be useful when inputing a keyword +} diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index dd530379b..a7f27f179 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -365,6 +365,11 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell // We handle it ourselves instead. completions = compEnforceNoSpace(completions) } + if !includeFiles { + // If we should not include files in the completions, + // we should disable file completion + directive = directive | cobra.ShellCompDirectiveNoFileComp + } return completions, directive } diff --git a/cmd/helm/search_repo_test.go b/cmd/helm/search_repo_test.go index 402ef2970..39c9c53f5 100644 --- a/cmd/helm/search_repo_test.go +++ b/cmd/helm/search_repo_test.go @@ -87,3 +87,7 @@ func TestSearchRepositoriesCmd(t *testing.T) { func TestSearchRepoOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "search repo") } + +func TestSearchRepoFileCompletion(t *testing.T) { + checkFileCompletion(t, "search repo", true) // File completion may be useful when inputing a keyword +} diff --git a/cmd/helm/search_test.go b/cmd/helm/search_test.go new file mode 100644 index 000000000..6cf845b06 --- /dev/null +++ b/cmd/helm/search_test.go @@ -0,0 +1,23 @@ +/* +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 "testing" + +func TestSearchFileCompletion(t *testing.T) { + checkFileCompletion(t, "search", false) +} diff --git a/cmd/helm/show.go b/cmd/helm/show.go index fe5eaee26..888d2d3f3 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -55,11 +55,12 @@ func newShowCmd(out io.Writer) *cobra.Command { client := action.NewShow(action.ShowAll) showCommand := &cobra.Command{ - Use: "show", - Short: "show information of a chart", - Aliases: []string{"inspect"}, - Long: showDesc, - Args: require.NoArgs, + Use: "show", + Short: "show information of a chart", + Aliases: []string{"inspect"}, + Long: showDesc, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, // Disable file completion } // Function providing dynamic auto-completion diff --git a/cmd/helm/show_test.go b/cmd/helm/show_test.go index 6c550282f..2734faf5e 100644 --- a/cmd/helm/show_test.go +++ b/cmd/helm/show_test.go @@ -118,3 +118,23 @@ func TestShowVersionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestShowFileCompletion(t *testing.T) { + checkFileCompletion(t, "show", false) +} + +func TestShowAllFileCompletion(t *testing.T) { + checkFileCompletion(t, "show all", true) +} + +func TestShowChartFileCompletion(t *testing.T) { + checkFileCompletion(t, "show chart", true) +} + +func TestShowReadmeFileCompletion(t *testing.T) { + checkFileCompletion(t, "show readme", true) +} + +func TestShowValuesFileCompletion(t *testing.T) { + checkFileCompletion(t, "show values", true) +} diff --git a/cmd/helm/status_test.go b/cmd/helm/status_test.go index 0d2500e65..18731e465 100644 --- a/cmd/helm/status_test.go +++ b/cmd/helm/status_test.go @@ -171,3 +171,8 @@ func TestStatusRevisionCompletion(t *testing.T) { func TestStatusOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "status") } + +func TestStatusFileCompletion(t *testing.T) { + checkFileCompletion(t, "status", false) + checkFileCompletion(t, "status myrelease", false) +} diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index f8cd31347..6f7ca939d 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -154,3 +154,10 @@ func TestTemplateVersionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestTemplateFileCompletion(t *testing.T) { + checkFileCompletion(t, "template", false) + checkFileCompletion(t, "template --generate-name", true) + checkFileCompletion(t, "template myname", true) + checkFileCompletion(t, "template myname mychart", false) +} diff --git a/cmd/helm/uninstall_test.go b/cmd/helm/uninstall_test.go index a34934077..ad78361c1 100644 --- a/cmd/helm/uninstall_test.go +++ b/cmd/helm/uninstall_test.go @@ -66,3 +66,8 @@ func TestUninstall(t *testing.T) { } runTestCmd(t, tests) } + +func TestUninstallFileCompletion(t *testing.T) { + checkFileCompletion(t, "uninstall", false) + checkFileCompletion(t, "uninstall myrelease", false) +} diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 64daf2934..6fe79ebce 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -407,3 +407,9 @@ func TestUpgradeVersionCompletion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestUpgradeFileCompletion(t *testing.T) { + checkFileCompletion(t, "upgrade", false) + checkFileCompletion(t, "upgrade myrelease", true) + checkFileCompletion(t, "upgrade myrelease repo/chart", false) +} diff --git a/cmd/helm/verify.go b/cmd/helm/verify.go index f26fb377f..d126c9ef3 100644 --- a/cmd/helm/verify.go +++ b/cmd/helm/verify.go @@ -44,6 +44,14 @@ func newVerifyCmd(out io.Writer) *cobra.Command { Short: "verify that a chart at the given path has been signed and is valid", Long: verifyDesc, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + // Allow file completion when completing the argument for the path + return nil, cobra.ShellCompDirectiveDefault + } + // No more completions, so disable file completion + return nil, cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { err := client.Run(args[0]) if err != nil { diff --git a/cmd/helm/verify_test.go b/cmd/helm/verify_test.go index ccbcb3cf2..23b793557 100644 --- a/cmd/helm/verify_test.go +++ b/cmd/helm/verify_test.go @@ -90,3 +90,8 @@ func TestVerifyCmd(t *testing.T) { }) } } + +func TestVerifyFileCompletion(t *testing.T) { + checkFileCompletion(t, "verify", true) + checkFileCompletion(t, "verify mypath", false) +} diff --git a/cmd/helm/version.go b/cmd/helm/version.go index 2f50c876c..72f93e545 100644 --- a/cmd/helm/version.go +++ b/cmd/helm/version.go @@ -59,10 +59,11 @@ func newVersionCmd(out io.Writer) *cobra.Command { o := &versionOptions{} cmd := &cobra.Command{ - Use: "version", - Short: "print the client version information", - Long: versionDesc, - Args: require.NoArgs, + Use: "version", + Short: "print the client version information", + Long: versionDesc, + Args: require.NoArgs, + ValidArgsFunction: noCompletions, RunE: func(cmd *cobra.Command, args []string) error { return o.run(out) }, diff --git a/cmd/helm/version_test.go b/cmd/helm/version_test.go index 134401948..aa3cbfb7d 100644 --- a/cmd/helm/version_test.go +++ b/cmd/helm/version_test.go @@ -43,3 +43,7 @@ func TestVersion(t *testing.T) { }} runTestCmd(t, tests) } + +func TestVersionFileCompletion(t *testing.T) { + checkFileCompletion(t, "version", false) +} From b07b2589fb157f017079c0f7285f98bc56a33ae2 Mon Sep 17 00:00:00 2001 From: Tariq Ibrahim Date: Tue, 18 Aug 2020 21:58:21 -0700 Subject: [PATCH 59/76] optimise if condition in service ready logic Signed-off-by: Tariq Ibrahim --- 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 90a9f8b38..c3beb232d 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -188,8 +188,8 @@ func (w *waiter) serviceReady(s *corev1.Service) bool { return true } - // Make sure the service is not explicitly set to "None" before checking the IP - if s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "" { + // Ensure that the service cluster IP is not empty + if s.Spec.ClusterIP == "" { w.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) return false } From 9664bb2a0a528e1b9262501dfaee587e0e459c39 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Wed, 19 Aug 2020 13:20:54 -0700 Subject: [PATCH 60/76] add v4 to list of exempt labels Signed-off-by: Matthew Fisher --- .github/workflows/stale-issue-bot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index e21e6d5ca..946e7f09b 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -10,6 +10,6 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' - exempt-issue-labels: 'keep open' + exempt-issue-labels: 'keep open,v4.x' days-before-stale: 90 days-before-close: 30 From d13c43d3e8d54c09d82d2aff68b06255538649c8 Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Wed, 19 Aug 2020 13:33:12 -0700 Subject: [PATCH 61/76] use URL escape codes Signed-off-by: Matthew Fisher --- .github/workflows/stale-issue-bot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index 946e7f09b..32ea22418 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -10,6 +10,6 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' - exempt-issue-labels: 'keep open,v4.x' + exempt-issue-labels: 'keep+open,v4.x' days-before-stale: 90 days-before-close: 30 From 11f658e2234f5d3c02212e30ba38cd579d134aaa Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 20 Aug 2020 13:08:48 -0400 Subject: [PATCH 62/76] Fixing linting of templates on Windows When the engine stored templates in the map the keys were generated based on path and not filepath. filepath was being used in the linter when retrieving content from the keys. On Windows the keys ended up being different. This change is to use path joins to create the lookup key. Since the name path was used in the code it needed to be changed in order to import the package. Tests already exist and were failing on windows. This got in because CI is not run on Windows. Closes #6418 Signed-off-by: Matt Farina --- pkg/lint/rules/template.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 3da5b63fa..9a3a2a1ba 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -19,6 +19,7 @@ package rules import ( "fmt" "os" + "path" "path/filepath" "regexp" "strings" @@ -47,10 +48,10 @@ var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([- // Templates lints the templates in the Linter. func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { - path := "templates/" - templatesPath := filepath.Join(linter.ChartDir, path) + fpath := "templates/" + templatesPath := filepath.Join(linter.ChartDir, fpath) - templatesDirExist := linter.RunLinterRule(support.WarningSev, path, validateTemplatesDir(templatesPath)) + templatesDirExist := linter.RunLinterRule(support.WarningSev, fpath, validateTemplatesDir(templatesPath)) // Templates directory is optional for now if !templatesDirExist { @@ -60,7 +61,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // Load chart and parse templates chart, err := loader.Load(linter.ChartDir) - chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err) + chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err) if !chartLoaded { return @@ -77,14 +78,14 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace } valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil) if err != nil { - linter.RunLinterRule(support.ErrorSev, path, err) + linter.RunLinterRule(support.ErrorSev, fpath, err) return } var e engine.Engine e.LintMode = true renderedContentMap, err := e.Render(chart, valuesToRender) - renderOk := linter.RunLinterRule(support.ErrorSev, path, err) + renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err) if !renderOk { return @@ -99,13 +100,13 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace */ for _, template := range chart.Templates { fileName, data := template.Name, template.Data - path = fileName + fpath = fileName - linter.RunLinterRule(support.ErrorSev, path, validateAllowedExtension(fileName)) + linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName)) // These are v3 specific checks to make sure and warn people if their // chart is not compatible with v3 - linter.RunLinterRule(support.WarningSev, path, validateNoCRDHooks(data)) - linter.RunLinterRule(support.ErrorSev, path, validateNoReleaseTime(data)) + linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data)) + linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data)) // We only apply the following lint rules to yaml files if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { @@ -114,12 +115,12 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463 // Check that all the templates have a matching value - //linter.RunLinterRule(support.WarningSev, path, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) + //linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037 - // linter.RunLinterRule(support.WarningSev, path, validateQuotes(string(preExecutedTemplate))) + // linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate))) - renderedContent := renderedContentMap[filepath.Join(chart.Name(), fileName)] + renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] if strings.TrimSpace(renderedContent) != "" { var yamlStruct K8sYamlStruct // Even though K8sYamlStruct only defines a few fields, an error in any other @@ -128,10 +129,10 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // If YAML linting fails, we sill progress. So we don't capture the returned state // on this linter run. - linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) - linter.RunLinterRule(support.ErrorSev, path, validateMetadataName(&yamlStruct)) - linter.RunLinterRule(support.ErrorSev, path, validateNoDeprecations(&yamlStruct)) - linter.RunLinterRule(support.ErrorSev, path, validateMatchSelector(&yamlStruct, renderedContent)) + linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) + linter.RunLinterRule(support.ErrorSev, fpath, validateMetadataName(&yamlStruct)) + linter.RunLinterRule(support.ErrorSev, fpath, validateNoDeprecations(&yamlStruct)) + linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(&yamlStruct, renderedContent)) } } } From 8a545d6ca7e40ae412c53f5dc683ecc8a8ecdb96 Mon Sep 17 00:00:00 2001 From: Ma Xinjian Date: Fri, 21 Aug 2020 14:47:29 +0800 Subject: [PATCH 63/76] Correct checksum file links Signed-off-by: Ma Xinjian --- scripts/release-notes.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index dd48d4a17..3625aaa9a 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -39,8 +39,8 @@ 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/" +if [[ ! -e "./_dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256sum" ]]; then + echo "checksum file ./_dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256sum not found in ./_dist/" echo "Did you forget to run \`make fetch-dist\` first ?" exit 1 fi From 4abcdc40efb9ad455ed99bc73c8ee716fe89123d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Fri, 21 Aug 2020 11:21:43 +0200 Subject: [PATCH 64/76] pkg/*: Small linting fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Rüger --- pkg/action/package.go | 2 +- pkg/repo/chartrepo_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/package.go b/pkg/action/package.go index 19d845cf3..5059eee07 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -135,7 +135,7 @@ func (p *Package) Clearsign(filename string) error { // promptUser implements provenance.PassphraseFetcher func promptUser(name string) ([]byte, error) { fmt.Printf("Password for key %q > ", name) - pw, err := terminal.ReadPassword(int(syscall.Stdin)) + pw, err := terminal.ReadPassword(syscall.Stdin) fmt.Println() return pw, err } diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index f50d6a2b6..eceb3009e 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -172,7 +172,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) { func verifyIndex(t *testing.T, actual *IndexFile) { var empty time.Time - if actual.Generated == empty { + if actual.Generated.Equal(empty) { t.Errorf("Generated should be greater than 0: %s", actual.Generated) } @@ -242,7 +242,7 @@ func verifyIndex(t *testing.T, actual *IndexFile) { if len(g.Maintainers) != 2 { t.Error("Expected 2 maintainers.") } - if g.Created == empty { + if g.Created.Equal(empty) { t.Error("Expected created to be non-empty") } if g.Description == "" { From 3fc88f24929b3cac6e5284bbb5701ff94b3f11e0 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 21 Aug 2020 11:51:58 -0400 Subject: [PATCH 65/76] Fixing failing CI for windows A fix introduced in #8631 caused a bug in Windows builds due to a type difference between POSIX and Windows environments. This change corrects that problem and provides a code comment to warn others. Signed-off-by: Matt Farina --- pkg/action/package.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/action/package.go b/pkg/action/package.go index 5059eee07..0a927cd41 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -135,7 +135,9 @@ func (p *Package) Clearsign(filename string) error { // promptUser implements provenance.PassphraseFetcher func promptUser(name string) ([]byte, error) { fmt.Printf("Password for key %q > ", name) - pw, err := terminal.ReadPassword(syscall.Stdin) + // syscall.Stdin is not an int in all environments and needs to be coerced + // into one there (e.g., Windows) + pw, err := terminal.ReadPassword(int(syscall.Stdin)) fmt.Println() return pw, err } From 10d4d2ed999a403e2464f673e0e19b95ee4e1ac6 Mon Sep 17 00:00:00 2001 From: rudeigerc Date: Sat, 22 Aug 2020 02:30:22 +0800 Subject: [PATCH 66/76] feat(env): add support of accepting a specific variable for helm env Signed-off-by: Yuchen Cheng --- cmd/helm/env.go | 48 +++++++++++++++++++-------- cmd/helm/env_test.go | 9 +++++ cmd/helm/testdata/output/env-comp.txt | 16 +++++++++ 3 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 cmd/helm/testdata/output/env-comp.txt diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 4db3d80de..3754b748d 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -32,25 +32,45 @@ Env prints out all the environment information in use by Helm. func newEnvCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "env", - Short: "helm client environment information", - Long: envHelp, - Args: require.NoArgs, - ValidArgsFunction: noCompletions, + Use: "env", + Short: "helm client environment information", + Long: envHelp, + Args: require.MaximumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + keys := getSortedEnvVarKeys() + return keys, cobra.ShellCompDirectiveNoFileComp + } + + return nil, cobra.ShellCompDirectiveNoFileComp + }, Run: func(cmd *cobra.Command, args []string) { envVars := settings.EnvVars() - // Sort the variables by alphabetical order. - // This allows for a constant output across calls to 'helm env'. - var keys []string - for k := range envVars { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - fmt.Fprintf(out, "%s=\"%s\"\n", k, envVars[k]) + if len(args) == 0 { + // Sort the variables by alphabetical order. + // This allows for a constant output across calls to 'helm env'. + keys := getSortedEnvVarKeys() + + for _, k := range keys { + fmt.Fprintf(out, "%s=\"%s\"\n", k, envVars[k]) + } + } else { + fmt.Fprintf(out, "%s\n", envVars[args[0]]) } }, } return cmd } + +func getSortedEnvVarKeys() []string { + envVars := settings.EnvVars() + + var keys []string + for k := range envVars { + keys = append(keys, k) + } + sort.Strings(keys) + + return keys +} diff --git a/cmd/helm/env_test.go b/cmd/helm/env_test.go index a7170a8cc..abdc9c94e 100644 --- a/cmd/helm/env_test.go +++ b/cmd/helm/env_test.go @@ -20,6 +20,15 @@ import ( "testing" ) +func TestEnv(t *testing.T) { + tests := []cmdTestCase{{ + name: "completion for env", + cmd: "__complete env ''", + golden: "output/env-comp.txt", + }} + runTestCmd(t, tests) +} + func TestEnvFileCompletion(t *testing.T) { checkFileCompletion(t, "env", false) } diff --git a/cmd/helm/testdata/output/env-comp.txt b/cmd/helm/testdata/output/env-comp.txt new file mode 100644 index 000000000..c4b46ae6b --- /dev/null +++ b/cmd/helm/testdata/output/env-comp.txt @@ -0,0 +1,16 @@ +HELM_BIN +HELM_CACHE_HOME +HELM_CONFIG_HOME +HELM_DATA_HOME +HELM_DEBUG +HELM_KUBEAPISERVER +HELM_KUBECONTEXT +HELM_KUBETOKEN +HELM_MAX_HISTORY +HELM_NAMESPACE +HELM_PLUGINS +HELM_REGISTRY_CONFIG +HELM_REPOSITORY_CACHE +HELM_REPOSITORY_CONFIG +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp From 195d7a650712d8eee2db887bf8e1a4e2c363328b Mon Sep 17 00:00:00 2001 From: Yuchen Cheng Date: Sat, 22 Aug 2020 22:04:09 +0800 Subject: [PATCH 67/76] add checkFileCompletion for env HELM_BIN Signed-off-by: Yuchen Cheng --- cmd/helm/env_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/helm/env_test.go b/cmd/helm/env_test.go index abdc9c94e..01ef25933 100644 --- a/cmd/helm/env_test.go +++ b/cmd/helm/env_test.go @@ -31,4 +31,5 @@ func TestEnv(t *testing.T) { func TestEnvFileCompletion(t *testing.T) { checkFileCompletion(t, "env", false) + checkFileCompletion(t, "env HELM_BIN", false) } From 0669f40e814707ce43c560f33f363d19f40e6800 Mon Sep 17 00:00:00 2001 From: Zhou Hao Date: Fri, 28 Aug 2020 13:38:29 +0800 Subject: [PATCH 68/76] cleanup tempfiles for load_test Signed-off-by: Zhou Hao --- pkg/chart/loader/load_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 40b86dec2..16a94d4eb 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -310,7 +310,7 @@ func TestLoadInvalidArchive(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.Remove(tmpdir) + defer os.RemoveAll(tmpdir) writeTar := func(filename, internalPath string, body []byte) { dest, err := os.Create(filename) From 45b084b25504a1e19592684f964f43b57792875a Mon Sep 17 00:00:00 2001 From: knrt10 Date: Fri, 28 Aug 2020 18:23:33 +0530 Subject: [PATCH 69/76] Fix spelling in completion.go Signed-off-by: knrt10 --- cmd/helm/completion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index c7e763c3d..db50b375a 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -151,7 +151,7 @@ __helm_compgen() { fi for w in "${completions[@]}"; do if [[ "${w}" = "$1"* ]]; then - # Use printf instead of echo beause it is possible that + # Use printf instead of echo because it is possible that # the value to print is -n, which would be interpreted # as a flag to echo printf "%s\n" "${w}" From 96d9ab9663b69cbd85444ca5232d8283017eeeea Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 1 Sep 2020 10:44:52 -0600 Subject: [PATCH 70/76] fix name length check on lint (#8543) Signed-off-by: Matt Butcher --- go.mod | 1 + pkg/action/upgrade.go | 3 ++- pkg/lint/rules/template.go | 3 +++ pkg/lint/rules/template_test.go | 10 ++++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 66447ee3d..4b7c90f9d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 github.com/DATA-DOG/go-sqlmock v1.4.1 + github.com/Masterminds/goutils v1.1.0 github.com/Masterminds/semver/v3 v3.1.0 github.com/Masterminds/sprig/v3 v3.1.0 github.com/Masterminds/squirrel v1.4.0 diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index e7c2aec25..b707e7e69 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -147,7 +147,8 @@ func validateReleaseName(releaseName string) error { return errMissingRelease } - if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) { + // Check length first, since that is a less expensive operation. + if len(releaseName) > releaseNameMaxLen || !ValidName.MatchString(releaseName) { return errInvalidName } diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 9a3a2a1ba..cd54c8915 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -165,6 +165,9 @@ func validateYamlContent(err error) error { } func validateMetadataName(obj *K8sYamlStruct) error { + if len(obj.Metadata.Name) == 0 || len(obj.Metadata.Name) > 253 { + return fmt.Errorf("object name must be between 0 and 253 characters: %q", obj.Metadata.Name) + } // This will return an error if the characters do not abide by the standard OR if the // name is left empty. if validName.MatchString(obj.Metadata.Name) { diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index ae82c8922..3b0307c1d 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -22,6 +22,8 @@ import ( "strings" "testing" + "github.com/Masterminds/goutils" + "helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" @@ -119,6 +121,14 @@ func TestValidateMetadataName(t *testing.T) { "a..b": false, "%^&#$%*@^*@&#^": false, } + + // The length checker should catch this first. So this is not true fuzzing. + tooLong, err := goutils.RandomAlphaNumeric(300) + if err != nil { + t.Fatalf("Randomizer failed to initialize: %s", err) + } + names[tooLong] = false + for input, expectPass := range names { obj := K8sYamlStruct{Metadata: k8sYamlMetadata{Name: input}} if err := validateMetadataName(&obj); (err == nil) != expectPass { From 70d03e5cefa8f42727e29db310f78aeae4d65bb0 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 1 Sep 2020 10:45:59 -0600 Subject: [PATCH 71/76] Fix/8467 linter failing (#8496) * add output to get debug info on linter failing Signed-off-by: Matt Butcher * trap cases where the YAML indent is incorrect. Signed-off-by: Matt Butcher --- pkg/lint/rules/template.go | 29 +++++++++++++++++++++++++++++ pkg/lint/rules/template_test.go | 17 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index cd54c8915..ac65b3932 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -17,6 +17,8 @@ limitations under the License. package rules import ( + "bufio" + "bytes" "fmt" "os" "path" @@ -122,6 +124,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] if strings.TrimSpace(renderedContent) != "" { + linter.RunLinterRule(support.WarningSev, path, validateTopIndentLevel(renderedContent)) var yamlStruct K8sYamlStruct // Even though K8sYamlStruct only defines a few fields, an error in any other // key will be raised as well @@ -137,6 +140,32 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace } } +// validateTopIndentLevel checks that the content does not start with an indent level > 0. +// +// This error can occur when a template accidentally inserts space. It can cause +// unpredictable errors dependening on whether the text is normalized before being passed +// into the YAML parser. So we trap it here. +// +// See https://github.com/helm/helm/issues/8467 +func validateTopIndentLevel(content string) error { + // Read lines until we get to a non-empty one + scanner := bufio.NewScanner(bytes.NewBufferString(content)) + for scanner.Scan() { + line := scanner.Text() + // If line is empty, skip + if strings.TrimSpace(line) == "" { + continue + } + // If it starts with one or more spaces, this is an error + if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { + return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line) + } + // Any other condition passes. + return nil + } + return scanner.Err() +} + // Validation functions func validateTemplatesDir(templatesPath string) error { if fi, err := os.Stat(templatesPath); err != nil { diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 3b0307c1d..b4397851b 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -315,3 +315,20 @@ spec: t.Error("expected Deployment with no selector to fail") } } + +func TestValidateTopIndentLevel(t *testing.T) { + for doc, shouldFail := range map[string]bool{ + // Should not fail + "\n\n\n\t\n \t\n": false, + "apiVersion:foo\n bar:baz": false, + "\n\n\napiVersion:foo\n\n\n": false, + // Should fail + " apiVersion:foo": true, + "\n\n apiVersion:foo\n\n": true, + } { + if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { + t.Errorf("Expected %t for %q", shouldFail, doc) + } + } + +} From 04fb35814f64122c0aa08165f6fdb7b67c216558 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 1 Sep 2020 11:51:40 -0600 Subject: [PATCH 72/76] Fixed a variable name collision caused by two PR merges (#8681) Signed-off-by: Matt Butcher --- pkg/lint/rules/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index ac65b3932..73d645264 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -124,7 +124,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] if strings.TrimSpace(renderedContent) != "" { - linter.RunLinterRule(support.WarningSev, path, validateTopIndentLevel(renderedContent)) + linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent)) var yamlStruct K8sYamlStruct // Even though K8sYamlStruct only defines a few fields, an error in any other // key will be raised as well From 317616482cbdca1b47605d707803f31b4ee2a26f Mon Sep 17 00:00:00 2001 From: Liu Ming Date: Wed, 2 Sep 2020 10:30:41 +0800 Subject: [PATCH 73/76] Remove duplicate variable definition Variable values `helm.sh/resource-policy` and `keep` are duplicately defined in resource_policy.go (`resourcePolicyAnno` `keepPolicy`) and resource_policy.go (`ResourcePolicyAnno` `KeepPolicy`), remove the varibales in resource_policy.go to keep the code clean. Signed-off-by: Liu Ming --- pkg/action/resource_policy.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go index cfabdf7ba..63e83f3d9 100644 --- a/pkg/action/resource_policy.go +++ b/pkg/action/resource_policy.go @@ -19,18 +19,10 @@ package action import ( "strings" + "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/releaseutil" ) -// 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" - func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { for _, m := range manifests { if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 { @@ -38,14 +30,14 @@ func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining [] continue } - resourcePolicyType, ok := m.Head.Metadata.Annotations[resourcePolicyAnno] + resourcePolicyType, ok := m.Head.Metadata.Annotations[kube.ResourcePolicyAnno] if !ok { remaining = append(remaining, m) continue } resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) - if resourcePolicyType == keepPolicy { + if resourcePolicyType == kube.KeepPolicy { keep = append(keep, m) } From da6878dc0f5c99d1062c2c220b0ab5a8d548a773 Mon Sep 17 00:00:00 2001 From: Ling Samuel Date: Wed, 29 Jul 2020 09:36:21 +0800 Subject: [PATCH 74/76] feat: status command display description Signed-off-by: Ling Samuel --- cmd/helm/get_all.go | 2 +- cmd/helm/install.go | 2 +- cmd/helm/release_testing.go | 2 +- cmd/helm/status.go | 13 ++++++++++--- cmd/helm/status_test.go | 8 ++++++++ cmd/helm/testdata/output/status-with-desc.txt | 7 +++++++ cmd/helm/upgrade.go | 4 ++-- pkg/action/status.go | 5 +++++ 8 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 cmd/helm/testdata/output/status-with-desc.txt diff --git a/cmd/helm/get_all.go b/cmd/helm/get_all.go index a5037e4df..53f8d5905 100644 --- a/cmd/helm/get_all.go +++ b/cmd/helm/get_all.go @@ -59,7 +59,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return tpl(template, data, out) } - return output.Table.Write(out, &statusPrinter{res, true}) + return output.Table.Write(out, &statusPrinter{res, true, false}) }, } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index f7d0239fb..7edd98091 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -122,7 +122,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug}) + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) }, } diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 37cac6186..e4e09ef3b 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -61,7 +61,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command return runErr } - if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug}); err != nil { + if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}); err != nil { return err } diff --git a/cmd/helm/status.go b/cmd/helm/status.go index abd32a9c2..7a3204cb9 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -39,6 +39,8 @@ The status consists of: - last deployment time - k8s namespace in which the release lives - state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback) +- revision of the release +- description of the release (can be completion message or error message, need to enable --show-desc) - list of resources that this release consists of, sorted by kind - details on last test suite run, if applicable - additional notes provided by the chart @@ -68,7 +70,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { // strip chart metadata from the output rel.Chart = nil - return outfmt.Write(out, &statusPrinter{rel, false}) + return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription}) }, } @@ -88,13 +90,15 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } bindOutputFlag(cmd, &outfmt) + f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release") return cmd } type statusPrinter struct { - release *release.Release - debug bool + release *release.Release + debug bool + showDescription bool } func (s statusPrinter) WriteJSON(out io.Writer) error { @@ -116,6 +120,9 @@ func (s statusPrinter) WriteTable(out io.Writer) error { fmt.Fprintf(out, "NAMESPACE: %s\n", s.release.Namespace) fmt.Fprintf(out, "STATUS: %s\n", s.release.Info.Status.String()) fmt.Fprintf(out, "REVISION: %d\n", s.release.Version) + if s.showDescription { + fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description) + } executions := executionsByHookEvent(s.release) if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 { diff --git a/cmd/helm/status_test.go b/cmd/helm/status_test.go index 0d2500e65..2d0ce4b03 100644 --- a/cmd/helm/status_test.go +++ b/cmd/helm/status_test.go @@ -44,6 +44,14 @@ func TestStatusCmd(t *testing.T) { rels: releasesMockWithStatus(&release.Info{ Status: release.StatusDeployed, }), + }, { + name: "get status of a deployed release, with desc", + cmd: "status --show-desc flummoxed-chickadee", + golden: "output/status-with-desc.txt", + rels: releasesMockWithStatus(&release.Info{ + Status: release.StatusDeployed, + Description: "Mock description", + }), }, { name: "get status of a deployed release with notes", cmd: "status flummoxed-chickadee", diff --git a/cmd/helm/testdata/output/status-with-desc.txt b/cmd/helm/testdata/output/status-with-desc.txt new file mode 100644 index 000000000..c681fe3ec --- /dev/null +++ b/cmd/helm/testdata/output/status-with-desc.txt @@ -0,0 +1,7 @@ +NAME: flummoxed-chickadee +LAST DEPLOYED: Sat Jan 16 00:00:00 2016 +NAMESPACE: default +STATUS: deployed +REVISION: 0 +DESCRIPTION: Mock description +TEST SUITE: None diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index dbddaa368..12d797545 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -115,7 +115,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug}) + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) } else if err != nil { return err } @@ -160,7 +160,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0]) } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug}) + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) }, } diff --git a/pkg/action/status.go b/pkg/action/status.go index a0c7f6e21..1c556e28d 100644 --- a/pkg/action/status.go +++ b/pkg/action/status.go @@ -27,6 +27,11 @@ type Status struct { cfg *Configuration Version int + + // If true, display description to output format, + // only affect print type table. + // TODO Helm 4: Remove this flag and output the description by default. + ShowDescription bool } // NewStatus creates a new Status object with the given configuration. From 36d931105212758fb7054068e392a75d40d3a6b9 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 27 Aug 2020 10:38:38 -0400 Subject: [PATCH 75/76] feat(comp): Add support for fish completion Signed-off-by: Marc Khouzam --- cmd/helm/completion.go | 38 ++++++++++++++++++++++++++++++++++++- cmd/helm/completion_test.go | 1 + 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index db50b375a..275483f45 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -52,6 +52,25 @@ To load completions for every new session, execute once: $ helm completion zsh > "${fpath[1]}/_helm" ` +const fishCompDesc = ` +Generate the autocompletion script for Helm for the fish shell. + +To load completions in your current shell session: +$ helm completion fish | source + +To load completions for every new session, execute once: +$ helm completion fish > ~/.config/fish/completions/helm.fish + +You will need to start a new shell for this setup to take effect. +` + +const ( + noDescFlagName = "no-descriptions" + noDescFlagText = "disable completion descriptions" +) + +var disableCompDescriptions bool + func newCompletionCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "completion", @@ -85,7 +104,20 @@ func newCompletionCmd(out io.Writer) *cobra.Command { }, } - cmd.AddCommand(bash, zsh) + fish := &cobra.Command{ + Use: "fish", + Short: "generate autocompletions script for fish", + Long: fishCompDesc, + Args: require.NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: noCompletions, + RunE: func(cmd *cobra.Command, args []string) error { + return runCompletionFish(out, cmd) + }, + } + fish.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText) + + cmd.AddCommand(bash, zsh, fish) return cmd } @@ -257,6 +289,10 @@ __helm_bash_source <(__helm_convert_bash_to_zsh) return nil } +func runCompletionFish(out io.Writer, cmd *cobra.Command) error { + return cmd.Root().GenFishCompletion(out, !disableCompDescriptions) +} + // Function to disable file completion func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/helm/completion_test.go b/cmd/helm/completion_test.go index 44c74f85a..7eee39832 100644 --- a/cmd/helm/completion_test.go +++ b/cmd/helm/completion_test.go @@ -55,4 +55,5 @@ func TestCompletionFileCompletion(t *testing.T) { checkFileCompletion(t, "completion", false) checkFileCompletion(t, "completion bash", false) checkFileCompletion(t, "completion zsh", false) + checkFileCompletion(t, "completion fish", false) } From daa104d60e258fff57da12f13c49ecdcba1263c6 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Thu, 3 Sep 2020 17:13:31 +0000 Subject: [PATCH 76/76] Revert PR 8562 Revert of PR 8562 as the container version may not represent the application version. Signed-off-by: Martin Hickey --- pkg/chartutil/create.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 8d8f48176..6e382b961 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -425,7 +425,9 @@ Common labels {{- define ".labels" -}} helm.sh/chart: {{ include ".chart" . }} {{ include ".selectorLabels" . }} -app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }}