terraform v0.12 アップデート terraform 0.12upgrade,terraform 0.12checklistサブコマンド実行結果と、ファイルの変更例

# はじめに
terraform v0.12 きましたね！
今回のアップデートはかなり気合いを感じます！
なんかいろいろ変わってる感ありすぎてアップデートするか迷ってしまいましたが、
とりあえず公式手順を参考にしながら、アップデートしてみました！


まずはみなさんも公式ドキュメントとこちらのページを参考にしてみてください！

Upgrading to Terraform v0.12
https://www.terraform.io/upgrade-guides/0-12.html

Terraform 0.12がリリースされたのでアップグレードしてみた
https://dev.classmethod.jp/tool/terraform-upgrade-from-0-11-to-0-12/


# 1. terraform v0.11.14
まずは 現在のterraform のバージョンを確認します。 **v0.11.14 でない場合は v0.11.14にアップデート** しましょう。
v0.11.14 には terraform v0.12 にアップデートする準備が整っているか確認できる、terraform 0.12checklist というサブコマンドが実装されています。そのため、このバージョンにしておく必要があるのです。


```
> tfenv install 0.11.14 # Terraform 0.11.14のダウンロード
> tfenv use 0.11.14     # Terraform 0.11.14へスイッチ
> terraform -v
Terraform v0.11.14
```


自分の場合はすでに v0.11.14 でした！

```
> terraform -v
Terraform v0.11.14
+ provider.aws v1.60.0

Your version of Terraform is out of date! The latest version
is 0.12.1. You can update by downloading from www.terraform.io/downloads.html
```

v0.12へのアップデート準備をする前に、terraform init と terraform apply でリソースを最新の状態にしておきましょう。

```
> terraform init

Initializing the backend...

Initializing provider plugins...

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.

> terraform plan
省略

> terraform apply
省略

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
```
v0.12に互換性のあるコードに書き換える作業をこのあと行うので、
この状態までやったら、gitにあげて、いつでも作業前のコード戻れるようにしておきましょう。



# 2. terraform 0.12checklist コマンドの実行結果

terraform v0.11.14 には、v0.12 にアップデートする前にやっておかなければならないことがわかるサブコマンドが実装されています。

このコマンドを打った結果は人それぞれ違うと思いますが、自分の例を紹介します。


```
> terraform 0.12checklist
After analyzing this configuration and working directory, we have identified some necessary steps that we recommend you take before upgrading to Terraform v0.12:

- [ ] Upgrade provider "aws" to version 2.14.0 or newer.

  No currently-installed version is compatible with Terraform 0.12. To upgrade, set the version constraint for this provider as follows and then run `terraform init`:

      version = "~> 2.14.0"

Taking these steps before upgrading to Terraform v0.12 will simplify the upgrade process by avoiding syntax errors and other compatibility problems.
```


aws の provider version が 1.60 だったので、指定された2.14.0のバージョンに引き上げます。

```
> vim region.tf
provider "aws" {
  version  = "~> 2.14.0"
  region   = "${lookup(var.common,"${terraform.workspace}.region")}"
}

```

改めて、terraform init

```
> terraform init

Initializing the backend...

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

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.
```


チェックリストが通りました！
アップデートの準備が全て終わっていると、terraform 0.12checklist の結果は下記のようになります！

```
> terraform 0.12checklist
Looks good! We did not detect any problems that ought to be
addressed before upgrading to Terraform v0.12.

This tool is not perfect though, so please check the v0.12 upgrade
guide for additional guidance, and for next steps:
    https://www.terraform.io/upgrade-guides/0-12.html
```





# 3. terraform 0.12 update

ここでようやく terraform 0.12に updateしましょう！
update自体の手順は省略させていただきます。


アップデート後にも忘れず、 terraform init をしましょう。

v0.12ではファイルの書き方がかなり変わっており、v0.12に互換性がないコードが残っている場合は下記のメッセージがでてきます。

```
> terraform init

Initializing provider plugins...


Warning: Skipping backend initialization pending configuration upgrade

The root module configuration contains errors that may be fixed by running the
configuration upgrade tool, so Terraform is skipping backend initialization.
See below for more information.


Terraform has initialized, but configuration upgrades may be needed.

Terraform found syntax errors in the configuration that prevented full
initialization. If you've recently upgraded to Terraform v0.12, this may be
because your configuration uses syntax constructs that are no longer valid,
and so must be updated before full initialization is possible.

Terraform has installed the required providers to support the configuration
upgrade process. To begin upgrading your configuration, run the following:
    terraform 0.12upgrade

To see the full set of errors that led to this message, run:
    terraform validate
```


