Ограничения типов
Авторы модулей 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).
Необязательные атрибуты типа object
Обычно 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.9/language/expressions/type-constraints/