Terraformの設定値をYAMLやTOMLで定義したい
はじめに
皆様こんにちは、あかいけです。
Terraformを書いていると、設定値(variable、locals)の肥大化に悩む場面はありませんか?私はあります。
そして肥大化した設定値は、HCL構文に慣れていないメンバーにとって読みづらいことが往々にしてあります…。
今回はTerraformコードの可読性を向上させる方法の一つとして、
そんな肥大化した複雑な設定値をYAMLやTOMLファイルに切り出す方法をまとめてみました。
設定値が複雑になる例
最初にlocalsブロックが複雑になりがちな典型的な例を見てみましょう。
以下は、AWS IAM Identity Centerのアサインメントを定義するための例です。
なんだか複雑に見えますが、データ構造は以下の通りで、
複数のプロジェクトに対して、それぞれの環境(AWSアカウント)ごとにグループと許可セットの組み合わせを定義しています。
account_assignments # ルートオブジェクト
├── Project-A # プロジェクト名
│ ├── DEV # 環境名
│ │ ├── account_id # 対象のAWSアカウントID
│ │ └── assignments[] # アサインメントの配列
│ │ └── { group, permission_set } # グループ名と許可セット名のペア
│ └── PRD
│ ├── account_id
│ └── assignments[]
└── Project-B
├── DEV
└── PRD
locals {
account_assignments = {
# プロジェクトA
"Project-A" = {
DEV = {
account_id = "111111111111"
assignments = [
{ group = "AWS-ProjectA-DEV-ADMIN", permission_set = "administrator" },
{ group = "AWS-ProjectA-DEV-DEVELOPER", permission_set = "power_user" },
{ group = "AWS-ProjectA-DEV-READONLY", permission_set = "view_only" },
]
}
PRD = {
account_id = "333333333333"
assignments = [
{ group = "AWS-ProjectA-PRD-ADMIN", permission_set = "administrator" },
{ group = "AWS-ProjectA-PRD-OPERATOR", permission_set = "power_user" },
{ group = "AWS-ProjectA-PRD-READONLY", permission_set = "view_only" },
]
}
}
# プロジェクトB
"Project-B" = {
DEV = {
account_id = "444444444444"
assignments = [
{ group = "AWS-ProjectB-DEV-ADMIN", permission_set = "administrator" },
{ group = "AWS-ProjectB-DEV-DEVELOPER", permission_set = "power_user" },
{ group = "AWS-ProjectB-DEV-READONLY", permission_set = "view_only" },
]
}
PRD = {
account_id = "666666666666"
assignments = [
{ group = "AWS-ProjectB-PRD-ADMIN", permission_set = "administrator" },
{ group = "AWS-ProjectB-PRD-OPERATOR", permission_set = "power_user" },
{ group = "AWS-ProjectB-PRD-READONLY", permission_set = "view_only" },
]
}
}
}
}
このlocalsを使ってアサインメントリソースを作成する場合、以下のようになります。
(フラット化するかどうかはオブジェクトの構造次第なので、YAMLでもTOMLでも変わりません)
locals {
# アサインメントをフラット化
assignments = {
for a in flatten([
for project, envs in local.account_assignments : [
for env, config in envs : [
for assignment in config.assignments : {
key = "${project}-${env}-${assignment.group}"
account_id = config.account_id
group = assignment.group
permission_set = assignment.permission_set
}
]
]
]) : a.key => a
}
# 一意なPermission Set名とグループ名
instance_arn = tolist(data.aws_ssoadmin_instances.main.arns)[0]
identity_store_id = tolist(data.aws_ssoadmin_instances.main.identity_store_ids)[0]
permission_set_names = distinct([for a in local.assignments : a.permission_set])
group_names = distinct([for a in local.assignments : a.group])
}
# アカウントアサインメントの作成
resource "aws_ssoadmin_account_assignment" "this" {
for_each = local.assignments
instance_arn = local.instance_arn
permission_set_arn = data.aws_ssoadmin_permission_set.this[each.value.permission_set].arn
principal_id = data.aws_identitystore_group.this[each.value.group].group_id
principal_type = "GROUP"
target_id = each.value.account_id
target_type = "AWS_ACCOUNT"
}
また必要な許可セットのARNやグループのIDが必要になるので、dataリソースで参照しておきます。
(本記事ではdataリソース経由で参照しますが、同じステートで管理している場合はそちらを直接参照すればOKです)
data "aws_ssoadmin_instances" "main" {}
# Permission Setの取得
data "aws_ssoadmin_permission_set" "this" {
for_each = toset(local.permission_set_names)
instance_arn = local.instance_arn
name = each.value
}
# Identity Storeグループの取得
data "aws_identitystore_group" "this" {
for_each = toset(local.group_names)
identity_store_id = local.identity_store_id
alternate_identifier {
unique_attribute {
attribute_path = "DisplayName"
attribute_value = each.value
}
}
}
さて、上記の例では2プロジェクトだけ定義していますが、
これがさらに10プロジェクト、100プロジェクトと増えていくと考えると、誰もメンテナンスしたくないlocalsブロックが出来上がる予感がします…。
そうならないためにも、できるだけ可読性の高い設定ファイルを作る方法を考えていきます。
YAMLで定義する場合
yamldecode関数
Terraformには標準でyamldecode()関数が用意されています。
この関数を使うことで、YAMLファイルを読み込んでTerraformのオブジェクトに変換できます。
YAMLファイルの作成
先ほどの複雑なlocalsブロックを、YAMLファイルに切り出してみましょう。
account_assignments:
# プロジェクトA
Project-A:
DEV:
account_id: "111111111111"
assignments:
- group: AWS-ProjectA-DEV-ADMIN
permission_set: administrator
- group: AWS-ProjectA-DEV-DEVELOPER
permission_set: power_user
- group: AWS-ProjectA-DEV-READONLY
permission_set: view_only
PRD:
account_id: "333333333333"
assignments:
- group: AWS-ProjectA-PRD-ADMIN
permission_set: administrator
- group: AWS-ProjectA-PRD-OPERATOR
permission_set: power_user
- group: AWS-ProjectA-PRD-READONLY
permission_set: view_only
# プロジェクトB
Project-B:
# ... 以下省略
どうでしょうか?行数は大して変わりませんが、
HCLで書くよりも、YAMLで書いた方が構造が見やすくなっている気がしませんか?
Terraformでの読み込み
ではこのYAMLファイルを読み込む方法ですが、非常にシンプルです。
以下のようにyamldecodeするだけで、先ほどのlocalsブロックと同じデータ構造へ変換できます。
locals {
config = yamldecode(file("${path.module}/account_assignments.yaml"))
account_assignments = local.config.account_assignments
# ... 以下省略
読み込んだ設定値は、通常のlocals変数と同じように参照できるので、
必要に応じてフラット化してリソースにfor_eachで渡してあげればOKです。
TOMLで定義する場合
TOMLプロバイダー
次にTOMLの場合ですが、サードパーティのTobotimus/tomlプロバイダーを使用します。
プロバイダーの設定
terraform {
required_providers {
toml = {
source = "Tobotimus/toml"
version = "~> 0.3"
}
}
}
TOMLファイルの作成
同じ設定をTOMLで記述してみましょう。
TOMLはセクションベースの構文で、設定ファイルとしての可読性に優れています。
# プロジェクトA
[account_assignments.Project-A.DEV]
account_id = "111111111111"
[[account_assignments.Project-A.DEV.assignments]]
group = "AWS-ProjectA-DEV-ADMIN"
permission_set = "administrator"
[[account_assignments.Project-A.DEV.assignments]]
group = "AWS-ProjectA-DEV-DEVELOPER"
permission_set = "power_user"
[[account_assignments.Project-A.DEV.assignments]]
group = "AWS-ProjectA-DEV-READONLY"
permission_set = "view_only"
[account_assignments.Project-A.PRD]
account_id = "333333333333"
[[account_assignments.Project-A.PRD.assignments]]
group = "AWS-ProjectA-PRD-ADMIN"
permission_set = "administrator"
[[account_assignments.Project-A.PRD.assignments]]
group = "AWS-ProjectA-PRD-OPERATOR"
permission_set = "power_user"
[[account_assignments.Project-A.PRD.assignments]]
group = "AWS-ProjectA-PRD-READONLY"
permission_set = "view_only"
# プロジェクトB
[account_assignments.Project-B.DEV]
account_id = "444444444444"
# ... 以下省略
TOMLの場合、配列の各要素を[[section]]で表現するため、YAMLよりも冗長になります。
この例のようにネストが深く配列を多用する場合は、YAMLの方が簡潔に書けます。
Terraformでの読み込み
dataリソースとしてTOMLを読み込んだ後は、通常のlocals変数と同じように参照できます。
あとは必要に応じてフラット化してリソースにfor_eachで渡してあげればOKです。
data "toml_file" "config" {
input = file("${path.module}/account_assignments.toml")
}
locals {
account_assignments = data.toml_file.config.content.account_assignments
# ... 以下省略
YAML と TOML どちらが良さそう?
さて、2種類紹介しましたが、どちらを使えばいいのでしょうか?
正直ここは好み次第ですが、私のおすすめは YAML です。
理由としては、追加プロバイダーが不要な点と、AWSでよく使うCloudFormationやAnsibleなど周辺ツールでもYAMLが利用されているためです。
そのためチームメンバーがYAMLに慣れていることも多く、学習コストを抑えられます。
ただし、設定値がフラットで、よりシンプルな設定ファイルが望ましい場合はTOMLも選択肢としてアリだと思うので、この辺りは好み決めていいと思います。
さいごに
以上、Terraformの設定値をYAMLやTOMLで定義する方法でした。
localsブロックが複雑になってきたら、外部ファイルへの切り出しを検討してみてください。
きっとコードレビューの負担軽減や、設定値の見通しの良さにつながります。
この記事が皆様のTerraformコードの可読性向上に役立てば幸いです。






