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