クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

Terraformプロバイダから動的に型定義情報を取得するtfschemaというツールを作った

Terraform職人の @minamijoyo です。

クラウドワークスではAWSのインフラ構成管理にHashiCorpの Terraform を利用しており、 日々Terraformの設定ファイルを書いてるわけですが、 コード書いてると、リソースタイプの名前がうろ覚えとか、属性値の名前のスペルに自信がないとか、この属性値って必須項目だっけ? とか、なんだかんだで公式ドキュメントを見ながらコードを書いてることが多いです。

Vimのプラグインで補完してくれるやつがあるのは知ってるんだけど、 あらかじめリストファイルを持っているアプローチだと、バージョンに合わせてリストファイルの更新しないといけなくて、仕組み上の限界があるし、サポートされてない自作プロバイダとかだとそもそも使えない。

なんかもっといいかんじにできないかなぁと思って、趣味でTerraformのソースコードを読んでたら、最近入ったGetSchema APIというのを見つけて、欲しかったのはこれだよ感で早速試してみたらバグってるし。まじかよ。で、バグ報告修正に協力したり、AWSプロバイダにも修正を取り込んでもらったり、なんやかんやあって、結果的にTerraformプロバイダから動的に型定義情報を取得するtfschemaという俺得ツールができたので、テラフォーマーズの皆さんの役に立つかなーと思って紹介します。

github.com

特徴

  • Terraformのコアと同じgo-pluginプロトコルを使ってTerraformプロバイダから動的に型定義情報を取得できます。
  • リソースタイプの一覧が取得できます。
  • このリソースタイプの一覧を使ってbash/zshでコマンド引数の補完も可能。
  • なので、このリソースタイプの一覧補完を使ってシュッと公式ドキュメントを開くこともできる。

TerraformプロバイダとはAWS/GCP/AzureなどのTerraformのプラグインのことです。 Terraformのコアと同じ方法でTerraformプロバイダのバイナリと実際に通信しているのがポイントで、 あらかじめリストファイルを作成しておくわけでもないので、最新版のリストファイルがまだ更新されていないみたいな問題は原理的にありません。

とりあえずまぁデモを見てくれ。

f:id:minamijoyo:20180323183008g:plain

tfschemaはCLIツールで、最終的にはLanguage Server Protocolとか実装してエディタにインテグレーションしたいなーと妄想してるんだけど、CLIツール単体としても十分有用なので、ここらで使い方を説明しておきます。

本稿執筆時点のtfschemaのバージョンは最新版のv0.1.1です。最新の状況についてはREADMEをご確認下さい。

インストール

いくつか方法があるんだけど、Macユーザの場合はHomebrewのtapを用意してあるので、これを使うのが一番簡単かなと思います。

$ brew install minamijoyo/tfschema/tfschema

あとツール自体はGoで書いてるので、Goの開発環境があればgo getでもビルド&インストールできます。

$ go get -u github.com/minamijoyo/tfschema

その他ビルド済のバイナリをGitHubのリリースページに置いてあるので、ダウンロードして展開して適当にパスが通ってるところに置いて下さい。

https://github.com/minamijoyo/tfschema/releases

Windowsは手元に開発環境がなく稼働確認できてないですが、もし需要があってデバッグ手伝ってくれる人がいればIssueを立てて頂ければなんとかします。

サポートしてるTerraformプロバイダのバージョン

AWS/GCP/Azureのメジャーどころのプロバイダは以下のバージョン以降が必要です。

  • terraform-provider-aws >= v1.11.0
  • terraform-provider-google >= v1.5.0
  • terraform-provider-azurerm >= v1.3.0

その他のプロバイダについては、動くかもしれないし、動かないかもしれません。 というのもtfschemaが内部的に使用しているGetSchema APIというのがわりと最近入ったTerraformの機能で、プロバイダが最新のTerraformのAPIをサポートしていないと動きません。

具体的にはプロバイダをビルドするのに依存しているライブラリに以下のバージョンが必要です。

  • hashicorp/terraform >= v0.10.8
  • zclconf/go-cty >= 14e23b14828dd12cc7ae0956813c7e91a196e68f (2018/01/06)

