入力バリデーションの別解を書いてみたよ

# tl;dr

- この記事は[terraform Advent Calendar 2023](https://qiita.com/advent-calendar/2023/terraform)の13日目です
- ちょうどいい感じの記事（[弊社で使っているAzureリソースのスルメ系命名規則を紹介します](https://zenn.dev/aeonpeople/articles/0b4a4be83d0dfd)）を拝見した
- ↑の記事のとおり、input で validation するのが常に正です。だいたい。
- ただまぁできるので別解でも書いてみようかなと
- こんな風にも書けるのよ、程度の別解です

# input variables の残念なところ

- input ブロック内では他の variables や locals を参照できない
    - 以前から要望はあるけどね。。。
    - [Allow reference to locals in (experimental) variable validation · Issue #24187 · hashicorp/terraform](https://github.com/hashicorp/terraform/issues/24187)
- ゆえに、必要な条件、取り得る値などはベタ書きする必要がある
- なので、条件の追加変更削除時には、それらテキストも同時に修正する必要がある
- そして、許可しない値の時は通らない（通さないのが正だからいいんだけどね）

# 以前の（バリデーションの）代替方式

- locals 等で file() なんかを使ってエラーにさせる（今でも動くけどあんまりよくないよね。file() の引数エラー扱いだし。エラーメッセージ部分はちゃんと出力されるけど用途が違う）

```hcl
locals {
   env_id_validation = var.env_id == "d" || var.env_id == "p" 
        ? var.env_id 
        : file("エラーメッセージ")
}

```

- null_resource 等で lifecycle の precondition, postcondition を使用する
- 今なら [terraform_data](https://developer.hashicorp.com/terraform/language/resources/terraform-data) が使えるかな
- 好みの問題かもしれないけど、バリデーションのためにリソースつけたくないかなって


# こんな条件の時は違うやり方ができる

- バリデーションエラーでも通したい（一時的にとか、整備前とか）ワーニングが出ればいいなどの場合
- 入力値の説明を input block に書かなくてもいい（ドキュメント化処理で取り込まれなくてもいい場合）
- 要は手抜きで構わない
- あと、チェックに変数使いたい（条件の追加変更削除で楽したい。もしくはDRYにしたい）

# そんなあなたに別解

※変数名とチェックの条件と取り得る値は元の記事から拝借しました

```hcl:variables.tf

variable "env_id" {
  description = "One-letter of environment ID."
  type        = string
  default     = "d"
  // デフォルトはあってもなくてもOK
}

locals {
  allow_env_ids = {
    "d" = "Development",
    "t" = "Test",
    "s" = "Staging",
    "p" = "Production",
    "g" = "General",
    "b" = "Sandbox",
  }
}

check "env_id" {
  assert {
    condition = contains(keys(local.allow_env_ids), var.env_id)
    error_message = join("", [
      "The env_id value must be: ",
      join(", ", keys(local.allow_env_ids)),
      // output の例で差し替え可
    ])
  }
}

// 以上３つでセットにしておくと追加変更削除があった時に楽かなって。
// 以下 output はおまけ

output "env_id" {
  description = "現在の env_id"
  value       = var.env_id
}

output "examples_error_message_001" {
  description = "ダブルクォーテーションで囲む"
  value       = "\"${join("\", \"", keys(local.allow_env_ids))}\""
  // きれいに見たい（エスケープが邪魔だなって）時は
  // terraform output -raw examples_error_message_001
}

output "examples_error_message_002" {
  description = "丸かっこのほうが見やすいかも（そうでもないか）"
  value       = "(${join("), (", keys(local.allow_env_ids))})"
}

output "examples_error_message_003" {
  description = "一般的には角括弧かな"
  value       = "[${join("], [", keys(local.allow_env_ids))}]"
}

output "examples_error_message_004" {
  description = "Value を表示したいなら"
  value       = join(", ", values(local.allow_env_ids))
}

output "examples_error_message_005" {
  description = "key と value と両方表示したいなら"
  value = join(", ", flatten([
    for key, value in local.allow_env_ids :
    "${key}:${value}"
  ]))
}

```

- このまま動くのでディレクトリにでも放り込んで試してみて（このファイルだけで動く）
- locals block は何回でも書けるので、このような順番で３つをセットにして扱うとわかりやすくない？
- ちなみに input でのチェックと違って plan 後にチェックされるし、ワーニングにしかならないので実戦投入する時は要件に注意
- いろいろ遊んでみるといいと思う

# 最後に

- ご利用は自己責任でお願いします
- 別解を考えるの楽しいよね（アドカレにあと２日？くらい空きがあるのでどなたか！）
- ちなみにイオンのエンジニアさんってやっぱイオンピープルなの？ｗ