cmd.go 18 KB

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