[超意訳]Terraform v0.6系からv0.7へのアップグレードガイド

待ちに待ったTerraform v0.7がリリースされました！！

Hashicorp社のブログにもリリース情報が掲載されています。
https://www.hashicorp.com/blog/terraform-0-7.html

今回は機能追加/変更など大きめのものが多かったからか、アップグレードガイドが用意されています。
https://www.terraform.io/upgrade-guides/0-7.html

アップグレードについて以下に意訳がてらメモを残しておきます。
(詳しいことは上のアップグレードガイドを参照してくださいね！)


## Plugin Binaries

0.6系までは以下のようにバイナリが分割されていました。

    terraform               # core binary
    terraform-provider-*    # provider plugins
    terraform-provisioner-* # provisioner plugins

これらのバイナリは`$PATH`が通ったところ、または`~/.terraform.d`に配置されているはずです。

0.7系ではこれらのバイナリが統合され、`terraform`バイナリのみとなりました。

このためアップグレードする際は`terraform`バイナリを置き換えたのちに`terraform-provider-*`や`terraform-provisioner-*`バイナリは削除してください。

#### 筆者注

Terraform組み込みのプロバイダ/プロビジョナ以外の外部プラグインについてはこれまで通りバイナリ分割されています。

  - Terraform for さくらのクラウド(`terraform-provider-sakuracloud`バイナリ)
  - Terraform for Arukas(`terraform-provider-arukas`バイナリ)

などです。これらはこれまで通り、`$PATH`が通ったところ、または`~/.terraform.d`に配置すればOKです。

## Maps in Displayed Plans

これまで、配列の要素数は`#`で表現されていました。
これからはListについては`#`、Mapについては`%`で表現されます。

    somelist.#:  "0" => "1"
    somelist.0:  "" => "someitem"
    somemap.%:   "0" => "1"
    somemap.foo: "" => "bar"

## String Concatenation

`concat()`関数はこれまで配列の結合、文字列の結合両方に対応していましたが、
0.7系では配列でのみ利用可能です。文字列で利用するとエラーとなります。

    "${concat(var.foo, "-suffix")}"     # => Error! No longer supported.

文字列の場合は、以下のようにします。

    "${var.foo}-suffix"

## Nested Quotes and Escaping

これまで、後方互換性確保のために以下のような書き方ができましたが今後エラーとなります。

    "${lookup(var.somemap, \"somekey\")}"  # => Syntax Error!

`${}`の内側では新たなクォートコンテキストとなるため、以下のように書けばOKです

    "${lookup(var.somemap, "somekey")}"

もし`${}`の内側の文字列内でダブルクォートを使いたい場合はエスケープを行います。

    "${upper("\"quoted\"")}"    # => "QUOTED"

`upper()`関数の引数として`"quoted"`というダブルクォートを含む文字を指定していますね。
この場合はエスケープが必要ということです。

## Safer terraform plan Behavior

0.6系までは`terraform plan`実行時にstateファイルが更新されてしまうことがありました。
`terraform plan`ではリソース状態のリフレッシュ(APIなどで情報を再取得)が行われるのですが、
そのタイミングでstateファイルが書き換わってしまうということがありました。

0.7系ではこの点が改善され、`plan`は副作用なく(stateファイルが更新されることなく)実行できます。`apply`または`refresh`を実行するとstateファイルの更新が行われます。

## Migrating to Data Sources

0.7系では`Data Sources`が追加されました。
(読み取り専用のリソースみたいなやつですね)

これにより、幾つかのリソースが非推奨となりました。
(まだ使えるけど警告が出ます)

以下のリソースが非推奨となったものです。

 - `atlas_artifact`
 - `template_file`
 - `template_cloudinit_config`
 - `tls_cert_request`

これらのリソースを`Data Sources`に対応させるには、以下のように`resource`キーワード部分を`data`に変えるだけです。

変更前：

    resource "template_file" "example" {
      template = "someconfig"
    }
    resource "aws_instance" "example" {
      user_data = "${template_file.example.rendered}" 
      # ...
    }

変更後：

    data "template_file" "example" {
      template = "someconfig"
    }
    resource "aws_instance" "example" {
      user_data = "${template_file.example.rendered}" 
      # ...
    }

## Migrating to native lists and maps

0.7系ではList、Mapをファーストクラスオブジェクトとして扱うようになりました。
これまで`join()`と`split()`などで擬似的に表現していた配列などの記述が直感的に、すっきりと表現できるようになりました。

### 0.6系での書き方

