メインコンテンツにスキップ

型の制約

OpenTofuモジュールの作成者とプロバイダーの開発者は、詳細な型の制約を使用して、ユーザーが入力変数とリソース引数に提供する値を検証できます。これには、OpenTofuの型システムに関する追加の知識が必要ですが、モジュールとリソースのより堅牢なユーザーインターフェースを構築できます。

型キーワードとコンストラクタ

型の制約は、_型キーワード_と_型コンストラクタ_と呼ばれる関数のような構造体の組み合わせを使用して表現されます。

  • 型キーワードは、静的型を表す引用符で囲まれていないシンボルです。
  • 型コンストラクタは、引用符で囲まれていないシンボルとそれに続く一対の括弧です。括弧内には、型に関する詳細情報を指定する引数が含まれています。引数がない場合、型コンストラクタは型を完全に表すものではなく、類似した型の_種類_を表します。

型の制約は、他の種類のOpenTofu に似ていますが、特別な構文です。 OpenTofu言語では、入力変数type引数でのみ有効です。

プリミティブ型

_プリミティブ_型は、他の型から作成されていない単純な型です。 OpenTofuのすべてのプリミティブ型は、型キーワードで表されます。使用可能なプリミティブ型は次のとおりです。

  • string"hello"などのテキストを表すUnicode文字のシーケンス。
  • number:数値。 number型は、15のような整数と、6.283185のような小数値の両方を表すことができます。
  • booltrueまたはfalsebool値は、条件付きロジックで使用できます。

プリミティブ型の変換

OpenTofu言語は、必要に応じて、文字列に数値またはブール値の有効な表現が含まれている限り、number値とbool値をstring値に自動的に変換します。また、その逆も可能です。

  • true"true"に変換され、その逆も同様です。
  • false"false"に変換され、その逆も同様です。
  • 15"15"に変換され、その逆も同様です。

複合型

_複合_型は、複数の値を単一の値にグループ化する型です。複合型は型コンストラクタによって表されますが、そのうちのいくつかは短縮形のキーワードバージョンも持っています。

複合型には、コレクション型(類似した値をグループ化するため)と構造体型(潜在的に異なる値をグループ化するため)の2つのカテゴリがあります。

コレクション型

_コレクション_型では、_1つ_の他の型の複数の値を単一の値としてグループ化できます。コレクション_内_の値の型は、その_要素型_と呼ばれます。すべてのコレクション型には要素型が必要です。これは、コンストラクタへの引数として提供されます。

たとえば、型list(string)は「文字列のリスト」を意味し、これは数値のリストであるlist(number)とは異なる型です。コレクションのすべての要素は、常に同じ型である必要があります。

OpenTofu言語のコレクション型の3種類は次のとおりです。

  • list(...):ゼロから始まる連続した整数で識別される値のシーケンス。

    キーワードlistlist(any)の省略形であり、すべての要素が同じ型である限り、あらゆる要素型を受け入れます。これは、古い構成との互換性のためです。新しいコードでは、完全な形式を使用することをお勧めします。

  • map(...):それぞれが文字列ラベルで識別される値のコレクション。

    キーワードmapmap(any)の省略形であり、すべての要素が同じ型である限り、あらゆる要素型を受け入れます。これは、古い構成との互換性のためです。新しいコードでは、完全な形式を使用することをお勧めします。

    マップは、中括弧()とコロン(:)または等号(=)を使用して作成できます。{ "foo": "bar", "bar": "baz" } または { foo = "bar", bar = "baz" }。キーが数字で始まる場合を除き、キーの引用符は省略できます。その場合は引用符が必要です。単一行マップのキーと値のペアの間には、コンマが必要です。複数行マップでは、キーと値のペアの間に改行があれば十分です。

  • set(...):二次識別子や順序を持たない一意の値のコレクション。

構造体型

構造体型は、複数の異なる型の複数の値を単一の値としてグループ化することを可能にします。構造体型は、どの要素にどの型が許可されるかを指定するために、引数としてスキーマを必要とします。

