TerraformのBuilt-in関数や小ネタのあんちょこを作った
このブログを一言で
今まで使ったことあるorよく使う、TerraformのBuilt-in関数の説明や動作例、小ネタをまとめてみたかったので、重い腰あげてみたよ。
今後も順次メンテしていくよ。
関数(順不同)
参考:Built-in Functions
以下によく使う関数の動作例を記載していきます。
Collection Function
flatten関数
flattenは配列で入れ子になっているものを平準化します。
# flatten
variable "example_list_of_lists" {
default = [["a", "b"], ["c", "d"], ["e", "f"]]
}
output "as_list_of_lists" {
value = var.example_list_of_lists
}
output "as_flattened_list" {
value = flatten(var.example_list_of_lists)
}
Changes to Outputs:
as_list_of_lists = [
[
"a",
"b",
],
[
"c",
"d",
],
[
"e",
"f",
],
]
as_flattened_list = [
"a",
"b",
"c",
"d",
"e",
"f",
]
以下のように、list(object)の場合、env, subnet, ami, instanceのように要素を同じ階層にすることが可能です。
locals {
default = [
{
name = "dev"
subnets = ["10.0.1.0/24", "10.0.2.0/24"]
ami = "ami-12345678"
instance = "t3.micro"
},
{
name = "prod"
subnets = ["10.0.11.0/24", "10.0.12.0/24"]
ami = "ami-87654321"
instance = "t3.small"
}
]
# flattenで「環境 × サブネット」の組み合わせを1次元にする
instances = flatten([
for env in local.default : [
for sn in env.subnets : {
env = env.name
subnet = sn
ami = env.ami
instance = env.instance
}
]
])
}
output "instances" {
value = local.instances
}
Changes to Outputs:
instances = [
{
ami = "ami-12345678"
env = "dev"
instance = "t3.micro"
subnet = "10.0.1.0/24"
},
{
ami = "ami-12345678"
env = "dev"
instance = "t3.micro"
subnet = "10.0.2.0/24"
},
{
ami = "ami-87654321"
env = "prod"
instance = "t3.small"
subnet = "10.0.11.0/24"
},
{
ami = "ami-87654321"
env = "prod"
instance = "t3.small"
subnet = "10.0.12.0/24"
},
]
これは、以下のようにfor_eachで利用できます。
for_eachでmapを生成し、EC2を作成することも可能です。
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "this"
}
}
resource "aws_subnet" "this" {
# キーの重複があるため、独自キーを作成して、map化する
for_each = {
for v in local.instances : "${v.env}-${v.subnet}" => v
}
vpc_id = aws_vpc.this.id
cidr_block = each.value.subnet
tags = {
Name = "${each.value.env}-subnet"
}
}
resource "aws_instance" "this" {
# キーの重複があるため、独自キーを作成して、map化する
for_each = {
for v in local.instances : "${v.env}-${v.subnet}" => v
}
ami = each.value.ami
instance_type = each.value.instance
subnet_id = aws_subnet.this[each.key].id
tags = {
Name = "${each.value.env}-instance"
}
}
merge関数
複数のmapを結合して、重複を除いた形で出力します。
複数のKeyが重複している場合は、後続のKeyが優先されます。
variable "map1" {
default = {
a = 1
b = 2
}
}
variable "map2" {
default = {
b = 20
c = 30
}
}
output "merged_map" {
value = merge(var.map1, var.map2)
}
Changes to Outputs:
merged_map = {
a = 1
b = 20
c = 30
}
setintersection関数
複数のsetの重複を出力します。
locals {
list1 = ["a", "b", "c"]
list2 = ["b", "c", "d"]
intersection = setintersection(toset(local.list1), toset(local.list2))
}
output "intersection" {
value = local.intersection
}
Changes to Outputs:
intersection = [
"b",
"c",
]
index関数
listの要素番号を返します。
locals {
my_list = ["a", "b", "c"]
my_index = index(local.my_list, "b")
}
output "my_index" {
value = local.my_index
}
Changes to Outputs:
my_index = 1
keys関数
mapのKeyを返します。
locals {
example_map = {
a = 1
b = 2
c = 3
}
example_keys = keys(local.example_map)
}
output "example_keys" {
value = local.example_keys
}
Changes to Outputs:
example_keys = [
"a",
"b",
"c",
]
values関数
mapのValueを返します。
locals {
example_map = {
a = 1
b = 2
c = 3
}
example_values = values(local.example_map)
}
output "example_values" {
value = local.example_values
}
Changes to Outputs:
example_values = [
1,
2,
3,
]
length関数
set, list, mapの長さを返します。
locals {
my_list = ["a", "b", "c"]
my_length = length(local.my_list)
}
output "my_length" {
value = local.my_length
}
Changes to Outputs:
my_length = 3
setproduct関数
複数のリストを1つのリストに直積でまとめます。
こんな感じで二重ループすることも可能です。
locals {
set1 = ["a", "b"]
set2 = [1, 2]
product = setproduct(local.set1, local.set2)
}
output "setproduct_example" {
value = local.product
}
Changes to Outputs:
setproduct_example = [
[
"a",
1,
],
[
"a",
2,
],
[
"b",
1,
],
[
"b",
2,
],
]
String Function
join関数
文字列のリストを区切り文字で連結した文字列を生成します。
locals {
list = ["apple", "banana", "cherry"]
joined = join(", ", local.list)
}
output "join_example" {
value = local.joined
}
Changes to Outputs:
join_example = "apple, banana, cherry"
Type Conversion Function
toset/tolist関数
for_eachはsetかmapしか受け付けないため、tosetでlistをsetに変換する場合に使われることが多い。逆にtolist関数はsetをlistに変換します。
locals {
example_set = toset(["a", "b", "c"])
example_list = tolist(local.example_set)
}
❯ terraform console
> type(local.example_set)
set(string)
> type(local.example_list)
list(string)
IP Network Function
cidrsubnet関数
CIDRを順番に生成する便利な関数です。
CIDR計算が不要となる、かつ計算ミスをしなくても良くなるためSubnetを複数作成する場合に便利です。
構文はcidrsubnet(prefix, newbits, netnum)。
prefix: ベースとなる CIDR(例: "10.0.0.0/16")
newbits: 追加で借りるビット数(例: 6 → /16 が /22 になる)
netnum: 生成するサブネットの番号(0, 1, 2...)
例:
cidrsubnet("10.0.0.0/16", 6, 0) → 10.0.0.0/22
cidrsubnet("10.0.0.0/16", 6, 1) → 10.0.4.0/22
locals {
base_cidr = "10.0.0.0/16"
new_bits = 6
}
output "subnet_cidr1" {
value = cidrsubnet(local.base_cidr, local.new_bits, 0)
}
output "subnet_cidr2" {
value = cidrsubnet(local.base_cidr, local.new_bits, 1)
}
output "subnet_cidr3" {
value = cidrsubnet(local.base_cidr, local.new_bits, 2)
}
output "subnet_cidr4" {
value = cidrsubnet(local.base_cidr, local.new_bits, 3)
}
Changes to Outputs:
subnet_cidr1 = "10.0.0.0/22"
subnet_cidr2 = "10.0.4.0/22"
subnet_cidr3 = "10.0.8.0/22"
subnet_cidr4 = "10.0.12.0/22"
Filesystem Function
templatefile 関数
与えられたテンプレートファイルの変数をレンダリングします。
%{ for addr in ip_addrs ~}
backend ${addr}:${port}
%{ endfor ~}
> templatefile("${path.module}/backends.tftpl", { port = 8080, ip_addrs = ["10.0.0.1", "10.0.0.2"] })
backend 10.0.0.1:8080
backend 10.0.0.2:8080
使用例:
templatefileでファイルをレンダリングしつつ、AWS S3にファイルをアップロード、レンダリングした結果が異なれば、変更を検知して再度Uploadする例です。
# template.tftpl
jsonファイルにプレースホルダの変数を準備します。
{
"message": "Hello, ${name}!",
"environment": "${env}"
}
# modules.main.tf
templatefile関数でtemplate.tftplをレンダリングします。
さらにaws_s3_objectで etagを定義してあげて、変更を検知するようにします。
この時、レンダリング結果は実ファイルにないので、sourceでなく、contentで読み込んであげます。
さらにetagに渡す際は、md5関数でハッシュを渡します。実ファイルでないので、filemd5関数は利用できません。
locals {
rendered = templatefile("${path.module}/template.tftpl", {
name = var.name
env = var.env
})
}
resource "aws_s3_bucket" "this" {
bucket = "junya0530-etag-test"
}
resource "aws_s3_object" "this" {
bucket = aws_s3_bucket.this.bucket
key = "example.json"
content = local.rendered
etag = md5(local.rendered)
}
# main.tf(module呼び出し)
module "main" {
source = "./modules"
name = "junya0530"
env = "develop"
}
ここで、moudle呼び出し時のnameやenvといった、テンプレートのプレースホルダ用の変数の値を変えると、以下のように変更を検知できます。
# module.etag_test.aws_s3_object.this will be updated in-place
~ resource "aws_s3_object" "this" {
~ content = jsonencode(
~ {
~ environment = "develop" -> "prod"
# (1 unchanged attribute hidden)
}
)
~ etag = "6921d18413e25e7b8d6ceff4f6db41d2" -> "9045f6e723d986b31a029e0ea06c192d"
id = "junya0530-etag-test/example.json"
tags = {}
+ version_id = (known after apply)
# (24 unchanged attributes hidden)
}
小ネタ
動的にmapを作成する
locals {
subnets = [
"10.0.0.0/24",
"10.0.1.0/24",
"10.0.2.0/24",
]
azs = [
"ap-northeast-1a",
"ap-northeast-1c",
"ap-northeast-1d",
]
}
locals {
from_az_to_public_subnet = { for i, subnet in local.subnets :
local.azs[i] => subnet
}
}
output "test" {
value = local.from_az_to_public_subnet
}
Changes to Outputs:
test = {
ap-northeast-1a = "10.0.0.0/24"
ap-northeast-1c = "10.0.1.0/24"
ap-northeast-1d = "10.0.2.0/24"
}
Availability Zonesに均等にサブネットを配置する例
locals {
azs = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
subnets = [
"10.0.1.0/24",
"10.0.2.0/24",
"10.0.3.0/24",
"10.0.4.0/24",
"10.0.5.0/24",
"10.0.6.0/24"
]
number_of_azs = length(local.azs)
subnet_az_map = [
for subnet in local.subnets : {
cidr = subnet
az = local.azs[index(local.subnets, subnet) % local.number_of_azs]
}
]
}
output "subnet_az_map" {
value = local.subnet_az_map
}
Changes to Outputs:
subnet_az_map = [
{
az = "ap-northeast-1a"
cidr = "10.0.1.0/24"
},
{
az = "ap-northeast-1c"
cidr = "10.0.2.0/24"
},
{
az = "ap-northeast-1d"
cidr = "10.0.3.0/24"
},
{
az = "ap-northeast-1a"
cidr = "10.0.4.0/24"
},
{
az = "ap-northeast-1c"
cidr = "10.0.5.0/24"
},
{
az = "ap-northeast-1d"
cidr = "10.0.6.0/24"
},
]
Discussion