基本的な使い方

terraform init でインストールされたプラグインのバイナリはデフォルトで .terraform/plugins/<OS>_<ARCH> 配下に保存されています。(Macの場合 <OS>_<ARCH>darwin_amd64 です)

tfschemaはこのバイナリを利用することができるので、terraformコマンドを実行するのと同じディレクトリで実行できます。

ここでは例としてterraform initAWSプロバイダをインストールします。

$ echo 'provider "aws" {}' > main.tf
$ terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.11.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 1.11"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

この状態で、 tfschema resource show aws_security_group を実行すると、aws_security_groupの型定義が取得できます。

$ tfschema resource show aws_security_group
+------------------------+-------------+----------+----------+----------+-----------+
| ATTRIBUTE              | TYPE        | REQUIRED | OPTIONAL | COMPUTED | SENSITIVE |
+------------------------+-------------+----------+----------+----------+-----------+
| description            | String      | false    | true     | false    | false     |
| name                   | String      | false    | true     | true     | false     |
| name_prefix            | String      | false    | true     | false    | false     |
| owner_id               | String      | false    | false    | true     | false     |
| revoke_rules_on_delete | Bool        | false    | true     | false    | false     |
| tags                   | Map(String) | false    | true     | false    | false     |
| vpc_id                 | String      | false    | true     | true     | false     |
+------------------------+-------------+----------+----------+----------+-----------+

block_type: egress, nesting: NestingSet, min_items: 0, max_items: 0
+------------------+--------------+----------+----------+----------+-----------+
| ATTRIBUTE        | TYPE         | REQUIRED | OPTIONAL | COMPUTED | SENSITIVE |
+------------------+--------------+----------+----------+----------+-----------+
| cidr_blocks      | List(String) | false    | true     | false    | false     |
| description      | String       | false    | true     | false    | false     |
| from_port        | Number       | true     | false    | false    | false     |
| ipv6_cidr_blocks | List(String) | false    | true     | false    | false     |
| prefix_list_ids  | List(String) | false    | true     | false    | false     |
| protocol         | String       | true     | false    | false    | false     |
| security_groups  | Set(String)  | false    | true     | false    | false     |
| self             | Bool         | false    | true     | false    | false     |
| to_port          | Number       | true     | false    | false    | false     |
+------------------+--------------+----------+----------+----------+-----------+

block_type: ingress, nesting: NestingSet, min_items: 0, max_items: 0
+------------------+--------------+----------+----------+----------+-----------+
| ATTRIBUTE        | TYPE         | REQUIRED | OPTIONAL | COMPUTED | SENSITIVE |
+------------------+--------------+----------+----------+----------+-----------+
| cidr_blocks      | List(String) | false    | true     | false    | false     |
| description      | String       | false    | true     | false    | false     |
| from_port        | Number       | true     | false    | false    | false     |
| ipv6_cidr_blocks | List(String) | false    | true     | false    | false     |
| protocol         | String       | true     | false    | false    | false     |
| security_groups  | Set(String)  | false    | true     | false    | false     |
| self             | Bool         | false    | true     | false    | false     |
| to_port          | Number       | true     | false    | false    | false     |
+------------------+--------------+----------+----------+----------+-----------+

ただみんなが使ってるTerraformのコードベースがtfschemaがサポートしていない古いバージョンのプラグインを使用している場合は、別の場所に置いたプラグインのバイナリを読み込むことも可能です。

tfschemaは以下のパスからプロバイダのバイナリを探します。

※これはterraformがプロバイダのバイナリを探すルールとおおよそ同じですが、厳密には若干異なります。詳細は長くなるので割愛。

  1. カレントディレクト
  2. tfschema のバイナリと同じディレクト
  3. ユーザベンダディレクトリ ( terraform.d/plugins/<OS>_<ARCH> )
  4. 自動インストールディレクトリ ( .terraform/plugins/<OS>_<ARCH> )
  5. グローバルプラグインディレクトリ ( $HOME/.terraform.d/plugins )
  6. グローバルプラグインディレクトリ(OS/ARCH) ( $HOME/.terraform.d/plugins/<OS>_<ARCH> )
  7. GOPATH ( $GOPATH/bin )