OpenTofu 言語には、次の 2 種類の構造体型があります。

  • object(...): それぞれが独自の型を持つ名前付き属性のコレクション。

    オブジェクト型のスキーマは、{ <KEY> = <TYPE>, <KEY> = <TYPE>, ... } です。これは、カンマ区切りの <KEY> = <TYPE> のペアを含む中括弧のペアです。オブジェクト型に一致する値には、指定されたすべてのキーが含まれている必要があり、各キーの値は指定された型に一致する必要があります。(追加のキーを持つ値はオブジェクト型に一致する可能性がありますが、追加の属性は型変換中に破棄されます。)

  • tuple(...): ゼロから始まる連続した整数で識別される要素のシーケンスで、各要素は独自の型を持ちます。

    タプル型のスキーマは、[<TYPE>, <TYPE>, ...] です。これは、カンマ区切りの型のリストを含む角括弧のペアです。タプル型に一致する値は、正確に同じ数の要素(それ以上でもそれ以下でもない)を持っている必要があり、各位置の値はその位置に指定された型に一致する必要があります。

例: object({ name=string, age=number }) というオブジェクト型は、次のような値に一致します。

コードブロック
{
name = "John"
age = 52
}

また、object({ id=string, cidr_block=string }) というオブジェクト型は、aws_vpc.example_vpc のような aws_vpc リソースへの参照によって生成されるオブジェクトに一致します。リソースには追加の属性がありますが、型変換中に破棄されます。

最後に、tuple([string, number, bool]) というタプル型は、次のような値に一致します。

コードブロック
["a", 15, true]

複合型リテラル

OpenTofu 言語には、タプル値とオブジェクト値を作成するためのリテラル式があります。これらは、式: リテラル式でそれぞれ「リスト/タプル」リテラルと「マップ/オブジェクト」リテラルとして説明されています。

OpenTofu は、リスト、マップ、またはセットを直接表現する方法は提供していません。ただし、(以下で説明する)複合型の自動変換により、類似した複合型の違いは通常のユーザーにはほとんど関係がなく、OpenTofu ドキュメントのほとんどはリストとタプル、マップとオブジェクトを混同しています。これらの区別は、モジュールまたはリソースの入力値を制限する場合にのみ役立ちます。

複合型の変換

同様の種類の複合型(リスト/タプル/セットとマップ/オブジェクト)は、通常、OpenTofu 言語内で互換的に使用できます。OpenTofu のドキュメントのほとんどは、複合型の種類の違いを無視しています。これは、次の 2 つの変換動作によるものです。

  • 可能な限り、OpenTofu は、提供された値が要求された型と正確に一致しない場合、類似した種類の複合型間で値を変換します。「類似した種類」は次のように定義されます。
    • オブジェクトとマップは類似しています。
      • マップ(またはより大きなオブジェクト)は、オブジェクトスキーマで必要なキーを少なくとも持っていれば、オブジェクトに変換できます。追加の属性は変換中に破棄されます。つまり、マップ -> オブジェクト -> マップの変換は損失を伴う可能性があります。
    • タプルとリストは類似しています。
      • リストは、必要な数の要素を正確に持っている場合にのみタプルに変換できます。
    • セットは、タプルとリストの両方にほぼ類似しています。
      • リストまたはタプルがセットに変換されると、重複する値は破棄され、要素の順序は失われます。
      • set がリストまたはタプルに変換されると、要素は任意の順序になります。セットの要素が文字列の場合、辞書順になります。他の要素型のセットは、要素の特定の順序を保証しません。
  • 可能な限り、OpenTofu は、複合型内の要素値を、複合型の要素を再帰的に変換するか、プリミティブ型の変換で前述したように変換します。

例: モジュール引数に list(string) 型の値が必要で、ユーザーがタプル ["a", 15, true] を提供した場合、OpenTofu は要素を必要な string 要素型に変換することにより、内部で値を ["a", "15", "true"] に変換します。その後、モジュールがこれらの要素を使用して、文字列、数値、およびブール値(それぞれ)を必要とする異なるリソース引数を設定する場合、OpenTofu は、数値とブール値の有効な表現が含まれているため、その時点で 2 番目と 3 番目の文字列を必要な型に自動的に変換します。

一方、提供された値(要素値を含む)が必須の型と互換性がない場合、自動変換は失敗します。引数に map(string) 型が必要で、ユーザーがオブジェクト {name = ["Kristy", "Claudia", "Mary Anne", "Stacey"], age = 12} を提供した場合、タプルは文字列に変換できないため、OpenTofu は型不一致エラーを発生させます。

