feat(comp): Move custom completions to Cobra 1.0

Cobra 1.0 introduces custom Go completions.  This commit replaces Helm's
own solution to use Cobra's solution.

This allows to completely remove Helm's internal "completion" package.

Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>
pull/8306/head
Marc Khouzam 4 years ago committed by Marc Khouzam
parent a01adcebfd
commit 88a3d6eaea

@ -18,12 +18,12 @@ package main
import (
"fmt"
"log"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values"
@ -55,19 +55,22 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
// bindOutputFlag will add the output flag to the given command and bind the
// value to the given format pointer
func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
f := cmd.Flags()
flag := f.VarPF(newOutputValue(output.Table, varRef), outputFlag, "o",
cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o",
fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", ")))
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var formatNames []string
for _, format := range output.Formats() {
if strings.HasPrefix(format, toComplete) {
formatNames = append(formatNames, format)
}
}
return formatNames, completion.BashCompDirectiveDefault
return formatNames, cobra.ShellCompDirectiveDefault
})
if err != nil {
log.Fatal(err)
}
}
type outputValue output.Format

@ -18,11 +18,11 @@ package main
import (
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -41,6 +41,12 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all information for a named release",
Long: getAllHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -57,24 +63,19 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
f.StringVar(&template, "template", "", "go template for formatting the output, eg: {{.Release.Name}}")
return cmd

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -41,6 +41,12 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all hooks for a named release",
Long: getHooksHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -53,23 +59,17 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -43,6 +43,12 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "download the manifest for a named release",
Long: getManifestHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -53,23 +59,17 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -39,6 +39,12 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the notes for a named release",
Long: getNotesHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
res, err := client.Run(args[0])
if err != nil {
@ -51,23 +57,18 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
return cmd
}

@ -19,11 +19,11 @@ package main
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -46,6 +46,12 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the values file for a named release",
Long: getValuesHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
vals, err := client.Run(args[0])
if err != nil {
@ -55,23 +61,19 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values")
bindOutputFlag(cmd, &outfmt)

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/cli/output"
@ -61,6 +60,12 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "fetch release history",
Aliases: []string{"hist"},
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
history, err := getHistory(client, args[0])
if err != nil {
@ -71,14 +76,6 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
bindOutputFlag(cmd, &outfmt)
@ -187,7 +184,7 @@ func min(x, y int) int {
return y
}
func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, completion.BashCompDirective) {
func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
client := action.NewHistory(cfg)
var revisions []string
@ -195,7 +192,7 @@ func compListRevisions(cfg *action.Configuration, releaseName string) ([]string,
for _, release := range hist {
revisions = append(revisions, strconv.Itoa(release.Version))
}
return revisions, completion.BashCompDirectiveDefault
return revisions, cobra.ShellCompDirectiveDefault
}
return nil, completion.BashCompDirectiveError
return nil, cobra.ShellCompDirectiveError
}

@ -26,7 +26,6 @@ import (
"github.com/spf13/pflag"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
@ -114,6 +113,9 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "install a chart",
Long: installDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client)
},
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
@ -124,11 +126,6 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compInstall(args, toComplete, client)
})
addInstallFlags(cmd.Flags(), client, valueOpts)
bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer)
@ -242,7 +239,7 @@ func isChartInstallable(ch *chart.Chart) (bool, error) {
}
// Provide dynamic auto-completion for the install and template commands
func compInstall(args []string, toComplete string, client *action.Install) ([]string, completion.BashCompDirective) {
func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) {
requiredArgs := 1
if client.GenerateName {
requiredArgs = 0
@ -250,5 +247,5 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
if len(args) == requiredArgs {
return compListCharts(toComplete, true)
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/release"
@ -186,8 +185,8 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error {
}
// Provide dynamic auto-completion for release names
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, completion.BashCompDirective) {
completion.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete))
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)
client := action.NewList(cfg)
client.All = true
@ -197,7 +196,7 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
client.SetStateMask()
results, err := client.Run()
if err != nil {
return nil, completion.BashCompDirectiveDefault
return nil, cobra.ShellCompDirectiveDefault
}
var choices []string
@ -205,5 +204,5 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
choices = append(choices, res.Name)
}
return choices, completion.BashCompDirectiveNoFileComp
return choices, cobra.ShellCompDirectiveNoFileComp
}

