Terraform 1.5 で実現する config-driven なインポート作業

Posted on

Terraform 1.5 で追加された import ブロックと -generate-config-out フラグを組み合わせると、既存のパイプラインの中で安全にインポート作業が行えるようになります。 Hashicorp ではこのことを "config-driven import" と呼んでいます。

これまでは terraform import コマンドでリソースを 1 つずつインポートし、それを定義する HCL も自分で書く必要がありました。コマンドを実行すると即座に tfstate に反映されるので、HCL がマージされるまでの間にほかの人が誤ってリソースを削除してしまう危険性がありました。そのため、チームで共同作業するリポジトリでは気軽にインポートできませんでした。

Terraform 1.5 はこれらの問題をスマートに解決してくれます。

既存のパイプラインでインポートするステップ

チームで共同作業する場合、すでに何らかのパイプラインが存在すると思います。その中で config-driven なインポート作業を行うには、次のようなステップになると思います。

  1. インポートしたいリソースを import ブロックに定義する
  2. ローカルで -generate-config-out フラグを付けて terraform plan を実行する
  3. 生成された HCL をチェックして、不要な引数を削除したり、ベタ書きの値を直す
  4. ローカルで terraform plan を実行して差分がないことを確認する
  5. HCL をコミットして Pull request を作成し、チームメンバーにレビューしてもらう
  6. レビューが通ったらマージして、パイプラインから terraform apply を実行してインポートする
  7. (オプション)不要になった import ブロックを削除する

自分の理解を深めるために、Terraform Cloud で実際にインポートしてみました。

インポートの検証に使うリソースを作成する

今回は AWS CLI で IAM user を作成して、それを上記のステップでインポートしてみます。

まず IAM policy を作成します。

$ cat test-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

$ aws iam create-policy --policy-name test-policy --policy-document file://test-policy.json

次に IAM user を作って先ほどの policy をアタッチします。

$ aws iam create-user --user-name test-user

$ aws iam attach-user-policy --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/test-policy --user-name test-user

-generate-config-out フラグを付けて HCL を生成する

既存のリポジトリの中に import.tf を用意します。 id で指定する値は terraform import で指定するものと同じです。コマンドでインポートするのと違い、複数のリソースをまとめてインポートできるのは楽ですね。

import {
  id = "test-user"
  to = aws_iam_user.test_user
}

import {
  id = "arn:aws:iam::xxxxxxxxxxxx:policy/test-policy"
  to = aws_iam_policy.test_policy
}

import {
  id = "test-user/arn:aws:iam::xxxxxxxxxxxx:policy/test-policy"
  to = aws_iam_user_policy_attachment.test_policy
}

-generate-config-out フラグを付けて terraform plan を実行します。

$ terraform version
Terraform v1.5.0
on linux_arm64

$ terraform init

$ terraform plan -generate-config-out=test_user.tf

(snip)

Plan: 3 to import, 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Config generation is experimental
│
│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.
╵

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Terraform has generated configuration and written it to test_user.tf. Please review the configuration and edit it as necessary before adding it to version control.

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

test_user.tf を確認すると次のような HCL が生成されていました。

# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform from "test-user/arn:aws:iam::xxxxxxxxxxxx:policy/test-policy"
resource "aws_iam_user_policy_attachment" "test_policy" {
  policy_arn = "arn:aws:iam::xxxxxxxxxxxx:policy/test-policy"
  user       = "test-user"
}

# __generated__ by Terraform from "test-user"
resource "aws_iam_user" "test_user" {
  force_destroy        = null
  name                 = "test-user"
  path                 = "/"
  permissions_boundary = null
  tags                 = {}
  tags_all             = {}
}

# __generated__ by Terraform from "arn:aws:iam::xxxxxxxxxxxx:policy/test-policy"
resource "aws_iam_policy" "test_policy" {
  description = null
  name        = "test-policy"
  name_prefix = null
  path        = "/"
  policy      = "{\"Statement\":[{\"Action\":[\"s3:Get*\",\"s3:List*\"],\"Effect\":\"Allow\",\"Resource\":[\"arn:aws:s3:::my-bucket/*\"]}],\"Version\":\"2012-10-17\"}"
  tags        = {}
  tags_all    = {}
}

このままでも間違いはありませんが、不要な引数やベタ書きされた値、ワンライナーで書かれた policy などを直します。

resource "aws_iam_user" "test_user" {
  name = "test-user"
  path = "/"
}

data "aws_iam_policy_document" "test_policy" {
  statement {
    actions = [
      "s3:Get*",
      "s3:List*",
    ]

    resources = ["arn:aws:s3:::my-bucket/*"]
  }
}

resource "aws_iam_policy" "test_policy" {
  name   = "test-policy"
  path   = "/"
  policy = data.aws_iam_policy_document.test_policy.json
}

resource "aws_iam_user_policy_attachment" "test_policy" {
  policy_arn = aws_iam_policy.test_policy.arn
  user       = aws_iam_user.test_user.name
}

再度 terraform plan を実行して差分がないことを確認します。

パイプラインからインポートを実行する

HCL をコミットし Pull request を作成します。 Terraform Cloud の場合はインポート対象のリソースが分かりやすく表示されます。

インポートの plan 結果

あとは通常のレビュープロセスを経てマージし、パイプラインから terraform apply を実行すればインポート作業は完了です。 import ブロックはもう不要なので削除できます。

おわりに

リソースのインポートはさまざまな OSS が存在していますが、個人的にはどれも決定打に欠けるものでした。そんな中 Terraform 1.5 が示したソリューションは、ユーザーのペインをよく理解しているなと感じました。

これで手動で作られた AWS リソースを効率よくインポートしていけそうです!

Popular Entries

Recent Entries