1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package cmd
18
19import (
20	"flag"
21	"fmt"
22	"io"
23	"os"
24	"os/exec"
25	"runtime"
26	"strings"
27	"syscall"
28
29	"github.com/spf13/cobra"
30
31	"k8s.io/client-go/rest"
32	"k8s.io/client-go/tools/clientcmd"
33	cliflag "k8s.io/component-base/cli/flag"
34	"k8s.io/kubectl/pkg/cmd/annotate"
35	"k8s.io/kubectl/pkg/cmd/apiresources"
36	"k8s.io/kubectl/pkg/cmd/apply"
37	"k8s.io/kubectl/pkg/cmd/attach"
38	"k8s.io/kubectl/pkg/cmd/auth"
39	"k8s.io/kubectl/pkg/cmd/autoscale"
40	"k8s.io/kubectl/pkg/cmd/certificates"
41	"k8s.io/kubectl/pkg/cmd/clusterinfo"
42	"k8s.io/kubectl/pkg/cmd/completion"
43	cmdconfig "k8s.io/kubectl/pkg/cmd/config"
44	"k8s.io/kubectl/pkg/cmd/cp"
45	"k8s.io/kubectl/pkg/cmd/create"
46	"k8s.io/kubectl/pkg/cmd/debug"
47	"k8s.io/kubectl/pkg/cmd/delete"
48	"k8s.io/kubectl/pkg/cmd/describe"
49	"k8s.io/kubectl/pkg/cmd/diff"
50	"k8s.io/kubectl/pkg/cmd/drain"
51	"k8s.io/kubectl/pkg/cmd/edit"
52	cmdexec "k8s.io/kubectl/pkg/cmd/exec"
53	"k8s.io/kubectl/pkg/cmd/explain"
54	"k8s.io/kubectl/pkg/cmd/expose"
55	"k8s.io/kubectl/pkg/cmd/get"
56	"k8s.io/kubectl/pkg/cmd/label"
57	"k8s.io/kubectl/pkg/cmd/logs"
58	"k8s.io/kubectl/pkg/cmd/options"
59	"k8s.io/kubectl/pkg/cmd/patch"
60	"k8s.io/kubectl/pkg/cmd/plugin"
61	"k8s.io/kubectl/pkg/cmd/portforward"
62	"k8s.io/kubectl/pkg/cmd/proxy"
63	"k8s.io/kubectl/pkg/cmd/replace"
64	"k8s.io/kubectl/pkg/cmd/rollout"
65	"k8s.io/kubectl/pkg/cmd/run"
66	"k8s.io/kubectl/pkg/cmd/scale"
67	"k8s.io/kubectl/pkg/cmd/set"
68	"k8s.io/kubectl/pkg/cmd/taint"
69	"k8s.io/kubectl/pkg/cmd/top"
70	cmdutil "k8s.io/kubectl/pkg/cmd/util"
71	"k8s.io/kubectl/pkg/cmd/version"
72	"k8s.io/kubectl/pkg/cmd/wait"
73	"k8s.io/kubectl/pkg/util/i18n"
74	"k8s.io/kubectl/pkg/util/templates"
75	"k8s.io/kubectl/pkg/util/term"
76
77	"k8s.io/cli-runtime/pkg/genericclioptions"
78	"k8s.io/kubectl/pkg/cmd/kustomize"
79)
80
81const (
82	bashCompletionFunc = `# call kubectl get $1,
83__kubectl_debug_out()
84{
85    local cmd="$1"
86    __kubectl_debug "${FUNCNAME[1]}: get completion by ${cmd}"
87    eval "${cmd} 2>/dev/null"
88}
89
90__kubectl_override_flag_list=(--kubeconfig --cluster --user --context --namespace --server -n -s)
91__kubectl_override_flags()
92{
93    local ${__kubectl_override_flag_list[*]##*-} two_word_of of var
94    for w in "${words[@]}"; do
95        if [ -n "${two_word_of}" ]; then
96            eval "${two_word_of##*-}=\"${two_word_of}=\${w}\""
97            two_word_of=
98            continue
99        fi
100        for of in "${__kubectl_override_flag_list[@]}"; do
101            case "${w}" in
102                ${of}=*)
103                    eval "${of##*-}=\"${w}\""
104                    ;;
105                ${of})
106                    two_word_of="${of}"
107                    ;;
108            esac
109        done
110    done
111    for var in "${__kubectl_override_flag_list[@]##*-}"; do
112        if eval "test -n \"\$${var}\""; then
113            eval "echo -n \${${var}}' '"
114        fi
115    done
116}
117
118__kubectl_config_get_contexts()
119{
120    __kubectl_parse_config "contexts"
121}
122
123__kubectl_config_get_clusters()
124{
125    __kubectl_parse_config "clusters"
126}
127
128__kubectl_config_get_users()
129{
130    __kubectl_parse_config "users"
131}
132
133# $1 has to be "contexts", "clusters" or "users"
134__kubectl_parse_config()
135{
136    local template kubectl_out
137    template="{{ range .$1  }}{{ .name }} {{ end }}"
138    if kubectl_out=$(__kubectl_debug_out "kubectl config $(__kubectl_override_flags) -o template --template=\"${template}\" view"); then
139        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
140    fi
141}
142
143# $1 is the name of resource (required)
144# $2 is template string for kubectl get (optional)
145__kubectl_parse_get()
146{
147    local template
148    template="${2:-"{{ range .items  }}{{ .metadata.name }} {{ end }}"}"
149    local kubectl_out
150    if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) -o template --template=\"${template}\" \"$1\""); then
151        COMPREPLY+=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
152    fi
153}
154
155__kubectl_get_resource()
156{
157    if [[ ${#nouns[@]} -eq 0 ]]; then
158      local kubectl_out
159      if kubectl_out=$(__kubectl_debug_out "kubectl api-resources $(__kubectl_override_flags) -o name --cached --request-timeout=5s --verbs=get"); then
160          COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
161          return 0
162      fi
163      return 1
164    fi
165    __kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
166}
167
168__kubectl_get_resource_namespace()
169{
170    __kubectl_parse_get "namespace"
171}
172
173__kubectl_get_resource_pod()
174{
175    __kubectl_parse_get "pod"
176}
177
178__kubectl_get_resource_rc()
179{
180    __kubectl_parse_get "rc"
181}
182
183__kubectl_get_resource_node()
184{
185    __kubectl_parse_get "node"
186}
187
188__kubectl_get_resource_clusterrole()
189{
190    __kubectl_parse_get "clusterrole"
191}
192
193# $1 is the name of the pod we want to get the list of containers inside
194__kubectl_get_containers()
195{
196    local template
197    template="{{ range .spec.initContainers }}{{ .name }} {{end}}{{ range .spec.containers  }}{{ .name }} {{ end }}"
198    __kubectl_debug "${FUNCNAME} nouns are ${nouns[*]}"
199
200    local len="${#nouns[@]}"
201    if [[ ${len} -ne 1 ]]; then
202        return
203    fi
204    local last=${nouns[${len} -1]}
205    local kubectl_out
206    if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) -o template --template=\"${template}\" pods \"${last}\""); then
207        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
208    fi
209}
210
211# Require both a pod and a container to be specified
212__kubectl_require_pod_and_container()
213{
214    if [[ ${#nouns[@]} -eq 0 ]]; then
215        __kubectl_parse_get pods
216        return 0
217    fi;
218    __kubectl_get_containers
219    return 0
220}
221
222__kubectl_cp()
223{
224    if [[ $(type -t compopt) = "builtin" ]]; then
225        compopt -o nospace
226    fi
227
228    case "$cur" in
229        /*|[.~]*) # looks like a path
230            return
231            ;;
232        *:*) # TODO: complete remote files in the pod
233            return
234            ;;
235        */*) # complete <namespace>/<pod>
236            local template namespace kubectl_out
237            template="{{ range .items }}{{ .metadata.namespace }}/{{ .metadata.name }}: {{ end }}"
238            namespace="${cur%%/*}"
239            if kubectl_out=$(__kubectl_debug_out "kubectl get $(__kubectl_override_flags) --namespace \"${namespace}\" -o template --template=\"${template}\" pods"); then
240                COMPREPLY=( $(compgen -W "${kubectl_out[*]}" -- "${cur}") )
241            fi
242            return
243            ;;
244        *) # complete namespaces, pods, and filedirs
245            __kubectl_parse_get "namespace" "{{ range .items  }}{{ .metadata.name }}/ {{ end }}"
246            __kubectl_parse_get "pod" "{{ range .items  }}{{ .metadata.name }}: {{ end }}"
247            _filedir
248            ;;
249    esac
250}
251
252__kubectl_custom_func() {
253    case ${last_command} in
254        kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_edit | kubectl_patch |\
255        kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_* |\
256        kubectl_apply_edit-last-applied | kubectl_apply_view-last-applied)
257            __kubectl_get_resource
258            return
259            ;;
260        kubectl_logs)
261            __kubectl_require_pod_and_container
262            return
263            ;;
264        kubectl_exec | kubectl_port-forward | kubectl_top_pod | kubectl_attach)
265            __kubectl_get_resource_pod
266            return
267            ;;
268        kubectl_cordon | kubectl_uncordon | kubectl_drain | kubectl_top_node)
269            __kubectl_get_resource_node
270            return
271            ;;
272        kubectl_config_use-context | kubectl_config_rename-context | kubectl_config_delete-context)
273            __kubectl_config_get_contexts
274            return
275            ;;
276        kubectl_config_delete-cluster)
277            __kubectl_config_get_clusters
278            return
279            ;;
280        kubectl_cp)
281            __kubectl_cp
282            return
283            ;;
284        *)
285            ;;
286    esac
287}
288`
289)
290
291var (
292	bashCompletionFlags = map[string]string{
293		"namespace": "__kubectl_get_resource_namespace",
294		"context":   "__kubectl_config_get_contexts",
295		"cluster":   "__kubectl_config_get_clusters",
296		"user":      "__kubectl_config_get_users",
297	}
298)
299
300// NewDefaultKubectlCommand creates the `kubectl` command with default arguments
301func NewDefaultKubectlCommand() *cobra.Command {
302	return NewDefaultKubectlCommandWithArgs(NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args, os.Stdin, os.Stdout, os.Stderr)
303}
304
305// NewDefaultKubectlCommandWithArgs creates the `kubectl` command with arguments
306func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string, in io.Reader, out, errout io.Writer) *cobra.Command {
307	cmd := NewKubectlCommand(in, out, errout)
308
309	if pluginHandler == nil {
310		return cmd
311	}
312
313	if len(args) > 1 {
314		cmdPathPieces := args[1:]
315
316		// only look for suitable extension executables if
317		// the specified command does not already exist
318		if _, _, err := cmd.Find(cmdPathPieces); err != nil {
319			if err := HandlePluginCommand(pluginHandler, cmdPathPieces); err != nil {
320				fmt.Fprintf(errout, "Error: %v\n", err)
321				os.Exit(1)
322			}
323		}
324	}
325
326	return cmd
327}
328
329// PluginHandler is capable of parsing command line arguments
330// and performing executable filename lookups to search
331// for valid plugin files, and execute found plugins.
332type PluginHandler interface {
333	// exists at the given filename, or a boolean false.
334	// Lookup will iterate over a list of given prefixes
335	// in order to recognize valid plugin filenames.
336	// The first filepath to match a prefix is returned.
337	Lookup(filename string) (string, bool)
338	// Execute receives an executable's filepath, a slice
339	// of arguments, and a slice of environment variables
340	// to relay to the executable.
341	Execute(executablePath string, cmdArgs, environment []string) error
342}
343
344// DefaultPluginHandler implements PluginHandler
345type DefaultPluginHandler struct {
346	ValidPrefixes []string
347}
348
349// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
350// given filename prefixes used to identify valid plugin filenames.
351func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler {
352	return &DefaultPluginHandler{
353		ValidPrefixes: validPrefixes,
354	}
355}
356
357// Lookup implements PluginHandler
358func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) {
359	for _, prefix := range h.ValidPrefixes {
360		path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename))
361		if err != nil || len(path) == 0 {
362			continue
363		}
364		return path, true
365	}
366
367	return "", false
368}
369
370// Execute implements PluginHandler
371func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
372
373	// Windows does not support exec syscall.
374	if runtime.GOOS == "windows" {
375		cmd := exec.Command(executablePath, cmdArgs...)
376		cmd.Stdout = os.Stdout
377		cmd.Stderr = os.Stderr
378		cmd.Stdin = os.Stdin
379		cmd.Env = environment
380		err := cmd.Run()
381		if err == nil {
382			os.Exit(0)
383		}
384		return err
385	}
386
387	// invoke cmd binary relaying the environment and args given
388	// append executablePath to cmdArgs, as execve will make first argument the "binary name".
389	return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment)
390}
391
392// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
393// a plugin executable on the PATH that satisfies the given arguments.
394func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error {
395	var remainingArgs []string // all "non-flag" arguments
396	for _, arg := range cmdArgs {
397		if strings.HasPrefix(arg, "-") {
398			break
399		}
400		remainingArgs = append(remainingArgs, strings.Replace(arg, "-", "_", -1))
401	}
402
403	if len(remainingArgs) == 0 {
404		// the length of cmdArgs is at least 1
405		return fmt.Errorf("flags cannot be placed before plugin name: %s", cmdArgs[0])
406	}
407
408	foundBinaryPath := ""
409
410	// attempt to find binary, starting at longest possible name with given cmdArgs
411	for len(remainingArgs) > 0 {
412		path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
413		if !found {
414			remainingArgs = remainingArgs[:len(remainingArgs)-1]
415			continue
416		}
417
418		foundBinaryPath = path
419		break
420	}
421
422	if len(foundBinaryPath) == 0 {
423		return nil
424	}
425
426	// invoke cmd binary relaying the current environment and args given
427	if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil {
428		return err
429	}
430
431	return nil
432}
433
434// NewKubectlCommand creates the `kubectl` command and its nested children.
435func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
436	warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
437	warningsAsErrors := false
438
439	// Parent command to which all subcommands are added.
440	cmds := &cobra.Command{
441		Use:   "kubectl",
442		Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
443		Long: templates.LongDesc(`
444      kubectl controls the Kubernetes cluster manager.
445
446      Find more information at:
447            https://kubernetes.io/docs/reference/kubectl/overview/`),
448		Run: runHelp,
449		// Hook before and after Run initialize and write profiles to disk,
450		// respectively.
451		PersistentPreRunE: func(*cobra.Command, []string) error {
452			rest.SetDefaultWarningHandler(warningHandler)
453			return initProfiling()
454		},
455		PersistentPostRunE: func(*cobra.Command, []string) error {
456			if err := flushProfiling(); err != nil {
457				return err
458			}
459			if warningsAsErrors {
460				count := warningHandler.WarningCount()
461				switch count {
462				case 0:
463					// no warnings
464				case 1:
465					return fmt.Errorf("%d warning received", count)
466				default:
467					return fmt.Errorf("%d warnings received", count)
468				}
469			}
470			return nil
471		},
472		BashCompletionFunction: bashCompletionFunc,
473	}
474
475	flags := cmds.PersistentFlags()
476	flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
477
478	// Normalize all flags that are coming from other packages or pre-configurations
479	// a.k.a. change all "_" to "-". e.g. glog package
480	flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
481
482	addProfilingFlags(flags)
483
484	flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
485
486	kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
487	kubeConfigFlags.AddFlags(flags)
488	matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
489	matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
490
491	cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
492
493	f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
494
495	// Sending in 'nil' for the getLanguageFn() results in using
496	// the LANG environment variable.
497	//
498	// TODO: Consider adding a flag or file preference for setting
499	// the language, instead of just loading from the LANG env. variable.
500	i18n.LoadTranslations("kubectl", nil)
501
502	// From this point and forward we get warnings on flags that contain "_" separators
503	cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
504
505	ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
506
507	groups := templates.CommandGroups{
508		{
509			Message: "Basic Commands (Beginner):",
510			Commands: []*cobra.Command{
511				create.NewCmdCreate(f, ioStreams),
512				expose.NewCmdExposeService(f, ioStreams),
513				run.NewCmdRun(f, ioStreams),
514				set.NewCmdSet(f, ioStreams),
515			},
516		},
517		{
518			Message: "Basic Commands (Intermediate):",
519			Commands: []*cobra.Command{
520				explain.NewCmdExplain("kubectl", f, ioStreams),
521				get.NewCmdGet("kubectl", f, ioStreams),
522				edit.NewCmdEdit(f, ioStreams),
523				delete.NewCmdDelete(f, ioStreams),
524			},
525		},
526		{
527			Message: "Deploy Commands:",
528			Commands: []*cobra.Command{
529				rollout.NewCmdRollout(f, ioStreams),
530				scale.NewCmdScale(f, ioStreams),
531				autoscale.NewCmdAutoscale(f, ioStreams),
532			},
533		},
534		{
535			Message: "Cluster Management Commands:",
536			Commands: []*cobra.Command{
537				certificates.NewCmdCertificate(f, ioStreams),
538				clusterinfo.NewCmdClusterInfo(f, ioStreams),
539				top.NewCmdTop(f, ioStreams),
540				drain.NewCmdCordon(f, ioStreams),
541				drain.NewCmdUncordon(f, ioStreams),
542				drain.NewCmdDrain(f, ioStreams),
543				taint.NewCmdTaint(f, ioStreams),
544			},
545		},
546		{
547			Message: "Troubleshooting and Debugging Commands:",
548			Commands: []*cobra.Command{
549				describe.NewCmdDescribe("kubectl", f, ioStreams),
550				logs.NewCmdLogs(f, ioStreams),
551				attach.NewCmdAttach(f, ioStreams),
552				cmdexec.NewCmdExec(f, ioStreams),
553				portforward.NewCmdPortForward(f, ioStreams),
554				proxy.NewCmdProxy(f, ioStreams),
555				cp.NewCmdCp(f, ioStreams),
556				auth.NewCmdAuth(f, ioStreams),
557				debug.NewCmdDebug(f, ioStreams, false),
558			},
559		},
560		{
561			Message: "Advanced Commands:",
562			Commands: []*cobra.Command{
563				diff.NewCmdDiff(f, ioStreams),
564				apply.NewCmdApply("kubectl", f, ioStreams),
565				patch.NewCmdPatch(f, ioStreams),
566				replace.NewCmdReplace(f, ioStreams),
567				wait.NewCmdWait(f, ioStreams),
568				kustomize.NewCmdKustomize(ioStreams),
569			},
570		},
571		{
572			Message: "Settings Commands:",
573			Commands: []*cobra.Command{
574				label.NewCmdLabel(f, ioStreams),
575				annotate.NewCmdAnnotate("kubectl", f, ioStreams),
576				completion.NewCmdCompletion(ioStreams.Out, ""),
577			},
578		},
579	}
580	groups.Add(cmds)
581
582	filters := []string{"options"}
583
584	// Hide the "alpha" subcommand if there are no alpha commands in this build.
585	alpha := NewCmdAlpha(f, ioStreams)
586	if !alpha.HasSubCommands() {
587		filters = append(filters, alpha.Name())
588	}
589
590	templates.ActsAsRootCommand(cmds, filters, groups...)
591
592	for name, completion := range bashCompletionFlags {
593		if cmds.Flag(name) != nil {
594			if cmds.Flag(name).Annotations == nil {
595				cmds.Flag(name).Annotations = map[string][]string{}
596			}
597			cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
598				cmds.Flag(name).Annotations[cobra.BashCompCustom],
599				completion,
600			)
601		}
602	}
603
604	cmds.AddCommand(alpha)
605	cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
606	cmds.AddCommand(plugin.NewCmdPlugin(f, ioStreams))
607	cmds.AddCommand(version.NewCmdVersion(f, ioStreams))
608	cmds.AddCommand(apiresources.NewCmdAPIVersions(f, ioStreams))
609	cmds.AddCommand(apiresources.NewCmdAPIResources(f, ioStreams))
610	cmds.AddCommand(options.NewCmdOptions(ioStreams.Out))
611
612	return cmds
613}
614
615func runHelp(cmd *cobra.Command, args []string) {
616	cmd.Help()
617}
618