はじめに
Terraformのリソースの引数にJSONを渡す、というのはよくあるシチュエーションだと思います。AWSを使用してしる場合は特に。
また、引数として渡すJSONのパラメータの一部をテンプレートとして変数化して環境間で値を変える、というのもよくあるシチュエーションだと思います。
例えばAWSのTaskDefinitionを例に挙げるとこんな感じです。
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = templatefile(
"task-definitions/service.json.tpl",
{
cpu = var.cpu
}
)
}
JSONファイルをテンプレート化して、CPUの値をvariableで変更するように書いています。
該当のテンプレートのJSONファイル、中身は以下のようになってます。
[
{
"name": "alpine",
"image": "alpine:latest",
"command": [
"env"
],
"cpu": ${cpu},
"memory": 128,
"essential": true,
"environment": [
{
"name": "TERRAFORM",
"value": "BENRI"
},
{
"name": "AWS",
"value": "BENRI"
}
]
}
]
"cpu": ${cpu}
がテンプレートとしてパラメータ化している部分ですね。
ここでひとつ問題がでます。このJSONファイルはnumber型のcpuの部分をパラメータ化したため、JSONとして正しい書式では無くなってしまったのです。
この場合エディタが警告を出しくるのでなんだか嫌だなぁという気持ちになります。また使ってるエディタ次第ですがフォーマッタの挙動がおかしくなり整形に手間がかかります。
そこでエディタに怒られずにJSONをテンプレート化する方法する方法を紹介します。
なお、実用的なテクニックかどうかは怪しいのであしからずw
環境
Terraform v0.12.18
を使って本記事の検証を行いました。
方法その1:VariablesからJSONを生成する
JSONがJSONである以上、number型の所に数値以外のものを突っ込むと怒られるに決まっています。
つまり怒られずにJSONをテンプレート化するにはJSON以外でJSONを書けば良いのです(?)
terraform v0.12では豊富な変数の型をサポートしています。その中にはMap,List,Objectなどの複合型もサポートしており、またネストした変数もサポートしています。
そう、つまりはVariablesで求めるJSONを満たす複合型を作ってjsonencode
してやればよいのです。
こんな感じで複合型を作ります。
variable "ecs_tasks" {
type = list(object({
name = string
image = string
command = list(string)
essential = bool
cpu = number
memory = number
environment = list(object({
name = string
value = string
}))
}))
default = [{
name = "alpine"
image = "alpine:latest"
command = [
"env"
]
essential = true
cpu = 128 //コメントも書けるよ
memory = 128
environment = [
{
name = "TERRAFORM"
value = "BENRI"
},
{
name = "AWS"
value = "BENRI"
}
]
}]
}
JSONにコメント書きたいというのは全人類の共通の悩みですが、この方法でもコメントを自由に書けます。やったね!!
ではconsoleで確認してみます。
» terraform console
> var.ecs_tasks
[
{
"command" = [
"env",
]
"cpu" = 128
"environment" = [
{
"name" = "TERRAFORM"
"value" = "BENRI"
},
{
"name" = "AWS"
"value" = "BENRI"
},
]
"essential" = true
"image" = "alpine:latest"
"memory" = 128
"name" = "alpine"
},
]
> jsonencode(var.ecs_tasks)
[{"command":["env"],"cpu":128,"environment":[{"name":"TERRAFORM","value":"BENRI"},{"name":"AWS","value":"BENRI"}],"essential":true,"image":"alpine:latest","memory":128,"name":"alpine"}]
イエーイ!!目的のJSONがゲットできました。当然、main.tfは正しいHCL2の書式なので怒られません!!
が、この方法だと変数のdefaultの部分的な上書きがうまく動きません。
//cpuだけ上書きしたい
ecs_tasks = [{
cpu = 256
}]
» terraform console -var-file=fail.tfvars
Error: Invalid value for input variable
on fail.tfvars line 1:
1: ecs_tasks = [{
2: cpu = 256
3: }]
The given value is not valid for variable "ecs_tasks": element 0:
attributes "command", "environment", "essential", "image", "memory", and
"name" are required.
全部を上書きする場合はうまく動きます。
ecs_tasks = [{
name = "alpine"
image = "alpine:latest"
command = [
"env"
]
essential = true
cpu = 256
memory = 128
environment = [
{
name = "TERRAFORM"
value = "BENRI"
},
{
name = "AWS"
value = "BENRI"
}
]
}]
» terraform console -var-file=success.tfvars
> var.ecs_tasks
[
{
"command" = [
"env",
]
"cpu" = 256
"environment" = [
{
"name" = "TERRAFORM"
"value" = "BENRI"
},
{
"name" = "AWS"
"value" = "BENRI"
},
]
"essential" = true
"image" = "alpine:latest"
"memory" = 128
"name" = "alpine"
},
]
方法その1はパラメータの上書きという観点でちょっと使いづらいですね。そこで方法その2。
方法その2:ネストしたlocalsからJSONを生成する
方法その1と大体一緒ですが、今度はlocalsを使います。
テンプレートの置き換え、という意味ではこちらの方がテンプレートの使い勝手に近いです。
localsにネストした値を定義します。
variable "cpu" {
type = number
default = 256
}
locals {
ecs_tasks = [{
name = "alpine"
image = "alpine:latest"
command = [
"env"
]
essential = true
cpu = var.cpu //コメントも書けるよ
memory = 128
environment = [
{
name = "TERRAFORM"
value = "BENRI"
},
{
name = "AWS"
value = "BENRI"
}
]
}]
}
当然こちらの方法でもコメントを自由に書けます。
» terraform console
> local.ecs_tasks
[
{
"command" = [
"env",
]
"cpu" = 256
"environment" = [
{
"name" = "TERRAFORM"
"value" = "BENRI"
},
{
"name" = "AWS"
"value" = "BENRI"
},
]
"essential" = true
"image" = "alpine:latest"
"memory" = 128
"name" = "alpine"
},
]
> jsonencode(local.ecs_tasks)
[{"command":["env"],"cpu":256,"environment":[{"name":"TERRAFORM","value":"BENRI"},{"name":"AWS","value":"BENRI"}],"essential":true,"image":"alpine:latest","memory":128,"name":"alpine"}]
こっちの方法だと部分的に変数で値を上書きすることが簡単です!やったね。
おわりに
ケースにもよりますが、テンプレート化したJSONの代わりにlocalsでネストした変数を使う、というテクニックはそこそこ便利なのではないかな?と書いてて思いました。
HCLの変数はJSONの見た目にも近いですし、フォーマッタ効きますし、コメントも書けますし。
おまけ
1つのローカル変数を使いまわしながら値を上書きしたい時は、forとmerge関数の組み合わせが使えます。
トリッキーすぎてイマイチですが、試してみたら動いたので紹介。もっとシンプルな方法あったら教えて下さい。
variable "cpu" {
type = number
default = 256
}
locals {
ecs_tasks = [{
name = "alpine"
image = "alpine:latest"
command = [
"env"
]
essential = true
cpu = var.cpu //コメントも書けるよ
memory = 128
environment = [
{
name = "TERRAFORM"
value = "BENRI"
},
{
name = "AWS"
value = "BENRI"
}
]
}]
//ecs_tasksのcpuを上書き その1
overwrite_a = [{ cpu = 512 }]
ecs_tasks_a = [for key, value in local.ecs_tasks : merge(value, local.overwrite_a[key])]
//ecs_tasksのcpuを上書き その2
overwrite_b = [{ cpu = 1024 }]
ecs_tasks_b = [for key, value in local.ecs_tasks : merge(value, local.overwrite_b[key])]
}
» terraform console
> local.ecs_tasks_a
[
{
"command" = [
"env",
]
"cpu" = 512
"environment" = [
{
"name" = "TERRAFORM"
"value" = "BENRI"
},
{
"name" = "AWS"
"value" = "BENRI"
},
]
"essential" = true
"image" = "alpine:latest"
"memory" = 128
"name" = "alpine"
},
]
> local.ecs_tasks_b
[
{
"command" = [
"env",
]
"cpu" = 1024
"environment" = [
{
"name" = "TERRAFORM"
"value" = "BENRI"
},
{
"name" = "AWS"
"value" = "BENRI"
},
]
"essential" = true
"image" = "alpine:latest"
"memory" = 128
"name" = "alpine"
},
]