例えば、モジュールからのOutputですが、これまでは1つの値しか返せなかったため、
`join()`でカンマ区切り文字列などにすることで擬似的に配列扱いさせていました。

    output "private_subnets" {
      value = "${join(",", aws_subnet.private.*.id)}"
    }

この値を利用する側では、`split()`してカンマ区切りから配列に戻して使っていました。

    subnet_id = "${element(split(",", var.private_subnets), count.index)}"

注: `element([list] , index)`は指定配列の中からindexの位置にある要素を返す関数

### 0.7系での書き方

0.7系では配列を直接返すことができるようになりました。

    output "private_subnets" {
      value = ["${aws_subnet.private.*.id}"]
    }

この値を利用する側も`[]`構文が使えるようになったため、直感的に書けるようになりました。

    subnet_id = "${var.private_subnets[count.index]}"

これらはモジュールだけでなく、variableなどでも使えます。

### おまけ

`element`関数もこれまで通り使えます。`[]`と比べると、配列要素数より大きい値が指定できるという点があります。
例えば以下のように指定した場合、

    subnet_id = "${element(var.private_subnets, count.index)}"

private_subnetsの要素数が3、要素数以上のindexが指定されたら`mod`してくれたりします。
(index=3の場合はprivate_subnets[0]、index=4の場合はprivate_subnets[1]、、、ですね)

## Map value overrides

これまではmap型変数の上書きにはカンマ区切りでの名前指定を行っていました。

例として、以下のような変数定義に対し、

    variable "amis" {
      type = "map"
      default = {
        us-east-1 = "ami-123456"
        us-west-2 = "ami-456789"
        eu-west-1 = "ami-789123"
      }
    }

コマンドラインオプションで`-var "amis.us-west-2=overriden_value"`という書き方で上書きしていました。
(環境変数、tfvarファイルでの指定も同様)

0.7系ではこれが変更され、以下のようになります。

    -var 'amis = { us-west-2 = "overriden_value" }'

map型変数はdefault値とコマンドライン(など)で指定された値がマージされます。
先ほどの変数定義は以下のような値になります。

    {
      us-east-1 = "ami-123456"  
      us-west-2 = "overriden_value"
      eu-west-1 = "ami-789123"
    }

注：これまでの書き方`-var "amis.us-west-2=overriden_value"`をしてもエラーになりませんが、変数の上書きはできないようです。
`terraform plan`するだけでは気づけない可能性もありますのでこの部分は注意が必要そうですね。
見るべき点は

 - `terraform.tfvars`ファイルでの指定
 - `-var-file`フラグで指定しているファイル
 - 環境変数`TF_VAR_[name]`など

などでしょうか。

変数定義、上書き方法についての詳細は[公式ドキュメント:variables](https://www.terraform.io/docs/configuration/variables.html)に記載されていますので参照してみてください。


## その他注意点(筆者追記)

### エラーの例1:

アップグレード時に以下のようなエラーが出ることがあります。

```
Error configuring: 1 error(s) occurred:

* Unrecognized remote plugin message: 2|unix|/var/folders/wh/5gklk3s17hn9c60827qnkgk99999gn/T/tf-plugin9999999

This usually means that the plugin is either invalid or simply
needs to be recompiled to support the latest protocol.
```

これは、プラグイン(`terraform-provider-*`バイナリ、または`terraform-provisioner-*`バイナリ)のバージョンが古い場合に表示されます。

0.6系のプラグインのバイナリが残っていないか確認しましょう。
(プラグイン自体が0.7系に対応していない場合もありますが、、、その場合は該当プラグイン開発者にIssue投げましょう)

### エラーの例2:

```
Error configuring: 1 error(s) occurred:

* Incompatible API version with plugin. Plugin version: 1, Ours: 2
```

これはプラグインは0.7系になっているが、`terraform`本体のバージョンが古い場合に出ます。
`terraform version`コマンドを実行して、きちんとterraform本体が0.7系になっているか確認しましょう。

## 終わりに

Terraform v0.7になって様々な機能がより使いやすくなりました。

特に`import`については強力な機能です。
参考記事：[Terraform v0.7が誘う「Infrastructure as Code」の世界](http://knowledge.sakura.ad.jp/knowledge/5657/)

また、List/Mapが使える範囲が拡大したことで、よりモジュール化しやすくなっています。
この新しいバージョンを機にぜひTerraformでInfrastructure as Codeしてみてください！

こちらもよろしくお願いします！もちろんTerraform v0.7対応してます。

  - [Terraform for さくらのクラウド](https://github.com/yamamoto-febc/terraform-provider-sakuracloud)
  - [Terraform for Arukas](https://github.com/yamamoto-febc/terraform-provider-arukas)

以上です。