動的型: 「any」制約

キーワード any は、まだ決定されていない型のプレースホルダーとして機能する特別な構造です。anyそれ自体は型ではありません。any を含む型制約に対して値を解釈する場合、OpenTofu は any キーワードを置き換えて有効な結果を生成できる単一の実際の型を見つけようとします。

any を使用するのが適切な唯一の状況は、コンテンツに直接アクセスすることなく、指定された値を他のシステムに直接渡す場合です。たとえば、次の例に示すように、jsonencode でのみ使用して値全体をリソースに直接渡す場合、型 any の変数を使用しても問題ありません。

コードブロック
variable "settings" {
type = any
}

resource "aws_s3_object" "example" {
# ...

# This is a reasonable use of "any" because this module
# just writes any given data to S3 as JSON, without
# inspecting it further or applying any constraints
# to its type or value.
content = jsonencode(var.settings)
}

モジュールの一部が値の要素または属性にアクセスする場合、または値が文字列、数値、またはその他の不透明でない処理であると予想される場合、any を使用するのは正しくありません。モジュールが期待している正確な型を代わりに記述してください。

コレクション型を持つ any

コレクションのすべての要素は同じ型である必要があるため、コレクションの要素型のプレースホルダーとして any を使用すると、OpenTofu は結果のコレクションに使用する単一の正確な要素型を見つけようとします。

たとえば、型制約 list(any) が与えられた場合、OpenTofu は指定された値を調べて、結果を有効にする any の置き換えを選択しようとします。

指定された値が ["a", "b", "c"](物理型は tuple([string, string, string]))の場合、OpenTofu はこれを次のように分析します。

  • 前のセクションに従って、タプル型とリスト型は類似しているため、タプルからリストへの変換ルールが適用されます。
  • タプルのすべての要素は文字列であるため、型制約 string はすべてのリスト要素に対して有効です。
  • したがって、この場合、any 引数は string に置き換えられ、最終的な具体的な値の型は list(string) になります。

指定されたタプルの要素がすべて同じ型でない場合、OpenTofu はそれらすべてを変換できる単一の型を見つけようとします。OpenTofu は、前のセクションで説明したように、さまざまな変換ルールを検討します。

  • 指定された値が ["a", 1, "b"] であった場合、プリミティブ型の変換ルールにより、OpenTofu は yine de list(string) を選択します。その型制約によって暗示される文字列変換により、結果の値は ["a", "1", "b"] になります。
  • 指定された値が ["a", [], "b"] であった場合、値は型制約に適合できません。文字列と空のタプルの両方を変換できる単一の型はありません。OpenTofu はこの値を拒否し、すべての要素が同じ型である必要があると訴えます。

上記の例では list(any) を使用していますが、同様の原則が map(any)set(any) にも適用されます。

オプションのオブジェクト型属性

OpenTofu は通常、指定されたオブジェクト属性の値を受け取らないとエラーを返します。属性をオプションとしてマークすると、OpenTofu は代わりに欠落している属性のデフォルト値を挿入します。これにより、受信モジュールは適切なフォールバック動作を記述できます。

属性をオプションとしてマークするには、オブジェクト型制約で optional 修飾子を使用します。次の例では、オプションの属性 b とデフォルト値 c を持つオプションの属性を作成します。

コードブロック
variable "with_optional_attribute" {
type = object({
a = string # a required attribute
b = optional(string) # an optional attribute
c = optional(number, 127) # an optional attribute with default value
})
}

optional 修飾子は、1 つまたは 2 つの引数を取ります。

  • 型: (必須)最初の引数は属性の型を指定します。
  • デフォルト: (オプション)2 番目の引数は、属性が存在しない場合に OpenTofu が使用するデフォルト値を定義します。これは属性型と互換性がある必要があります。指定しない場合、OpenTofu は適切な型の null 値をデフォルトとして使用します。

null 以外のデフォルト値を持つオプションの属性は、受信モジュール内で値 null を持つことが保証されません。OpenTofu は、呼び出し元が属性を完全に省略した場合と、呼び出し元が明示的に null に設定した場合の両方でデフォルト値を代用するため、null 値を処理するための追加のチェックが不要になります。

