Repository: helm/helm
Pull request #9243 (2021-01-17 04:53:03)

Add descriptions to custom completions (part 1)
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/master/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:

This is part 1 of the implementation of HIP 008: https://github.com/helm/community/pull/161

For the `zsh` and `fish` shells, this PR adds descriptions to Helm's custom completions to make them more informative to the the user.  Specifically:
- release names
```
$ helm history n<TAB>
nginx   -- nginx-6.0.2 -> deployed
nginx2  -- nginx-ingress-1.41.2 -> deployed
```
- plugin names
```
$ helm plugin uninstall <TAB>
2to3        -- migrate and cleanup Helm v2 configuration and releases in-place to Helm v3
diff        -- Preview helm upgrade changes as a diff
fullstatus  -- provide status of resources part of the release
github      -- Install or upgrade Helm charts from GitHub repos
```
- kubernetes contexts
```
$ helm --kube-context <TAB>
acc     -- accept
default -- k3d-k3s-default
lab     -- lab
lab2    -- cluster.local
```
- release revisions
```
$ helm get manifest nginx --revision <TAB>
1  -- App: 1.19.1, Chart: nginx-6.0.2
2  -- App: v0.34.1, Chart: nginx-ingress-1.41.2
```
- chart versions
```
$ helm upgrade nginx stable/grafana --version <TAB>
0.8.4   -- Created: March 30, 2018
0.8.5   -- App: 5.0.4, Created: April 10, 2018
1.0.0   -- App: 5.0.4, Created: April 11, 2018 (deprecated)
1.10.0  -- App: 5.1.2, Created: June 1, 2018
```
- output formats
```
$ helm status -o <TAB>
json   -- Output result in JSON format
table  -- Output result in human-readable format
yaml   -- Output result in YAML format
```

**Special notes for your reviewer**:

This is part 1 of HIP 0008 because the remaining aspects require a little more discussion, so I thought I would do them in a separate PR:
- chart names - completion for chart names uses the special `<repo>-charts.txt` file, which currently does not contain enough information to provide a description for chart names
- helm environment variables - the code changes are more intrusive, so I wanted to keep them seperate
- repo names - adding descriptions will cause a bug in completions like `helm show all bitna[TAB]` due to a couple of bugs in Cobra
    - https://github.com/spf13/cobra/pull/1213
    - https://github.com/spf13/cobra/pull/1249 

**If applicable**:
- [x] this PR contains unit tests



Patch:
diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go
index 5b0288632..87dc23fdc 100644
--- a/cmd/helm/flags.go
+++ b/cmd/helm/flags.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"log"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/spf13/cobra"
@@ -66,11 +67,14 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
 
 	err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		var formatNames []string
-		for _, format := range output.Formats() {
+		for format, desc := range output.FormatsWithDesc() {
 			if strings.HasPrefix(format, toComplete) {
-				formatNames = append(formatNames, format)
+				formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
 			}
 		}
+
+		// Sort the results to get a deterministic order for the tests
+		sort.Strings(formatNames)
 		return formatNames, cobra.ShellCompDirectiveNoFileComp
 	})
 
@@ -150,7 +154,21 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
 		for _, details := range indexFile.Entries[chartName] {
 			version := details.Metadata.Version
 			if strings.HasPrefix(version, toComplete) {
-				versions = append(versions, version)
+				appVersion := details.Metadata.AppVersion
+				appVersionDesc := ""
+				if appVersion != "" {
+					appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
+				}
+				created := details.Created.Format("January 2, 2006")
+				createdDesc := ""
+				if created != "" {
+					createdDesc = fmt.Sprintf("Created: %s ", created)
+				}
+				deprecated := ""
+				if details.Metadata.Deprecated {
+					deprecated = "(deprecated)"
+				}
+				versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated))
 			}
 		}
 	}
