Terraformで使った黒魔術2021

こんにちは。
今年も私はTerraformをひたすら書いていました。毎週どこかでは必ず触り、ほぼ1人で基盤を作り込んだくらい、クラウドというかTerraformにどっぷりつかり込んだ1年でした。そのおかげもあり、かなり理解は深まったり、会社の技術ブログでも[ちょっとアツ目な記事](https://future-architect.github.io/articles/20211029a/)を書きました。
ただ、とはいえ、100%きれいに書けた自信はないですし、明らかに汚くなってしまった書き方もあるので、今回はそれらを供養する意味を込めてこちらを書きます。
いろいろ行き詰まった時にこの記事を読んでいただければ幸いです。

## countとfor_eachの合わせ技
複数リソースを使うケースとして、countとfor_eachがあり、さらにfor_eachはリソースないで特定のブロックを複製したりできる便利なものですが、やむなくこの2つを組み合わせないといけない時があり、以下のコードを書きました

```terraform
locals {
  env = "stg"
  ip_list = [
    "xx.xx.xx.xx/",
    "xx.xx.xx.xx/",
    "xx.xx.xx.xx/",
    "xx.xx.xx.xx/",
    "xx.xx.xx.xx/",
    ....
  ]

  ingress_selfs = [
    ["0", "0", "-1"]
  ]
  ingress_cidrs = [
    ["8080", "8080", "tcp", flatten(concat(local.ip_list))],
  ]
  ingress_security_groups = [
    ["8080", "8080", "tcp", flatten([
      aws_security_group.this.id,
      aws_security_group.hoge.id,
      aws_security_group.huga.id,])
    ]
  ]
}

resource "aws_security_group" "this" {
  count       = floor((length(local.ip_list) / 50) + 1)
  name        = format("sample-sg0%d", count.index + 1)
  description = format("sample-sg0%d", count.index + 1)
  vpc_id      = aws_vpc.this.id
  tags        = local.tags

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  dynamic "ingress" {
    for_each = local.ingress_cidrs
    content {
      from_port   = ingress.value[0]
      to_port     = ingress.value[1]
      protocol    = ingress.value[2]
      cidr_blocks = element(chunklist(ingress.value[3], 40), count.index)
    }
  }

  dynamic "ingress" {
    for_each = count.index == 0 ? local.ingress_security_groups : []
    content {
      from_port       = ingress.value[0]
      to_port         = ingress.value[1]
      protocol        = ingress.value[2]
      security_groups = ingress.value[3]
    }
  }

  dynamic "ingress" {
    for_each = count.index == 0 ? local.ingress_selfs : []
    content {
      description = ""
      from_port   = ingress.value[0]
      to_port     = ingress.value[1]
      protocol    = ingress.value[2]
      self        = true
    }
  }
}
```
AWSのセキュリティグループは、デフォルトでは1つに対して、60の別のセキュリティグループやCIDRなどのルールをアタッチできますが、上記のように`ip_list`などでリスト更新をした場合、頭打ちになるとエラーになってしまいます。それを回避するための策として捻り出したコードが上のものです。少し細かくみます。

### floor関数
```terraform
count       = floor((length(local.ip_list) / 50) + 1)
```
Terraformには`floor`と`ceil`という真逆のことをしてくれる関数があり、`floor`は小数点以下を切り捨て、`ceil`は切り上げしてくれます。
滅多に使う機会がないですが、ことに上記のようなパターンでは登場させると便利です。
（今回の場合ではceilではなくてfloorにしている理由がありましたが、記憶を探っている途中）

### dynamicブロック
```terraform
  dynamic "ingress" {
    for_each = count.index == 0 ? local.ingress_security_groups : []
    content {
      from_port       = ingress.value[0]
      to_port         = ingress.value[1]
      protocol        = ingress.value[2]
      security_groups = ingress.value[3]
    }
  }
```
dynamicブロックは、同一のリソースで同じブロックを利用する時に、コードを無意に長くしない、ハードコードを軽減できるなどいいことがたくさんあります。そのdynamicブロックで使うfor_eachの中にcountを仕込みました。
2つ以上countで作るときには、2つ目以降は`ip_list`以外のブロックを受け付けないようにするために、`count.index == 0`を仕込んで、最初のリソースのみに反映するようにしています。

## 終わりに
今年、書いてみて、一番複雑にしてしまったコードを懺悔の意味も込めて書きました。
小ネタみたいな感じでしたが、変数だけでリソースを動かせるというところでは比較的堅牢なコードでもあると思うので、誰かの役に立てば幸いです。