Пользовательские условия
Вы можете создавать условия, которые выводят пользовательские сообщения об ошибках для разных типов объектов в конфигурации. Например, можно добавить условие для входной переменной, проверяющее правильность формата поступающих идентификаторов образов. Пользовательские условия позволяют зафиксировать предположения, чтобы будущим сопровождающим было проще понять замысел и структуру конфигурации. Они также помогают раньше и в соответствующем контексте получать полезную информацию об ошибках, благодаря чему пользователям проще диагностировать проблемы в своих конфигурациях.
На этой странице рассматриваются следующие темы:
- Создание проверок с помощью утверждений для проверки инфраструктуры в целом
- Создание условий валидации входных переменных
- Создание предварительных и последующих условий для ресурсов, источников данных и выходных значений
- Создание эффективных выражений условий и сообщений об ошибках
- Когда OpenTofu проверяет пользовательские условия во время циклов планирования и применения
Выбор пользовательского условия для вашего случая
Разные пользовательские условия OpenTofu лучше всего подходят для разных ситуаций. При выборе подходящего пользовательского условия руководствуйтесь следующими общими рекомендациями:
- Блоки check с утверждениями проверяют инфраструктуру в целом. Кроме того, блоки check не препятствуют выполнению операций OpenTofu и не блокируют его.
- Условия валидации или последующие условия выходных значений позволяют гарантировать, что входные и выходные данные конфигурации соответствуют определённым требованиям.
- Предварительные и последующие условия ресурсов позволяют проверять, что OpenTofu создаёт конфигурацию с предсказуемыми результатами.
Дополнительную информацию о том, когда использовать те или иные пользовательские условия, см. в разделах Выбор между предварительными и последующими условиями и Выбор проверок или других пользовательских условий.
Валидация входных переменных
Добавьте один или несколько блоков validation в блок variable, чтобы задать пользовательские условия. Для каждой проверки требуется аргумент condition — выражение, которое должно использовать значение переменной и возвращать true, если значение допустимо, или false, если оно недопустимо. Выражение может ссылаться на переменные, локальные значения, ресурсы и т. д. Если во время validate/plan значение переменной неизвестно, проверка будет отложена до тех пор, пока значение не станет известно на этапе apply.
Если условие принимает значение false, OpenTofu выводит сообщение об ошибке, которое включает результат выражения error_message. Если вы объявите несколько проверок, OpenTofu выведет сообщения об ошибках для всех невыполненных условий.
В следующем примере проверяется, правильно ли задан синтаксис идентификатора AMI.
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 проверяет пользовательские условия как можно раньше, но должен отложить проверку условий, зависящих от неизвестных значений, до этапа apply. Дополнительные сведения см. в разделе Условия, проверяемые только на этапе apply.
Использование
Для каждого предварительного и последующего условия требуется аргумент 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\"."
}
}
}Ресурсы и источники данных
Блок lifecycle внутри блока resource или data может содержать блоки precondition и postcondition.
- OpenTofu проверяет блоки
preconditionпосле вычисления существующих аргументовcountиfor_each. Благодаря этому OpenTofu может отдельно проверять предварительное условие для каждого экземпляра, а затем предоставлять этим условиям доступ кeach.key,count.indexи т. д. OpenTofu также проверяет предварительные условия до вычисления аргументов конфигурации ресурса. Предварительные условия имеют приоритет над ошибками вычисления аргументов. - OpenTofu проверяет блоки
postconditionпосле планирования и применения изменений к управляемому ресурсу или после чтения источника данных. Ошибки последующих условий препятствуют изменению других ресурсов, зависящих от ресурса с ошибкой.
В большинстве случаев мы не рекомендуем включать в одну конфигурацию блоки data и resource, которые представляют один и тот же объект. Это может помешать OpenTofu понять, что результат блока data может зависеть от изменений в блоке resource. Однако, если нужно проверить результат блока resource, который сам ресурс напрямую не экспортирует, можно безопасно использовать блок data для проверки этого объекта, если разместить проверку непосредственно внутри блока postcondition блока data. Это указывает OpenTofu, что блок data проверяет объект, определённый в другом месте, и позволяет OpenTofu выполнять действия в правильном порядке.
Выходные значения
Блок output может содержать блок precondition.
Предварительные условия могут выполнять функцию, симметричную блокам validation валидации входных переменных. Валидация входных переменных проверяет предположения модуля относительно входных данных, а предварительные условия проверяют гарантии модуля относительно выходных данных. С помощью предварительных условий можно запретить OpenTofu сохранять в состоянии недопустимое новое выходное значение. При необходимости их также можно использовать, чтобы сохранить допустимое выходное значение из предыдущего применения.
OpenTofu проверяет предварительные условия выходного значения до вычисления выражения value для окончательного формирования результата. Предварительные условия имеют приоритет над возможными ошибками в выражении value.
Примеры
В следующем примере показаны варианты использования предварительных и последующих условий. Предварительные и последующие условия задают следующие предположения и гарантии.
-
Идентификатор AMI должен указывать на AMI с операционной системой для архитектуры
x86_64. Предварительное условие обнаружит, если вызывающий модуль случайно создал AMI для другой архитектуры, которая может оказаться несовместимой с программным обеспечением, для размещения которого предназначена эта виртуальная машина. -
Экземпляру EC2 должно быть назначено общедоступное DNS-имя хоста. В Amazon Web Services общедоступные DNS-имена хостов назначаются экземплярам EC2, только если они принадлежат виртуальной сети, настроенной определённым образом. Последующее условие обнаружит, если выбранная виртуальная сеть настроена неправильно, и предложит пользователю проверить параметры сети.
-
Корневой том экземпляра 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.
Мы рекомендуем использовать предварительные условия для предположений, чтобы будущие сопровождающие могли найти их рядом с другими выражениями, зависящими от этого условия. Так им будет проще понять, для чего предназначен этот ресурс.
Используйте последующие условия для гарантий
Гарантия — это характеристика или поведение объекта, на которые должна иметь возможность полагаться остальная конфигурация. Например, конфигурация aws_instance может гарантировать, что экземпляр EC2 будет работать в сети, назначающей ему частную DNS-запись.
Мы рекомендуем использовать последующие условия для гарантий, чтобы будущие сопровождающие могли найти их рядом с конфигурацией ресурса, отвечающей за выполнение этих гарантий. Так им будет проще определить, какое поведение следует сохранить при изменении конфигурации.
Дополнительные факторы выбора
При создании предварительных и последующих условий также следует учитывать следующие вопросы.
- Какой ресурс или выходное значение будет полезнее всего указать в сообщении об ошибке? OpenTofu всегда сообщает об ошибках в месте, где было объявлено условие.
- Какой подход удобнее? Если у определённого ресурса много зависимостей, каждая из которых предполагает определённое поведение этого ресурса, практичнее объявить это предположение один раз как последующее условие ресурса, а не повторять его как предварительное условие для каждой зависимости.
- Полезно ли объявить одинаковые или похожие условия одновременно как предварительные и последующие? Это может пригодиться, если последующее условие находится в другом модуле, чем предварительное: тогда модули смогут проверять друг друга по мере независимого развития.
Проверки с утверждениями
Блоки check позволяют проверять инфраструктуру вне обычного жизненного цикла ресурсов. Можно добавлять пользовательские условия в блоках assert, которые выполняются в конце этапов планирования и применения и выводят предупреждения о проблемах в инфраструктуре.
Добавьте один или несколько блоков assert в блок check, чтобы проверять пользовательские условия. Для каждого утверждения требуется аргумент condition — логическое выражение, которое должно возвращать true, если предполагаемое условие или гарантия выполнены, либо false, если они не выполнены. Выражение condition может ссылаться на любые ресурсы, источники данных или переменные, доступные в окружающем блоке check.
В следующем примере блок check с утверждением используется для проверки работоспособности веб-сайта OpenTofu.
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 развернёт инфраструктуру. Например, можно написать check для непрерывного мониторинга действительности сертификата шлюза API. Непрерывная валидация уведомит вас, если условие перестанет выполняться, чтобы вы могли обновить сертификат и избежать ошибок при следующем обновлении инфраструктуры.
Выражения условий
Для утверждений в блоках check, валидации входных переменных, предварительных и последующих условий требуется аргумент condition. Это логическое выражение должно возвращать true, если предполагаемое условие или гарантия выполнены, либо false, если они не выполнены.
В условии можно использовать любые встроенные функции OpenTofu и операторы языка, если выражение допустимо и возвращает логический результат. При написании выражений условий особенно полезны следующие возможности языка.
Логические операторы
Используйте логические операторы && (И), || (ИЛИ) и ! (НЕ), чтобы объединять несколько условий.
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. OpenTofu показывает сообщение вместе с именем ресурса, обнаружившего проблему, и любыми внешними значениями, включёнными в выражение условия.
Условия, проверяемые только на этапе apply
OpenTofu проверяет пользовательские условия как можно раньше.
Валидация входных переменных может ссылаться только на значение переменной, поэтому OpenTofu всегда выполняет её сразу. Для проверки утверждений, предварительных и последующих условий OpenTofu должен определить, известны ли значения, связанные с условием, до или после применения конфигурации.
- Известно до применения: OpenTofu проверяет условие на этапе планирования. Например, OpenTofu может знать значение идентификатора образа во время планирования, если оно не создано другим ресурсом.
- Известно после применения: OpenTofu откладывает проверку условия до этапа применения. Например, AWS назначает идентификатор корневого тома только при запуске экземпляра EC2, поэтому OpenTofu не может узнать это значение до применения.
На этапе применения невыполненное предварительное условие не позволит OpenTofu выполнить запланированные действия для связанного ресурса. Однако невыполненное последующее условие остановит обработку после того, как OpenTofu уже выполнит эти действия. Невыполненное последующее условие препятствует дальнейшим действиям для зависимых ресурсов, но не отменяет уже выполненные OpenTofu действия.
При первоначальном создании полной конфигурации OpenTofu обычно располагает меньшим объёмом информации, чем при применении последующих изменений. Поэтому при первоначальном создании OpenTofu может проверять условия на этапе применения, а при последующих обновлениях — на этапе планирования.
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.10/language/expressions/custom-conditions/