@ -32,7 +32,6 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
)
@ -97,6 +96,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
// This passes all the flags to the subcommand.
DisableFlagParsing: true,
}
// TODO: Make sure a command with this name does not already exist.
baseCmd.AddCommand(c)
@ -105,7 +105,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
// We only do this when necessary (for the "completion" and "__complete" commands) to avoid the
// risk of a rogue plugin affecting Helm's normal behavior.
subCmd, _, err := baseCmd.Find(os.Args[1:])
if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == completion.CompRequestCmd)) ||
if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == cobra.ShellCompRequestCmd)) ||
/* for the tests */ subCmd == baseCmd.Root() {
loadCompletionForPlugin(c, plug)
}
@ -247,9 +247,9 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// Only setup dynamic completion if there are no sub-commands. This avoids
// calling plugin.complete at every completion, which greatly simplifies
// development of plugin.complete for plugin developers.
completion.RegisterValidArgsFunc(baseCmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
baseCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return pluginDynamicComp(plugin, cmd, args, toComplete)
})
}
}
// Create fake flags.
@ -322,12 +322,12 @@ func loadFile(path string) (*pluginCommand, error) {
// pluginDynamicComp call the plugin.complete script of the plugin (if available)
// to obtain the dynamic completion choices. It must pass all the flags and sub-commands
// specified in the command-line to the plugin.complete executable (except helm's global flags)
func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
md := plug.Metadata
u, err := processParent(cmd, args)
if err != nil {
return nil, completion.BashCompDirectiveError
return nil, cobra.ShellCompDirectiveError
}
// We will call the dynamic completion script of the plugin
@ -343,12 +343,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
}
plugin.SetupPluginEnv(settings, md.Name, plug.Dir)
completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv))
cobra.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv), settings.Debug)
buf := new(bytes.Buffer)
if err := callPluginExecutable(md.Name, main, argv, buf); err != nil {
// The dynamic completion file is optional for a plugin, so this error is ok.
completion.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()))
return nil, completion.BashCompDirectiveDefault
cobra.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()), settings.Debug)
return nil, cobra.ShellCompDirectiveDefault
}
var completions []string
@ -361,12 +361,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
// Check if the last line of output is of the form :<integer>, which
// indicates the BashCompletionDirective.
directive := completion.BashCompDirectiveDefault
directive := cobra.ShellCompDirectiveDefault
if len(completions) > 0 {
lastLine := completions[len(completions)-1]
if len(lastLine) > 1 && lastLine[0] == ':' {
if strInt, err := strconv.Atoi(lastLine[1:]); err == nil {
directive = completion.BashCompDirective(strInt)
directive = cobra.ShellCompDirective(strInt)
completions = completions[:len(completions)-1]
}
}

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
)
@ -39,6 +38,12 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
Use: "uninstall <plugin>...",
Aliases: []string{"rm", "remove"},
Short: "uninstall one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args)
},
@ -46,15 +51,6 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -24,7 +24,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/plugin"
"helm.sh/helm/v3/pkg/plugin/installer"
)
@ -40,6 +39,12 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
Use: "update <plugin>...",
Aliases: []string{"up"},
Short: "update one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return o.complete(args)
},
@ -47,15 +52,6 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -23,7 +23,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -51,6 +50,12 @@ func newPullCmd(out io.Writer) *cobra.Command {
Aliases: []string{"fetch"},
Long: pullDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListCharts(toComplete, false)
},
RunE: func(cmd *cobra.Command, args []string) error {
client.Settings = settings
if client.Version == "" && client.Devel {
@ -69,14 +74,6 @@ func newPullCmd(out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListCharts(toComplete, false)
})
f := cmd.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.")
f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")

@ -24,7 +24,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
@ -46,6 +45,12 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "run tests for a release",
Long: releaseTestHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace()
rel, runErr := client.Run(args[0])
@ -72,14 +77,6 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo"
)
@ -45,6 +44,9 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm"},
Short: "remove one or more chart repositories",
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache
@ -52,12 +54,6 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
return o.run(out)
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compListRepos(toComplete, args), completion.BashCompDirectiveNoFileComp
})
return cmd
}

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -47,6 +46,12 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
ver, err := strconv.Atoi(args[1])
@ -65,14 +70,6 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")