# 4. terraform 0.12upgrade サブコマンドの実行

このサブコマンドを実行すると、自動で v0.12 仕様のコードに書き換えてくれます！めちゃめちゃすごい！
場合によってはこのサブコマンドを実行しても、修正しなければならない箇所はいくつか出てくると思いますが、これのおかげで、少ない修正で済みます。
この作業でコードの書き換えが行われるので、git等で管理していない場合でも、なにがあってもいいように**必ずバックアップをとっておきましょう。**

terraform apply のときのように yes で応答しないと実行されません。
書き換えがうまくいくと下記のような結果が表示されます。


```
> terraform 0.12upgrade

This command will rewrite the configuration files in the given directory so
that they use the new syntax features from Terraform v0.12, and will identify
any constructs that may need to be adjusted for correct operation with
Terraform v0.12.

We recommend using this command in a clean version control work tree, so that
you can easily see the proposed changes as a diff against the latest commit.
If you have uncommited changes already present, we recommend aborting this
command and dealing with them before running this command again.

Would you like to upgrade the module in the current directory?
  Only 'yes' will be accepted to confirm.

  Enter a value: yes

-----------------------------------------------------------------------------

Upgrade complete!

The configuration files were upgraded successfully. Use your version control
system to review the proposed changes, make any necessary adjustments, and
then commit.
```



■書き換えがうまくいかないパターン

自分のファイルの書き換えがうまくいかなかったパターンを紹介します！

```
> terraform 0.12upgrade

This command will rewrite the configuration files in the given directory so
that they use the new syntax features from Terraform v0.12, and will identify
any constructs that may need to be adjusted for correct operation with
Terraform v0.12.

We recommend using this command in a clean version control work tree, so that
you can easily see the proposed changes as a diff against the latest commit.
If you have uncommited changes already present, we recommend aborting this
command and dealing with them before running this command again.

Would you like to upgrade the module in the current directory?
  Only 'yes' will be accepted to confirm.

  Enter a value: yes

-----------------------------------------------------------------------------
Error: Invalid ignore_changes argument

  on alb_maint02.tf line 66, in resource "aws_alb_target_group" "maint_test_ecs_alb_tg":
  66:     ignore_changes = "lambda_multi_value_headers_enabled"

The "ignore_changes" argument must be a list of attribute expressions relative
to this resource.

```

ignore_changes は　list 型を使うとのことでしたが、一旦コメントアウトで対応しました。

```
resource "aws_alb_target_group" "maint-tg" {
  name     = "${terraform.workspace}-maint-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.vpc.id

  health_check {
    interval            = 30
    path                = "/index.html"
    port                = 80
    protocol            = "HTTP"
    timeout             = 5
    unhealthy_threshold = 2
    matcher             = 200
  }
  #  lifecycle {
  #    ignore_changes = "lambda_multi_value_headers_enabled"
  #  }
}
```

試していませんが、下記のようにリスト型に書き換えてもうまくいくと思われます。

```
  lifecycle {
    ignore_changes = [
      lambda_multi_value_headers_enabled,
    ]
  }
```



そもそもなぜこの　ignore changes が設定されていたかは、こちらの issue を御覧ください。

https://github.com/terraform-providers/terraform-provider-aws/issues/7960


v0.11.13以降、ターゲットグループのコードで設定していないLambdaの部分でchangedとして出るバグがあったためです。ignore_changesを追加しましたが、v0.12では出ないように修正されたようです！修正されていたため、今回は最終的に、ignore changesの記述自体を削除することとしました。

※changed 出てたときの terraform plan の一部 
``` lambda_multi_value_headers_enabled: "" => "false" ```




# 5. terraform 0.12upgrade サブコマンドで変更されるコードの例、Before/Atter


## ■terraform\_remote\_state
パット見全然わかりませんでしたが、configの横に=がついています。

### ■Before

```
data "terraform_remote_state" "maint" {
  backend = "s3"

  config {
    bucket = "bucketname"
    key    = "env:/${terraform.workspace}/maint/terraform.tfstate"
    region = "ap-northeast-1"
  }
}
```

### ■After

```
> cat data_remote_state.tf
data "terraform_remote_state" "maint" {
  backend = "s3"

  config = {
    bucket = "bucketname"
    key    = "env:/${terraform.workspace}/maint/terraform.tfstate"
    region = "ap-northeast-1"
  }
}
```

## ■map変数の呼び出し

map変数の呼び出しがスマートになってました！

### ■Before

```
provider "aws" {
  version  = "~> 2.14.0"
  region   = "${lookup(var.common,"${terraform.workspace}.region")}"
}
```

