cmd.go 18 KB

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