なるべくvariable
で定義した内容で構築出来るようにしたかったので、クソみたいなリソース定義になったけどある程度設定で定義出来る形を考えてみた。
定義例
variable "project" {
type = "map"
default {
prefix = "main"
}
}
variable "ec2_list" {
type = "list"
default = [
"bastion-1",
"bastion-2",
]
}
variable "ec2_options" {
type = "map"
default {
// デフォルトの定義
default.ami = "ami-ceafcba8"
default.availability_zone = "ap-northeast-1a"
default.instance_type = "t2.micro"
default.key_name = "default"
default.tags_keys = "amirotate"
default.tags_value = "{\"NoReboot\": true, \"Retention\": {\"Count\": 2}}"
// グループ "bastion" の定義
bastion.tags_keys = "Type"
bastion.tags_value = "bastion"
// インスタンス "bastion-1" の定義
bastion-1.group = "bastion"
bastion-1.tags_keys = "Mode"
bastion-1.tags_value = "master"
// インスタンス "bastion-2" の定義
bastion-2.group = "bastion"
bastion-2.tags_keys = "Mode"
bastion-2.tags_value = "standby"
bastion-2.availability_zone = "ap-northeast-1c"
}
}
resource "aws_instance" "main" {
count = "${length(var.ec2_list)}"
ami = "${lookup(
var.ec2_options,
"${element(var.ec2_list, count.index)}.ami",
lookup(
var.ec2_options,
"${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.ami",
lookup(var.ec2_options, "default.ami", "false")
)
)}"
instance_type = "${lookup(
var.ec2_options,
"${element(var.ec2_list, count.index)}.instance_type",
lookup(
var.ec2_options,
"${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.instance_type",
lookup(var.ec2_options, "default.instance_type", "false")
)
)}"
key_name = "${lookup(
var.ec2_options,
"${element(var.ec2_list, count.index)}.key_name",
lookup(
var.ec2_options,
"${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.key_name",
lookup(var.ec2_options, "default.key_name", "default")
)
)}"
availability_zone = "${lookup(
var.ec2_options,
"${element(var.ec2_list, count.index)}.availability_zone",
lookup(
var.ec2_options,
"${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.availability_zone",
lookup(var.ec2_options, "default.availability_zone", "ap-northeast-1a")
)
)}"
tags = "${merge(
map("Name", "${lookup(var.project, "prefix", "default")}-${element(var.ec2_list, count.index)}"),
zipmap(
compact(split("#", lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.tags_value", "")))
),
zipmap(
compact(split("#", lookup(var.ec2_options, "${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.tags_value", "")))
),
zipmap(
compact(split("#", lookup(var.ec2_options, "default.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "default.tags_value", "")))
)
)}"
}
planはこんな感じに。
+ aws_instance.main[0]
id: <computed>
ami: "ami-ceafcba8"
associate_public_ip_address: <computed>
availability_zone: "ap-northeast-1a"
ebs_block_device.#: <computed>
ephemeral_block_device.#: <computed>
instance_state: <computed>
instance_type: "t2.micro"
ipv6_address_count: <computed>
ipv6_addresses.#: <computed>
key_name: "default"
network_interface.#: <computed>
network_interface_id: <computed>
placement_group: <computed>
primary_network_interface_id: <computed>
private_dns: <computed>
private_ip: <computed>
public_dns: <computed>
public_ip: <computed>
root_block_device.#: <computed>
security_groups.#: <computed>
source_dest_check: "true"
subnet_id: <computed>
tags.%: "4"
tags.Mode: "master"
tags.Name: "main-bastion-1"
tags.Type: "bastion"
tags.amirotate: "{\"NoReboot\": true, \"Retention\": {\"Count\": 2}}"
tenancy: <computed>
volume_tags.%: <computed>
vpc_security_group_ids.#: <computed>
+ aws_instance.main[1]
id: <computed>
ami: "ami-ceafcba8"
associate_public_ip_address: <computed>
availability_zone: "ap-northeast-1c"
ebs_block_device.#: <computed>
ephemeral_block_device.#: <computed>
instance_state: <computed>
instance_type: "t2.micro"
ipv6_address_count: <computed>
ipv6_addresses.#: <computed>
key_name: "default"
network_interface.#: <computed>
network_interface_id: <computed>
placement_group: <computed>
primary_network_interface_id: <computed>
private_dns: <computed>
private_ip: <computed>
public_dns: <computed>
public_ip: <computed>
root_block_device.#: <computed>
security_groups.#: <computed>
source_dest_check: "true"
subnet_id: <computed>
tags.%: "4"
tags.Mode: "standby"
tags.Name: "main-bastion-2"
tags.Type: "bastion"
tags.amirotate: "{\"NoReboot\": true, \"Retention\": {\"Count\": 2}}"
tenancy: <computed>
volume_tags.%: <computed>
vpc_security_group_ids.#: <computed>
使用している技
lookupでの値取得
lookup(<変数名>, <mapキー名>, <デフォルト値>)
でmapに定義したデータを引っ張る。
これを多段にする事で、bastion-1.ami
→bastion.ami
→default.ami
のような優先順位で設定値を取っている。
ami = "${lookup(
var.ec2_options,
"${element(var.ec2_list, count.index)}.ami",
lookup(
var.ec2_options,
"${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.ami",
lookup(var.ec2_options, "default.ami", "false")
)
)}"
グループの値を取得しているときにデフォルト値にdefault
と入れてdefault.ami
を引っ張らせようとしたけど、なぜか上手く展開してくれなくて泣いた。
動的なmap構築
タグには役割とか各種ツール用の情報入れたりをするんだけども、一つ一つで書くのが面倒なので、全体定義・グループ定義・個別定義をマージしたmapを作るように書いてみた。
tags = "${merge(
map("Name", "${lookup(var.project, "prefix", "default")}-${element(var.ec2_list, count.index)}"),
zipmap(
compact(split("#", lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.tags_value", "")))
),
zipmap(
compact(split("#", lookup(var.ec2_options, "${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "${lookup(var.ec2_options, "${element(var.ec2_list, count.index)}.group")}.tags_value", "")))
),
zipmap(
compact(split("#", lookup(var.ec2_options, "default.tags_keys", ""))),
compact(split("#", lookup(var.ec2_options, "default.tags_value", "")))
)
)}"
mergeはそのまんまで、羅列したmapをマージしてくれるので定義別にmapを渡してあげれば良い。
でも、map定義の中で値がstringのものとmapのものを混ぜるとエラーになってしまうので、キーと値を別々にstringで定義してsplitで分割。今回は途中にカンマを入れたかったので#
を区切り文字として書いてみた。
splitで作成したlistをzipmapでmapに変換している。
で、定義が無い時は空文字列を取ってくるようにしているけど、これをそのままsplitに通すと空文字が含まれたリストになって、zipmapでmap化する際に空文字キーと空文字値のmapになってしまうので、compactに通して空リストにしてあげている。
空リスト同士でzipmapを通すと空mapになるので、mergeを通した時は無視されて、定義されている部分のみがちゃんとmapになる。
思った事
最初の定義は大変だけど、terraformをあまり知らない人にちょっとした修正をお願いする時とかにはvariables.tf
のここ直してーと言えば済むのが良いとは思うんだけど、あまりに読みづらい。
あとinterpolation組み合わせまくるの大変なので関数定義ほしい。