Terraforming未対応の既存リソースも自力でtfstateを書いてTerraform管理下に入れる


# はじめに

AWSのクラウドリソースをコードで管理するためにTerraformを使ってます。

https://www.terraform.io/

Terraformは新規にリソースを作成するのには良いのですが、Terraformを使ってると既存リソースも管理したくなるのが人情です。
現状Terraform自体にはその機能はなく、それを補完するものとしてTerraformingがあります。

https://github.com/dtan4/terraforming

Terraformingの使い方については、以前以下のような記事を書きました。

[Terraformingで既存のIAMユーザをTerraform管理下に入れる](http://engineer.crowdworks.jp/entry/2016/05/26/163012)

しかしながらTerraformingも完璧ではなく、Terraformが対応しているすべてのリソースタイプに対応しているわけでもないです。

これを書いてる現在の最新版のterraforming v0.8.0ではaws_iam_policy_attachment とか使いたいのだけど対応していない(´・ω・｀)

最近tfstateいじりにも慣れてきたので、自力でtfstateも書けばやればよかろうと思い、やってみたらうまく行ったので、知見を共有しておきます。

(2016/06/12追記) aws_iam_policy_attachmentはTerraforming v0.9.0で対応されました。dtan4さんに感謝。

# この記事でやること

既存のaws_iam_policy_attachmentリソースを気合でTerraform管理下に入れる

ターゲットとなるリソースはtfファイルで書くとこんなかんじ。

```aws_iam_policy_attachements.tf
resource "aws_iam_policy_attachment" "aws_codedeploy_role_attachment" {
    name = "AWSCodedeployRoleAttachment"
    roles = [
      "appProductionCodeDeployRole",
      "appStagingCodeDeployRole"
    ]
    policy_arn = "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"
}
```

# 流れ

- Terraformのドキュメントを眺めつつダミーのtfファイルを作る
- ダミーのリソースを `terraform apply` してtfstateの雛形作成
- AWSコンソールを眺めつつターゲットとなるリソースのtfファイルを作る
- tfstateの雛形をコピーしてターゲットとなるリソースのtfstateの枠を作る
- `terraform refresh` して terraformでtfstateを既存リソースの状態に合わせる
- ダミーリソースを削除して `terraform plan` で差分がなくなれば完成

お約束ですが、tfstateを手動で編集するのは各自自己責任でお願いします。
あとterraformのバージョンはこれを書いてる時点の最新版のv0.6.16です。

# ダミーのtfファイルを作る

まずは、対象となるリソースタイプのtfファイルを作ります。

Terraformの公式ドキュメントはこちら
https://www.terraform.io/docs/providers/aws/r/iam_policy_attachment.html


とりあえず、それっぽいダミーのリソースを作ってみます。今回はテスト用にIAMロールもついでに作る。
（注：最終的に作りたいターゲットリソースはAWSのマネージドポリシーをattachするのだが、aws_iam_policy_attachementsの特性上、マネージドポリシーでテストしちゃうと、既存の設定を変えちゃうことになるので、ロールもテスト用に作ってる）

```aws_iam_policy_attachements.tf

resource "aws_iam_role" "test_role" {
    name = "test_role"
    assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_policy" "test_policy" {
    name = "test_policy"
    path = "/"
    description = "My test policy"
    policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ec2:Describe*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF
}

resource "aws_iam_policy_attachment" "test_attachment" {
    name = "test_attachment"
    roles = ["${aws_iam_role.test_role.name}"]
    policy_arn = "${aws_iam_policy.test_policy.arn}"
}
```

# tfstateの雛形を作成


terraform planしてapplyしてtfstateの雛形を作成します。

```bash

$ terraform plan

+ aws_iam_policy.test_policy
    arn:         "" => "<computed>"
    description: "" => "My test policy"
    name:        "" => "test_policy"
    path:        "" => "/"
    policy:      "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": [\n        \"ec2:Describe*\"\n      ],\n      \"Effect\": \"Allow\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n"

+ aws_iam_policy_attachment.test_attachment
    name:             "" => "test_attachment"
    policy_arn:       "" => "${aws_iam_policy.test_policy.arn}"
    roles.#:          "" => "1"
    roles.1376821413: "" => "test_role"

+ aws_iam_role.test_role
    arn:                "" => "<computed>"
    assume_role_policy: "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": \"sts:AssumeRole\",\n      \"Principal\": {\n        \"Service\": \"ec2.amazonaws.com\"\n      },\n      \"Effect\": \"Allow\",\n      \"Sid\": \"\"\n    }\n  ]\n}\n"
    name:               "" => "test_role"
    path:               "" => "/"
    unique_id:          "" => "<computed>"


Plan: 3 to add, 0 to change, 0 to destroy.

$ terraform apply
```

一部省略してますが、こんなかんじでtfstateができました。
ダミーリソースで生成されたtfstateをよく観察します。

```json
{
    "version": 1,
    "serial": 54,
    "remote": {
        (略）
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
				(略）
                "aws_iam_policy_attachment.test_attachment": {
                    "type": "aws_iam_policy_attachment",
                    "depends_on": [
                        "aws_iam_policy.test_policy",
                        "aws_iam_role.test_role"
                    ],
                    "primary": {
                        "id": "test_attachment",
                        "attributes": {
                            "groups.#": "0",
                            "id": "test_attachment",
                            "name": "test_attachment",
                            "policy_arn": "arn:aws:iam::XXXXXXXXXXX:policy/test_policy",
                            "roles.#": "1",
                            "roles.1376821413": "test_role",
                            "users.#": "0"
                        }
                    }
                },
				(略）
            }
        }
    ]
}

```

# ターゲットとなるリソースのtfファイルを作る

AWSコンソールを眺めつつ、ターゲットとなるリソースのtfファイルを作ります。

```aws_iam_policy_attachements.tf

resource "aws_iam_policy_attachment" "aws_codedeploy_role_attachment" {
    name = "AWSCodedeployRoleAttachment"
    roles = [
      "appProductionCodeDeployRole",
      "appStagingCodeDeployRole"
    ]
    policy_arn = "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"
}

```

# ターゲットとなるリソースのtfstateの枠を作る

さっきダミーリソース作った際に出来たtfstateをコピペして来て、それっぽいtfstateを作る。

このときポイントは以下2点

- tfstateを手動で編集する場合はserialの値をインクリメントする
- 配列を受け取る場所、この例だとattributes.roles.# のところにattachするロールの数だけを入れ、`roles.1376821413` のようにIDが採番されてる子リソースの行を削除する。

IDが採番されているっぽい部分はあとで `terraform refresh` するとterraformが生成してくれます。

```json
{
    "version": 1,
    "serial": 55,
    "remote": {
        (略）
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
				(略）
                "aws_iam_policy_attachment.aws_codedeploy_role_attachment": {
                    "type": "aws_iam_policy_attachment",
                    "primary": {
                        "id": "aws_codedeploy_role_attachment",
                        "attributes": {
                            "groups.#": "0",
                            "id": "aws_codedeploy_role_attachment",
                            "name": "AWSCodedeployRoleAttachment",
                            "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole",
                            "roles.#": "2",
                            "users.#": "0"
                        }
                    }
                },
                "aws_iam_policy_attachment.test_attachment": {
                    "type": "aws_iam_policy_attachment",
                    "depends_on": [
                        "aws_iam_policy.test_policy",
                        "aws_iam_role.test_role"
                    ],
                    "primary": {
                        "id": "test_attachment",
                        "attributes": {
                            "groups.#": "0",
                            "id": "test_attachment",
                            "name": "test_attachment",
                            "policy_arn": "arn:aws:iam::XXXXXXXXXXX:policy/test_policy",
                            "roles.#": "1",
                            "roles.1376821413": "test_role",
                            "users.#": "0"
                        }
                    }
                },
				(略）
            }
        }
    ]
}

```

# terraform refreshで既存リソースの状態に合わせる

terraform refreshコマンドを実行すると、リソースの実体を正としてtfstateの状態を最新化してくれます。これにより、さっきIDが採番されていない箇所なども正しく状態が取得されます。

```bash
$ terraform refresh
```

```json
{
    "version": 1,
    "serial": 56,
    "remote": {
        (略）
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
				(略）
                "aws_iam_policy_attachment.aws_codedeploy_role_attachment": {
                    "type": "aws_iam_policy_attachment",
                    "primary": {
                        "id": "aws_codedeploy_role_attachment",
                        "attributes": {
                            "groups.#": "0",
                            "id": "aws_codedeploy_role_attachment",
                            "name": "AWSCodedeployRoleAttachment",
                            "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole",
                            "roles.#": "2",
                            "roles.2660578725": "appStagingCodeDeployRole",
                            "roles.3320175550": "appProductionCodeDeployRole",
                            "users.#": "0"
                        }
                    }
                },
                "aws_iam_policy_attachment.test_attachment": {
                    "type": "aws_iam_policy_attachment",
                    "depends_on": [
                        "aws_iam_policy.test_policy",
                        "aws_iam_role.test_role"
                    ],
                    "primary": {
                        "id": "test_attachment",
                        "attributes": {
                            "groups.#": "0",
                            "id": "test_attachment",
                            "name": "test_attachment",
                            "policy_arn": "arn:aws:iam::XXXXXXXXXXX:policy/test_policy",
                            "roles.#": "1",
                            "roles.1376821413": "test_role",
                            "users.#": "0"
                        }
                    }
                },
				(略）
            }
        }
    ]
}
```

この時点で一度terraform planして差分がなくなっていることを確認する。

```
$ terraform plan
```

デフォルト値だと思ってたものが明示的に定義しないといけない場合があるので、差分が出た場合は公式ドキュメントのリソースの定義とAWSコンソール上の実リソースなどを確認しつつ、適宜記載を調整していただければよいかと。

# 不要になったダミーリソースを削除

tfファイルから不要になったダミーリソースを削除してplan & applyする。

```bash
$ terraform plan

- aws_iam_policy.test_policy

- aws_iam_policy_attachment.test_attachment

- aws_iam_role.test_role


Plan: 0 to add, 0 to change, 3 to destroy.


$ terraform apply
Apply complete! Resources: 0 added, 0 changed, 3 destroyed.

```

この時点で、ダミーリソースもなく、ターゲットリソースだけが残った状態で、terraform planして差分がない状態になるはずなので、これで完成。

```
$ terraform paln
```

# まとめ
- tfstateなんてただのJSON
- 既存リソースもTerraformで管理するんだ、という熱いハートと気合が大事

これでTerraformingが対応していない既存リソースもTerraform管理し放題ですね〜。