Terraformを使っているとよく使う組み込み関数。今日は組み込み関数のfor_eachについて調べてみました。
例として、Terraformを使って複数のBucketを一度に作成してみようと思います。
コード
以下がそのコード。
provider "aws" { region = local.region } locals { region = "ap-northeast-1" } variable "bucket_names" { type = list(string) default = ["yatta47-my-s3-bucket-01", "yatta47-my-s3-bucket-02"] } module "s3_bucket" { for_each = toset(var.bucket_names) source = "terraform-aws-modules/s3-bucket/aws" version = "3.14.0" bucket = each.value acl = "private" control_object_ownership = true object_ownership = "ObjectWriter" versioning = { enabled = true } }
planで正常に動くか確認します。
% terraform plan : : Plan: 10 to add, 0 to change, 0 to destroy.
問題なさそうなのが確認出来たら、applyを実行します。
terraform apply : : Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
これで2つのBucketが作成できたかと思います。
コードを読み解く
数十行にもみたいないこーどですが、学びもいくつかありました。
- for_eachの使い方
- tosetの使い方
for_eachについて
for_each文は、Terraform 0.12以降で利用可能な機能で、リソースやモジュールを反復的に作成する際に使用します。for_eachに渡せるのは、セット(集合)やマップで、直接リストを渡すことはできません。
また、countとと同時に使うこともできないようです。
今回の使い方の
module "s3_bucket" { for_each = toset(var.bucket_names) source = "terraform-aws-modules/s3-bucket/aws" version = "3.14.0" bucket = each.value
は、var.bucket_namesというリストをセットに変換して、それをfor_eachに渡していることになります。
セットをfor_eachに渡すとそれでループ処理が行われます。また、セットがキーと値のペアを持たない(つまり、全ての要素が一意である)データ構造であるため、各要素はeach.keyとeach.valueの両方に設定されます。
なので実は今回のコードのbucket =
部分は以下のように書き換えることもできます。
bucket = each.key
マップを渡す場合、マップのキーがリソースの識別子として使われ、each.keyとeach.valueを使ってそれぞれのキーと値にアクセスすることができます。
tosetについて
tosetはTerraformの組み込み関数で、与えられたリストや他のコレクションをセット(集合)に変換します。
セットとは、重複する要素を持たない、順序が定義されていないデータ構造です。toset関数を使用することで、要素の重複を排除したり、要素の順序に依存しない操作を行ったりすることが可能になります。
このスクリプトでは、toset(var.bucket_names)によって、bucket_names変数(バケット名のリスト)をセットに変換しています。そして、このセットはfor_eachで利用されています。
ここで、読んでいたときに「コレクション」という言葉が出てきました。それも調べてみました。
コレクションとは
「コレクション」という言葉は、一般的には複数の値を持つことができるデータ型の総称を指します。Terraformにおいては、リスト(配列)、マップ、セットの3つのデータ型がコレクションとして扱われます。
これらのコレクション型の具体的な表現は以下のようになります:
リスト(配列):[]を使用して表現します。例:["one", "two", "three"]
マップ:{}を使用して表現します。例:{ key1 = "value1", key2 = "value2" }
セット:toset()関数を使用してリストや他のコレクションをセットに変換します。直接セットを表現するリテラル構文は存在しません。
つまり、「コレクション」自体が特定の形を持つわけではなく、それがリストであるか、マップであるか、セットであるかによって形が決まります。
なかなか奥が深い。
mapを使った複数Bucket作成
ということで、Listで定義して、それをsetに変換してfor_eachで複数Bucketを作成してみましたが、Mapを使っても同じことができます。
その例が以下です。
variable "bucket_info" { description = "Map of bucket names and their corresponding ACLs" type = map(string) default = { "yatta47-my-s3-bucket-01-map" = "private" "yatta47-my-s3-bucket-02-map" = "private" } } module "s3_bucket_map" { for_each = var.bucket_info source = "terraform-aws-modules/s3-bucket/aws" version = "3.14.0" bucket = each.key acl = each.value control_object_ownership = true object_ownership = "ObjectWriter" versioning = { enabled = true } }
まとめ
今回の気づきは以下。
- for_eachに渡せるのはセットかマップ。リストは渡せない。
- toset()にリストを渡すとセットに変換してくれる
- セットはキーと値のペアを持たないので、each.keyとeach.valueが同一の値が入る
どっちでも作れるとなると要件によって使い分ける必要が出てきますね。
たとえば、Bucket毎に設定を変えたいとかなったら、mapを渡して作ったほうがいいだろうし。うまく使い分けていきたい。