diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/annotate/annotate.go b/staging/src/k8s.io/kubectl/pkg/cmd/annotate/annotate.go index 00fe8423b6d4..e4c2ce0ad6b4 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/annotate/annotate.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/annotate/annotate.go @@ -38,6 +38,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -134,6 +135,7 @@ func NewCmdAnnotate(parent string, f cmdutil.Factory, ioStreams genericclioption Short: i18n.T("Update the annotations on a resource"), Long: annotateLong + "\n\n" + cmdutil.SuggestAPIResources(parent), Example: annotateExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go b/staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go index b1177a119e08..b0eff26a73c6 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go @@ -17,8 +17,11 @@ limitations under the License. package apiresources import ( + "bytes" "fmt" "io" + "io/ioutil" + "os" "sort" "strings" @@ -275,3 +278,38 @@ func (s sortableResource) compareValues(i, j int) (string, string) { } return s.resources[i].APIGroup, s.resources[j].APIGroup } + +// CompGetResourceList returns the list of api resources which begin with `toComplete`. +func CompGetResourceList(f cmdutil.Factory, cmd *cobra.Command, toComplete string) []string { + buf := new(bytes.Buffer) + streams := genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: ioutil.Discard} + o := NewAPIResourceOptions(streams) + + // Get the list of resources + o.Output = "name" + o.Cached = true + o.Verbs = []string{"get"} + // TODO:Should set --request-timeout=5s + + // Ignore errors as the output may still be valid + o.RunAPIResources(cmd, f) + + // Resources can be a comma-separated list. The last element is then + // the one we should complete. For example if toComplete=="pods,secre" + // we should return "pods,secrets" + prefix := "" + suffix := toComplete + lastIdx := strings.LastIndex(toComplete, ",") + if lastIdx != -1 { + prefix = toComplete[0 : lastIdx+1] + suffix = toComplete[lastIdx+1:] + } + var comps []string + resources := strings.Split(buf.String(), "\n") + for _, res := range resources { + if res != "" && strings.HasPrefix(res, suffix) { + comps = append(comps, fmt.Sprintf("%s%s", prefix, res)) + } + } + return comps +} diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go index da7065313c51..47e375cc6796 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go @@ -22,6 +22,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/cmd/util/editor" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -66,6 +67,7 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I Short: i18n.T("Edit latest last-applied-configuration annotations of a resource/object"), Long: applyEditLastAppliedLong, Example: applyEditLastAppliedExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, args, cmd)) cmdutil.CheckErr(o.Run()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_view_last_applied.go b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_view_last_applied.go index 4327ea6aef15..74e7f37f6d24 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_view_last_applied.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_view_last_applied.go @@ -77,6 +77,7 @@ func NewCmdApplyViewLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I Short: i18n.T("View latest last-applied-configuration annotations of a resource/object"), Long: applyViewLastAppliedLong, Example: applyViewLastAppliedExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(options.Complete(cmd, f, args)) cmdutil.CheckErr(options.Validate(cmd)) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go b/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go index f7cc2b833c9d..f89cfd83eb6c 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go @@ -35,6 +35,7 @@ import ( "k8s.io/kubectl/pkg/cmd/util/podcmd" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -104,6 +105,7 @@ func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra Short: i18n.T("Attach to a running container"), Long: i18n.T("Attach to a process that is already running inside an existing container."), Example: attachExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "pod"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/autoscale/autoscale.go b/staging/src/k8s.io/kubectl/pkg/cmd/autoscale/autoscale.go index bb09cbec91e0..afe7117dde47 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/autoscale/autoscale.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/autoscale/autoscale.go @@ -107,12 +107,12 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) * Short: i18n.T("Auto-scale a Deployment, ReplicaSet, StatefulSet, or ReplicationController"), Long: autoscaleLong, Example: autoscaleExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Run()) }, - ValidArgs: validArgs, } // bind flag structs diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/cmd.go b/staging/src/k8s.io/kubectl/pkg/cmd/cmd.go index 1f94844720ce..5c4713286e64 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/cmd.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/cmd.go @@ -71,6 +71,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/cmd/version" "k8s.io/kubectl/pkg/cmd/wait" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/term" @@ -79,261 +80,8 @@ import ( "k8s.io/kubectl/pkg/cmd/kustomize" ) -const ( - bashCompletionFunc = `# call kubectl get $1, -__kubectl_debug_out() -{ - local cmd="$1" - __kubectl_debug "${FUNCNAME[1]}: get completion by ${cmd}" - eval "${cmd} 2>/dev/null" -} - -__kubectl_override_flag_list=(--kubeconfig --cluster --user --context --namespace --server -n -s) -__kubectl_override_flags() -{ - local ${__kubectl_override_flag_list[*]##*-} two_word_of of var - for w in "${words[@]}"; do - if [ -n "${two_word_of}" ]; then - eval "${two_word_of##*-}=\"${two_word_of}=\${w}\"" - two_word_of= - continue - fi - for of in "${__kubectl_override_flag_list[@]}"; do - case "${w}" in - ${of}=*) - eval "${of##*-}=\"${w}\"" - ;; - ${of}) - two_word_of="${of}" - ;; - esac - done - done - for var in "${__kubectl_override_flag_list[@]##*-}"; do - if eval "test -n \"\$${var}\""; then - eval "echo -n \${${var}}' '" - fi - done -} - -__kubectl_config_get_contexts() -{ - __kubectl_parse_config "contexts" -} - -__kubectl_config_get_clusters() -{ - __kubectl_parse_config "clusters" -} - -__kubectl_config_get_users() -{ - __kubectl_parse_config "users" -} - -# $1 has to be "contexts", "clusters" or "users" -__kubectl_parse_config() -{ - local template kubectl_out - template="{{ range .$1 }}{{ .name }} {{ end }}" - if kubectl_out=$(__kubectl_debug_out "kubectl config $(__kubectl_override_flags) -o template --template=\"${template}\" view"); then - COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) - fi -} - -# $1 is the name of resource (required) -# $2 is template string for kubectl get (optional) -__kubectl_parse_get() -{ - local template - template="${2:-"{{ range .items }}{{ .metadata.name }} {{ end }}"}" - local kubectl_out - if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) -o template --template=\"${template}\" \"$1\""); then - COMPREPLY+=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) - fi -} - -# Same as __kubectl_get_resources (with s) but allows completion for only one resource name. -__kubectl_get_resource() -{ - if [[ ${#nouns[@]} -eq 0 ]]; then - __kubectl_get_resource_helper "" "$cur" - return # the return status is that of the last command executed in the function body - fi - __kubectl_parse_get "${nouns[${#nouns[@]} -1]}" -} - -# Same as __kubectl_get_resource (without s) but allows completion for multiple, comma-separated resource names. -__kubectl_get_resources() -{ - local SEPARATOR=',' - if [[ ${#nouns[@]} -eq 0 ]]; then - local kubectl_out HEAD TAIL - HEAD="" - TAIL="$cur" - # if SEPARATOR is contained in $cur, e.g. "pod,sec" - if [[ "$cur" = *${SEPARATOR}* ]] ; then - # set HEAD to "pod," - HEAD="${cur%${SEPARATOR}*}${SEPARATOR}" - # set TAIL to "sec" - TAIL="${cur##*${SEPARATOR}}" - fi - __kubectl_get_resource_helper "$HEAD" "$TAIL" - return # the return status is that of the last command executed in the function body - fi - __kubectl_parse_get "${nouns[${#nouns[@]} -1]}" -} - -__kubectl_get_resource_helper() -{ - local kubectl_out HEAD TAIL - HEAD="$1" - TAIL="$2" - if kubectl_out=$(__kubectl_debug_out "kubectl api-resources $(__kubectl_override_flags) -o name --cached --request-timeout=5s --verbs=get"); then - COMPREPLY=( $( compgen -P "$HEAD" -W "${kubectl_out[*]}" -- "$TAIL" ) ) - return 0 - fi - return 1 -} - -__kubectl_get_resource_namespace() -{ - __kubectl_parse_get "namespace" -} - -__kubectl_get_resource_pod() -{ - __kubectl_parse_get "pod" -} - -__kubectl_get_resource_rc() -{ - __kubectl_parse_get "rc" -} - -__kubectl_get_resource_node() -{ - __kubectl_parse_get "node" -} - -__kubectl_get_resource_clusterrole() -{ - __kubectl_parse_get "clusterrole" -} - -# $1 is the name of the pod we want to get the list of containers inside -__kubectl_get_containers() -{ - local template - template="{{ range .spec.initContainers }}{{ .name }} {{end}}{{ range .spec.containers }}{{ .name }} {{ end }}" - __kubectl_debug "${FUNCNAME} nouns are ${nouns[*]}" - - local len="${#nouns[@]}" - if [[ ${len} -ne 1 ]]; then - return - fi - local last=${nouns[${len} -1]} - local kubectl_out - if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) -o template --template=\"${template}\" pods \"${last}\""); then - COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) - fi -} - -# Require both a pod and a container to be specified -__kubectl_require_pod_and_container() -{ - if [[ ${#nouns[@]} -eq 0 ]]; then - __kubectl_parse_get pods - return 0 - fi; - __kubectl_get_containers - return 0 -} - -__kubectl_cp() -{ - if [[ $(type -t compopt) = "builtin" ]]; then - compopt -o nospace - fi - - case "$cur" in - /*|[.~]*) # looks like a path - return - ;; - *:*) # TODO: complete remote files in the pod - return - ;; - */*) # complete / - local template namespace kubectl_out - template="{{ range .items }}{{ .metadata.namespace }}/{{ .metadata.name }}: {{ end }}" - namespace="${cur%%/*}" - if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) --namespace \"${namespace}\" -o template --template=\"${template}\" pods"); then - COMPREPLY=( $(compgen -W "${kubectl_out[*]}" -- "${cur}") ) - fi - return - ;; - *) # complete namespaces, pods, and filedirs - __kubectl_parse_get "namespace" "{{ range .items }}{{ .metadata.name }}/ {{ end }}" - __kubectl_parse_get "pod" "{{ range .items }}{{ .metadata.name }}: {{ end }}" - _filedir - ;; - esac -} - -__kubectl_custom_func() { - case ${last_command} in - kubectl_get) - __kubectl_get_resources - return - ;; - kubectl_describe | kubectl_delete | kubectl_label | kubectl_edit | kubectl_patch |\ - kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_* |\ - kubectl_apply_edit-last-applied | kubectl_apply_view-last-applied) - __kubectl_get_resource - return - ;; - kubectl_logs) - __kubectl_require_pod_and_container - return - ;; - kubectl_exec | kubectl_port-forward | kubectl_top_pod | kubectl_attach) - __kubectl_get_resource_pod - return - ;; - kubectl_cordon | kubectl_uncordon | kubectl_drain | kubectl_top_node) - __kubectl_get_resource_node - return - ;; - kubectl_config_use-context | kubectl_config_rename-context | kubectl_config_delete-context) - __kubectl_config_get_contexts - return - ;; - kubectl_config_delete-cluster) - __kubectl_config_get_clusters - return - ;; - kubectl_cp) - __kubectl_cp - return - ;; - *) - ;; - esac -} -` -) - const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS" -var ( - bashCompletionFlags = map[string]string{ - "namespace": "__kubectl_get_resource_namespace", - "context": "__kubectl_config_get_contexts", - "cluster": "__kubectl_config_get_clusters", - "user": "__kubectl_config_get_users", - } -) - // NewDefaultKubectlCommand creates the `kubectl` command with default arguments func NewDefaultKubectlCommand() *cobra.Command { return NewDefaultKubectlCommandWithArgs(NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args, os.Stdin, os.Stdout, os.Stderr) @@ -505,7 +253,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { } return nil }, - BashCompletionFunction: bashCompletionFunc, } flags := cmds.PersistentFlags() @@ -633,17 +380,8 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { templates.ActsAsRootCommand(cmds, filters, groups...) - for name, completion := range bashCompletionFlags { - if cmds.Flag(name) != nil { - if cmds.Flag(name).Annotations == nil { - cmds.Flag(name).Annotations = map[string][]string{} - } - cmds.Flag(name).Annotations[cobra.BashCompCustom] = append( - cmds.Flag(name).Annotations[cobra.BashCompCustom], - completion, - ) - } - } + util.SetFactoryForCompletion(f) + registerCompletionFuncForGlobalFlags(cmds, f) cmds.AddCommand(alpha) cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams)) @@ -691,3 +429,26 @@ func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.C func runHelp(cmd *cobra.Command, args []string) { cmd.Help() } + +func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory) { + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "namespace", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return get.CompGetResource(f, cmd, "namespace", toComplete), cobra.ShellCompDirectiveNoFileComp + })) + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "context", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return util.ListContextsInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp + })) + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "cluster", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return util.ListClustersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp + })) + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "user", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return util.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp + })) +} diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/completion/completion.go b/staging/src/k8s.io/kubectl/pkg/cmd/completion/completion.go index ac7235efb01b..6399ffe20fef 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/completion/completion.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/completion/completion.go @@ -17,7 +17,6 @@ limitations under the License. package completion import ( - "bytes" "io" "github.com/spf13/cobra" @@ -150,8 +149,7 @@ func runCompletionBash(out io.Writer, boilerPlate string, kubectl *cobra.Command } func runCompletionZsh(out io.Writer, boilerPlate string, kubectl *cobra.Command) error { - zshHead := "#compdef kubectl\n" - + zshHead := "#compdef kubectl\ncompdef _kubectl kubectl\n" out.Write([]byte(zshHead)) if len(boilerPlate) == 0 { @@ -161,118 +159,5 @@ func runCompletionZsh(out io.Writer, boilerPlate string, kubectl *cobra.Command) return err } - zshInitialization := ` -__kubectl_bash_source() { - alias shopt=':' - emulate -L sh - setopt kshglob noshglob braceexpand - - source "$@" -} - -__kubectl_type() { - # -t is not supported by zsh - if [ "$1" == "-t" ]; then - shift - - # fake Bash 4 to disable "complete -o nospace". Instead - # "compopt +-o nospace" is used in the code to toggle trailing - # spaces. We don't support that, but leave trailing spaces on - # all the time - if [ "$1" = "__kubectl_compopt" ]; then - echo builtin - return 0 - fi - fi - type "$@" -} - -__kubectl_compgen() { - local completions w - completions=( $(compgen "$@") ) || return $? - - # filter by given word as prefix - while [[ "$1" = -* && "$1" != -- ]]; do - shift - shift - done - if [[ "$1" == -- ]]; then - shift - fi - for w in "${completions[@]}"; do - if [[ "${w}" = "$1"* ]]; then - echo "${w}" - fi - done -} - -__kubectl_compopt() { - true # don't do anything. Not supported by bashcompinit in zsh -} - -__kubectl_ltrim_colon_completions() -{ - if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then - # Remove colon-word prefix from COMPREPLY items - local colon_word=${1%${1##*:}} - local i=${#COMPREPLY[*]} - while [[ $((--i)) -ge 0 ]]; do - COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} - done - fi -} - -__kubectl_get_comp_words_by_ref() { - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[${COMP_CWORD}-1]}" - words=("${COMP_WORDS[@]}") - cword=("${COMP_CWORD[@]}") -} - -__kubectl_filedir() { - # Don't need to do anything here. - # Otherwise we will get trailing space without "compopt -o nospace" - true -} - -autoload -U +X bashcompinit && bashcompinit - -# use word boundary patterns for BSD or GNU sed -LWORD='[[:<:]]' -RWORD='[[:>:]]' -if sed --version 2>&1 | grep -q GNU; then - LWORD='\<' - RWORD='\>' -fi - -__kubectl_convert_bash_to_zsh() { - sed \ - -e 's/declare -F/whence -w/' \ - -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ - -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ - -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ - -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ - -e "s/${LWORD}_filedir${RWORD}/__kubectl_filedir/g" \ - -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kubectl_get_comp_words_by_ref/g" \ - -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kubectl_ltrim_colon_completions/g" \ - -e "s/${LWORD}compgen${RWORD}/__kubectl_compgen/g" \ - -e "s/${LWORD}compopt${RWORD}/__kubectl_compopt/g" \ - -e "s/${LWORD}declare${RWORD}/builtin declare/g" \ - -e "s/\\\$(type${RWORD}/\$(__kubectl_type/g" \ - <<'BASH_COMPLETION_EOF' -` - out.Write([]byte(zshInitialization)) - - buf := new(bytes.Buffer) - kubectl.GenBashCompletion(buf) - out.Write(buf.Bytes()) - - zshTail := ` -BASH_COMPLETION_EOF -} - -__kubectl_bash_source <(__kubectl_convert_bash_to_zsh) -` - out.Write([]byte(zshTail)) - return nil + return kubectl.GenZshCompletion(out) } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_cluster.go b/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_cluster.go index 318629cf10cf..23a09b2e38db 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_cluster.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_cluster.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -41,6 +42,7 @@ func NewCmdConfigDeleteCluster(out io.Writer, configAccess clientcmd.ConfigAcces Short: i18n.T("Delete the specified cluster from the kubeconfig"), Long: i18n.T("Delete the specified cluster from the kubeconfig"), Example: deleteClusterExample, + ValidArgsFunction: util.ClusterCompletionFunc, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(runDeleteCluster(out, configAccess, cmd)) }, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_context.go b/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_context.go index 8221bba873df..7e12ba888c5a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_context.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/config/delete_context.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -41,6 +42,7 @@ func NewCmdConfigDeleteContext(out, errOut io.Writer, configAccess clientcmd.Con Short: i18n.T("Delete the specified context from the kubeconfig"), Long: i18n.T("Delete the specified context from the kubeconfig"), Example: deleteContextExample, + ValidArgsFunction: util.ContextCompletionFunc, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(runDeleteContext(out, errOut, configAccess, cmd)) }, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/config/rename_context.go b/staging/src/k8s.io/kubectl/pkg/cmd/config/rename_context.go index 3a8022538e40..153ba090ab59 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/config/rename_context.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/config/rename_context.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/clientcmd" cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -67,6 +68,7 @@ func NewCmdConfigRenameContext(out io.Writer, configAccess clientcmd.ConfigAcces Short: renameContextShort, Long: renameContextLong, Example: renameContextExample, + ValidArgsFunction: util.ContextCompletionFunc, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(options.Complete(cmd, args, out)) cmdutil.CheckErr(options.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/config/use_context.go b/staging/src/k8s.io/kubectl/pkg/cmd/config/use_context.go index c87dd5f7d875..758d82b9cbf6 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/config/use_context.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/config/use_context.go @@ -26,6 +26,7 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -52,6 +53,7 @@ func NewCmdConfigUseContext(out io.Writer, configAccess clientcmd.ConfigAccess) Aliases: []string{"use"}, Long: `Sets the current-context in a kubeconfig file`, Example: useContextExample, + ValidArgsFunction: util.ContextCompletionFunc, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(options.complete(cmd)) cmdutil.CheckErr(options.run()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go b/staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go index 8282e82497fe..79d064113bc5 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go @@ -35,6 +35,7 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/kubectl/pkg/cmd/exec" + "k8s.io/kubectl/pkg/cmd/get" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" @@ -104,6 +105,57 @@ func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.C Short: i18n.T("Copy files and directories to and from containers."), Long: i18n.T("Copy files and directories to and from containers."), Example: cpExample, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + if strings.IndexAny(toComplete, "/.~") == 0 { + // Looks like a path, do nothing + } else if strings.Index(toComplete, ":") != -1 { + // TODO: complete remote files in the pod + } else if idx := strings.Index(toComplete, "/"); idx > 0 { + // complete / + namespace := toComplete[:idx] + template := "{{ range .items }}{{ .metadata.namespace }}/{{ .metadata.name }}: {{ end }}" + comps = get.CompGetFromTemplate(&template, f, namespace, cmd, []string{"pod"}, toComplete) + } else { + // Complete namespaces followed by a / + for _, ns := range get.CompGetResource(f, cmd, "namespace", toComplete) { + comps = append(comps, fmt.Sprintf("%s/", ns)) + } + // Complete pod names followed by a : + for _, pod := range get.CompGetResource(f, cmd, "pod", toComplete) { + comps = append(comps, fmt.Sprintf("%s:", pod)) + } + + // Finally, provide file completion if we need to. + // We only do this if: + // 1- There are other completions found (if there are no completions, + // the shell will do file completion itself) + // 2- If there is some input from the user (or else we will end up + // listing the entire content of the current directory which could + // be too many choices for the user) + if len(comps) > 0 && len(toComplete) > 0 { + if files, err := ioutil.ReadDir("."); err == nil { + for _, file := range files { + filename := file.Name() + if strings.HasPrefix(filename, toComplete) { + if file.IsDir() { + filename = fmt.Sprintf("%s/", filename) + } + // We are completing a file prefix + comps = append(comps, filename) + } + } + } + } else if len(toComplete) == 0 { + // If the user didn't provide any input to complete, + // we provide a hint that a path can also be used + comps = append(comps, "./", "/") + } + } + } + return comps, cobra.ShellCompDirectiveNoSpace + }, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd)) cmdutil.CheckErr(o.Run(args)) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go index e973dc9517c8..7757b2c66097 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go @@ -29,6 +29,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" rbacclientv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + "k8s.io/kubectl/pkg/cmd/get" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/util" @@ -99,11 +100,18 @@ func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, ioStreams genericclioptio cmdutil.AddDryRunFlag(cmd) cmd.Flags().StringVar(&o.ClusterRole, "clusterrole", "", i18n.T("ClusterRole this ClusterRoleBinding should reference")) cmd.MarkFlagRequired("clusterrole") - cmd.MarkFlagCustom("clusterrole", "__kubectl_get_resource_clusterrole") cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the clusterrole") cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the clusterrole") cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the clusterrole, in the format :") cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + // Completion for relevant flags + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "clusterrole", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return get.CompGetResource(f, cmd, "clusterrole", toComplete), cobra.ShellCompDirectiveNoFileComp + })) + return cmd } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go index 022c99171812..1a48ecf2d657 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go @@ -36,6 +36,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdwait "k8s.io/kubectl/pkg/cmd/wait" "k8s.io/kubectl/pkg/rawhttp" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -135,6 +136,7 @@ func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra Short: i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"), Long: deleteLong, Example: deleteExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { o, err := deleteFlags.ToOptions(nil, streams) cmdutil.CheckErr(err) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go index dfd2c1e873f7..f2866f578d19 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go @@ -30,6 +30,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/describe" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -106,6 +107,7 @@ func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions. Short: i18n.T("Show details of a specific resource or group of resources"), Long: describeLong + "\n\n" + cmdutil.SuggestAPIResources(parent), Example: describeExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Run()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go b/staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go index c2a13a078e84..bf4cb5655b46 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go @@ -34,6 +34,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/drain" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -68,6 +69,7 @@ func NewCmdCordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cob Short: i18n.T("Mark node as unschedulable"), Long: cordonLong, Example: cordonExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "node"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.RunCordonOrUncordon(true)) @@ -96,6 +98,7 @@ func NewCmdUncordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *c Short: i18n.T("Mark node as schedulable"), Long: uncordonLong, Example: uncordonExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "node"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.RunCordonOrUncordon(false)) @@ -181,6 +184,7 @@ func NewCmdDrain(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr Short: i18n.T("Drain node in preparation for maintenance"), Long: drainLong, Example: drainExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "node"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.RunDrain()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go b/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go index d466fbc1efd0..7384fcc19344 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go @@ -22,6 +22,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/cmd/util/editor" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -76,6 +77,7 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra Short: i18n.T("Edit a resource on the server"), Long: editLong, Example: editExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, args, cmd)) cmdutil.CheckErr(o.Run()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go b/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go index 1ff45f26fd52..5c8923d4a1ee 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go @@ -37,6 +37,7 @@ import ( "k8s.io/kubectl/pkg/cmd/util/podcmd" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/interrupt" "k8s.io/kubectl/pkg/util/templates" @@ -88,6 +89,7 @@ func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C Short: i18n.T("Execute a command in a container"), Long: i18n.T("Execute a command in a container."), Example: execExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "pod"), Run: func(cmd *cobra.Command, args []string) { argsLenAtDash := cmd.ArgsLenAtDash() cmdutil.CheckErr(options.Complete(f, cmd, args, argsLenAtDash)) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go b/staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go index 008dcb32c34a..c95421dcc22b 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go @@ -134,11 +134,11 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) Short: i18n.T("Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service"), Long: exposeLong, Example: exposeExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd)) cmdutil.CheckErr(o.RunExpose(cmd, args)) }, - ValidArgs: validArgs, } o.RecordFlags.AddFlags(cmd) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go index 4f1f5a8c4ee0..3252bb14f047 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go @@ -17,11 +17,14 @@ limitations under the License. package get import ( + "bytes" "context" "encoding/json" "fmt" "io" + "io/ioutil" "net/url" + "os" "strings" "github.com/spf13/cobra" @@ -42,6 +45,7 @@ import ( kubernetesscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" watchtools "k8s.io/client-go/tools/watch" + "k8s.io/kubectl/pkg/cmd/apiresources" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/rawhttp" "k8s.io/kubectl/pkg/scheme" @@ -161,6 +165,15 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericclioptions.IOStr Short: i18n.T("Display one or many resources"), Long: getLong + "\n\n" + cmdutil.SuggestAPIResources(parent), Example: getExample, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = apiresources.CompGetResourceList(f, cmd, toComplete) + } else if len(args) == 1 { + comps = CompGetResource(f, cmd, args[0], toComplete) + } + return comps, cobra.ShellCompDirectiveNoFileComp + }, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate(cmd)) @@ -849,3 +862,61 @@ func multipleGVKsRequested(infos []*resource.Info) bool { } return false } + +// CompGetResource gets the list of the resource specified which begin with `toComplete`. +func CompGetResource(f cmdutil.Factory, cmd *cobra.Command, resourceName string, toComplete string) []string { + template := "{{ range .items }}{{ .metadata.name }} {{ end }}" + return CompGetFromTemplate(&template, f, "", cmd, []string{resourceName}, toComplete) +} + +// CompGetContainers gets the list of containers of the specified pod which begin with `toComplete`. +func CompGetContainers(f cmdutil.Factory, cmd *cobra.Command, podName string, toComplete string) []string { + template := "{{ range .spec.initContainers }}{{ .name }} {{end}}{{ range .spec.containers }}{{ .name }} {{ end }}" + return CompGetFromTemplate(&template, f, "", cmd, []string{"pod", podName}, toComplete) +} + +// CompGetFromTemplate executes a Get operation using the specified template and args and returns the results +// which begin with `toComplete`. +func CompGetFromTemplate(template *string, f cmdutil.Factory, namespace string, cmd *cobra.Command, args []string, toComplete string) []string { + buf := new(bytes.Buffer) + streams := genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: ioutil.Discard} + o := NewGetOptions("kubectl", streams) + + // Get the list of names of the specified resource + o.PrintFlags.TemplateFlags.GoTemplatePrintFlags.TemplateArgument = template + format := "go-template" + o.PrintFlags.OutputFormat = &format + + // Do the steps Complete() would have done. + // We cannot actually call Complete() or Validate() as these function check for + // the presence of flags, which, in our case won't be there + if namespace != "" { + o.Namespace = namespace + o.ExplicitNamespace = true + } else { + var err error + o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return nil + } + } + + o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) { + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + return printer.PrintObj, nil + } + + o.Run(f, cmd, args) + + var comps []string + resources := strings.Split(buf.String(), " ") + for _, res := range resources { + if res != "" && strings.HasPrefix(res, toComplete) { + comps = append(comps, res) + } + } + return comps +} diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/label/label.go b/staging/src/k8s.io/kubectl/pkg/cmd/label/label.go index 430ac7b5ff21..5781b6e628e7 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/label/label.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/label/label.go @@ -39,6 +39,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -132,6 +133,7 @@ func NewCmdLabel(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr Short: i18n.T("Update the labels on a resource"), Long: fmt.Sprintf(labelLong, validation.LabelValueMaxLength), Example: labelExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/logs/logs.go b/staging/src/k8s.io/kubectl/pkg/cmd/logs/logs.go index 4b6c0fc373c8..e4625f43f318 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/logs/logs.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/logs/logs.go @@ -152,6 +152,7 @@ func NewCmdLogs(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C Short: i18n.T("Print the logs for a container in a pod"), Long: logsLong, Example: logsExample, + ValidArgsFunction: util.PodResourceNameAndContainerCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/patch/patch.go b/staging/src/k8s.io/kubectl/pkg/cmd/patch/patch.go index fe563ecbc3fd..f98fe9e8cde0 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/patch/patch.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/patch/patch.go @@ -39,6 +39,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -114,6 +115,7 @@ func NewCmdPatch(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr Short: i18n.T("Update field(s) of a resource"), Long: patchLong, Example: patchExample, + ValidArgsFunction: util.ResourceTypeAndNameCompletionFunc(f), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go index 71922bb9d237..4770d1094029 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go @@ -109,6 +109,7 @@ func NewCmdPortForward(f cmdutil.Factory, streams genericclioptions.IOStreams) * Short: i18n.T("Forward one or more local ports to a pod"), Long: portforwardLong, Example: portforwardExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "pod"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(opts.Complete(f, cmd, args)) cmdutil.CheckErr(opts.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go index 9cf3639218d9..f51378793e4a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_history.go @@ -27,6 +27,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -82,12 +83,12 @@ func NewCmdRolloutHistory(f cmdutil.Factory, streams genericclioptions.IOStreams Short: i18n.T("View rollout history"), Long: historyLong, Example: historyExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Run()) }, - ValidArgs: validArgs, } cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "See the details, including podTemplate of the revision specified") diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go index 2470c004927a..b1ddd4970897 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_pause.go @@ -30,6 +30,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -82,12 +83,12 @@ func NewCmdRolloutPause(f cmdutil.Factory, streams genericclioptions.IOStreams) Short: i18n.T("Mark the provided resource as paused"), Long: pauseLong, Example: pauseExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunPause()) }, - ValidArgs: validArgs, } o.PrintFlags.AddFlags(cmd) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go index 0a980cf8f5d8..13a86ab2ac7a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_restart.go @@ -29,6 +29,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -86,12 +87,12 @@ func NewCmdRolloutRestart(f cmdutil.Factory, streams genericclioptions.IOStreams Short: i18n.T("Restart a resource"), Long: restartLong, Example: restartExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunRestart()) }, - ValidArgs: validArgs, } usage := "identifying the resource to get from a server." diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go index 915c779e5655..8659809c33a1 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_resume.go @@ -30,6 +30,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -86,12 +87,12 @@ func NewCmdRolloutResume(f cmdutil.Factory, streams genericclioptions.IOStreams) Short: i18n.T("Resume a paused resource"), Long: resumeLong, Example: resumeExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunResume()) }, - ValidArgs: validArgs, } usage := "identifying the resource to get from a server." diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go index 4abee4ba084e..11704af03a91 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_status.go @@ -37,6 +37,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/interrupt" "k8s.io/kubectl/pkg/util/templates" @@ -101,12 +102,12 @@ func NewCmdRolloutStatus(f cmdutil.Factory, streams genericclioptions.IOStreams) Short: i18n.T("Show the status of the rollout"), Long: statusLong, Example: statusExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Run()) }, - ValidArgs: validArgs, } usage := "identifying the resource to get from a server." diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go index 783ace37952d..aabf67d10f73 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/rollout/rollout_undo.go @@ -27,6 +27,7 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -86,12 +87,12 @@ func NewCmdRolloutUndo(f cmdutil.Factory, streams genericclioptions.IOStreams) * Short: i18n.T("Undo a previous rollout"), Long: undoLong, Example: undoExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.RunUndo()) }, - ValidArgs: validArgs, } cmd.Flags().Int64Var(&o.ToRevision, "to-revision", o.ToRevision, "The revision to rollback to. Default to 0 (last revision).") diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/scale/scale.go b/staging/src/k8s.io/kubectl/pkg/cmd/scale/scale.go index 1607ef3ff59f..a87c3e8e5f5d 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/scale/scale.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/scale/scale.go @@ -31,6 +31,7 @@ import ( "k8s.io/client-go/kubernetes" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scale" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -113,12 +114,12 @@ func NewCmdScale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr Short: i18n.T("Set a new size for a Deployment, ReplicaSet or Replication Controller"), Long: scaleLong, Example: scaleExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate(cmd)) cmdutil.CheckErr(o.RunScale()) }, - ValidArgs: validArgs, } o.RecordFlags.AddFlags(cmd) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go index 9430028e628c..33b1608a3f02 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "k8s.io/klog/v2" "k8s.io/kubectl/pkg/explain" + "k8s.io/kubectl/pkg/util" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -108,12 +109,12 @@ func NewCmdTaint(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra. Short: i18n.T("Update the taints on one or more nodes"), Long: fmt.Sprintf(taintLong, validation.DNS1123SubdomainMaxLength, validation.LabelValueMaxLength), Example: taintExample, + ValidArgsFunction: util.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(options.Validate()) cmdutil.CheckErr(options.RunTaint()) }, - ValidArgs: validArgs, } options.PrintFlags.AddFlags(cmd) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_node.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_node.go index 2f530d78f515..5febad85d564 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_node.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_node.go @@ -29,6 +29,7 @@ import ( corev1client "k8s.io/client-go/kubernetes/typed/core/v1" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/metricsutil" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" metricsapi "k8s.io/metrics/pkg/apis/metrics" @@ -80,6 +81,7 @@ func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericclioptio Short: i18n.T("Display Resource (CPU/Memory) usage of nodes"), Long: topNodeLong, Example: topNodeExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "node"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go index 5d9649e707f5..bfaaf9270e1a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go @@ -29,6 +29,7 @@ import ( corev1client "k8s.io/client-go/kubernetes/typed/core/v1" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/metricsutil" + "k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" metricsapi "k8s.io/metrics/pkg/apis/metrics" @@ -97,6 +98,7 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions Short: i18n.T("Display Resource (CPU/Memory) usage of pods"), Long: topPodLong, Example: topPodExample, + ValidArgsFunction: util.ResourceNameCompletionFunc(f, "pod"), Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) diff --git a/staging/src/k8s.io/kubectl/pkg/util/completion.go b/staging/src/k8s.io/kubectl/pkg/util/completion.go new file mode 100644 index 000000000000..3253b75764c6 --- /dev/null +++ b/staging/src/k8s.io/kubectl/pkg/util/completion.go @@ -0,0 +1,158 @@ +/* +Copyright 2021 The Kubernetes 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 util + +import ( + "strings" + + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/cmd/apiresources" + "k8s.io/kubectl/pkg/cmd/get" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +var factory cmdutil.Factory + +// SetFactoryForCompletion Store the factory which is needed by the completion functions +// Not all commands have access to the factory, so cannot pass it to the completion functions. +func SetFactoryForCompletion(f cmdutil.Factory) { + factory = f +} + +// ResourceTypeAndNameCompletionFunc Returns a completion function that completes as a first argument +// the resource types that match the toComplete prefix, and as a second argument the resource names that match +// the toComplete prefix. +func ResourceTypeAndNameCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = apiresources.CompGetResourceList(f, cmd, toComplete) + } else if len(args) == 1 { + comps = get.CompGetResource(f, cmd, args[0], toComplete) + } + return comps, cobra.ShellCompDirectiveNoFileComp + } +} + +// SpecifiedResourceTypeAndNameCompletionFunc Returns a completion function that completes as a first +// argument the resource types that match the toComplete prefix and are limited to the allowedTypes, +// and as a second argument the specified resource names that match the toComplete prefix. +func SpecifiedResourceTypeAndNameCompletionFunc(f cmdutil.Factory, allowedTypes []string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + for _, comp := range allowedTypes { + if strings.HasPrefix(comp, toComplete) { + comps = append(comps, comp) + } + } + } else if len(args) == 1 { + comps = get.CompGetResource(f, cmd, args[0], toComplete) + } + return comps, cobra.ShellCompDirectiveNoFileComp + } +} + +// ResourceNameCompletionFunc Returns a completion function that completes as a first argument +// the resource names specified by the resourceType parameter, and which match the toComplete prefix. +func ResourceNameCompletionFunc(f cmdutil.Factory, resourceType string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = get.CompGetResource(f, cmd, resourceType, toComplete) + } + return comps, cobra.ShellCompDirectiveNoFileComp + } +} + +// PodResourceNameAndContainerCompletionFunc Returns a completion function that completes as a first +// argument pod names that match the toComplete prefix, and as a second argument the containers +// within the specified pod. +func PodResourceNameAndContainerCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var comps []string + if len(args) == 0 { + comps = get.CompGetResource(f, cmd, "pod", toComplete) + } else if len(args) == 1 { + comps = get.CompGetContainers(f, cmd, args[0], toComplete) + } + return comps, cobra.ShellCompDirectiveNoFileComp + } +} + +// ContextCompletionFunc is a completion function that completes as a first argument the +// context names that match the toComplete prefix +func ContextCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return ListContextsInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + +// ClusterCompletionFunc is a completion function that completes as a first argument the +// cluster names that match the toComplete prefix +func ClusterCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return ListClustersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + +// ListContextsInConfig returns a list of context names which begin with `toComplete` +func ListContextsInConfig(toComplete string) []string { + config, err := factory.ToRawKubeConfigLoader().RawConfig() + if err != nil { + return nil + } + var ret []string + for name := range config.Contexts { + if strings.HasPrefix(name, toComplete) { + ret = append(ret, name) + } + } + return ret +} + +// ListClustersInConfig returns a list of cluster names which begin with `toComplete` +func ListClustersInConfig(toComplete string) []string { + config, err := factory.ToRawKubeConfigLoader().RawConfig() + if err != nil { + return nil + } + var ret []string + for name := range config.Clusters { + if strings.HasPrefix(name, toComplete) { + ret = append(ret, name) + } + } + return ret +} + +// ListUsersInConfig returns a list of user names which begin with `toComplete` +func ListUsersInConfig(toComplete string) []string { + config, err := factory.ToRawKubeConfigLoader().RawConfig() + if err != nil { + return nil + } + var ret []string + for name := range config.AuthInfos { + if strings.HasPrefix(name, toComplete) { + ret = append(ret, name) + } + } + return ret +} diff --git a/staging/src/k8s.io/kubectl/pkg/util/completion_test.go b/staging/src/k8s.io/kubectl/pkg/util/completion_test.go new file mode 100644 index 000000000000..663b306720e3 --- /dev/null +++ b/staging/src/k8s.io/kubectl/pkg/util/completion_test.go @@ -0,0 +1,188 @@ +/* +Copyright 2021 The Kubernetes 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 util + +import ( + "net/http" + "sort" + "testing" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/rest/fake" + "k8s.io/kubectl/pkg/cmd/get" + cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + + // cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" +) + +func TestResourceTypeAndNameCompletionFuncOneArg(t *testing.T) { + tf, cmd := prepareCompletionTest() + addPodsToFactory(tf) + compFunc := ResourceTypeAndNameCompletionFunc(tf) + comps, directive := compFunc(cmd, []string{"pod"}, "b") + checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestResourceTypeAndNameCompletionFuncTooManyArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := ResourceTypeAndNameCompletionFunc(tf) + comps, directive := compFunc(cmd, []string{"pod", "pod-name"}, "") + checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestSpecifiedResourceTypeAndNameCompletionFuncNoArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod", "service", "statefulset"}) + comps, directive := compFunc(cmd, []string{}, "s") + checkCompletion(t, comps, []string{"service", "statefulset"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestSpecifiedResourceTypeAndNameCompletionFuncOneArg(t *testing.T) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + pods, _, _ := cmdtesting.TestData() + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, + } + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod"}) + comps, directive := compFunc(cmd, []string{"pod"}, "b") + checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestSpecifiedResourceTypeAndNameCompletionFuncTooManyArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := SpecifiedResourceTypeAndNameCompletionFunc(tf, []string{"pod"}) + comps, directive := compFunc(cmd, []string{"pod", "pod-name"}, "") + checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestResourceNameCompletionFuncNoArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + pods, _, _ := cmdtesting.TestData() + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, + } + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := ResourceNameCompletionFunc(tf, "pod") + comps, directive := compFunc(cmd, []string{}, "b") + checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestResourceNameCompletionFuncTooManyArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := ResourceNameCompletionFunc(tf, "pod") + comps, directive := compFunc(cmd, []string{"pod-name"}, "") + checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestPodResourceNameAndContainerCompletionFuncNoArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + pods, _, _ := cmdtesting.TestData() + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, + } + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := PodResourceNameAndContainerCompletionFunc(tf) + comps, directive := compFunc(cmd, []string{}, "b") + checkCompletion(t, comps, []string{"bar"}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func TestPodResourceNameAndContainerCompletionFuncTooManyArgs(t *testing.T) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + compFunc := PodResourceNameAndContainerCompletionFunc(tf) + comps, directive := compFunc(cmd, []string{"pod-name", "container-name"}, "") + checkCompletion(t, comps, []string{}, directive, cobra.ShellCompDirectiveNoFileComp) +} + +func prepareCompletionTest() (*cmdtesting.TestFactory, *cobra.Command) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + streams, _, _, _ := genericclioptions.NewTestIOStreams() + cmd := get.NewCmdGet("kubectl", tf, streams) + return tf, cmd +} + +func addPodsToFactory(tf *cmdtesting.TestFactory) { + pods, _, _ := cmdtesting.TestData() + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, + } +} + +func checkCompletion(t *testing.T, comps, expectedComps []string, directive, expectedDirective cobra.ShellCompDirective) { + if e, d := expectedDirective, directive; e != d { + t.Errorf("expected directive\n%v\nbut got\n%v", e, d) + } + + sort.Strings(comps) + sort.Strings(expectedComps) + + if len(expectedComps) != len(comps) { + t.Fatalf("expected completions\n%v\nbut got\n%v", expectedComps, comps) + } + + for i := range comps { + if expectedComps[i] != comps[i] { + t.Errorf("expected completions\n%v\nbut got\n%v", expectedComps, comps) + break + } + } +}