cmd.go 18 KB

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