123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- /*
- Copyright 2014 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 cmd
- import (
- "flag"
- "fmt"
- "io"
- "os"
- "os/exec"
- "runtime"
- "strings"
- "syscall"
- "github.com/spf13/cobra"
- "k8s.io/client-go/tools/clientcmd"
- cliflag "k8s.io/component-base/cli/flag"
- cmdpkg "k8s.io/kubectl/pkg/cmd"
- "k8s.io/kubectl/pkg/cmd/annotate"
- "k8s.io/kubectl/pkg/cmd/apiresources"
- "k8s.io/kubectl/pkg/cmd/apply"
- "k8s.io/kubectl/pkg/cmd/attach"
- "k8s.io/kubectl/pkg/cmd/autoscale"
- "k8s.io/kubectl/pkg/cmd/certificates"
- "k8s.io/kubectl/pkg/cmd/clusterinfo"
- "k8s.io/kubectl/pkg/cmd/completion"
- cmdconfig "k8s.io/kubectl/pkg/cmd/config"
- "k8s.io/kubectl/pkg/cmd/create"
- "k8s.io/kubectl/pkg/cmd/delete"
- "k8s.io/kubectl/pkg/cmd/describe"
- "k8s.io/kubectl/pkg/cmd/diff"
- "k8s.io/kubectl/pkg/cmd/drain"
- "k8s.io/kubectl/pkg/cmd/edit"
- cmdexec "k8s.io/kubectl/pkg/cmd/exec"
- "k8s.io/kubectl/pkg/cmd/explain"
- "k8s.io/kubectl/pkg/cmd/expose"
- "k8s.io/kubectl/pkg/cmd/get"
- "k8s.io/kubectl/pkg/cmd/label"
- "k8s.io/kubectl/pkg/cmd/logs"
- "k8s.io/kubectl/pkg/cmd/options"
- "k8s.io/kubectl/pkg/cmd/patch"
- "k8s.io/kubectl/pkg/cmd/plugin"
- "k8s.io/kubectl/pkg/cmd/portforward"
- "k8s.io/kubectl/pkg/cmd/proxy"
- "k8s.io/kubectl/pkg/cmd/replace"
- "k8s.io/kubectl/pkg/cmd/rollout"
- "k8s.io/kubectl/pkg/cmd/run"
- "k8s.io/kubectl/pkg/cmd/scale"
- "k8s.io/kubectl/pkg/cmd/set"
- "k8s.io/kubectl/pkg/cmd/taint"
- "k8s.io/kubectl/pkg/cmd/top"
- cmdutil "k8s.io/kubectl/pkg/cmd/util"
- "k8s.io/kubectl/pkg/cmd/version"
- "k8s.io/kubectl/pkg/cmd/wait"
- "k8s.io/kubectl/pkg/util/i18n"
- "k8s.io/kubectl/pkg/util/templates"
- "k8s.io/kubernetes/pkg/kubectl/cmd/auth"
- "k8s.io/kubernetes/pkg/kubectl/cmd/convert"
- "k8s.io/kubernetes/pkg/kubectl/cmd/cp"
- "k8s.io/cli-runtime/pkg/genericclioptions"
- "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
- }
- __kubectl_get_resource()
- {
- if [[ ${#nouns[@]} -eq 0 ]]; then
- local kubectl_out
- if kubectl_out=$(__kubectl_debug_out "kubectl api-resources $(__kubectl_override_flags) -o name --cached --request-timeout=5s --verbs=get"); then
- COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
- return 0
- fi
- return 1
- fi
- __kubectl_parse_get "${nouns[${#nouns[@]} -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_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_get_contexts
- return
- ;;
- kubectl_config_delete-cluster)
- __kubectl_config_get_clusters
- return
- ;;
- kubectl_cp)
- __kubectl_cp
- return
- ;;
- *)
- ;;
- esac
- }
- `
- )
- 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)
- }
- // NewDefaultKubectlCommandWithArgs creates the `kubectl` command with arguments
- func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string, in io.Reader, out, errout io.Writer) *cobra.Command {
- cmd := NewKubectlCommand(in, out, errout)
- if pluginHandler == nil {
- return cmd
- }
- if len(args) > 1 {
- cmdPathPieces := args[1:]
- // only look for suitable extension executables if
- // the specified command does not already exist
- if _, _, err := cmd.Find(cmdPathPieces); err != nil {
- if err := HandlePluginCommand(pluginHandler, cmdPathPieces); err != nil {
- fmt.Fprintf(errout, "%v\n", err)
- os.Exit(1)
- }
- }
- }
- return cmd
- }
- // PluginHandler is capable of parsing command line arguments
- // and performing executable filename lookups to search
- // for valid plugin files, and execute found plugins.
- type PluginHandler interface {
- // exists at the given filename, or a boolean false.
- // Lookup will iterate over a list of given prefixes
- // in order to recognize valid plugin filenames.
- // The first filepath to match a prefix is returned.
- Lookup(filename string) (string, bool)
- // Execute receives an executable's filepath, a slice
- // of arguments, and a slice of environment variables
- // to relay to the executable.
- Execute(executablePath string, cmdArgs, environment []string) error
- }
- // DefaultPluginHandler implements PluginHandler
- type DefaultPluginHandler struct {
- ValidPrefixes []string
- }
- // NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
- // given filename prefixes used to identify valid plugin filenames.
- func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler {
- return &DefaultPluginHandler{
- ValidPrefixes: validPrefixes,
- }
- }
- // Lookup implements PluginHandler
- func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) {
- for _, prefix := range h.ValidPrefixes {
- path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename))
- if err != nil || len(path) == 0 {
- continue
- }
- return path, true
- }
- return "", false
- }
- // Execute implements PluginHandler
- func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
- // Windows does not support exec syscall.
- if runtime.GOOS == "windows" {
- cmd := exec.Command(executablePath, cmdArgs...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Stdin = os.Stdin
- cmd.Env = environment
- err := cmd.Run()
- if err == nil {
- os.Exit(0)
- }
- return err
- }
- // invoke cmd binary relaying the environment and args given
- // append executablePath to cmdArgs, as execve will make first argument the "binary name".
- return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment)
- }
- // HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
- // a plugin executable on the PATH that satisfies the given arguments.
- func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error {
- remainingArgs := []string{} // all "non-flag" arguments
- for idx := range cmdArgs {
- if strings.HasPrefix(cmdArgs[idx], "-") {
- break
- }
- remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1))
- }
- foundBinaryPath := ""
- // attempt to find binary, starting at longest possible name with given cmdArgs
- for len(remainingArgs) > 0 {
- path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
- if !found {
- remainingArgs = remainingArgs[:len(remainingArgs)-1]
- continue
- }
- foundBinaryPath = path
- break
- }
- if len(foundBinaryPath) == 0 {
- return nil
- }
- // invoke cmd binary relaying the current environment and args given
- if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil {
- return err
- }
- return nil
- }
- // NewKubectlCommand creates the `kubectl` command and its nested children.
- func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
- // Parent command to which all subcommands are added.
- cmds := &cobra.Command{
- Use: "kubectl",
- Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
- Long: templates.LongDesc(`
- kubectl controls the Kubernetes cluster manager.
- Find more information at:
- https://kubernetes.io/docs/reference/kubectl/overview/`),
- Run: runHelp,
- // Hook before and after Run initialize and write profiles to disk,
- // respectively.
- PersistentPreRunE: func(*cobra.Command, []string) error {
- return initProfiling()
- },
- PersistentPostRunE: func(*cobra.Command, []string) error {
- return flushProfiling()
- },
- BashCompletionFunction: bashCompletionFunc,
- }
- flags := cmds.PersistentFlags()
- flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
- // Normalize all flags that are coming from other packages or pre-configurations
- // a.k.a. change all "_" to "-". e.g. glog package
- flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
- addProfilingFlags(flags)
- kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
- kubeConfigFlags.AddFlags(flags)
- matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
- matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
- cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
- f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
- // Sending in 'nil' for the getLanguageFn() results in using
- // the LANG environment variable.
- //
- // TODO: Consider adding a flag or file preference for setting
- // the language, instead of just loading from the LANG env. variable.
- i18n.LoadTranslations("kubectl", nil)
- // From this point and forward we get warnings on flags that contain "_" separators
- cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
- ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
- groups := templates.CommandGroups{
- {
- Message: "Basic Commands (Beginner):",
- Commands: []*cobra.Command{
- create.NewCmdCreate(f, ioStreams),
- expose.NewCmdExposeService(f, ioStreams),
- run.NewCmdRun(f, ioStreams),
- set.NewCmdSet(f, ioStreams),
- },
- },
- {
- Message: "Basic Commands (Intermediate):",
- Commands: []*cobra.Command{
- explain.NewCmdExplain("kubectl", f, ioStreams),
- get.NewCmdGet("kubectl", f, ioStreams),
- edit.NewCmdEdit(f, ioStreams),
- delete.NewCmdDelete(f, ioStreams),
- },
- },
- {
- Message: "Deploy Commands:",
- Commands: []*cobra.Command{
- rollout.NewCmdRollout(f, ioStreams),
- scale.NewCmdScale(f, ioStreams),
- autoscale.NewCmdAutoscale(f, ioStreams),
- },
- },
- {
- Message: "Cluster Management Commands:",
- Commands: []*cobra.Command{
- certificates.NewCmdCertificate(f, ioStreams),
- clusterinfo.NewCmdClusterInfo(f, ioStreams),
- top.NewCmdTop(f, ioStreams),
- drain.NewCmdCordon(f, ioStreams),
- drain.NewCmdUncordon(f, ioStreams),
- drain.NewCmdDrain(f, ioStreams),
- taint.NewCmdTaint(f, ioStreams),
- },
- },
- {
- Message: "Troubleshooting and Debugging Commands:",
- Commands: []*cobra.Command{
- describe.NewCmdDescribe("kubectl", f, ioStreams),
- logs.NewCmdLogs(f, ioStreams),
- attach.NewCmdAttach(f, ioStreams),
- cmdexec.NewCmdExec(f, ioStreams),
- portforward.NewCmdPortForward(f, ioStreams),
- proxy.NewCmdProxy(f, ioStreams),
- cp.NewCmdCp(f, ioStreams),
- auth.NewCmdAuth(f, ioStreams),
- },
- },
- {
- Message: "Advanced Commands:",
- Commands: []*cobra.Command{
- diff.NewCmdDiff(f, ioStreams),
- apply.NewCmdApply("kubectl", f, ioStreams),
- patch.NewCmdPatch(f, ioStreams),
- replace.NewCmdReplace(f, ioStreams),
- wait.NewCmdWait(f, ioStreams),
- convert.NewCmdConvert(f, ioStreams),
- kustomize.NewCmdKustomize(ioStreams),
- },
- },
- {
- Message: "Settings Commands:",
- Commands: []*cobra.Command{
- label.NewCmdLabel(f, ioStreams),
- annotate.NewCmdAnnotate("kubectl", f, ioStreams),
- completion.NewCmdCompletion(ioStreams.Out, ""),
- },
- },
- }
- groups.Add(cmds)
- filters := []string{"options"}
- // Hide the "alpha" subcommand if there are no alpha commands in this build.
- alpha := cmdpkg.NewCmdAlpha(f, ioStreams)
- if !alpha.HasSubCommands() {
- filters = append(filters, alpha.Name())
- }
- 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,
- )
- }
- }
- cmds.AddCommand(alpha)
- cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
- cmds.AddCommand(plugin.NewCmdPlugin(f, ioStreams))
- cmds.AddCommand(version.NewCmdVersion(f, ioStreams))
- cmds.AddCommand(apiresources.NewCmdAPIVersions(f, ioStreams))
- cmds.AddCommand(apiresources.NewCmdAPIResources(f, ioStreams))
- cmds.AddCommand(options.NewCmdOptions(ioStreams.Out))
- return cmds
- }
- func runHelp(cmd *cobra.Command, args []string) {
- cmd.Help()
- }
|