tfschema自体にはプラグインのインストール機能はないので、terraformが使っているのと異なるバージョンのプラグインをtfschemaで使いたい場合は、terraform init で取得したバイナリを適宜tfschemaで見えるディレクトリに移動して下さい。

コマンド補完

bash/zshでコマンドの引数の補完を有効にするには以下のコマンドを実行して下さい。

$ tfschema -install-autocomplete

このコマンドを実行すると ~/.bashrc~/.zshrc に以下の行が追加されます。

.bashrc

complete -C </path/to/tfschema> tfschema

.zshrc

autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C </path/to/tfschema> tfschema

bash/zshをリロードするとタブでサブコマンドやリソースタイプの補完が有効になるはずです。 ちなみにこの補完機能も動的にリソースタイプ一覧を取得しているので、プロバイダのバージョンが上がっても補完を再インストールする必要はありません。

コマンドの使い方もうちょっと補足

tfschemaには現状provider, resource, dataのサブコマンドがあり、それぞれTerraformのprovider, resource, dataブロックに対応します。

$ tfschema --help
Usage: tfschema [--version] [--help] <command> [<args>]

Available commands are:
    data
    provider
    resource

resourceの下にはlist, show, browseのサブコマンドがあります。 listはリソースタイプの一覧の取得、showはリソースの型定義の取得、browseは公式ドキュメントをシステムのWebブラウザで開きます。 パラメータの意味調べるのに結局公式ドキュメント見るんかいって言われそうだけどw

$ tfschema resource --help
This command is accessed by using one of the subcommands below.

Subcommands:
    browse    Browse a documentation of resource
    list      List resource types
    show      Show a type definition of resource
$ tfschema resource list aws | grep aws_security
aws_security_group
aws_security_group_rule

resource show はリソースの型定義を取得し、デフォルトで人が読みやすいようにテーブル形式で出力しますが 、プログラムでパースできるように -format=json を指定するとJSONで出力もできるようにしてあります。

$ tfschema resource show --help
Usage: tfschema resource show [options] RESOURCE_TYPE

Options:

  -format=type    Set output format to table or json (default: table)

既知の問題

既知の問題として一部のリソースで以下のような無効なスキーマエラーが出ることがあります。

$ tfschema resource show aws_glue_catalog_database
Failed to get schema from provider: unexpected EOF
The child panicked:

panic: invalid Schema.Elem 4; need *Schema or *Resource

これはTerraformの既知のバグで、コアとプロバイダ側でバリデーションの条件が矛盾していることに起因します。Terraformのコア側のバグ修正は、Terraform v0.11.4で既にマージされており、Terraformプロバイダ側のバグ修正もそのうちマージされる予定です。

現時点で以下のリソースでこの不具合が確認されています。

terraform-provider-aws

これで治る見込み。

terraform-provider-google

  • data/source_storage_object_signed_url
  • resource/bigquery_dataset
  • resource/bigquery_table
  • resource/compute_instance
  • resource/compute_project_metadata
  • resource/dataproc_cluster
  • resource/pubsub_subscription

一旦修正されたけど、リバートされた。Terraformコア側の修正がマージされたのでそのうちリトライされるはず。

おわりに

Terraformプロバイダから動的に型定義情報を取得するtfschemaというツールを作ったので紹介しました。

まだとりあえず自分が欲しいものを作ったというかんじなので、使ってみて動いたーとか、エラーが出て動かないとか、なにがしかフィードバックもらえるとうれしいです。GitHubにスターをもらえると喜びます。

GitHubのIssueを立てたりするのに抵抗あれば、雑にTwitter@minamijoyo にフィードバックいただければと思います。

あと クラウドソーシングのクラウドワークス ではTerraform大好きっ子を募集しています。

www.wantedly.com

© 2016 CrowdWorks, Inc., All rights reserved.