Merge pull request #96087 from VilledeMontreal/feat/goComp

Move all bash custom completions to Go

Kubernetes-commit: f7d2ecdc3e52b408833aa6269c57ad9ef408467e
This commit is contained in:
Kubernetes Publisher 2021-06-25 17:41:23 -07:00
commit 3ffa097df9
37 changed files with 599 additions and 393 deletions

View File

@ -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())

View File

@ -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
}

View File

@ -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())

View File

@ -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))

View File

@ -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())

View File

@ -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

View File

@ -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 <namespace>/<pod>
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
}))
}

View File

@ -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)
}

View File

@ -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))
},

View File

@ -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))
},

View File

@ -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())

View File

@ -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())

View File

@ -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>/<pod>
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))

View File

@ -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 <namespace>:<name>")
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
}

View File

@ -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)

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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))

View File

@ -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)

View File

@ -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
}

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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")

View File

@ -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)

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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).")

View File

@ -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)

View File

@ -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)

View File

@ -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())

View File

@ -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())

158
pkg/util/completion.go Normal file
View File

@ -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
}

188
pkg/util/completion_test.go Normal file
View File

@ -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
}
}
}