@ -20,6 +20,7 @@ import (
"context"
"fmt"
"io"
"log"
"strings"
"github.com/spf13/cobra"
@ -27,7 +28,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action"
)
@ -70,24 +70,22 @@ By default, the default directories depend on the Operating System. The defaults
func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
cmd := &cobra.Command{
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
BashCompletionFunction: completion.GetBashCustomFunction(),
Use: "helm",
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
}
flags := cmd.PersistentFlags()
settings.AddFlags(flags)
// Setup shell completion for the namespace flag
flag := flags.Lookup("namespace")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if client, err := actionConfig.KubernetesClientSet(); err == nil {
// Choose a long enough timeout that the user notices somethings is not working
// but short enough that the user is not made to wait very long
to := int64(3)
completion.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to))
cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)
nsNames := []string{}
if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
@ -96,16 +94,19 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
nsNames = append(nsNames, ns.Name)
}
}
return nsNames, completion.BashCompDirectiveNoFileComp
return nsNames, cobra.ShellCompDirectiveNoFileComp
}
}
return nil, completion.BashCompDirectiveDefault
return nil, cobra.ShellCompDirectiveDefault
})
if err != nil {
log.Fatal(err)
}
// Setup shell completion for the kube-context flag
flag = flags.Lookup("kube-context")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
completion.CompDebugln("About to get the different kube-contexts")
err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if len(settings.KubeConfig) > 0 {
@ -120,11 +121,15 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
ctxs = append(ctxs, name)
}
}
return ctxs, completion.BashCompDirectiveNoFileComp
return ctxs, cobra.ShellCompDirectiveNoFileComp
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
// We can safely ignore any errors that flags.Parse encounters since
// those errors will be caught later during the call to cmd.Execution.
// This call is required to gather configuration information prior to
@ -164,9 +169,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Hidden documentation generator command: 'helm docs'
newDocsCmd(out),
// Setup the special hidden __complete command to allow for dynamic auto-completion
completion.NewCompleteCmd(settings, out),
)
// Add *experimental* subcommands

@ -32,7 +32,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/search"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo"
@ -290,8 +289,8 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
// Provide dynamic auto-completion for commands that operate on charts (e.g., helm show)
// When true, the includeFiles argument indicates that completion should include local files (e.g., local charts)
func compListCharts(toComplete string, includeFiles bool) ([]string, completion.BashCompDirective) {
completion.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete))
func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete), settings.Debug)
noSpace := false
noFile := false
@ -312,7 +311,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
noSpace = true
}
}
completion.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
// Now handle completions for url prefixes
for _, url := range []string{"https://", "http://", "file://"} {
@ -328,7 +327,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
noSpace = true
}
}
completion.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions), settings.Debug)
// Finally, provide file completion if we need to.
// We only do this if:
@ -347,22 +346,22 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion.
}
}
}
completion.CompDebugln(fmt.Sprintf("Completions after files: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after files: %v", completions), settings.Debug)
// If the user didn't provide any input to completion,
// we provide a hint that a path can also be used
if includeFiles && len(toComplete) == 0 {
completions = append(completions, "./", "/")
}
completion.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions))
cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug)
directive := completion.BashCompDirectiveDefault
directive := cobra.ShellCompDirectiveDefault
if noFile {
directive = directive | completion.BashCompDirectiveNoFileComp
directive = directive | cobra.ShellCompDirectiveNoFileComp
}
if noSpace {
directive = directive | completion.BashCompDirectiveNoSpace
// The completion.BashCompDirective flags do not work for zsh right now.
directive = directive | cobra.ShellCompDirectiveNoSpace
// The cobra.ShellCompDirective flags do not work for zsh right now.
// We handle it ourselves instead.
completions = compEnforceNoSpace(completions)
}
@ -380,7 +379,7 @@ func compEnforceNoSpace(completions []string) []string {
// We only do this if there is a single choice for completion.
if len(completions) == 1 {
completions = append(completions, completions[0]+".")
completion.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions))
cobra.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions), settings.Debug)
}
return completions
}

