Ограничения типов
Авторы модулей 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 не получает необязательные атрибуты и для них не заданы значения по умолчанию, он присваивает этим атрибутам значение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.12/language/expressions/type-constraints/