completion.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /*
  2. Copyright 2016 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 completion
  14. import (
  15. "bytes"
  16. "io"
  17. "github.com/spf13/cobra"
  18. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  19. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  20. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  21. )
  22. const defaultBoilerPlate = `
  23. # Copyright 2016 The Kubernetes Authors.
  24. #
  25. # Licensed under the Apache License, Version 2.0 (the "License");
  26. # you may not use this file except in compliance with the License.
  27. # You may obtain a copy of the License at
  28. #
  29. # http://www.apache.org/licenses/LICENSE-2.0
  30. #
  31. # Unless required by applicable law or agreed to in writing, software
  32. # distributed under the License is distributed on an "AS IS" BASIS,
  33. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  34. # See the License for the specific language governing permissions and
  35. # limitations under the License.
  36. `
  37. var (
  38. completionLong = templates.LongDesc(i18n.T(`
  39. Output shell completion code for the specified shell (bash or zsh).
  40. The shell code must be evaluated to provide interactive
  41. completion of kubectl commands. This can be done by sourcing it from
  42. the .bash_profile.
  43. Detailed instructions on how to do this are available here:
  44. https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion
  45. Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`))
  46. completionExample = templates.Examples(i18n.T(`
  47. # Installing bash completion on macOS using homebrew
  48. ## If running Bash 3.2 included with macOS
  49. brew install bash-completion
  50. ## or, if running Bash 4.1+
  51. brew install bash-completion@2
  52. ## If kubectl is installed via homebrew, this should start working immediately.
  53. ## If you've installed via other means, you may need add the completion to your completion directory
  54. kubectl completion bash > $(brew --prefix)/etc/bash_completion.d/kubectl
  55. # Installing bash completion on Linux
  56. ## If bash-completion is not installed on Linux, please install the 'bash-completion' package
  57. ## via your distribution's package manager.
  58. ## Load the kubectl completion code for bash into the current shell
  59. source <(kubectl completion bash)
  60. ## Write bash completion code to a file and source if from .bash_profile
  61. kubectl completion bash > ~/.kube/completion.bash.inc
  62. printf "
  63. # Kubectl shell completion
  64. source '$HOME/.kube/completion.bash.inc'
  65. " >> $HOME/.bash_profile
  66. source $HOME/.bash_profile
  67. # Load the kubectl completion code for zsh[1] into the current shell
  68. source <(kubectl completion zsh)
  69. # Set the kubectl completion code for zsh[1] to autoload on startup
  70. kubectl completion zsh > "${fpath[1]}/_kubectl"`))
  71. )
  72. var (
  73. completionShells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{
  74. "bash": runCompletionBash,
  75. "zsh": runCompletionZsh,
  76. }
  77. )
  78. // NewCmdCompletion creates the `completion` command
  79. func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command {
  80. shells := []string{}
  81. for s := range completionShells {
  82. shells = append(shells, s)
  83. }
  84. cmd := &cobra.Command{
  85. Use: "completion SHELL",
  86. DisableFlagsInUseLine: true,
  87. Short: i18n.T("Output shell completion code for the specified shell (bash or zsh)"),
  88. Long: completionLong,
  89. Example: completionExample,
  90. Run: func(cmd *cobra.Command, args []string) {
  91. err := RunCompletion(out, boilerPlate, cmd, args)
  92. cmdutil.CheckErr(err)
  93. },
  94. ValidArgs: shells,
  95. }
  96. return cmd
  97. }
  98. // RunCompletion checks given arguments and executes command
  99. func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error {
  100. if len(args) == 0 {
  101. return cmdutil.UsageErrorf(cmd, "Shell not specified.")
  102. }
  103. if len(args) > 1 {
  104. return cmdutil.UsageErrorf(cmd, "Too many arguments. Expected only the shell type.")
  105. }
  106. run, found := completionShells[args[0]]
  107. if !found {
  108. return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0])
  109. }
  110. return run(out, boilerPlate, cmd.Parent())
  111. }
  112. func runCompletionBash(out io.Writer, boilerPlate string, kubectl *cobra.Command) error {
  113. if len(boilerPlate) == 0 {
  114. boilerPlate = defaultBoilerPlate
  115. }
  116. if _, err := out.Write([]byte(boilerPlate)); err != nil {
  117. return err
  118. }
  119. return kubectl.GenBashCompletion(out)
  120. }
  121. func runCompletionZsh(out io.Writer, boilerPlate string, kubectl *cobra.Command) error {
  122. zshHead := "#compdef kubectl\n"
  123. out.Write([]byte(zshHead))
  124. if len(boilerPlate) == 0 {
  125. boilerPlate = defaultBoilerPlate
  126. }
  127. if _, err := out.Write([]byte(boilerPlate)); err != nil {
  128. return err
  129. }
  130. zshInitialization := `
  131. __kubectl_bash_source() {
  132. alias shopt=':'
  133. alias _expand=_bash_expand
  134. alias _complete=_bash_comp
  135. emulate -L sh
  136. setopt kshglob noshglob braceexpand
  137. source "$@"
  138. }
  139. __kubectl_type() {
  140. # -t is not supported by zsh
  141. if [ "$1" == "-t" ]; then
  142. shift
  143. # fake Bash 4 to disable "complete -o nospace". Instead
  144. # "compopt +-o nospace" is used in the code to toggle trailing
  145. # spaces. We don't support that, but leave trailing spaces on
  146. # all the time
  147. if [ "$1" = "__kubectl_compopt" ]; then
  148. echo builtin
  149. return 0
  150. fi
  151. fi
  152. type "$@"
  153. }
  154. __kubectl_compgen() {
  155. local completions w
  156. completions=( $(compgen "$@") ) || return $?
  157. # filter by given word as prefix
  158. while [[ "$1" = -* && "$1" != -- ]]; do
  159. shift
  160. shift
  161. done
  162. if [[ "$1" == -- ]]; then
  163. shift
  164. fi
  165. for w in "${completions[@]}"; do
  166. if [[ "${w}" = "$1"* ]]; then
  167. echo "${w}"
  168. fi
  169. done
  170. }
  171. __kubectl_compopt() {
  172. true # don't do anything. Not supported by bashcompinit in zsh
  173. }
  174. __kubectl_ltrim_colon_completions()
  175. {
  176. if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
  177. # Remove colon-word prefix from COMPREPLY items
  178. local colon_word=${1%${1##*:}}
  179. local i=${#COMPREPLY[*]}
  180. while [[ $((--i)) -ge 0 ]]; do
  181. COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
  182. done
  183. fi
  184. }
  185. __kubectl_get_comp_words_by_ref() {
  186. cur="${COMP_WORDS[COMP_CWORD]}"
  187. prev="${COMP_WORDS[${COMP_CWORD}-1]}"
  188. words=("${COMP_WORDS[@]}")
  189. cword=("${COMP_CWORD[@]}")
  190. }
  191. __kubectl_filedir() {
  192. local RET OLD_IFS w qw
  193. __kubectl_debug "_filedir $@ cur=$cur"
  194. if [[ "$1" = \~* ]]; then
  195. # somehow does not work. Maybe, zsh does not call this at all
  196. eval echo "$1"
  197. return 0
  198. fi
  199. OLD_IFS="$IFS"
  200. IFS=$'\n'
  201. if [ "$1" = "-d" ]; then
  202. shift
  203. RET=( $(compgen -d) )
  204. else
  205. RET=( $(compgen -f) )
  206. fi
  207. IFS="$OLD_IFS"
  208. IFS="," __kubectl_debug "RET=${RET[@]} len=${#RET[@]}"
  209. for w in ${RET[@]}; do
  210. if [[ ! "${w}" = "${cur}"* ]]; then
  211. continue
  212. fi
  213. if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
  214. qw="$(__kubectl_quote "${w}")"
  215. if [ -d "${w}" ]; then
  216. COMPREPLY+=("${qw}/")
  217. else
  218. COMPREPLY+=("${qw}")
  219. fi
  220. fi
  221. done
  222. }
  223. __kubectl_quote() {
  224. if [[ $1 == \'* || $1 == \"* ]]; then
  225. # Leave out first character
  226. printf %q "${1:1}"
  227. else
  228. printf %q "$1"
  229. fi
  230. }
  231. autoload -U +X bashcompinit && bashcompinit
  232. # use word boundary patterns for BSD or GNU sed
  233. LWORD='[[:<:]]'
  234. RWORD='[[:>:]]'
  235. if sed --help 2>&1 | grep -q GNU; then
  236. LWORD='\<'
  237. RWORD='\>'
  238. fi
  239. __kubectl_convert_bash_to_zsh() {
  240. sed \
  241. -e 's/declare -F/whence -w/' \
  242. -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
  243. -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
  244. -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
  245. -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
  246. -e "s/${LWORD}_filedir${RWORD}/__kubectl_filedir/g" \
  247. -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kubectl_get_comp_words_by_ref/g" \
  248. -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kubectl_ltrim_colon_completions/g" \
  249. -e "s/${LWORD}compgen${RWORD}/__kubectl_compgen/g" \
  250. -e "s/${LWORD}compopt${RWORD}/__kubectl_compopt/g" \
  251. -e "s/${LWORD}declare${RWORD}/builtin declare/g" \
  252. -e "s/\\\$(type${RWORD}/\$(__kubectl_type/g" \
  253. <<'BASH_COMPLETION_EOF'
  254. `
  255. out.Write([]byte(zshInitialization))
  256. buf := new(bytes.Buffer)
  257. kubectl.GenBashCompletion(buf)
  258. out.Write(buf.Bytes())
  259. zshTail := `
  260. BASH_COMPLETION_EOF
  261. }
  262. __kubectl_bash_source <(__kubectl_convert_bash_to_zsh)
  263. _complete kubectl 2>/dev/null
  264. `
  265. out.Write([]byte(zshTail))
  266. return nil
  267. }