@ -23,7 +23,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -63,18 +62,19 @@ func newShowCmd(out io.Writer) *cobra.Command {
}
// Function providing dynamic auto-completion
validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListCharts(toComplete, true)
}
all := &cobra.Command{
Use: "all [CHART]",
Short: "show all information of the chart",
Long: showAllDesc,
Args: require.ExactArgs(1),
Use: "all [CHART]",
Short: "show all information of the chart",
Long: showAllDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll
output, err := runShow(args, client)
@ -87,10 +87,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
}
valuesSubCmd := &cobra.Command{
Use: "values [CHART]",
Short: "show the chart's values",
Long: showValuesDesc,
Args: require.ExactArgs(1),
Use: "values [CHART]",
Short: "show the chart's values",
Long: showValuesDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues
output, err := runShow(args, client)
@ -103,10 +104,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
}
chartSubCmd := &cobra.Command{
Use: "chart [CHART]",
Short: "show the chart's definition",
Long: showChartDesc,
Args: require.ExactArgs(1),
Use: "chart [CHART]",
Short: "show the chart's definition",
Long: showChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart
output, err := runShow(args, client)
@ -119,10 +121,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
}
readmeSubCmd := &cobra.Command{
Use: "readme [CHART]",
Short: "show the chart's README",
Long: readmeChartDesc,
Args: require.ExactArgs(1),
Use: "readme [CHART]",
Short: "show the chart's README",
Long: readmeChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme
output, err := runShow(args, client)
@ -138,9 +141,6 @@ func newShowCmd(out io.Writer) *cobra.Command {
for _, subCmd := range cmds {
addShowFlags(subCmd, client)
showCommand.AddCommand(subCmd)
// Register the completion function for each subcommand
completion.RegisterValidArgsFunc(subCmd, validArgsFunc)
}
return showCommand

@ -19,13 +19,13 @@ package main
import (
"fmt"
"io"
"log"
"strings"
"time"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/output"
@ -53,6 +53,12 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "display the status of the named release",
Long: statusHelp,
Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
rel, err := client.Run(args[0])
if err != nil {
@ -66,25 +72,21 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
flag := f.Lookup("revision")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(cfg, args[0])
}
return nil, completion.BashCompDirectiveNoFileComp
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
bindOutputFlag(cmd, &outfmt)
return cmd

@ -28,7 +28,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/values"
@ -56,6 +55,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "locally render templates",
Long: templateDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client)
},
RunE: func(_ *cobra.Command, args []string) error {
client.DryRun = true
client.ReleaseName = "RELEASE-NAME"
@ -136,11 +138,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
return compInstall(args, toComplete, client)
})
f := cmd.Flags()
addInstallFlags(f, client, valueOpts)
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")

@ -2,3 +2,4 @@ table
json
yaml
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -3,3 +3,4 @@ Namespace: default
Num args received: 1
Args received:
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -3,3 +3,4 @@ Namespace: default
Num args received: 2
Args received: --myflag
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 2
Args received: --myflag start
:2
Completion ended with directive: ShellCompDirectiveNoSpace

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 1
Args received:
:2
Completion ended with directive: ShellCompDirectiveNoSpace

@ -3,3 +3,4 @@ Namespace: default
Num args received: 1
Args received:
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -3,3 +3,4 @@ Namespace: mynamespace
Num args received: 1
Args received:
:0
Completion ended with directive: ShellCompDirectiveDefault

@ -3,3 +3,4 @@
10
11
:0
Completion ended with directive: ShellCompDirectiveDefault

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

@ -1,3 +1,4 @@
aramis
athos
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

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