diff --git a/cmd/helm/history.go b/cmd/helm/history.go
index f55eea9fd..61f45c946 100644
--- a/cmd/helm/history.go
+++ b/cmd/helm/history.go
@@ -193,7 +193,9 @@ func compListRevisions(toComplete string, cfg *action.Configuration, releaseName
 		for _, release := range hist {
 			version := strconv.Itoa(release.Version)
 			if strings.HasPrefix(version, toComplete) {
-				revisions = append(revisions, version)
+				appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
+				chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
+				revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc))
 			}
 		}
 		return revisions, cobra.ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/list.go b/cmd/helm/list.go
index 9d7bea439..a1db03cf6 100644
--- a/cmd/helm/list.go
+++ b/cmd/helm/list.go
@@ -203,14 +203,15 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
 	client.Filter = fmt.Sprintf("^%s", toComplete)
 
 	client.SetStateMask()
-	results, err := client.Run()
+	releases, err := client.Run()
 	if err != nil {
 		return nil, cobra.ShellCompDirectiveDefault
 	}
 
 	var choices []string
-	for _, res := range results {
-		choices = append(choices, res.Name)
+	for _, rel := range releases {
+		choices = append(choices,
+			fmt.Sprintf("%s\t%s-%s -> %s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version, rel.Info.Status.String()))
 	}
 
 	return choices, cobra.ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go
index eb4e5c815..6c3926a9d 100644
--- a/cmd/helm/plugin_list.go
+++ b/cmd/helm/plugin_list.go
@@ -83,7 +83,7 @@ func compListPlugins(toComplete string, ignoredPluginNames []string) []string {
 		filteredPlugins := filterPlugins(plugins, ignoredPluginNames)
 		for _, p := range filteredPlugins {
 			if strings.HasPrefix(p.Metadata.Name, toComplete) {
-				pNames = append(pNames, p.Metadata.Name)
+				pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage))
 			}
 		}
 	}
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index 8025a9ddf..de24a6208 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -131,13 +131,13 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 		if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 			loadingRules,
 			&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
-			ctxs := []string{}
-			for name := range config.Contexts {
+			comps := []string{}
+			for name, context := range config.Contexts {
 				if strings.HasPrefix(name, toComplete) {
-					ctxs = append(ctxs, name)
+					comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
 				}
 			}
-			return ctxs, cobra.ShellCompDirectiveNoFileComp
+			return comps, cobra.ShellCompDirectiveNoFileComp
 		}
 		return nil, cobra.ShellCompDirectiveNoFileComp
 	})
diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go
index e4eb046fc..a46c977ad 100644
--- a/pkg/cli/output/output.go
+++ b/pkg/cli/output/output.go
@@ -40,6 +40,16 @@ func Formats() []string {
 	return []string{Table.String(), JSON.String(), YAML.String()}
 }
 
+// FormatsWithDesc returns a list of the string representation of the supported formats
+// including a description
+func FormatsWithDesc() map[string]string {
+	return map[string]string{
+		Table.String(): "Output result in human-readable format",
+		JSON.String():  "Output result in JSON format",
+		YAML.String():  "Output result in YAML format",
+	}
+}
+
 // ErrInvalidFormatType is returned when an unsupported format type is used
 var ErrInvalidFormatType = fmt.Errorf("invalid format type")
 


Test patch:
diff --git a/cmd/helm/completion_test.go b/cmd/helm/completion_test.go
index 7eee39832..bd94f6b4c 100644
--- a/cmd/helm/completion_test.go
+++ b/cmd/helm/completion_test.go
@@ -29,9 +29,14 @@ import (
 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{},
+		Name: "myrelease",
+		Info: &release.Info{Status: release.StatusDeployed},
+		Chart: &chart.Chart{
+			Metadata: &chart.Metadata{
+				Name:    "Myrelease-Chart",
+				Version: "1.2.3",
+			},
+		},
 		Version: 1,
 	})
 
