Terraforming で既存のインフラを Terraform 管理下におく

Terraform は、主にインフラをスクラッチから構築する際に有用なツールです。
ですが、いま動いている既存のインフラに Terraform を導入したい、既存のインフラを Terraform で管理したいと思う方もいるのではないでしょうか。
今回は、[Terraforming](https://github.com/dtan4/terraforming) を使って既存のインフラを Terraform で管理できるようにする方法を紹介します。

## Terraforming とは

Terraforming は、AWS の API を叩いて既存のインフラリソースから Terraform のコードを生成するツールです。
兄弟分に、DNSimple 用の [Terraforming::DNSimple](https://github.com/dtan4/terraforming) があります。

## インストール

RubyGems として公開されているので、

```bash
$ gem install terraforming
```

また、[Docker Image](https://quay.io/repository/dtan4/terraforming) も公開してあるので、そちらを使いたい方は

```bash
$ docker pull quay.io/dtan4/terraforming:latest
```

## 使い方

### tf 形式の出力

予め AWS のクレデンシャルを環境変数に入れておきます。
Mac ユーザなら [envchain](https://github.com/sorah/envchain) おすすめです。

```bash
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_DEFAULT_REGION=xx-yyyy-0
```

リソース名を指定するだけだと、tf 形式で出力されます。
S3 bucket の場合だと:

```bash
$ terraforming s3
```

```go
resource "aws_s3_bucket" "hoge" {
    bucket = "hoge"
    acl    = "private"
}

resource "aws_s3_bucket" "fuga" {
    bucket = "fuga"
    acl    = "private"
}
```

これを `s3.tf` とかに書き出せばよいです。
Docker Container として動かす場合は以下のようになります。


```bash
$ docker run \
    --rm \
    --name terraforming \
    -e AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX \
    -e AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
    -e AWS_DEFAULT_REGION=xx-yyyy-0 \
    quay.io/dtan4/terraforming:latest \
    terraforming s3
```

### tfstate 形式の出力

既存のインフラを Terraform 管理に置くためには、tf ファイルを書くだけでは足りません。
状態管理ファイルである `terraform.tfstate` も書き換える必要があります。

`--tfstate` オプションをつけると、tfstate 形式で出力されます。

```bash
$ terraforming s3 --tfstate
```

```json
{
  "version": 1,
  "serial": 1,
  "modules": {
    "path": [
      "root"
    ],
    "outputs": {
    },
    "resources": {
      "aws_s3_bucket.hoge": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "hoge",
          "attributes": {
            "acl": "private",
            "bucket": "hoge",
            "id": "hoge"
          }
        }
      },
      "aws_s3_bucket.fuga": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "fuga",
          "attributes": {
            "acl": "private",
            "bucket": "fuga",
            "id": "fuga"
          }
        }
      }
    }
  }
}
```

また、tfstate は既存の `terraform.tfstate` にマージした形で出力することもできます。

```bash
$ terraforming s3 --tfstate --merge=/path/to/tfstate
```

```json
{
  "version": 1,
  "serial": 89,
  "remote": {
    "type": "s3",
    "config": {
      "bucket": "terraforming-tfstate",
      "key": "tf"
    }
  },
  "modules": {
    "path": [
      "root"
    ],
    "outputs": {
    },
    "resources": {
      "aws_iam_user.dtan4": {
        "type": "aws_iam_user",
        "primary": {
          "id": "dtan4",
          "attributes": {
             "arn": "arn:aws:iam::012345678901:user/dtan4",
             "id": "dtan4",
             "name": "dtan4",
             "path": "/",
             "unique_id": "ABCDEFGHIJKLMN1234567"
          }
        }
      },
      "aws_s3_bucket.hoge": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "hoge",
          "attributes": {
            "acl": "private",
            "bucket": "hoge",
            "id": "hoge"
          }
        }
      },
      "aws_s3_bucket.fuga": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "fuga",
          "attributes": {
            "acl": "private",
            "bucket": "fuga",
            "id": "fuga"
          }
        }
      }
    }
  }
}
```

もしリモート (S3, Atlas etc.) で terraform.tfstate を管理している場合は、手元でいじったあと `serial` を上げないとリモートのものと一致しないと怒られます。
が、Terraforming の `--merge` オプションはちゃんと `serial` (`terraform.tfstate` のバージョン番号) もインクリメントします。

これを `terraform.tfstate` に上書きすればよいです。
最後に `terraform plan` を実行して、差分が出ないことを確認して下さい。

```bash
$ terraform plan
No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.
```

## 対応している AWS リソース

v0.1.0 時点では以下のリソースに対応しています。

- Database Parameter Group
- Database Security Group
- Database Subnet Group
- EC2
- ELB
- IAM Group
- IAM Group Policy
- IAM Instance Profile
- IAM Policy
- IAM Role
- IAM Role Policy
- IAM User
- IAM User Policy
- Network ACL
- Route53 Record
- Route53 Hosted Zone
- RDS
- S3
- Security Group
- Subnet
- VPC

## おわりに

Terraforming を使って、既存のインフラリソースを Terraform コードに落としこむための方法を紹介しました。

なぜ Terraforming を作ることにしたのか等の小話が下のブログ記事に書いてありますので、合わせてご覧いただけると幸いです。

- [Terraforming: 既存のインフラリソースを Terraform コードに落としこむ - 端子録](http://dtan4.hatenablog.com/entry/2015/06/20/181559)

## REF
- [dtan4/terraforming](https://github.com/dtan4/terraforming)
- [Terraforming: 既存のインフラリソースを Terraform コードに落としこむ - 端子録](http://dtan4.hatenablog.com/entry/2015/06/20/181559)
- [Import resources into Terraform · Issue #581 · hashicorp/terraform](https://github.com/hashicorp/terraform/issues/581)
- [Amazon S3 で Terraform の状態管理ファイル terraform.tfstate を管理 / 共有する - Qiita](http://qiita.com/dtan4/items/04632f1c2f35388a3283)