- OpenTofu言語
- 式
- カスタム条件
カスタム条件
設定内のいくつかの種類のオブジェクトに対して、カスタムエラーメッセージを生成する条件を作成できます。たとえば、入力変数に条件を追加して、受信したイメージIDの形式が適切かどうかを確認できます。カスタム条件は仮定を捉えることで、将来の保守者が設定の設計と意図を理解するのに役立ちます。また、早期かつコンテキスト内で有用なエラー情報を返すため、コンシューマが設定の問題をより簡単に診断するのに役立ちます。
このページでは、以下について説明します。
- インフラストラクチャ全体を検証するためのアサーションを使用したチェックの作成
- 入力変数のための検証条件の作成
- リソース、データソース、出力のための事前条件と事後条件の作成
- 効果的な条件式とエラーメッセージの作成
- 計画と適用サイクル中にOpenTofuがカスタム条件を評価するタイミング
ユースケースに合わせたカスタム条件の選択
OpenTofuのさまざまなカスタム条件は、さまざまな状況に最適です。次の広範なガイドラインを使用して、ユースケースに最適なカスタム条件を選択してください。
- アサーションを使用したチェックブロックは、インフラストラクチャ全体を検証します。さらに、チェックブロックはOpenTofu操作の全体的な実行を妨げたり、ブロックしたりしません。
- 検証条件または出力事後条件を使用すると、設定の入力と出力が特定の要件を満たしていることを確認できます。
- リソースの事前条件と事後条件を使用すると、OpenTofuが予測可能な結果で設定を生成することを検証できます。
特定のカスタム条件を使用するタイミングの詳細については、事前条件と事後条件の選択とチェックまたはその他のカスタム条件の選択を参照してください。
入力変数の検証
カスタム条件を指定するには、variable
ブロック内に1つ以上のvalidation
ブロックを追加します。各検証にはcondition
引数が必要です。これは、値が有効な場合はtrue
を、無効な場合はfalse
を返す必要がある式です。この式は、含まれている変数のみを参照でき、エラーを生成できません。
条件がfalse
と評価された場合、OpenTofuはerror_message
式の結果を含むエラーメッセージを生成します。複数の検証を宣言した場合、OpenTofuは失敗したすべての条件のエラーメッセージを返します。
次の例では、AMI IDに有効な構文があるかどうかを確認します。
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
式の失敗によって検証の決定がなされる場合は、次の例に示すようにcan
関数を使用します。
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
事前条件と事後条件
precondition
ブロックとpostcondition
ブロックを使用して、リソース、データソース、出力のカスタムルールを作成します。
OpenTofuは、関連付けられているオブジェクトを評価する前に事前条件をチェックし、オブジェクトを評価した後に事後条件をチェックします。OpenTofuは、できるだけ早くカスタム条件を評価しますが、未知の値に依存する条件は、適用フェーズまで延期する必要があります。詳細については、適用時のみチェックされる条件を参照してください。
使用方法
各事前条件と事後条件にはcondition
引数が必要です。これは、条件が満たされた場合はtrue
を、無効な場合はfalse
を返す必要がある式です。この式は、同じモジュール内の他のオブジェクトを参照できますが、参照によって循環依存関係が作成されないようにする必要があります。リソースの事後条件では、self
オブジェクトを使用して、それらが設定されているリソースの各インスタンスの属性を参照することもできます。
条件がfalse
と評価された場合、OpenTofuはerror_message
式の結果を含むエラーメッセージを生成します。複数の事前条件または事後条件を宣言した場合、OpenTofuは失敗したすべての条件のエラーメッセージを返します。
以下の例では、事後条件を使用して、呼び出し元が誤ったシステムコンポーネントを対象としたAMIを誤って提供していないかどうかを検出します。
data "aws_ami" "example" {
id = var.aws_ami_id
lifecycle {
# The AMI ID must refer to an existing AMI that has the tag "nomad-server".
postcondition {
condition = self.tags["Component"] == "nomad-server"
error_message = "tags[\"Component\"] must be \"nomad-server\"."
}
}
}
リソースとデータソース
resource
ブロックまたはdata
ブロック内のlifecycle
ブロックには、precondition
ブロックとpostcondition
ブロックの両方を指定できます。
- OpenTofuは、既存の
count
引数とfor_each
引数を評価した後にprecondition
ブロックを評価します。これにより、OpenTofuは各インスタンスに対して個別に事前条件を評価し、each.key
、count.index
などをこれらの条件で使用できるようにします。OpenTofuは、リソースの構成引数を評価する前にも事前条件を評価します。事前条件は、引数の評価エラーよりも優先される場合があります。 - OpenTofuは、管理対象リソースへの変更の計画と適用後、またはデータソースからの読み込み後に、
postcondition
ブロックを評価します。事後条件の失敗は、失敗したリソースに依存する他のリソースへの変更を妨げます。
ほとんどの場合、同じオブジェクトを表すdata
ブロックとresource
ブロックの両方を同じ構成に含めることはお勧めしません。そうすると、OpenTofuがdata
ブロックの結果がresource
ブロックの変更の影響を受ける可能性があることを理解できなくなる可能性があります。ただし、リソース自体が直接エクスポートしないresource
ブロックの結果を確認する必要がある場合は、data
ブロックをdata
ブロックの直接的なpostcondition
として配置する限り、そのオブジェクトを安全に確認するためにdata
ブロックを使用できます。これにより、OpenTofuはdata
ブロックが他の場所で定義されたオブジェクトのチェックとして機能していることを理解し、正しい順序でアクションを実行できます。
出力
output
ブロックには、precondition
ブロックを含めることができます。
事前条件は、入力変数のvalidation
ブロックと対称的な目的を果たすことができます。入力変数の検証では、モジュールが入力について行う仮定をチェックするのに対し、事前条件では、モジュールが出力について行う保証をチェックします。事前条件を使用して、OpenTofuが状態に無効な新しい出力値を保存するのを防ぐことができます。また、該当する場合は、以前の適用からの有効な出力値を保持するためにも使用できます。
OpenTofuは、結果を確定するためにvalue
式を評価する前に、出力値の事前条件を評価します。事前条件は、value
式における潜在的なエラーよりも優先される場合があります。
例
次の例は、事前条件と事後条件の使用例を示しています。事前条件と事後条件は、以下の仮定と保証を宣言しています。
-
**AMI IDは、
x86_64
アーキテクチャのオペレーティングシステムを含むAMIを参照する必要があります。**事前条件は、呼び出し元が誤って異なるアーキテクチャ用のAMIを作成した場合(この仮想マシンがホストすることを目的としたソフトウェアを実行できない可能性があります)を検出します。 -
**EC2インスタンスには、パブリックDNSホスト名が割り当てられている必要があります。**Amazon Web Servicesでは、EC2インスタンスには、特定の方法で構成された仮想ネットワークに属している場合にのみ、パブリックDNSホスト名が割り当てられます。事後条件は、選択した仮想ネットワークが正しく構成されていない場合に検出し、ユーザーにネットワーク設定のデバッグを促します。
-
**EC2インスタンスには、暗号化されたルートボリュームがあります。**事前条件は、ルートボリュームが暗号化されていることを保証します。これは、このEC2インスタンスで実行されているソフトウェアは、暗号化されていないボリュームでもおそらく期待通りに動作するとしてもです。これにより、OpenTofuは、他のコンポーネントが新しいEC2インスタンスに依存する前に、すぐにエラーを生成できます。
data "aws_ami" "example" {
owners = ["amazon"]
filter {
name = "image-id"
values = ["ami-abc123"]
}
}
resource "aws_instance" "example" {
instance_type = "t3.micro"
ami = data.aws_ami.example.id
lifecycle {
# The AMI ID must refer to an AMI that contains an operating system
# for the `x86_64` architecture.
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must be for the x86_64 architecture."
}
# The EC2 instance must be allocated a public DNS hostname.
postcondition {
condition = self.public_dns != ""
error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled."
}
}
}
data "aws_ebs_volume" "example" {
# Use data resources that refer to other resources to
# load extra data that isn't directly exported by a resource.
#
# Read the details about the root storage volume for the EC2 instance
# declared by aws_instance.example, using the exported ID.
filter {
name = "volume-id"
values = [aws_instance.example.root_block_device.volume_id]
}
# Whenever a data resource is verifying the result of a managed resource
# declared in the same configuration, you MUST write the checks as
# postconditions of the data resource. This ensures OpenTofu will wait
# to read the data resource until after any changes to the managed resource
# have completed.
lifecycle {
# The EC2 instance will have an encrypted root volume.
postcondition {
condition = self.encrypted
error_message = "The server's root volume is not encrypted."
}
}
}
output "api_base_url" {
value = "https://${aws_instance.example.private_dns}:8433/"
}
事前条件と事後条件の選択
検証チェックは、データを生成するリソースの事後条件として、またはデータを使用してリソースまたは出力値の事前条件として実装できることがよくあります。どちらが最も適切かを決定するには、チェックが仮定と保証のどちらを表しているかを検討してください。
仮定には事前条件を使用する
仮定とは、特定のリソースの構成を使用可能にするために真である必要がある条件です。たとえば、aws_instance
構成には、指定されたAMIが常にx86_64
CPUアーキテクチャ用に構成されているという仮定を設定できます。
将来の保守担当者が、その条件に依存する他の式に近い場所でそれらを見つけることができるように、仮定には事前条件を使用することをお勧めします。これにより、そのリソースが許可することを意図している内容について、より深く理解できます。
保証には事後条件を使用する
保証とは、構成の残りの部分が依存できるオブジェクトの特性または動作です。たとえば、aws_instance
構成には、EC2インスタンスがプライベートDNSレコードを割り当てるネットワークで実行されるという保証を設定できます。
将来の保守担当者が、それらの保証を実装する責任があるリソース構成に近い場所でそれらを見つけることができるように、保証には事後条件を使用することをお勧めします。これにより、構成を変更する際に、どの動作を保持する必要があるかをより簡単に判断できます。
追加の決定要因
事前条件と事後条件を作成する際には、次の点も考慮する必要があります。
- エラーメッセージで報告するのに最も役立つリソースまたは出力値はどれですか?OpenTofuは、常に条件が宣言された場所でエラーを報告します。
- どちらのアプローチがより便利ですか?特定のリソースに、すべてそのリソースに関する仮定を行う多くの依存関係がある場合、それらを依存関係ごとに複数回事前条件として宣言するのではなく、リソースの事後条件として一度宣言することが実際的です。
- 同じ条件または類似の条件を事前条件と事後条件の両方として宣言することは役に立ちますか?これは、事後条件が事前条件とは異なるモジュールにある場合に役立ちます。これは、モジュールが独立して進化する際に互いに検証できるようになるためです。
アサーションを使用したチェック
チェックブロックは、通常の資源ライフサイクルの外側でインフラストラクチャを検証できます。計画と適用ステージの最後に実行され、インフラストラクチャ内の問題を通知するための警告を生成するassert
ブロックを使用して、カスタム条件を追加できます。
カスタム条件を検証するには、check
ブロック内に1つ以上のassert
ブロックを追加できます。各アサーションには、condition
引数が必要です。これは、意図した仮定または保証が満たされている場合はtrue
、満たされていない場合はfalse
を返すブール式です。condition
式は、周囲のcheck
ブロックで使用可能なリソース、データソース、または変数を参照できます。
次の例では、アサーションを含むチェックブロックを使用して、OpenTofu Webサイトが正常に動作していることを検証しています。
check "health_check" {
data "http" "opentofu_org" {
url = "https://www.opentofu.org"
}
assert {
condition = data.http.opentofu_org.status_code == 200
error_message = "${data.http.opentofu_org.url} returned an unhealthy status code"
}
}
条件がfalse
と評価された場合、OpenTofuはerror_message
式の結果を含むエラーメッセージを生成します。複数のアサーションを宣言した場合、OpenTofuは失敗したすべての条件に対してエラーメッセージを返します。
クラウドバックエンドでの継続的な検証
クラウドバックエンドは、OpenTofuがインフラストラクチャをプロビジョニングした後も、ワークスペースの構成内のチェックが引き続きパスすることを自動的にチェックできます。たとえば、APIゲートウェイ証明書の有効性を継続的に監視するcheck
を作成できます。継続的な検証により、条件が失敗したときに警告が表示されるため、証明書を更新して、次回インフラストラクチャを更新しようとしたときにエラーを回避できます。
条件式
チェックアサーション、入力変数の検証、事前条件、事後条件にはすべて、condition
引数が必要です。これは、意図した仮定または保証が満たされている場合はtrue
、満たされていない場合はfalse
を返すブール式です。
式が有効でブール値の結果を返す限り、条件ではOpenTofuの組み込み関数または言語演算子のいずれかを使用できます。条件式を作成する際には、次の言語機能が特に役立ちます。
論理演算子
複数の条件を組み合わせるには、論理演算子&&
(AND)、||
(OR)、!
(NOT)を使用します。
condition = var.name != "" && lower(var.name) == var.name
算術演算子(例:a + b
)、等価演算子(例:a == b
)、比較演算子(例:a < b
)も使用できます。詳細については、算術演算子と論理演算子を参照してください。
contains
関数
contains
関数を使用して、指定された値が事前に定義された有効値のセットのいずれかであるかどうかをテストします。
condition = contains(["STAGE", "PROD"], var.environment)
length
関数
length
関数を使用して、コレクションの長さをテストし、空ではないリストまたはマップを要求します。
condition = length(var.items) != 0
これは、==
または!=
を使用して別のコレクションと直接比較するよりも優れたアプローチです。これは、比較演算子は、両方のオペランドがまったく同じ型である場合にのみtrue
を返すことができ、空のコレクションではしばしば曖昧であるためです。
for
式
for
式と関数alltrue
およびanytrue
を組み合わせて、条件がコレクションのすべての要素または任意の要素に当てはまるかどうかをテストします。
condition = alltrue([
for v in var.instances : contains(["t2.micro", "m3.medium"], v.type)
])
can
関数
can
関数を使用して、式の有効性を条件として簡潔に使用します。指定された式が正常に評価された場合はtrue
、エラーを返した場合はfalse
を返すため、通常はエラーを返す他のさまざまな関数を条件式の一部として使用できます。
たとえば、can
をregex
と組み合わせて、文字列が特定のパターンに一致するかどうかをテストできます。これは、regex
が一致しない文字列が与えられた場合にエラーを返すためです。
condition = can(regex("^[a-z]+$", var.name))
型変換関数を使用して、値を型または型制約に変換できるかどうかをテストするためにもcan
を使用できます。
# This remote output value must have a value that can
# be used as a string, which includes strings themselves
# but also allows numbers and boolean values.
condition = can(tostring(data.terraform_remote_state.example.outputs["name"]))
# This remote output value must be convertible to a list
# type of with element type.
condition = can(tolist(data.terraform_remote_state.example.outputs["items"]))
属性アクセスまたはインデックス演算子を使用して、コレクションまたは構造値に特定の要素またはインデックスがあるかどうかをテストするためにもcan
を使用できます。
# var.example must have an attribute named "foo"
condition = can(var.example.foo)
# var.example must be a sequence with at least one element
condition = can(var.example[0])
# (although it would typically be clearer to write this as a
# test like length(var.example) > 0 to better represent the
# intent of the condition.)
self
オブジェクト
事後条件ブロックでは、self
オブジェクトを使用して、評価対象のインスタンスの属性を参照します。
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "ami-abc123"
lifecycle {
postcondition {
condition = self.instance_state == "running"
error_message = "EC2 instance must be running."
}
}
}
each
オブジェクトとcount
オブジェクト
for_each
またはcount
が設定されているブロックでは、each
オブジェクトとcount
オブジェクトを使用して、チェーンで展開される他のリソースを参照します。
variable "vpc_cidrs" {
type = set(string)
}
data "aws_vpc" "example" {
for_each = var.vpc_cidrs
filter {
name = "cidr"
values = [each.key]
}
}
resource "aws_internet_gateway" "example" {
for_each = data.aws_vpc.example
vpc_id = each.value.id
lifecycle {
precondition {
condition = data.aws_vpc.example[each.key].state == "available"
error_message = "VPC ${each.key} must be available."
}
}
}
エラーメッセージ
入力変数の検証、前提条件、および事後条件にはすべて、error_message
引数を指定する必要があります。これには、OpenTofuが条件を満たしていないことを検出した場合に、エラーメッセージの一部として含めるテキストが含まれます。
Error: Resource postcondition failed
with data.aws_ami.example,
on ec2.tf line 19, in data "aws_ami" "example":
72: condition = self.tags["Component"] == "nomad-server"
|----------------
| self.tags["Component"] is "consul-server"
The selected AMI must be tagged with the Component value "nomad-server".
error_message
引数には、文字列として評価される任意の式を指定できます。これには、リテラル文字列、heredoc、およびテンプレート式が含まれます。null
、list
、またはmap
型のアイテムをフォーマットされた文字列に変換するには、format
関数を使用できます。複数行のエラーメッセージがサポートされており、先頭に空白のある行は折り返されません。
エラーメッセージは、OpenTofu自身のエラーメッセージと同様のスタイルで、1つ以上の完全な文で記述することをお勧めします。OpenTofuは、問題を検出したリソースの名前と、条件式に含まれる外部値と共にメッセージを表示します。
適用時のみチェックされる条件
OpenTofuは、カスタム条件をできるだけ早く評価します。
入力変数の検証は変数値のみを参照できるため、OpenTofuは常にそれらをただちに評価します。チェックアサーション、前提条件、および事後条件は、条件に関連付けられた値が構成の適用前または適用後に既知であるかどうかをOpenTofuが評価するかに依存します。
- 適用前に既知: OpenTofuは、計画フェーズ中に条件をチェックします。たとえば、別のリソースから生成されていない限り、OpenTofuは計画中にイメージIDの値を知ることができます。
- 適用後に既知: OpenTofuは、適用フェーズまで条件のチェックを遅延させます。たとえば、AWSはEC2インスタンスの起動時にルートボリュームIDのみを割り当てるため、OpenTofuは適用時までこの値を知ることができません。
適用フェーズ中に、失敗した前提条件は、関連するリソースに対する計画されたアクションの実装をOpenTofuが実行するのを妨げます。ただし、失敗した事後条件は、OpenTofuがこれらのアクションを既に実装した後に処理を停止します。失敗した事後条件は、リソースに依存するそれ以降の下流のアクションを防止しますが、OpenTofuが既に実行したアクションを元に戻すことはありません。
OpenTofuは、完全な構成の最初の作成時よりも、後続の変更を適用する際に通常、情報が少ないです。したがって、OpenTofuは、最初の作成時には適用時に条件をチェックし、後続の更新時には計画時に条件をチェックする場合があります。