Terraformで複数のサブネットを作りmapにまとめる

# 複数のサブネットを作成する

いきなりですが、例えば以下のようにAzure環境で4つのサブネットを作るとします。
(providerは省略)

```terraform

variable "location" {
  type    = string
  default = "japaneast"
}

# リソースグループを作成する
resource "azurerm_resource_group" "rg" {
  name     = "rg-test"
  location = var.location
}

# vNetを作成する
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-test-01"
  location            = var.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]
}

# サブネットを作成する
resource "azurerm_subnet" "subnet" {
  count                = 4
  name                 = "subnet-${count.index + 1}"
  virtual_network_name = azurerm_virtual_network.vnet.name
# cidrsubnet関数は第一引数のCIDRを分割してくれます
  address_prefixes     = [cidrsubnet(element(azurerm_virtual_network.vnet.address_space, 0), 10, count.index)]
  resource_group_name  = azurerm_resource_group.rg.name
}
```

以下はPlan結果。
```terminal
Terraform will perform the following actions:

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg-test"
    }

  # azurerm_subnet.subnet[0] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.0/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-1"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet[1] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.64/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-2"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet[2] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.128/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-3"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet[3] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.192/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-4"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space       = [
          + "10.0.0.0/16",
        ]
      + dns_servers         = (known after apply)
      + guid                = (known after apply)
      + id                  = (known after apply)
      + location            = "japaneast"
      + name                = "vnet-test-01"
      + resource_group_name = "rg-test"
      + subnet              = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.

```

はい、こんな感じになりますね。
ちょっと内部で利用している関数を説明します。

## cidrsubnet関数
cidrsubnet関数は第一引数のCIDRを分割してくれます。
例えば以下の通り。

```terminal
> cidrsubnet("172.17.0.0/16",10,0)
"172.17.0.0/26"
> cidrsubnet("172.17.0.0/16",10,1)
"172.17.0.64/26"
> cidrsubnet("172.17.0.0/16",10,2)
"172.17.0.128/26"
```
## element関数
element関数は、listから単一の要素を取り出します。
つまり、
`element(azurerm_virtual_network.vnet.address_space, 0)`
は
`azurerm_virtual_network.vnet.address_space[0]`
と同じです。好みによりますが、element関数を使ったほうがスタイリッシュですね。
ちなみにこんな感じにも書けますね。
`one(azurerm_virtual_network.vnet.address_space)`
この場合だったらこれが一番シンプルかもしれません。

## count
`count`はその数字の回数だけ処理を繰り返してくれます。
`count.index`を指定すると、その繰り返した回数を取得できます。
例では、subnet名を変えるのに利用していますね。

`count`はほとんど同じリソースをつくるのであれば`for_each`よりも有効です。
(`for_each`の説明は別の機会に)

# 4つサブネットを名前とIPアドレス範囲のmapにする

サブネット名をキーにしてサブネットのIPアドレスを拾いたいとします。
生成された4つのサブネットを名前とIPアドレスの`map`にしてみましょう。
それにはまずlocal valuesを利用します。

## local values

local valuesはvariableの親戚みたいなものですが、大きな違いは以下の通りです。

1. `local`では関数や変数が利用できる
1. `local`で定義したものは外部から上書きできない
1. 宣言されたモジュール内でのみ使える

例えば、以下のような指定はvariableではできません。

NG例
```terraform
variable "env"{
    type = string
    default = "dev"
}

variable "subnet_name"{
    type = string
    default = "subnet-${var.env}"
}
```

ですが、localならできちゃいます。
```terraform

variable "env"{
    type = string
    default = "dev"
}

locals{
    subnet_name = "subnet-${var.env}"
}
```

local valuesを利用することで、効率化したコードを書くことができます。
便利でなおかつ気持ちがいい！

## サブネット名とアドレスをlist→mapにしてみる

早速local valuesを利用して4つのサブネット名とアドレスをそれぞれリストにし、`zipmap`関数で統合します。`zipmap`関数は特に説明しませんが、2つのlistを合体させてmapにできます。

```terraform
locals {
  subnet_name    = [for s in azurerm_subnet.subnet : s.name]
  subnet_address = [for s in azurerm_subnet.subnet : s.address_prefixes]
  subnet         = zipmap(local.subnet_name, local.subnet_address)
}
output "output_subnet" {
  value = local.subnet
}
```

ここで注意してほしいのは、`azurerm_subnet.subnet.address_prefixes`はもともとlistであること。つまりlist化するとlistの中にlistがあるという構造となってしまいます。つまり以下。

```
Changes to Outputs:
  + output_subnet = {
      + subnet-1 = [
          + "10.0.0.0/26",
        ]
      + subnet-2 = [
          + "10.0.0.64/26",
        ]
      + subnet-3 = [
          + "10.0.0.128/26",
        ]
      + subnet-4 = [
          + "10.0.0.192/26",
        ]
    }
```
これを解決するために flatten関数を使います。

## flatten関数

flatten関数は複数のリストを1つのリストに統合してくれます(フラット化)
入れ子構造になっている場合は有効です。

サンプル
```terminal
> flatten(["a","b"])
[
  "a",
  "b",
]
> flatten(["a","b",["c","d","e"]])
[
  "a",
  "b",
  "c",
  "d",
  "e",
]
```


では以下のように書き換えてみましょう。

```terraform
locals {
  subnet_name    = [for s in azurerm_subnet.subnet : s.name]
  subnet_address = flatten([for s in azurerm_subnet.subnet : s.address_prefixes])
  subnet         = zipmap(local.subnet_name, local.subnet_address)
}
output "output_subnet" {
  value = local.subnet
}
```

すると、以下のようにきれいにmapとなりました。

```terminal
Changes to Outputs:
  + output_subnet = {
      + subnet-1 = "10.0.0.0/26"
      + subnet-2 = "10.0.0.64/26"
      + subnet-3 = "10.0.0.128/26"
      + subnet-4 = "10.0.0.192/26"
    }
```

やったね！


# 参考(公式ドキュメント)
element
https://developer.hashicorp.com/terraform/language/functions/element
cidrsubnet
https://developer.hashicorp.com/terraform/language/functions/cidrsubnets
count
https://developer.hashicorp.com/terraform/language/meta-arguments/count
for_each
https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
locals
https://developer.hashicorp.com/terraform/language/values/locals
zipmap
https://developer.hashicorp.com/terraform/language/functions/zipmap
flatten
https://developer.hashicorp.com/terraform/language/functions/flatten