Help us understand the problem. What is going on with this article?

Terraform0.12 のfor_eachで幸せになれるってよ

はじめに

Terraform 0.12.6が出ました!
このリリースにより、for_eachの幅が広がりました。

具体的にどういった恩恵が授かれるのかをIAM User&Groupの作成で紹介できればと思います。

環境

以下の環境で実施します。

  • Cloud: AWS
  • Terraform: 0.12.6

ツラミ

Terraform を使ってIAM UserやGroupを管理していてツラミにぶち当たった人は少なからずいると思います。
それは、ユーザを追加や削除する時です。
そして見覚えがあると思います。。以下のエラー

User with name X already exists.

もうツラミでしかない。。

ここからは、いままでのやり方(僕の)やこれからのやり方について書いていきたいと思います。

いままで

まずはこのエラーが発生する場面を体験したいと思います。

Terraformコードの作成

Terraformのファイル構成は以下です。
リソース単位でファイルを分けておりますが、まとめても問題ありません。

.
├── backend.tf
├── iam_group.tf
├── iam_group_policy.tf
├── iam_membership.tf
├── iam_user.tf
├── policy
│   └── example_policy.json
├── provider.tf
├── variable.tf
└── versions.tf

前提としてtfstateファイルを格納するS3バケットはあるもとのとします。

backend.tf
terraform {
  backend "s3" {
    bucket = "example-bucket"
    key    = "state/example"
    region = "ap-southeast-2"
  }
}

リージョンは、お好きなところを選んでください。

provider.tf
provider "aws" {
  region = "ap-southeast-2"
}

Versions.tfはなくても問題ありませんが、運用を考えるとあった方がいいので入れておきます。

versions.tf
terraform {
  required_version = ">= 0.12"
}

IAM User、Groupとグループポリシーのリソースを書きます。

iam_user.tf
resource "aws_iam_user" "example" {
  count = length(local.members.example)
  name  = local.members.example[count.index]
}
iam_group.tf
resource "aws_iam_group" "example" {
  name = "admin-${local.project_name}"
}
iam_group_policy.tf
resource "aws_iam_group_policy" "example" {
  name   = "admin-policy"
  group  = aws_iam_group.example.id
  policy = file("policy/example_policy.json")
}

ポリシーは、jsonファイルで定義し、file functionで取得します。
ちなみに、このポリシーはとりあえず管理者権限を付与しているだけです。

policy/example_policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

IAM UserとGroupの紐付けをします。

iam_membership.tf
resource "aws_iam_user_group_membership" "example" {
  count      = length(local.members.example)
  user       = local.members.example[count.index]
  groups     = [aws_iam_group.example.name]
  depends_on = [aws_iam_user.example]
}

最後にvarible.tfです。
3ユーザが作成されるようにリストに記述しています。

variable.tf
locals {
  project_name = "example"

  members = {
    example = [
      "user01",
      "user02",
      "user03"
    ]
  }
}

ここからが問題

terraform applyを実行し、ユーザを作成します。

$ terraform apply
~省略~
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

ここまでは問題ありませんね。
次に3ユーザのうちの1ユーザを削除します。
例えば、user02を削除すると以下な感じですね。

variable.tf
locals {
  project_name = "example"

  members = {
    example = [
      "user01",
      "user03"
    ]
  }
}

それでは、applyします。
変更箇所が多い!これは。。

terragrunt.hcl
n execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place
  - destroy

