feat(comp): Move kube-context completion to Go

Signed-off-by: Marc Khouzam <marc.khouzam@montreal.ca>
pull/7560/head
Marc Khouzam 6 years ago committed by Marc Khouzam
parent 2d191fc111
commit d6fad6b3c6

@ -30,6 +30,7 @@ import (
// Import to initialize client auth plugins. // Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/gates" "helm.sh/helm/v3/pkg/gates"
@ -67,6 +68,16 @@ func main() {
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if calledCmd, _, err := cmd.Find(os.Args[1:]); err == nil && calledCmd.Name() == completion.CompRequestCmd {
// If completion is being called, we have to check if the completion is for the "--kube-context"
// value; if it is, we cannot call the action.Init() method with an incomplete kube-context value
// or else it will fail immediately. So, we simply unset the invalid kube-context value.
if args := os.Args[1:]; len(args) > 2 && args[len(args)-2] == "--kube-context" {
// We are completing the kube-context value! Reset it as the current value is not valid.
settings.KubeContext = ""
}
}
if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), debug); err != nil { if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), debug); err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/internal/completion"
@ -31,30 +32,6 @@ import (
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
) )
const (
contextCompFunc = `
__helm_get_contexts()
{
__helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local template out
template="{{ range .contexts }}{{ .name }} {{ end }}"
if out=$(kubectl config -o template --template="${template}" view 2>/dev/null); then
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
`
)
var (
// Mapping of global flags that can have dynamic completion and the
// completion function to be used.
bashCompletionFlags = map[string]string{
// Cannot convert the kube-context flag to Go completion yet because
// an incomplete kube-context will make actionConfig.Init() fail at the very start
"kube-context": "__helm_get_contexts",
}
)
var globalUsage = `The Kubernetes package manager var globalUsage = `The Kubernetes package manager
Common actions for Helm: Common actions for Helm:
@ -101,14 +78,14 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
Args: require.NoArgs, Args: require.NoArgs,
BashCompletionFunction: fmt.Sprintf("%s%s", contextCompFunc, completion.GetBashCustomFunction()), BashCompletionFunction: completion.GetBashCustomFunction(),
} }
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()
settings.AddFlags(flags) settings.AddFlags(flags)
flag := flags.Lookup("namespace")
// Setup shell completion for the namespace flag // 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) { completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
if client, err := actionConfig.KubernetesClientSet(); err == nil { if client, err := actionConfig.KubernetesClientSet(); err == nil {
// Choose a long enough timeout that the user notices somethings is not working // Choose a long enough timeout that the user notices somethings is not working
@ -129,6 +106,29 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
return nil, completion.BashCompDirectiveDefault return nil, completion.BashCompDirectiveDefault
}) })
// Setup shell completion for the kube-context flag
flag = flags.Lookup("kube-context")
completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
completion.CompDebugln("About to get the different kube-contexts")
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if len(settings.KubeConfig) > 0 {
loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig}
}
if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
ctxs := []string{}
for name := range config.Contexts {
if strings.HasPrefix(name, toComplete) {
ctxs = append(ctxs, name)
}
}
return ctxs, completion.BashCompDirectiveNoFileComp
}
return nil, completion.BashCompDirectiveNoFileComp
})
// We can safely ignore any errors that flags.Parse encounters since // We can safely ignore any errors that flags.Parse encounters since
// those errors will be caught later during the call to cmd.Execution. // those errors will be caught later during the call to cmd.Execution.
// This call is required to gather configuration information prior to // This call is required to gather configuration information prior to
@ -173,19 +173,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
completion.NewCompleteCmd(settings, out), completion.NewCompleteCmd(settings, out),
) )
// Add annotation to flags for which we can generate completion choices
for name, completion := range bashCompletionFlags {
if cmd.Flag(name) != nil {
if cmd.Flag(name).Annotations == nil {
cmd.Flag(name).Annotations = map[string][]string{}
}
cmd.Flag(name).Annotations[cobra.BashCompCustom] = append(
cmd.Flag(name).Annotations[cobra.BashCompCustom],
completion,
)
}
}
// Add *experimental* subcommands // Add *experimental* subcommands
registryClient, err := registry.NewClient( registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug), registry.ClientOptDebug(settings.Debug),

@ -35,9 +35,9 @@ import (
// This should ultimately be pushed down into Cobra. // This should ultimately be pushed down into Cobra.
// ================================================================================== // ==================================================================================
// compRequestCmd Hidden command to request completion results from the program. // CompRequestCmd Hidden command to request completion results from the program.
// Used by the shell completion script. // Used by the shell completion script.
const compRequestCmd = "__complete" const CompRequestCmd = "__complete"
// Global map allowing to find completion functions for commands or flags. // 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){} var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){}
@ -123,7 +123,7 @@ __helm_custom_func()
done < <(compgen -W "${out[*]}" -- "$cur") done < <(compgen -W "${out[*]}" -- "$cur")
fi fi
} }
`, compRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp) `, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)
} }
// RegisterValidArgsFunc should be called to register a function to provide argument completion for a command // RegisterValidArgsFunc should be called to register a function to provide argument completion for a command
@ -177,14 +177,14 @@ func (d BashCompDirective) string() string {
func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command {
debug = settings.Debug debug = settings.Debug
return &cobra.Command{ return &cobra.Command{
Use: fmt.Sprintf("%s [command-line]", compRequestCmd), Use: fmt.Sprintf("%s [command-line]", CompRequestCmd),
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Hidden: true, Hidden: true,
DisableFlagParsing: true, DisableFlagParsing: true,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
Short: "Request shell completion choices for the specified command-line", 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", Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s",
compRequestCmd, "to request completion choices for the specified command-line."), CompRequestCmd, "to request completion choices for the specified command-line."),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args)) CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args))

Loading…
Cancel
Save