Ограничения типов
Авторы модулей OpenTofu и разработчики провайдеров могут использовать подробные ограничения типов для проверки значений, заданных пользователями для входных переменных и аргументов ресурсов. Для этого нужно лучше разбираться в системе типов OpenTofu, но это позволяет создавать более устойчивый интерфейс для модулей и ресурсов.
Ключевые слова типов и конструкторы
Ограничения типов задаются с помощью сочетания ключевых слов типов и похожих на функции конструкций, называемых конструкторами типов.
- Ключевые слова типов — это символы без кавычек, представляющие статический тип.
- Конструкторы типов — это символы без кавычек, за которыми следуют круглые скобки с аргументом, задающим дополнительные сведения о типе. Без аргумента конструктор типа не представляет тип полностью; вместо этого он представляет вид похожих типов.
Ограничения типов похожи на другие виды выражений OpenTofu, но имеют специальный синтаксис. В языке OpenTofu они допустимы только в аргументе type входной переменной.
Примитивные типы
Примитивный тип — это простой тип, не состоящий из других типов. Все примитивные типы в OpenTofu представлены ключевыми словами типов. Доступны следующие примитивные типы:
-
string: последовательность символов Юникода, представляющая текст, например"hello". -
number: числовое значение. Типnumberможет представлять как целые числа, например15, так и дробные значения, например6.283185. -
bool: либоtrue, либоfalse. Значенияboolможно использовать в условной логике.
Преобразование примитивных типов
При необходимости язык OpenTofu автоматически преобразует значения number и bool в значения string и наоборот, если строка содержит допустимое представление числа или логического значения.
-
trueпреобразуется в"true", и наоборот -
falseпреобразуется в"false", и наоборот -
15преобразуется в"15", и наоборот
Сложные типы
Сложный тип — это тип, объединяющий несколько значений в одно. Сложные типы представлены конструкторами типов, некоторые из которых также имеют сокращённые варианты в виде ключевых слов.
Существует две категории сложных типов: типы-коллекции (для группировки похожих значений) и структурные типы (для группировки потенциально непохожих значений).
Типы-коллекции
Тип коллекции позволяет объединить несколько значений одного другого типа в одно значение. Тип значения, находящегося внутри коллекции, называется её типом элемента. Для всех типов-коллекций необходимо указать тип элемента в качестве аргумента конструктора.
Например, тип list(string) означает «список строк» и отличается от типа list(number) — списка чисел. Все элементы коллекции всегда должны иметь один и тот же тип.
В языке OpenTofu есть три вида типов-коллекций:
-
list(...): последовательность значений, идентифицируемых последовательными целыми числами, начиная с нуля.Ключевое слово
list— это сокращение дляlist(any), которое допускает любой тип элемента при условии, что все элементы имеют один и тот же тип. Этот вариант поддерживается для совместимости с более старыми конфигурациями; в новом коде рекомендуется использовать полную форму. -
map(...): коллекция значений, каждое из которых идентифицируется строковой меткой.Ключевое слово
map— это сокращение дляmap(any), которое допускает любой тип элемента при условии, что все элементы имеют один и тот же тип. Этот вариант поддерживается для совместимости с более старыми конфигурациями; в новом коде рекомендуется использовать полную форму.Для создания отображений используются фигурные скобки () и двоеточия (:) или знаки равенства (=): { "foo": "bar", "bar": "baz" } ИЛИ { foo = "bar", bar = "baz" }. Ключи можно указывать без кавычек, кроме случаев, когда ключ начинается с цифры: тогда кавычки обязательны. В однострочных отображениях между парами ключ-значение необходимо ставить запятые. В многострочных отображениях между парами ключ-значение достаточно переноса строки.
ПримечаниеХотя двоеточия являются допустимыми разделителями между ключами и значениями,
tofu fmtих игнорирует. В отличие от него,tofu fmtпытается выровнять знаки равенства по вертикали. -
set(...): коллекция уникальных значений, у которых нет дополнительных идентификаторов или порядка.
Структурные типы
Структурный тип позволяет объединять в одно значение несколько значений разных типов. Для структурных типов в качестве аргумента требуется схема, указывающая, какие типы допустимы для каких элементов.
В языке OpenTofu есть два вида структурных типов:
-
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, например aws_vpc.example_vpc; хотя у ресурса есть дополнительные атрибуты, при преобразовании типов они будут отброшены.
Наконец, тип кортежа tuple([string, number, bool]) будет соответствовать значению следующего вида:
["a", 15, true]
Литералы сложных типов
В языке OpenTofu есть литеральные выражения для создания значений кортежей и объектов. Они описаны в разделе «Выражения: литеральные выражения» как литералы «список/кортеж» и «отображение/объект» соответственно.
В OpenTofu нет способа напрямую представить списки, отображения или множества. Однако благодаря автоматическому преобразованию сложных типов (описанному ниже) различие между похожими сложными типами почти никогда не имеет значения для обычного пользователя, и в большинстве документации OpenTofu списки и кортежи, а также отображения и объекты рассматриваются как одно и то же. Эти различия полезны только при ограничении входных значений для модуля или ресурса.
Преобразование сложных типов
В языке OpenTofu обычно можно взаимозаменяемо использовать похожие виды сложных типов (список/кортеж/множество и отображение/объект), и в большинстве документации OpenTofu различия между видами сложных типов опускаются. Это обусловлено двумя правилами преобразования:
- Если это возможно, OpenTofu преобразует значения между похожими видами сложных типов, когда предоставленное значение не имеет в точности запрошенный тип. «Похожие виды» определяются следующим образом:
- Объекты и отображения похожи.
- Отображение (или объект большего размера) можно преобразовать в объект, если оно содержит как минимум ключи, требуемые схемой объекта. При преобразовании все дополнительные атрибуты отбрасываются, поэтому преобразования отображение -> объект -> отображение могут приводить к потере данных.
- Кортежи и списки похожи.
- Список можно преобразовать в кортеж, только если он содержит ровно требуемое количество элементов.
- Множества почти похожи и на кортежи, и на списки:
- При преобразовании списка или кортежа в множество повторяющиеся значения отбрасываются, а порядок элементов теряется.
- При преобразовании
setв список или кортеж элементы будут расположены в произвольном порядке. Если элементы множества — строки, они будут упорядочены лексикографически; для множеств с элементами других типов определённый порядок элементов не гарантируется.
- Объекты и отображения похожи.
- Если это возможно, OpenTofu преобразует значения элементов внутри сложного типа: рекурсивно преобразуя элементы сложных типов или применяя описанные выше правила из раздела «Преобразование примитивных типов».
Например, если аргумент модуля требует значение типа list(string), а пользователь передаёт кортеж ["a", 15, true], OpenTofu внутренне преобразует значение в ["a", "15", "true"], преобразовав элементы в требуемый тип элементов string. Позже, если модуль использует эти элементы для задания различных аргументов ресурсов, которым требуются строка, число и логическое значение соответственно, OpenTofu автоматически преобразует вторую и третью строки в требуемые типы, поскольку они содержат допустимые представления числа и логического значения.
С другой стороны, автоматическое преобразование завершится ошибкой, если предоставленное значение (включая любые значения его элементов) несовместимо с требуемым типом. Если для аргумента требуется тип map(string), а пользователь передаёт объект {name = ["Kristy", "Claudia", "Mary Anne", "Stacey"], age = 12}, OpenTofu выдаст ошибку несоответствия типов, поскольку кортеж нельзя преобразовать в строку.
Динамические типы: ограничение «any»
any очень редко является подходящим ограничением типа. Не используйте any только для того, чтобы избежать указания ограничения типа. Всегда указывайте точное ограничение типа, если только вы действительно не работаете с динамическими данными.
Ключевое слово any — это специальная конструкция, служащая заполнителем для типа, который ещё предстоит определить. any само по себе не является типом: при интерпретации значения с ограничением типа, содержащим any, OpenTofu попытается найти один конкретный тип, который можно подставить вместо ключевого слова any, чтобы получить допустимый результат.
Использовать any уместно только в том случае, если вы передаёте заданное значение напрямую в другую систему, не обращаясь непосредственно к его содержимому. Например, можно использовать переменную типа any, если вы применяете её только с jsonencode, чтобы передать всё значение непосредственно ресурсу, как показано в следующем примере:
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 всё равно выбрал бы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 принимает один или два аргумента.
- Тип: (Обязательный) Первый аргумент задаёт тип атрибута.
-
Значение по умолчанию: (Необязательный) Второй аргумент задаёт значение по умолчанию, которое OpenTofu должен использовать, если атрибут отсутствует. Оно должно быть совместимо с типом атрибута. Если значение не указано, OpenTofu использует в качестве значения по умолчанию значение
nullсоответствующего типа.
Для необязательного атрибута с не-null значением по умолчанию гарантируется, что в принимающем модуле он никогда не будет иметь значение null. OpenTofu подставит значение по умолчанию как в случае, когда вызывающий код полностью опускает атрибут, так и в случае, когда явно задаёт ему значение null. Это избавляет от необходимости выполнять дополнительные проверки на возможное значение null.
Вложенным типам переменных OpenTofu применяет значения по умолчанию для атрибутов объектов сверху вниз. Это означает, что OpenTofu сначала применяет значение по умолчанию, указанное в модификаторе optional, а затем применяет к этому атрибуту все вложенные значения по умолчанию.
Пример: вложенные структуры с необязательными атрибутами и значениями по умолчанию
В следующем примере определяется переменная для хранилищ, в которых размещён веб-сайт. Тип этой переменной использует несколько необязательных атрибутов, включая website, который сам является необязательным типом object с необязательными атрибутами и значениями по умолчанию.
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.
-
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иdocsOpenTofu задаёт дляenabledзначениеtrue. OpenTofu также подставляет значения по умолчанию дляwebsite, после чего значения, указанные вdocs, переопределяют эти значения по умолчанию. - Для хранилищ
archivedиdocsOpenTofu задаёт дляrouting_rulesзначениеnull. Если OpenTofu не получает необязательные атрибуты и для них не указаны значения по умолчанию, OpenTofu заполняет эти атрибуты значениемnull. - Для хранилища
archivedOpenTofu заполняет атрибут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 может использовать условное выражение, в одной из ветвей результата которого указано null, чтобы динамически оставить аргумент незаданным.
При объявлении variable "buckets", показанном в предыдущем разделе, следующий пример условно переопределяет параметры index_document и error_document в объекте website на основе новой переменной var.legacy_filenames:
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, вызов оставит оба имени файла незаданными, и модуль сможет использовать указанные для них значения по умолчанию.
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.10/language/expressions/type-constraints/