Пользовательские условия
Можно создавать условия, которые выводят пользовательские сообщения об ошибках для нескольких типов объектов в конфигурации. Например, можно добавить условие для входной переменной, проверяющее правильность формата идентификаторов входящих изображений. Пользовательские условия позволяют зафиксировать предположения, помогая будущим разработчикам понять структуру и назначение конфигурации. Кроме того, они раньше и в контексте предоставляют полезную информацию об ошибках, что помогает пользователям быстрее выявлять проблемы в своих конфигурациях.
На этой странице рассматриваются следующие темы:
- Создание проверок с помощью утверждений для проверки инфраструктуры в целом
- Создание условий валидации входных переменных
- Создание предусловий и постусловий для ресурсов, источников данных и выходных значений
- Написание эффективных выражений условий и сообщений об ошибках
- Когда OpenTofu вычисляет пользовательские условия во время циклов планирования и применения
Выбор пользовательского условия для вашего случая
Разные пользовательские условия OpenTofu лучше подходят для разных ситуаций. Чтобы выбрать подходящее пользовательское условие, воспользуйтесь следующими общими рекомендациями:
- Блоки check с утверждениями проверяют инфраструктуру в целом. Кроме того, блоки check не прерывают и не блокируют выполнение операций OpenTofu.
- Условия валидации или постусловия выходных значений помогают гарантировать, что входные и выходные данные конфигурации соответствуют определённым требованиям.
- Предусловия и постусловия ресурсов позволяют проверить, что OpenTofu создаёт инфраструктуру согласно конфигурации с предсказуемыми результатами.
Подробнее о том, когда использовать те или иные пользовательские условия, см. в разделах Выбор между предусловиями и постусловиями и Выбор проверок и других пользовательских условий.
Валидация входных переменных
Добавьте один или несколько блоков validation в блок variable, чтобы задать пользовательские условия. Для каждой проверки требуется аргумент condition — выражение, которое должно использовать значение переменной и возвращать true, если значение допустимо, или false, если оно недопустимо. Выражение может ссылаться на переменные, локальные значения, ресурсы и т. д. Если значение переменной неизвестно во время проверки или планирования, проверка будет отложена до тех пор, пока значение не станет известно на этапе применения.
Если условие вычисляется как 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 вычисляет пользовательские условия как можно раньше, но должен отложить проверку условий, зависящих от неизвестных значений, до этапа применения. Подробнее см. в разделе Условия, проверяемые только на этапе применения.
Использование
Для каждого предусловия и постусловия требуется аргумент 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 экземпляры 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.
Мы рекомендуем использовать предусловия для предположений, чтобы будущие разработчики могли найти их рядом с другими выражениями, которые зависят от этого условия. Это поможет им лучше понять, для чего предназначен ресурс.
Используйте постусловия для гарантий
Гарантия — это характеристика или поведение объекта, на которое должна иметь возможность полагаться остальная конфигурация. Например, конфигурация 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. Непрерывная проверка оповестит вас о невыполнении условия, чтобы вы могли обновить сертификат и избежать ошибок при следующем обновлении инфраструктуры.
Выражения условий
Для проверок с утверждениями, валидации входных переменных, предусловий и постусловий требуется аргумент 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 и шаблонные выражения. Функцию format можно использовать для преобразования элементов типов null, list или map в форматированную строку. Поддерживаются многострочные сообщения об ошибках; строки с отступом в начале не будут переноситься.
Рекомендуем составлять сообщения об ошибках из одного или нескольких полных предложений в стиле сообщений об ошибках самого OpenTofu. OpenTofu покажет сообщение вместе с именем ресурса, обнаружившего проблему, и внешними значениями, использованными в выражении условия.
Условия, проверяемые только на этапе применения
OpenTofu вычисляет пользовательские условия как можно раньше.
Валидация входных переменных может ссылаться только на значение переменной, поэтому OpenTofu всегда выполняет её немедленно. Проверки с утверждениями, предусловия и постусловия зависят от того, известно ли OpenTofu значение или значения, связанные с условием, до или после применения конфигурации.
- Известно до применения: OpenTofu проверяет условие на этапе планирования. Например, OpenTofu может знать значение идентификатора образа во время планирования, если оно не создаётся другим ресурсом.
- Известно после применения: OpenTofu откладывает проверку условия до этапа применения. Например, AWS назначает идентификатор корневого тома только при запуске экземпляра EC2, поэтому OpenTofu не может узнать это значение до применения.
На этапе применения невыполненное предусловие не позволит OpenTofu выполнить запланированные действия для связанного ресурса. Однако невыполненное постусловие остановит обработку после того, как OpenTofu уже выполнил эти действия. Невыполненное постусловие предотвращает дальнейшие действия, зависящие от этого ресурса, но не отменяет уже выполненные OpenTofu действия.
При первоначальном создании полной конфигурации OpenTofu обычно располагает меньшим объёмом информации, чем при применении последующих изменений. Поэтому при первоначальном создании OpenTofu может проверять условия на этапе применения, а при последующих обновлениях — на этапе планирования.
Использование эфемерных значений
Чтобы исключить раскрытие информации при сбоях условий, в блоках пользовательских условий также можно использовать эфемерные значения, но только в поле condition.
variable "my_ephemeral_variable" {
type = string
ephemeral = true
}
resource "null_resource" "test" {
// ...
lifecycle {
precondition {
condition = var.my_ephemeral_variable != "forbidden-value"
error_message = "cannot be used when this variable points to a value that is not allowed"
}
}
}Использование эфемерных значений в error_message приведёт к ошибке при формировании сообщения:
│ Warning: Error message refers to ephemeral values
│
│ on main.tf line 63, in resource "null_resources" "test":
│ 63: error_message = "cannot be used when this variable points to a value that is not allowed ${var.my_ephemeral_variable}"
│
│ The error expression used to explain this condition refers to ephemeral values, so OpenTofu will not display the
│ resulting message.
│
│ You can correct this by removing references to ephemeral values or by utilizing the builtin ephemeralasnull()
│ function.
╵
╷
│ Error: Resource precondition failed
│
│ on main.tf line 62, in resource "null_resource" "test":
│ 62: condition = var.my_ephemeral_variable != "forbidden-value"
│
│ This check failed, but has an invalid error message as described in the other accompanying messages.
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.12/language/expressions/custom-conditions/