@ -24,7 +24,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
)
@ -48,6 +47,12 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "uninstall a release",
Long: uninstallDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
for i := 0; i < len(args); i++ {
@ -65,14 +70,6 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) != 0 {
return nil, completion.BashCompDirectiveNoFileComp
}
return compListReleases(toComplete, cfg)
})
f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/output"
@ -72,6 +71,15 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "upgrade a release",
Long: upgradeDesc,
Args: require.ExactArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return compListReleases(toComplete, cfg)
}
if len(args) == 1 {
return compListCharts(toComplete, true)
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace()
@ -155,17 +163,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
},
}
// Function providing dynamic auto-completion
completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if len(args) == 0 {
return compListReleases(toComplete, cfg)
}
if len(args) == 1 {
return compListCharts(toComplete, true)
}
return nil, completion.BashCompDirectiveNoFileComp
})
f := cmd.Flags()
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")

@ -1,399 +0,0 @@
/*
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 completion
import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/pkg/cli"
)
// ==================================================================================
// The below code supports dynamic shell completion in Go.
// This should ultimately be pushed down into Cobra.
// ==================================================================================
// CompRequestCmd Hidden command to request completion results from the program.
// Used by the shell completion script.
const CompRequestCmd = "__complete"
// Global map allowing to find completion functions for commands or flags.
var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){}
// BashCompDirective is a bit map representing the different behaviors the shell
// can be instructed to have once completions have been provided.
type BashCompDirective int
const (
// BashCompDirectiveError indicates an error occurred and completions should be ignored.
BashCompDirectiveError BashCompDirective = 1 << iota
// BashCompDirectiveNoSpace indicates that the shell should not add a space
// after the completion even if there is a single completion provided.
BashCompDirectiveNoSpace
// BashCompDirectiveNoFileComp indicates that the shell should not provide
// file completion even when no completion is provided.
// This currently does not work for zsh or bash < 4
BashCompDirectiveNoFileComp
// BashCompDirectiveDefault indicates to let the shell perform its default
// behavior after completions have been provided.
BashCompDirectiveDefault BashCompDirective = 0
)
// GetBashCustomFunction returns the bash code to handle custom go completion
// This should eventually be provided by Cobra
func GetBashCustomFunction() string {
return fmt.Sprintf(`
__helm_custom_func()
{
__helm_debug "${FUNCNAME[0]}: c is $c, words[@] is ${words[@]}, #words[@] is ${#words[@]}"
__helm_debug "${FUNCNAME[0]}: cur is ${cur}, cword is ${cword}, words is ${words}"
local out requestComp lastParam lastChar
requestComp="${words[0]} %[1]s ${words[@]:1}"
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
__helm_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__helm_debug "${FUNCNAME[0]}: Adding extra empty parameter"
requestComp="${requestComp} \"\""
fi
__helm_debug "${FUNCNAME[0]}: calling ${requestComp}"
# Use eval to handle any environment variables and such
out=$(eval ${requestComp} 2>/dev/null)
# Extract the directive int at the very end of the output following a :
directive=${out##*:}
# Remove the directive
out=${out%%:*}
if [ "${directive}" = "${out}" ]; then
# There is not directive specified
directive=0
fi
__helm_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
__helm_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
if [ $((${directive} & %[2]d)) -ne 0 ]; then
__helm_debug "${FUNCNAME[0]}: received error, completion failed"
else
if [ $((${directive} & %[3]d)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__helm_debug "${FUNCNAME[0]}: activating no space"
compopt -o nospace
fi
fi
if [ $((${directive} & %[4]d)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__helm_debug "${FUNCNAME[0]}: activating no file completion"
compopt +o default
fi
fi
while IFS='' read -r comp; do
COMPREPLY+=("$comp")
done < <(compgen -W "${out[*]}" -- "$cur")
fi
}
`, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)
}
// RegisterValidArgsFunc should be called to register a function to provide argument completion for a command
func RegisterValidArgsFunc(cmd *cobra.Command, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) {
if _, exists := validArgsFunctions[cmd]; exists {
log.Fatal(fmt.Sprintf("RegisterValidArgsFunc: command '%s' already registered", cmd.Name()))
}
validArgsFunctions[cmd] = f
}
// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag
func RegisterFlagCompletionFunc(flag *pflag.Flag, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) {
if _, exists := validArgsFunctions[flag]; exists {
log.Fatal(fmt.Sprintf("RegisterFlagCompletionFunc: flag '%s' already registered", flag.Name))
}
validArgsFunctions[flag] = f
// Make sure the completion script call the __helm_custom_func for the registered flag.
// This is essential to make the = form work. E.g., helm -n=<TAB> or helm status --output=<TAB>
if flag.Annotations == nil {
flag.Annotations = map[string][]string{}
}
flag.Annotations[cobra.BashCompCustom] = []string{"__helm_custom_func"}
}
var debug = true
// Returns a string listing the different directive enabled in the specified parameter
func (d BashCompDirective) string() string {
var directives []string
if d&BashCompDirectiveError != 0 {
directives = append(directives, "BashCompDirectiveError")
}
if d&BashCompDirectiveNoSpace != 0 {
directives = append(directives, "BashCompDirectiveNoSpace")
}
if d&BashCompDirectiveNoFileComp != 0 {
directives = append(directives, "BashCompDirectiveNoFileComp")
}
if len(directives) == 0 {
directives = append(directives, "BashCompDirectiveDefault")
}
if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d)
}
return strings.Join(directives, ", ")
}
// NewCompleteCmd add a special hidden command that an be used to request completions
func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command {
debug = settings.Debug
return &cobra.Command{
Use: fmt.Sprintf("%s [command-line]", CompRequestCmd),
DisableFlagsInUseLine: true,
Hidden: true,
DisableFlagParsing: true,
Args: require.MinimumNArgs(1),
Short: "Request shell completion choices for the specified command-line",
Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s",
CompRequestCmd, "to request completion choices for the specified command-line."),
Run: func(cmd *cobra.Command, args []string) {
CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args))
// The last argument, which is not complete, should not be part of the list of arguments
toComplete := args[len(args)-1]
trimmedArgs := args[:len(args)-1]
// Find the real command for which completion must be performed
finalCmd, finalArgs, err := cmd.Root().Find(trimmedArgs)
if err != nil {
// Unable to find the real command. E.g., helm invalidCmd <TAB>
CompDebugln(fmt.Sprintf("Unable to find a command for arguments: %v", trimmedArgs))
return
}
CompDebugln(fmt.Sprintf("Found final command '%s', with finalArgs %v", finalCmd.Name(), finalArgs))
var flag *pflag.Flag
if !finalCmd.DisableFlagParsing {
// We only do flag completion if we are allowed to parse flags
// This is important for helm plugins which need to do their own flag completion.
flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
if err != nil {
// Error while attempting to parse flags
CompErrorln(err.Error())
return
}
}
// Parse the flags and extract the arguments to prepare for calling the completion function
if err = finalCmd.ParseFlags(finalArgs); err != nil {
CompErrorln(fmt.Sprintf("Error while parsing flags from args %v: %s", finalArgs, err.Error()))
return
}
// We only remove the flags from the arguments if DisableFlagParsing is not set.
// This is important for helm plugins, which need to receive all flags.
// The plugin completion code will do its own flag parsing.
if !finalCmd.DisableFlagParsing {
finalArgs = finalCmd.Flags().Args()
CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", finalArgs, len(finalArgs)))
}
// Find completion function for the flag or command
var key interface{}
var keyStr string
if flag != nil {
key = flag
keyStr = flag.Name
} else {
key = finalCmd
keyStr = finalCmd.Name()
}
completionFn, ok := validArgsFunctions[key]
if !ok {
CompErrorln(fmt.Sprintf("Dynamic completion not supported/needed for flag or command: %s", keyStr))
return
}
CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), finalArgs, toComplete))
completions, directive := completionFn(finalCmd, finalArgs, toComplete)
for _, comp := range completions {
// Print each possible completion to stdout for the completion script to consume.
fmt.Fprintln(out, comp)
}
if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
directive = BashCompDirectiveDefault
}
// As the last printout, print the completion directive for the
// completion script to parse.
// The directive integer must be that last character following a single :
// The completion script expects :directive
fmt.Fprintf(out, ":%d\n", directive)
// Print some helpful info to stderr for the user to understand.
// Output from stderr should be ignored from the completion script.
fmt.Fprintf(os.Stderr, "Completion ended with directive: %s\n", directive.string())
},
}
}
func isFlag(arg string) bool {
return len(arg) > 0 && arg[0] == '-'
}
func checkIfFlagCompletion(finalCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
var flagName string
trimmedArgs := args
flagWithEqual := false
if isFlag(lastArg) {
if index := strings.Index(lastArg, "="); index >= 0 {
flagName = strings.TrimLeft(lastArg[:index], "-")
lastArg = lastArg[index+1:]
flagWithEqual = true
} else {
return nil, nil, "", errors.New("Unexpected completion request for flag")
}
}
if len(flagName) == 0 {
if len(args) > 0 {
prevArg := args[len(args)-1]
if isFlag(prevArg) {
// If the flag contains an = it means it has already been fully processed
if index := strings.Index(prevArg, "="); index < 0 {
flagName = strings.TrimLeft(prevArg, "-")
// Remove the uncompleted flag or else Cobra could complain about
// an invalid value for that flag e.g., helm status --output j<TAB>
trimmedArgs = args[:len(args)-1]
}
}
}
}
if len(flagName) == 0 {
// Not doing flag completion
return nil, trimmedArgs, lastArg, nil
}
flag := findFlag(finalCmd, flagName)
if flag == nil {
// Flag not supported by this command, nothing to complete
err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
return nil, nil, "", err
}
if !flagWithEqual {
if len(flag.NoOptDefVal) != 0 {
// We had assumed dealing with a two-word flag but the flag is a boolean flag.
// In that case, there is no value following it, so we are not really doing flag completion.
// Reset everything to do argument completion.
trimmedArgs = args
flag = nil
}
}
return flag, trimmedArgs, lastArg, nil
}
func findFlag(cmd *cobra.Command, name string) *pflag.Flag {
flagSet := cmd.Flags()
if len(name) == 1 {
// First convert the short flag into a long flag
// as the cmd.Flag() search only accepts long flags
if short := flagSet.ShorthandLookup(name); short != nil {
CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found flag '%s' which we will change to '%s'", name, short.Name))
name = short.Name
} else {
set := cmd.InheritedFlags()
if short = set.ShorthandLookup(name); short != nil {
CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found inherited flag '%s' which we will change to '%s'", name, short.Name))
name = short.Name
} else {
return nil
}
}
}
return cmd.Flag(name)
}
// CompDebug prints the specified string to the same file as where the
// completion script prints its logs.
// Note that completion printouts should never be on stdout as they would
// be wrongly interpreted as actual completion choices by the completion script.
func CompDebug(msg string) {
msg = fmt.Sprintf("[Debug] %s", msg)
// Such logs are only printed when the user has set the environment
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
f, err := os.OpenFile(path,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
defer f.Close()
f.WriteString(msg)
}
}
if debug {
// Must print to stderr for this not to be read by the completion script.
fmt.Fprintln(os.Stderr, msg)
}
}
// CompDebugln prints the specified string with a newline at the end
// to the same file as where the completion script prints its logs.
// Such logs are only printed when the user has set the environment
// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
func CompDebugln(msg string) {
CompDebug(fmt.Sprintf("%s\n", msg))
}
// CompError prints the specified completion message to stderr.
func CompError(msg string) {
msg = fmt.Sprintf("[Error] %s", msg)
CompDebug(msg)
// If not already printed by the call to CompDebug().
if !debug {
// Must print to stderr for this not to be read by the completion script.
fmt.Fprintln(os.Stderr, msg)
}
}
// CompErrorln prints the specified completion message to stderr with a newline at the end.
func CompErrorln(msg string) {
CompError(fmt.Sprintf("%s\n", msg))
}
Loading…
Cancel
Save