OpenTofu は、ネストされた変数型にオブジェクト属性のデフォルトをトップダウンで適用します。つまり、OpenTofu は、optional 修飾子で指定したデフォルト値を最初に適用し、後でネストされたデフォルト値をその属性に適用します。

例: オプションの属性とデフォルトを持つネストされた構造

次の例では、Web サイトをホストするストレージバケットの変数を定義します。この変数型は、いくつかのオプションの属性を使用します。これには、それ自体がオプションの属性とデフォルトを持つオプションの object 型である website が含まれます。

コードブロック
variable "buckets" {
type = list(object({
name = string
enabled = optional(bool, true)
website = optional(object({
index_document = optional(string, "index.html")
error_document = optional(string, "error.html")
routing_rules = optional(string)
}), {})
}))
}

以下の`terraform.tfvars`ファイルの例では、`var.buckets`に3つのバケット設定を指定しています。

  • `production`は、リダイレクトを追加するためのルーティングルールを設定します。
  • `archived`はデフォルト設定を使用しますが、無効になっています。
  • `docs`は、インデックスドキュメントとエラードキュメントをテキストファイルを使用するようにオーバーライドします。

`production`バケットはインデックスドキュメントとエラードキュメントを指定しておらず、`archived`バケットはウェブサイト設定を完全に省略しています。OpenTofuは、`bucket`型制約で指定されたデフォルト値を使用します。

コードブロック
buckets = [
{
name = "production"
website = {
routing_rules = <<-EOT
[
{
"Condition" = { "KeyPrefixEquals": "img/" },
"Redirect" = { "ReplaceKeyPrefixWith": "images/" }
}
]
EOT
}
},
{
name = "archived"
enabled = false
},
{
name = "docs"
website = {
index_document = "index.txt"
error_document = "error.txt"
}
},
]

この設定は、以下の変数値を生成します。

  • `production`バケットと`docs`バケットの場合、OpenTofuは`enabled`を`true`に設定します。OpenTofuは`website`のデフォルト値も提供し、`docs`で指定された値がそれらのデフォルト値をオーバーライドします。
  • `archived`バケットと`docs`バケットの場合、OpenTofuは`routing_rules`を`null`値に設定します。OpenTofuがオプション属性を受け取らず、指定されたデフォルトがない場合、OpenTofuはそれらの属性に`null`値を設定します。
  • `archived`バケットの場合、OpenTofuは`website`属性に`buckets`型制約で指定されたデフォルト値を設定します。
コードブロック
tolist([
{
"enabled" = true
"name" = "production"
"website" = {
"error_document" = "error.html"
"index_document" = "index.html"
"routing_rules" = <<-EOT
[
{
"Condition" = { "KeyPrefixEquals": "img/" },
"Redirect" = { "ReplaceKeyPrefixWith": "images/" }
}
]

EOT
}
},
{
"enabled" = false
"name" = "archived"
"website" = {
"error_document" = "error.html"
"index_document" = "index.html"
"routing_rules" = tostring(null)
}
},
{
"enabled" = true
"name" = "docs"
"website" = {
"error_document" = "error.txt"
"index_document" = "index.txt"
"routing_rules" = tostring(null)
}
},
])

例: オプション属性を条件付きで設定する

オプション引数の値を設定するかどうかを、他のデータに基づいて動的に決定する必要がある場合があります。その場合、呼び出し元の`module`ブロックは、結果の選択肢の1つとして`null`を持つ条件式を使用して、引数を動的に未設定のままにすることができます。

前のセクションで示した`variable "buckets"`宣言では、次の例では、新しい変数`var.legacy_filenames`に基づいて、`website`オブジェクトの`index_document`と`error_document`の設定を条件付きでオーバーライドします。

コードブロック
variable "legacy_filenames" {
type = bool
default = false
nullable = false
}

module "buckets" {
source = "./modules/buckets"

buckets = [
{
name = "maybe_legacy"
website = {
error_document = var.legacy_filenames ? "ERROR.HTM" : null
index_document = var.legacy_filenames ? "INDEX.HTM" : null
}
},
]
}

`var.legacy_filenames`が`true`に設定されている場合、呼び出しはドキュメントのファイル名をオーバーライドします。`false`の場合、呼び出しは2つのファイル名を未指定のままにし、モジュールが指定されたデフォルト値を使用できるようにします。