TT Lab
はじめる
学ぶ 学習パス コース

Terraform/OpenTofu基礎

型は文書ではなく検問所だ

TT Labで続きを見る

一言でいうと

変数の型は、誤った値を境界で防ぎ、合わせられる値は静かに変換し、宣言にないものは黙って捨てます。この3つを区別する必要があります。

なぜ型を書く必要があるのか

型を書かなくても、設定は動きます。そのため、急いでいるときは省きやすく、省いても、その日は何も起きません。問題は、誤った値が入ってくる日です。

型がなければ、その値は、変数の境界をそのまま通過します。通過した値は、localsを通り、関数を通って、リソースの引数まで流れていき、結局、どこかで爆発します。そのときのメッセージは、「あなたが与えた値が間違っている」ではなく、「この関数呼び出しが失敗した」です。設定ファイルを上から遡って値を追跡してはじめて、原因にたどり着きます。

型を書いておけば、同じミスが次のように防がれます。

Error: Invalid value for input variable
  on v.tfvars line 1:
   1: tight = 7
number required.

行番号が出て、何を期待していたかが出ます。直すのにかかる時間が、まったく違います。

どう動くのか

型は、3つの系統に分かれます。プリミティブ型(string・number・bool)、コレクション型(list・set・map)、構造型(object・tuple)です。そのほかに、何でも受け取るanyがあります。

防ぎます。変換できない値が来たら、その場で拒否します。数値の場所にリストを与えたり、タプルの個数が違ったり、オブジェクトの必須の属性がなかったりすると、プランすら立てられません。

変換します。変換できれば、変換します。tfvarsにreplicas = "3"と書いても、numberの変数には3が入り、debug = "true"も真になります。もっと静かなのは、コレクションの内側です。

variable "tags" { type = map(string) }
# tags = { team = "core", rev = 3, tls = true }
# 결과   { "team" = "core", "rev" = "3", "tls" = "true" }

数値の3が文字列の"3"になったのに、誰も教えてくれません。その値で、あとから算術や等しいかの比較をしようとしたなら、そのときにおかしくなります。

捨てます。構造型には、もう1つの性質があります。オブジェクトは、宣言した属性だけを受け取り、宣言にない属性は、エラーではなく切り捨てです。属性名にタイプミスがあると、その値は消えて、デフォルト値が適用されたように見えます。これが、型の検査が捕まえてくれない代表的な穴です。

リストとセットの違いも、覚えておく価値があります。同じ["b", "a", "b"]を与えても、リストは3つを順番どおりに入れ、セットは重複を捨てて、自分の規則で並べ替えて、2つだけを入れます。長さが変わるので、個数を数えるコードが影響を受けます。

オプション属性は、短い入力を受け取れるようにしてくれます。

variable "service" {
  type = object({
    name = string
    port = optional(number, 8080)
    tls  = optional(bool, false)
  })
}

こうしておけば、使う側はnameだけを与えればよく、残りはデフォルト値が埋まります。デフォルト値を与えなければ、その属性はnullになり、下流で改めて確認する必要があるので、できるだけ一緒に書きます。

nullを受け取らないという宣言は、2つの場合に分かれて動作します。デフォルト値があれば、入ってきたnullがデフォルト値に置き換わり、デフォルト値がなければ、エラーになります。「値がないときに何になるか」を1か所で固定する仕組みです。

最後に、型と値の検査は別のことです。型は形を見て、値の範囲はvalidationブロックが見ます。ポートが数値かどうかは型が見ますが、その数値が1024以上かどうかは、型は知りません。

現場での姿

最もよくあるのは、モジュールの入力をanyで開けておく場合です。使う側が楽なように開けておいたのに、実際に誤って使った人は、モジュールの内側のエラーを見ることになります。モジュールの境界の型は、ドキュメントであり契約なので、狭く書くほうが、みんなにとってよいです。

2つ目は、マップの中で起きた変換を知らずに使った場合です。タグのマップに数値を入れておいて、あとでその値で比較をすると、永遠に偽になります。数値として使う値なら、マップの要素の型を数値として宣言するか、使う場所で変換関数を通します。

3つ目は、オブジェクトのタイプミスです。属性名を1つ間違えて書いたのに、適用は成功し、その値だけがデフォルト値で動作します。レビューでも見つけにくいです。この種のミスを減らすには、出力で実際に入った値を出して確認する習慣が役立ちます。

次のラボですること

8つのステップを進めます。プリミティブ型で何が防がれ、何が変換されるかを確認し、コレクションの内側の静かな変換を出力で覗き、同じ入力をリスト・セット・タプルで受け取ったときの違いを見ます。続けて、オブジェクトの欠落と余分をそれぞれ確認し、オプション属性とデフォルト値で短い入力を受け取り、nullを受け取らないという宣言の2つの場合を見ます。最後の2つのステップでは、型を書かなかったときと、狭く書いたときで、エラーがどこで出るかを並べて比較し、入れ子の型で入力の規格を固定して、採点ツールが入れる誤った入力が拒否されるかを確認します。