diff --git a/cmd/helm/status_test.go b/cmd/helm/status_test.go
index 280f486a3..7f305d56b 100644
--- a/cmd/helm/status_test.go
+++ b/cmd/helm/status_test.go
@@ -118,56 +118,72 @@ func mustParseTime(t string) helmtime.Time {
 }
 
 func TestStatusCompletion(t *testing.T) {
-	releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release {
-		info.LastDeployed = helmtime.Unix(1452902400, 0).UTC()
-		return []*release.Release{{
+	rels := []*release.Release{
+		{
 			Name:      "athos",
 			Namespace: "default",
-			Info:      info,
-			Chart:     &chart.Chart{},
-			Hooks:     hooks,
+			Info: &release.Info{
+				Status: release.StatusDeployed,
+			},
+			Chart: &chart.Chart{
+				Metadata: &chart.Metadata{
+					Name:    "Athos-chart",
+					Version: "1.2.3",
+				},
+			},
 		}, {
 			Name:      "porthos",
 			Namespace: "default",
-			Info:      info,
-			Chart:     &chart.Chart{},
-			Hooks:     hooks,
+			Info: &release.Info{
+				Status: release.StatusFailed,
+			},
+			Chart: &chart.Chart{
+				Metadata: &chart.Metadata{
+					Name:    "Porthos-chart",
+					Version: "111.222.333",
+				},
+			},
 		}, {
 			Name:      "aramis",
 			Namespace: "default",
-			Info:      info,
-			Chart:     &chart.Chart{},
-			Hooks:     hooks,
+			Info: &release.Info{
+				Status: release.StatusUninstalled,
+			},
+			Chart: &chart.Chart{
+				Metadata: &chart.Metadata{
+					Name:    "Aramis-chart",
+					Version: "0.0.0",
+				},
+			},
 		}, {
 			Name:      "dartagnan",
 			Namespace: "gascony",
-			Info:      info,
-			Chart:     &chart.Chart{},
-			Hooks:     hooks,
+			Info: &release.Info{
+				Status: release.StatusUnknown,
+			},
+			Chart: &chart.Chart{
+				Metadata: &chart.Metadata{
+					Name:    "Dartagnan-chart",
+					Version: "1.2.3-prerelease",
+				},
+			},
 		}}
-	}
 
 	tests := []cmdTestCase{{
 		name:   "completion for status",
 		cmd:    "__complete status a",
 		golden: "output/status-comp.txt",
-		rels: releasesMockWithStatus(&release.Info{
-			Status: release.StatusDeployed,
-		}),
+		rels:   rels,
 	}, {
 		name:   "completion for status with too many arguments",
 		cmd:    "__complete status dartagnan ''",
 		golden: "output/status-wrong-args-comp.txt",
-		rels: releasesMockWithStatus(&release.Info{
-			Status: release.StatusDeployed,
-		}),
+		rels:   rels,
 	}, {
-		name:   "completion for status with too many arguments",
+		name:   "completion for status with global flag",
 		cmd:    "__complete status --debug a",
 		golden: "output/status-comp.txt",
-		rels: releasesMockWithStatus(&release.Info{
-			Status: release.StatusDeployed,
-		}),
+		rels:   rels,
 	}}
 	runTestCmd(t, tests)
 }
diff --git a/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml b/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml
index 889d7d87a..91e4d463f 100644
--- a/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml
+++ b/cmd/helm/testdata/helmhome/helm/repository/testing-index.yaml
@@ -4,6 +4,8 @@ entries:
     - name: alpine
       url: https://charts.helm.sh/stable/alpine-0.1.0.tgz
       checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
+      created: "2018-06-27T10:00:18.230700509Z"
+      deprecated: true
       home: https://helm.sh/helm
       sources:
         - https://github.com/helm/helm
@@ -17,6 +19,7 @@ entries:
     - name: alpine
       url: https://charts.helm.sh/stable/alpine-0.2.0.tgz
       checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
+      created: "2018-07-09T11:34:37.797864902Z"
       home: https://helm.sh/helm
       sources:
         - https://github.com/helm/helm
@@ -30,6 +33,7 @@ entries:
     - name: alpine
       url: https://charts.helm.sh/stable/alpine-0.3.0-rc.1.tgz
       checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
+      created: "2020-11-12T08:44:58.872726222Z"
       home: https://helm.sh/helm
       sources:
         - https://github.com/helm/helm
@@ -44,6 +48,7 @@ entries:
     - name: mariadb
       url: https://charts.helm.sh/stable/mariadb-0.3.0.tgz
       checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
+      created: "2018-04-23T08:20:27.160959131Z"
       home: https://mariadb.org
       sources:
         - https://github.com/bitnami/bitnami-docker-mariadb
diff --git a/cmd/helm/testdata/output/output-comp.txt b/cmd/helm/testdata/output/output-comp.txt
index e7799a56b..6232b2928 100644
--- a/cmd/helm/testdata/output/output-comp.txt
+++ b/cmd/helm/testdata/output/output-comp.txt
@@ -1,5 +1,5 @@
-table
-json
-yaml
+json	Output result in JSON format
+table	Output result in human-readable format
+yaml	Output result in YAML format
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/plugin_list_comp.txt b/cmd/helm/testdata/output/plugin_list_comp.txt
index e33b6ce52..833efc5e9 100644
--- a/cmd/helm/testdata/output/plugin_list_comp.txt
+++ b/cmd/helm/testdata/output/plugin_list_comp.txt
@@ -1,7 +1,7 @@
-args
-echo
-env
-exitwith
-fullenv
+args	echo args
+echo	echo stuff
+env	env stuff
+exitwith	exitwith code
+fullenv	show env vars
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/plugin_repeat_comp.txt b/cmd/helm/testdata/output/plugin_repeat_comp.txt
index 9e2ee56ab..3fa05f0b3 100644
--- a/cmd/helm/testdata/output/plugin_repeat_comp.txt
+++ b/cmd/helm/testdata/output/plugin_repeat_comp.txt
@@ -1,6 +1,6 @@
-echo
-env
-exitwith
-fullenv
+echo	echo stuff
+env	env stuff
+exitwith	exitwith code
+fullenv	show env vars
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/revision-comp.txt b/cmd/helm/testdata/output/revision-comp.txt
index 50f7a9092..fe9faf1f1 100644
--- a/cmd/helm/testdata/output/revision-comp.txt
+++ b/cmd/helm/testdata/output/revision-comp.txt
@@ -1,6 +1,6 @@
-8
-9
-10
-11
+8	App: 1.0, Chart: foo-0.1.0-beta.1
+9	App: 1.0, Chart: foo-0.1.0-beta.1
+10	App: 1.0, Chart: foo-0.1.0-beta.1
+11	App: 1.0, Chart: foo-0.1.0-beta.1
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/rollback-comp.txt b/cmd/helm/testdata/output/rollback-comp.txt
index f7741af12..2cfeed1f9 100644
--- a/cmd/helm/testdata/output/rollback-comp.txt
+++ b/cmd/helm/testdata/output/rollback-comp.txt
@@ -1,4 +1,4 @@
-carabins
-musketeers
+carabins	foo-0.1.0-beta.1 -> superseded
+musketeers	foo-0.1.0-beta.1 -> deployed
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/status-comp.txt b/cmd/helm/testdata/output/status-comp.txt
index 8d4d21df7..4f56ab30a 100644
--- a/cmd/helm/testdata/output/status-comp.txt
+++ b/cmd/helm/testdata/output/status-comp.txt
@@ -1,4 +1,4 @@
-aramis
-athos
+aramis	Aramis-chart-0.0.0 -> uninstalled
+athos	Athos-chart-1.2.3 -> deployed
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/version-comp.txt b/cmd/helm/testdata/output/version-comp.txt
index 098e2cec2..5b0556cf5 100644
--- a/cmd/helm/testdata/output/version-comp.txt
+++ b/cmd/helm/testdata/output/version-comp.txt
@@ -1,5 +1,5 @@
-0.3.0-rc.1
-0.2.0
-0.1.0
+0.3.0-rc.1	App: 3.0.0, Created: November 12, 2020
+0.2.0	App: 2.3.4, Created: July 9, 2018
+0.1.0	App: 1.2.3, Created: June 27, 2018 (deprecated)
 :4
 Completion ended with directive: ShellCompDirectiveNoFileComp