Unyablog.

のにれんのブログ

kubectl の wrapper を作る際の tips (ZSH 補完など)

kubectl に自動で namespaceや context を付与するような wrapper を作るとする。

そんなコマンドのインターフェースとして、

$ kubewrapper [...wrapper flags] [kubectl args / flags]
(例) $ kubewrapper -p staging describe pod ...

とすると

$ kubectl --context foo --namespace bar [kubectl args / flags]
(例) $ kubectl --context foo --namespace bar describe pod ...

といったコマンドが exec される、すなわちいくつかの flags を受け取って kubectl 用に変換しつつ args としては kubectl の引数をまるまる受け取るといったものが考えられる。

こういったコマンドを作るためには、補完をはじめとしていくつか注意点があるのでメモ。

外部 plugin 対応

通常の kubectl サブコマンド(getlogs など)では、 --context や --namespace といった flag はサブコマンドの前に置いても動作する。

しかし、外部プラグインを呼ぶサブコマンドでは動作せず、サブコマンドのあとに flag をつける必要がある。

$ kubectl --context foo awesome-plugin
Error: flags cannot be placed before plugin name: --context

get, logs などは後置でも動作するので、常に後置にする(args 1つ目の直後)ように作ると良い。

上述の例でいうと、以下のようになる。

$ kubectl [kubectl 1st arg] --context foo --namespace bar [kubectl remaining args / flags]
(例) $ kubectl describe --context foo --namespace bar pod ...

kubectl の補完を利用する

kubectl では強力な補完機能を備えており、

source <(kubectl completion zsh)

すると _kubectl 関数が ZSH にインストールされて、kubectl の呼び出し時に補完用関数として呼び出されるようになる。

上述した kubewrapper での補完でも、 kubewrapper のオプションの補完もしつつ、kubectl の補完も使えるようにしたら便利だが、どうすればよいのか?

_kubectl は何をするのか

_kubectlcobra を通じて生成されているが、その中では type されたコマンドの __complete サブコマンドが呼ばれている。

($ kubectl completion zsh の結果より)
requestComp="${words[1]} __complete ${words[2,-1]}"

色々細かい処理を省略すると、$words には今まさに打ち込んでいるコマンドの配列となっており、ZSH は 1-index なので words[1] は実行しようとするコマンド、 ${words[2,-1} は残りの引数ということになる。

すなわち、 $ kubectl get po<tab> を打っているとき、裏では _kubectl 内で $ kubectl __complete get po が呼ばれている。

_kubectl をカスタムコマンドで使う

ここで上述の kubewrapper の補完関数として _kubectl を設定するとどうなるのか。

_kubectl はあくまで今打ち込まれているコマンドを打つので、

$ kubewrapper -p staging get po<tab>

と打ったときは

$ kubewrapper __complete -p staging get po

が呼ばれることになる。

ここで kubectl の補完結果を流用するには、 wrapper 独自のオプションを消し、namespace や context flag を付加して kubectl の __complete コマンドを exec する、すなわち kubewrapper が以下を exec すれば良い。

$ kubectl __complete --context foo --namespace bar get po

※ __complete の前に namespace や context をつけると plugin 同様エラーになる

これを実現するには、wrapper の argv で __complete が 2 つめに来ていたら、$ kubectl __complete を打とうとしていると解釈すれば良い。wrapper で必須の引数が __complete の後にくることになるので、順序を入れ替えて解釈すると楽。

$ kubewrapper __complete -p staging get po
-> wrapper 内部では以下のように解釈する
$ kubewrapper -p staging __complete get po
-> そうすると wrapper の機能で自然と __complete が打たれる
$ kubectl __complete --context foo --namespace bar get po

wrapper のオプション補完

ここまで話した通り kubectl 関係の補完は _kubectl を通じて wrapper を呼び出して $ kubectl __complete に変換すればいい感じになるが、 その前に wrapper の必須 flag を埋めてもらう必要がある。

wrapper の必須 flag がなければそれを補完、満たされていれば kubectl の補完を発動させたい。

これは気合でいい感じに補完関数を書くことになる。

How do I check whether a zsh array contains a given value? - Unix & Linux Stack Exchange を参考にしつつ、以下のようにした:

if [[ $words[(Ie)-p] == 0 ]]; then
  # _arguments に wrapper の補完のみを記載する
  _arguments ...
else
  # _arguments に kubectl の補完も加える
  _arguments ... \
    '(-)*:kubectl args: _kubectl'
fi

(-)* は kubectl の args が現れた後は全てのハイフンの補完を無視するという意味。

zsh.sourceforge.io

こうすると、最初は wrapper に必須の flag のみが補完され、全て埋まったら wrapper の他の flags とともに kubectl の補完も表示されるようになる。