はじめに
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バケットはあるもとのとします。
terraform {
backend "s3" {
bucket = "example-bucket"
key = "state/example"
region = "ap-southeast-2"
}
}
リージョンは、お好きなところを選んでください。
provider "aws" {
region = "ap-southeast-2"
}
Versions.tf
はなくても問題ありませんが、運用を考えるとあった方がいいので入れておきます。
terraform {
required_version = ">= 0.12"
}
IAM User、Groupとグループポリシーのリソースを書きます。
resource "aws_iam_user" "example" {
count = length(local.members.example)
name = local.members.example[count.index]
}
resource "aws_iam_group" "example" {
name = "admin-${local.project_name}"
}
resource "aws_iam_group_policy" "example" {
name = "admin-policy"
group = aws_iam_group.example.id
policy = file("policy/example_policy.json")
}
ポリシーは、jsonファイルで定義し、file functionで取得します。
ちなみに、このポリシーはとりあえず管理者権限を付与しているだけです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
IAM UserとGroupの紐付けをします。
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ユーザが作成されるようにリストに記述しています。
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
を削除すると以下な感じですね。
locals {
project_name = "example"
members = {
example = [
"user01",
"user03"
]
}
}
それでは、apply
します。
変更箇所が多い!これは。。
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
どちらでも要素にアクセス可能です。
resource "aws_iam_user" "example" {
for_each = toset(local.members.example)
name = each.value
}
上記と同じようにmembership
側も対応します。
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]
}
リベンジ実行するよ!
先ほどと同様にユーザを新規で作成します。
そして、user02
をvariable.tf
から削除してから実行します。
$ 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
のみが削除されてエラーも発生しないことが確認できました!
今までは、以下のようにインデックスに数字が入っていましたが、リストで定義した値が入っていることがわかります。
### before
aws_iam_user.example[0]
### after
aws_iam_user.example["user02"]
まとめ
for_each
で幸せになることができましたね!
少しでも誰かのお役に立てれば幸いです。
ありがとうございました!