### ■After

```
provider "aws" {
  version = "~> 2.14.0"
  region  = var.common["${terraform.workspace}.region"]
}
```

## ■EC2コードの変更点

### ■Before

```
## EC2 create(maint01)
resource "aws_instance" "maint01" {
    ami = "${lookup(var.ec2,"${terraform.workspace}.ami_id_maint01")}"
    instance_type = "${lookup(var.ec2,"${terraform.workspace}.instance_type_maint01")}"
    disable_api_termination = false
    key_name = "${lookup(var.ec2,"${terraform.workspace}.key_name_maint01")}"
    vpc_security_group_ids = ["${aws_security_group.SSH.id}","${aws_security_group.PROMETHEUS.id}","${aws_security_group.default.id}"]
    subnet_id = "${aws_subnet.ec2-public-a.id}" 
    root_block_device = {
      volume_type = "gp2"
      volume_size = "${lookup(var.ec2,"${terraform.workspace}.volume_size_maint01")}"
    }
    tags {
      Name = "${terraform.workspace}-maint01"
   }
}

## EIP(maint01)
resource "aws_eip" "maint01" {
    instance = "${aws_instance.maint01.id}"
    vpc = true
    tags {
      Name = "${terraform.workspace}-maint01"
   }
}
```

### ■After

```
## EC2 create(maint01)
resource "aws_instance" "maint01" {
  ami                     = var.ec2["${terraform.workspace}.ami_id_maint01"]
  instance_type           = var.ec2["${terraform.workspace}.instance_type_maint01"]
  disable_api_termination = false
  key_name                = var.ec2["${terraform.workspace}.key_name_maint01"]
  vpc_security_group_ids  = [aws_security_group.SSH.id, aws_security_group.PROMETHEUS.id, aws_security_group.default.id]
  subnet_id               = aws_subnet.ec2-public-a.id
  root_block_device {
    volume_type = "gp2"
    volume_size = var.ec2["${terraform.workspace}.volume_size_maint01"]
  }
  tags = {
    Name              = "${terraform.workspace}-maint01"
  }
}

## EIP(maint01)
resource "aws_eip" "maint01" {
  instance = aws_instance.maint01.id
  vpc      = true
  tags = {
    Name = "${terraform.workspace}-maint01"
  }
}

```

resource ID等の指定の仕方が短くシンプルになっています
```"${aws_security_group.PROMETHEUS.id}"```　→　```aws_security_group.PROMETHEUS.id```

map 型変数も先程と同じように呼び出し方が変わっています。
```"${lookup(var.ec2,"${terraform.workspace}.volume_size_maint01")}"``` → ```var.ec2["${terraform.workspace}.volume_size_maint01"]```

分かりづらいですが、tagsの部分に = が追記されています。　まとめながらあとあと気づいたのですが、インデントが統一されていて、全てのファイルがキレイになっていました！

```
    tags {
      Name = "${terraform.workspace}-maint01"
   }
```
↓

```
  tags = {
    Name              = "${terraform.workspace}-maint01"
  }
```

だいたいのファイルでこれと同じパターンの書き換えがありました！

## ■output の変更点 map型のoutputもアリ

### ■Before

```
output "vpc" {
  value = "${
    map(
      "vpc_id",           "${aws_vpc.vpc.id}",
      "subnet-public-a",  "${aws_subnet.ec2-public-a.id}",
      "subnet-public-c",  "${aws_subnet.ec2-public-c.id}",
    )
  }"
}

output "common" {
  value = "${var.common}"
}

```

### ■After

```
output "vpc" {
  value = {
    "vpc_id"          = aws_vpc.vpc.id
    "subnet-public-a" = aws_subnet.ec2-public-a.id
    "subnet-public-c" = aws_subnet.ec2-public-c.id
  }
}

output "common" {
  value = var.common
}

```


## ■map型の変数定義

mapからmap(string)に変更されていました！

### ■Before

```
## COMMON
variable "common" {
    type = "map"
    default = {
      default.region              = "us-west-2"

      oregon.region               = "us-west-2"
      oregon.availability_zone_a  = "us-west-2a"
      oregon.availability_zone_b  = "us-west-2b"
      oregon.availability_zone_c  = "us-west-2c"

      tokyo.region                = "ap-northeast-1"
      tokyo.availability_zone_a   = "ap-northeast-1a"
      tokyo.availability_zone_b   = "ap-northeast-1d"
      tokyo.availability_zone_c   = "ap-northeast-1c"

  }
}
```

### ■After

※見やすいように改行を追加しています。

```
> cat variables.tf
## COMMON
variable "common" {

  type = map(string)

  default = {

    default.region             = "us-west-2"

    oregon.region              = "us-west-2"
    oregon.availability_zone_a = "us-west-2a"
    oregon.availability_zone_b = "us-west-2b"
    oregon.availability_zone_c = "us-west-2c"

    tokyo.region               = "ap-northeast-1"
    tokyo.availability_zone_a  = "ap-northeast-1a"
    tokyo.availability_zone_b  = "ap-northeast-1d"
    tokyo.availability_zone_c  = "ap-northeast-1c"

  }
}
```


# 6. terraform 0.12upgrade サブコマンド実行後、terraform planがそのまま通らない
ここまででどんなファイルの書き換えがあったかご紹介できました！
しかし、サブコマンドで書き換えたままで terraform plan を実行すると、自分の場合は、下記エラーが変数の数だけ大量に出てしまいました。

変数のファイルは900行を超えるためめちゃめちゃエラーが出て、最初はかなり焦りましたが大丈夫です！

```

~~~~省略~~~~


Error: Ambiguous attribute key

  on variables.tf line 11, in variable "common":
  11:     oregon.availability_zone_a = "us-west-2a",

If this expression is intended to be a reference, wrap it in parentheses. If
it's instead intended as a literal name containing periods, wrap it in quotes
to create a string literal.


~~~~省略~~~~


```


ダブルコーテーションで左側の値も囲ってあげます。下記のようにすることでplanが通ります。1番ハマりました。
英語のメッセージをすんなり読みたいです。




```
> cat variables.tf
## COMMON
variable "common" {

  type = map(string)

  default = {

    default.region             = "us-west-2"

    oregon.region              = "us-west-2"
    oregon.availability_zone_a = "us-west-2a"
    oregon.availability_zone_b = "us-west-2b"
    oregon.availability_zone_c = "us-west-2c"

    tokyo.region               = "ap-northeast-1"
    tokyo.availability_zone_a  = "ap-northeast-1a"
    tokyo.availability_zone_b  = "ap-northeast-1d"
    tokyo.availability_zone_c  = "ap-northeast-1c"

  }
}
```

↓

```
> cat variables.tf
## COMMON
variable "common" {

  type = map(string)

  default = {

    "default.region"             = "us-west-2"

    "oregon.region"              = "us-west-2"
    "oregon.availability_zone_a" = "us-west-2a"
    "oregon.availability_zone_b" = "us-west-2b"
    "oregon.availability_zone_c" = "us-west-2c"

    "tokyo.region"               = "ap-northeast-1"
    "tokyo.availability_zone_a"  = "ap-northeast-1a"
    "tokyo.availability_zone_b"  = "ap-northeast-1d"
    "tokyo.availability_zone_c"  = "ap-northeast-1c"

  }
}

```



一旦ここまでの作業で、terraformplan は通りました！



# 7. terraform 0.12upgrade サブコマンド実行後、コメントが複数行挿入されている


しかし下記のファイルをよく見ると、コメントが複数行挿入されていました。
~~「1個しか値がないならリスト型を使わないでください」というような内容のコメントでした。~~

*2019/08/19追記*
**実際のコメントの内容は全然違ったものでした。**
**If the expression in the following list itself returns a list, remove the brackets to avoid interpretation as a list of lists.こちらのコメントはgoogle翻訳で、「次のリストの式自体がリストを返す場合は、リストのリストとして解釈されないように括弧を削除してください。」**

**If the expression returns a single list item then leave it as-is and remove this TODO comment.こちらのコメントは、「式が単一のリスト項目を返す場合は、そのままにしてこのTODOコメントを削除してください。」**
**という内容で、自分の場合はそのままにしてコメントを削除して終わりでよかったようです。**

```
 > cat ecs_service.tf
resource "aws_ecs_service" "maint-test" {
  name            = "maint-test-service"
  cluster         = data.terraform_remote_state.maint.outputs.ecs.maint_test_ecs_cluster_id
  task_definition = "maint_test_task_definition:11"

  # task_definition                    = "${aws_ecs_task_definition.maint_test_task_definition.arn}"
  platform_version                   = "LATEST"
  desired_count                      = 2
  launch_type                        = "FARGATE"
  deployment_minimum_healthy_percent = "50"
  deployment_maximum_percent         = "100"
  health_check_grace_period_seconds  = "180"

  load_balancer {
    target_group_arn = data.terraform_remote_state.maint.outputs.alb.maint_test_ecs_alb_tg_arn
    container_name   = "nginx"
    container_port   = 80
  }

  network_configuration {
    subnets = [data.terraform_remote_state.maint.outputs.vpc.subnet-public-a, data.terraform_remote_state.maint.outputs.vpc.subnet-public-c]
    # TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
    # force an interpolation expression to be interpreted as a list by wrapping it
    # in an extra set of list brackets. That form was supported for compatibilty in
    # v0.11, but is no longer supported in Terraform v0.12.
    #
    # If the expression in the following list itself returns a list, remove the
    # brackets to avoid interpretation as a list of lists. If the expression
    # returns a single list item then leave it as-is and remove this TODO comment.
    security_groups  = [data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id]
    assign_public_ip = "true"
  }

  service_registries {
    registry_arn = aws_service_discovery_service.maint_test.arn
  }
}

```

~~実際にリスト型をやめてみます。~~

```
    security_groups  = [data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id]
```

↓

```
    security_groups  = data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id
```

~~このあとに、 terraform plan を実行すると、「security_groupsではstring型である必要があります」と言われます。~~

```
------------------------------------------------------------------------

Error: Incorrect attribute value type

  on ecs_service.tf line 30, in resource "aws_ecs_service" "maint-test":
  30:     security_groups  = data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id

Inappropriate value for attribute "security_groups": set of string required.

```


~~公式に記載がありましたが、結構無理やりな感じで解決できました。~~
~~https://www.terraform.io/upgrade-guides/0-12.html#referring-to-list-variables~~

~~flattenを明示的に指定します。めちゃめちゃ見た目が悪いので修正してほしいですね！~~
~~値が1つしかない場合に、リスト型を使わないでシンプルにしたいのに、flattenの記述で行数や記述が増えるのが本末転倒すぎます。~~

~~いい解決策あったら教えていただきたく思います。~~

~~flatten は、多次元配列を1次元配列にフラット化する的なイメージみたいです。そこまで理解できていません。~~

~~修正が終わったら、コメントの行は削除して問題ありません。~~

```
> cat ecs_service.tf
resource "aws_ecs_service" "maint-test" {
  name            = "maint-test-service"
  cluster         = data.terraform_remote_state.maint.outputs.ecs.maint_test_ecs_cluster_id
  task_definition = "maint_test_task_definition:11"

  platform_version                   = "LATEST"
  desired_count                      = 2
  launch_type                        = "FARGATE"
  deployment_minimum_healthy_percent = "50"
  deployment_maximum_percent         = "100"
  health_check_grace_period_seconds  = "180"

  load_balancer {
    target_group_arn = data.terraform_remote_state.maint.outputs.alb.maint_test_ecs_alb_tg_arn
    container_name   = "nginx"
    container_port   = 80
  }

  network_configuration {
    subnets = [data.terraform_remote_state.maint.outputs.vpc.subnet-public-a, data.terraform_remote_state.maint.outputs.vpc.subnet-public-c]
    # TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
    # force an interpolation expression to be interpreted as a list by wrapping it
    # in an extra set of list brackets. That form was supported for compatibilty in
    # v0.11, but is no longer supported in Terraform v0.12.
    #
    # If the expression in the following list itself returns a list, remove the
    # brackets to avoid interpretation as a list of lists. If the expression
    # returns a single list item then leave it as-is and remove this TODO comment.

    security_groups  = flatten([
      data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id,
    ])

#    security_groups  = [data.terraform_remote_state.maint.outputs.sg.security_group_WEB_id]
    assign_public_ip = "true"
  }

  service_registries {
    registry_arn = aws_service_discovery_service.maint_test.arn
  }
}

```




まとめると、自分の場合は、

・変数の定義の仕方

・変数の呼び出し方

・値が1つのリスト型

・outputの書き方

・=がつく場所がある

のパターンで変更や修正がありました。


# 最後に

terraform v0.12 へのアップデート作業は変更点が多く、結構勇気がいると思いますが是非やってみてください！

変数定義ファイルで、変数名を無限にダブルコーテーションで挟む作業が一番しんどかったです。


# 参考リンク

■Upgrading to Terraform v0.12

https://www.terraform.io/upgrade-guides/0-12.html

■Terraform 0.12がリリースされたのでアップグレードしてみた

https://dev.classmethod.jp/tool/terraform-upgrade-from-0-11-to-0-12/

■aws\_lb\_target\_group for an instance type target group is trying to add (or make a change) of attribute: lambda_multi\_value\_headers\_enabled

https://github.com/terraform-providers/terraform-provider-aws/issues/7960

■Referring to List Variables

https://www.terraform.io/upgrade-guides/0-12.html#referring-to-list-variables