feat(comp): Provide completion for --version flag

This commit allows to use shell completion to obtain the list of
available versions of a chart referenced in a 'repo/chart' format.
It applies to:
- helm install
- helm template
- helm upgrade
- helm show
- helm pull

The 'repo/chart' argument must be present for completion to be triggered
(or else we don't yet know which chart to fetch the versions for).

The completion can be slow for the 'stable' repo because its index
file takes some time to parse.

Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>
pull/7431/head
Marc Khouzam 5 years ago committed by Marc Khouzam
parent 8e5e6caee2
commit 561cc42808

@ -19,6 +19,7 @@ package main
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@ -27,7 +28,9 @@ import (
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/repo"
)
const outputFlag = "output"
@ -127,3 +130,27 @@ func (p postRenderer) Set(s string) error {
*p.renderer = pr
return nil
}
func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) {
chartInfo := strings.Split(chartRef, "/")
if len(chartInfo) != 2 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
repoName := chartInfo[0]
chartName := chartInfo[1]
path := filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(repoName))
var versions []string
if indexFile, err := repo.LoadIndexFile(path); err == nil {
for _, details := range indexFile.Entries[chartName] {
version := details.Metadata.Version
if strings.HasPrefix(version, toComplete) {
versions = append(versions, version)
}
}
}
return versions, cobra.ShellCompDirectiveNoFileComp
}

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"log"
"time"
"github.com/pkg/errors"
@ -126,14 +127,14 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
addInstallFlags(cmd.Flags(), client, valueOpts)
addInstallFlags(cmd, cmd.Flags(), client, valueOpts)
bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer)
return cmd
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
@ -151,6 +152,21 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
requiredArgs := 2
if client.GenerateName {
requiredArgs = 1
}
if len(args) != requiredArgs {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[requiredArgs-1], toComplete)
})
if err != nil {
log.Fatal(err)
}
}
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"fmt"
"testing"
)
@ -208,3 +209,33 @@ func TestInstall(t *testing.T) {
func TestInstallOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "install")
}
func TestInstallVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for install version flag with release name",
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for install version flag with generate-name",
cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for install version flag too few args",
cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for install version flag too many args",
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for install version flag invalid chart",
cmd: fmt.Sprintf("%s __complete install releasename invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
@ -82,5 +83,16 @@ func newPullCmd(out io.Writer) *cobra.Command {
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[0], toComplete)
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -195,3 +195,29 @@ func TestPullCmd(t *testing.T) {
})
}
}
func TestPullVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for pull version flag",
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for pull version flag too few args",
cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for pull version flag too many args",
cmd: fmt.Sprintf("%s __complete pull testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for pull version flag invalid chart",
cmd: fmt.Sprintf("%s __complete pull invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
@ -151,6 +152,17 @@ 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")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := subCmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[0], toComplete)
})
if err != nil {
log.Fatal(err)
}
}
func runShow(args []string, client *action.Show) (string, error) {

@ -80,3 +80,41 @@ func TestShowPreReleaseChart(t *testing.T) {
})
}
}
func TestShowVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for show version flag",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag too few args",
cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag too many args",
cmd: fmt.Sprintf("%s __complete show chart testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag invalid chart",
cmd: fmt.Sprintf("%s __complete show chart invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for show version flag with all",
cmd: fmt.Sprintf("%s __complete show all testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag with readme",
cmd: fmt.Sprintf("%s __complete show readme testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for show version flag with values",
cmd: fmt.Sprintf("%s __complete show values testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}}
runTestCmd(t, tests)
}

@ -139,7 +139,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
f := cmd.Flags()
addInstallFlags(f, client, valueOpts)
addInstallFlags(cmd, f, client, valueOpts)
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install")

@ -124,3 +124,33 @@ func TestTemplateCmd(t *testing.T) {
}
runTestCmd(t, tests)
}
func TestTemplateVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for template version flag with release name",
cmd: fmt.Sprintf("%s __complete template releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for template version flag with generate-name",
cmd: fmt.Sprintf("%s __complete template --generate-name testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for template version flag too few args",
cmd: fmt.Sprintf("%s __complete template testing/alpine --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for template version flag too many args",
cmd: fmt.Sprintf("%s __complete template releasename testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for template version flag invalid chart",
cmd: fmt.Sprintf("%s __complete template releasename invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}

@ -0,0 +1,5 @@
0.3.0-rc.1
0.2.0
0.1.0
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1,2 @@
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"log"
"time"
"github.com/pkg/errors"
@ -188,5 +189,16 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 2 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compVersionFlag(args[1], toComplete)
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -381,3 +381,29 @@ func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int,
func TestUpgradeOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "upgrade")
}
func TestUpgradeVersionCompletion(t *testing.T) {
repoFile := "testdata/helmhome/helm/repositories.yaml"
repoCache := "testdata/helmhome/helm/repository"
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
tests := []cmdTestCase{{
name: "completion for upgrade version flag",
cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version ''", repoSetup),
golden: "output/version-comp.txt",
}, {
name: "completion for upgrade version flag too few args",
cmd: fmt.Sprintf("%s __complete upgrade releasename --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for upgrade version flag too many args",
cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine badarg --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}, {
name: "completion for upgrade version flag invalid chart",
cmd: fmt.Sprintf("%s __complete upgrade releasename invalid/invalid --version ''", repoSetup),
golden: "output/version-invalid-comp.txt",
}}
runTestCmd(t, tests)
}

Loading…
Cancel
Save