Terraform will perform the following actions:

  # aws_iam_group_membership.example[0] will be updated in-place
  ~ resource "aws_iam_group_membership" "example" {
        group = "admin-example"
        id    = "test"
        name  = "test"
      ~ users = [
            "user01",
          - "user02",
          - "user03",
        ]
    }

  # aws_iam_group_membership.example[1] will be updated in-place
  ~ resource "aws_iam_group_membership" "example" {
        group = "admin-example"
        id    = "test"
        name  = "test"
      ~ users = [
          - "user01",
          - "user02",
            "user03",
        ]
    }

  # aws_iam_group_membership.example[2] will be destroyed
  - resource "aws_iam_group_membership" "example" {
      - group = "admin-example" -> null
      - id    = "test" -> null
      - name  = "test" -> null
      - users = [
          - "user01",
          - "user02",
          - "user03",
        ] -> null
    }

  # aws_iam_user.example[1] will be updated in-place
  ~ resource "aws_iam_user" "example" {
        arn           = "arn:aws:iam::446105290347:user/user02"
        force_destroy = false
        id            = "user02"
      ~ name          = "user02" -> "user03"
        path          = "/"
        tags          = {}
        unique_id     = "AIDAWPXPSQZVRQTUGGAO4"
    }

  # aws_iam_user.example[2] will be destroyed
  - resource "aws_iam_user" "example" {
      - arn           = "arn:aws:iam::446105290347:user/user03" -> null
      - force_destroy = false -> null
      - id            = "user03" -> null
      - name          = "user03" -> null
      - path          = "/" -> null
      - tags          = {} -> null
      - unique_id     = "AIDAWPXPSQZV6WEUDV65V" -> null
    }

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

はい!期待通りのエラーです。

Error: Error updating IAM User user02: EntityAlreadyExists: User with name user03 already exists.
    status code: 409, request id: xx

  on iam.tf line 1, in resource "aws_iam_user" "example":
   1: resource "aws_iam_user" "example" {

このエラーが出たら、今までは仕方ないものとして対処していました。
しかし、これが解決されます!

これから

ここからはどうやって対応して先ほどのエラーを解消するかを記載します。

コードの書き換え

今まで苦しめてきた問題を解決するためにfor_eachを利用します。
書き換えるのは、以下の二つのファイルです。

  • iam_membership.tf
  • iam_user.tf

toset functionを使用してfor_eachに渡します。
ちなみに、each.keyでもeach.valueどちらでも要素にアクセス可能です。

iam_user.tf
resource "aws_iam_user" "example" {
  for_each = toset(local.members.example)
  name     = each.value
}

上記と同じようにmembership側も対応します。

iam_membership.tf
resource "aws_iam_user_group_membership" "example" {
  for_each   = toset(local.members.example)
  user       = each.value
  groups     = [aws_iam_group.example.name]
  depends_on = [aws_iam_user.example]
}

リベンジ実行するよ!

先ほどと同様にユーザを新規で作成します。
そして、user02variable.tfから削除してから実行します。

result
$ terraform apply

  - destroy

Terraform will perform the following actions:

  # aws_iam_user.example["user02"] will be destroyed
  - resource "aws_iam_user" "example" {
      - arn           = "arn:aws:iam::446105290347:user/user02" -> null
      - force_destroy = false -> null
      - id            = "user02" -> null
      - name          = "user02" -> null
      - path          = "/" -> null
      - tags          = {} -> null
      - unique_id     = "xxx" -> null
    }

  # aws_iam_user_group_membership.example["user02"] will be destroyed
  - resource "aws_iam_user_group_membership" "example" {
      - groups = [
          - "admin-example",
        ] -> null
      - id     = "terraform-xxx" -> null
      - user   = "user02" -> null
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_iam_user_group_membership.example["user02"]: Destroying... [id=terraform-xxx]
aws_iam_user_group_membership.example["user02"]: Destruction complete after 1s
aws_iam_user.example["user02"]: Destroying... [id=user02]
aws_iam_user.example["user02"]: Destruction complete after 1s

Apply complete! Resources: 0 added, 0 changed, 2 destroyed.

user02のみが削除されてエラーも発生しないことが確認できました!
今までは、以下のようにインデックスに数字が入っていましたが、リストで定義した値が入っていることがわかります。

apply実行時
### before
aws_iam_user.example[0]
### after
aws_iam_user.example["user02"]

まとめ

for_eachで幸せになることができましたね!
少しでも誰かのお役に立てれば幸いです。
ありがとうございました!

参考

Resources

micci184
future
ITを武器とした課題解決型のコンサルティングサービスを提供します
http://future-architect.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした