PowerShellでTerraformの入力補完(Auto Complete)を有効にする

2021.09.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

しばたです。

最近Terraformを使う機会が増えておりその実行環境は様々なのですが「そろそろ入力補完が欲しいなぁ」と思う様になったので調べた内容を共有します。

現時点で最新のTerraform 1.0.7、PowerShell 7.1.4の環境で動作確認をしています。

terraform -install-autocomplete コマンド

Terraformでは標準で入力補完のためのterraform -install-autocompleteコマンドが提供されています。

このコマンドはBashおよびZsh環境を対象としており、実行すると~/.bashrcおよび~/.zshrcに独自の設定を追記します。

# BashおよびZsh環境で実行
terraform -install-autocomplete

Bash環境の場合は以下の様なcomplete -Cの設定を追記します。

~/.bashrc

# Ubuntu 20.04 on WSL2のTerraformで terraform -install-autocomplete した後の .bashrc より抜粋
complete -C /usr/bin/terraform terraform

残念ながらこのコマンドはPowerShellには対応しておらず、PowerShell環境で実行するとエラーとなってしまいます。

C:\> terraform -install-autocomplete
Error executing CLI: Did not find any shells to install

Terraformの入力補完を読み解く

で、このterraform -install-autocompleteと同等のことをどうにかしてPowerShellで実現したいわけですが、そのために設定された

complete -C /usr/bin/terraform terraform

が何をしているか調べてみることにします。

completeコマンドはBashの内部コマンドで様々なオプションで入力補完の設定を追加するものです。
このうち-Cオプションについては指定されたコマンド(上記例では/usr/bin/terraform)の実行結果を補完内容とするものになります。

これだけ見ると引数なしでterraformコマンドを実行した結果をterraformの補完に使うことになります。
私は初見ではこの意味が全く理解できなかったのですが、調べてみるとBashでは入力補完の際にCOMP_LINECOMP_POINTといった特別な環境変数を設定し、これらの環境変数と合わせて使うことが分かりました。

Terraformのソースを見てみると確かにCOMP_LINE環境変数に対応した処理が入っていました。

  • 参考 : main.go
    • COMP_LINE環境変数が設定されている場合は通常のサブコマンド実行をスキップしてるのがわかります

この機能はPowerShell環境でも利用可能で、例えば以下の様に手動でCOMP_LINE環境変数を設定してterraformコマンドを実行してみると良い感じに補完文字列を返してくれます。

# 手動で COMP_LINE 環境変数を設定してみる
$env:COMP_LINE="terraform v"
terraform

ここまで分かればあとはPowerShellの入力補完機構に組み入れてやるだけで良さそうです。

Register-ArgumentCompleter コマンドレット

PowerShellではPowerShell 5.0からRegister-ArgumentCompleterコマンドレットが追加されておりユーザー独自の入力補完処理を追加することが可能です。

このコマンドレットでは-Nativeパラメーターを指定するとネイティブコマンド(外部コマンド)用の補完処理を記述可能で、ざっくり以下の様に記述します。

Register-ArgumentCompleter -Native -CommandName "<補完対象となる外部コマンド>" -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
    # 入力補完処理をスクリプトブロックの形で記述。引数は以下の通り
    # 第1引数 $wordToComplete (String) : 補完処理を呼び出した時のワード (例えば "terraform v" で補完処理を呼ぶと "v" が設定される)
    # 第2引数 $commandAst (System.Management.Automation.Language.CommandAst) : 補完処理を呼び出した時のAST
    # 第3引数 $cursorPosition (Int32) : 補完処理を呼び出した時のカーソル位置

    # 戻り値は System.Management.Automation.CompletionResult 型のオブジェクト
    # 以下の4つのパラメーターをコンストラクタに持つ
    [System.Management.Automation.CompletionResult]::new(
        "<completionText>", # 補完に使われる文字列 
        "<listItemText>",   # リスト形式の補完に使われる文字列
        "<resultType>",     # 文字列の種別。'ParameterValue'以外の種別を使うことは無いと思う
        "<ToolTip>"         # ツールチップ表示に使う文字列
    )
}

ここまでの内容を踏まえTerraform向けには以下の様にすれば最低限の要求を満たせます。

# completer for Terraform
Register-ArgumentCompleter -Native -CommandName terraform -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    $env:COMP_LINE=$commandAst.ToString()
    terraform | ForEach-Object {
        [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
    }
    Remove-Item Env:\COMP_LINE     
}

スクリプトブロックを呼び出している間だけCOMP_LINE環境変数の値を設定しterraformコマンドの実行結果を返してやります。
スクリプトブロックの終わりでCOMP_LINE環境変数を削除してやります。

これをプロファイルに設定しPowerShellの起動時に呼び出す様にします。
すると下図の様に良い感じに入力補完が利きます。

(terraform vまでキー入力して補完を実行した場合。validateversionが候補に挙がってくれている)

補足 : 偉大なる先人AWS

設定としては以上となりますが、この実装に至るまでには先人のヒントがありました。
去年AWS CloudShellがリリースされた際にCloudShell内のPowerShellに類似の実装があることに気が付き、これが本記事の実装の元ネタとなっています。

Microsoft.PowerShell_profile.ps1

# AWS CloudSHellに初期実装されている処理。AWS CLIの入力補完用
Register-ArgumentCompleter -Native -CommandName aws -ScriptBlock {
    param($commandName, $wordToComplete, $cursorPosition)
        $env:COMP_LINE=$wordToComplete
        $env:COMP_POINT=$cursorPosition
        aws_completer | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }
        Remove-Item Env:\COMP_LINE     
        Remove-Item Env:\COMP_POINT    
}

AWS CLIでは入力補完にaws_completerコマンドを使っており、PowerShellで有効にするために上記の様な実装となっています。

今回の実装とほぼ同じですね。というかほぼ丸パクリです。

ただ、この実装は古い間違ったドキュメントをベースにしてるためスクリプトブロックのパラメータ指定が間違っています。
(間違ってるのですが動作に支障はありません)

あとでAWSへフィードバックしようと思いますが、上記実装の厳密に正しい形は以下となります。

Microsoft.PowerShell_profile.ps1

# スクリプトブロックの引数を正しい形に修正
Register-ArgumentCompleter -Native -CommandName aws -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
        $env:COMP_LINE=$commandAst.ToString()
        $env:COMP_POINT=$cursorPosition
        aws_completer | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }
        Remove-Item Env:\COMP_LINE     
        Remove-Item Env:\COMP_POINT    
}

この補完設定はWindows環境でも利用可能ですので併せて利用してみてください。

(Windows環境でもAWS CLIの入力補完は可能)

最後に

以上となります。

内容としてはシンプルですが応用範囲は広いと思います。
本記事ではTerraformとAWS CLIの例を出していますが他のコマンドでも使えますのでみなさんがお使いのコマンドで試してみると良いでしょう。