Синтаксис конфигурации JSON
Большинство конфигураций OpenTofu записываются с использованием собственного синтаксиса языка OpenTofu, разработанного так, чтобы его было относительно легко читать и изменять.
OpenTofu также поддерживает альтернативный синтаксис, совместимый с JSON. Этот синтаксис полезен при программной генерации частей конфигурации, поскольку для подготовки генерируемых файлов конфигурации можно использовать существующие библиотеки JSON.
Синтаксис JSON определён на основе собственного синтаксиса. Всё, что можно выразить в собственном синтаксисе, можно выразить и в синтаксисе JSON, однако некоторые конструкции сложнее представить в JSON из-за ограничений грамматики JSON.
OpenTofu ожидает собственный синтаксис в файлах с суффиксом .tf или .tofu и синтаксис JSON в файлах с суффиксом .tf.json или .tofu.json.
Низкоуровневый синтаксис JSON, как и собственный синтаксис, определён в терминах спецификации под названием HCL. Для работы с OpenTofu необязательно знать все подробности синтаксиса HCL или его отображения в JSON, поэтому на этой странице кратко описаны наиболее важные различия между собственным синтаксисом и синтаксисом JSON. Если вам интересно, полное описание синтаксиса JSON для HCL можно найти в его спецификации.
Приоритет расширений
Если в каталоге есть файлы .tf.json и .tofu.json с одинаковым базовым именем, OpenTofu отдаст приоритет файлу .tofu.json и проигнорирует файл .tf.json. Например:
- Если в одном каталоге есть
foo.tf.jsonиfoo.tofu.json, OpenTofu загрузит толькоfoo.tofu.jsonи проигнорируетfoo.tf.json.
Это гарантирует, что файлы .tofu.json всегда имеют приоритет над файлами .tf.json, если присутствуют оба варианта. Такая возможность может быть полезна авторам модулей, которые хотят, чтобы их модули поддерживали и OpenTofu, и Terraform.
Структура файла JSON
Корневым элементом любой конфигурации OpenTofu на основе JSON является объект JSON. Его свойства соответствуют типам блоков верхнего уровня языка OpenTofu. Например:
{
"variable": {
"example": {
"default": "hello"
}
}
}Каждое свойство объекта верхнего уровня должно соответствовать имени одного из ожидаемых типов блоков верхнего уровня. Типы блоков, для которых требуются метки, например variable выше, представлены вложенным объектом для каждого уровня меток. Для блоков resource требуются две метки, поэтому необходимы два уровня вложенности:
{
"resource": {
"aws_instance": {
"example": {
"instance_type": "t2.micro",
"ami": "ami-abc123"
}
}
}
}После вложенных объектов, представляющих метки, ещё один вложенный объект описывает тело самого блока. В приведённых выше примерах указаны аргумент default для variable "example", а также аргументы instance_type и ami для resource "aws_instance" "example".
Вместе эти два файла конфигурации эквивалентны следующим блокам в собственном синтаксисе:
variable "example" {
default = "hello"
}
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "ami-abc123"
}Правила преобразования в JSON немного различаются для каждого типа блока верхнего уровня (см. приведённые ниже исключения для отдельных типов блоков), однако в большинстве случаев применяются следующие общие правила:
-
Объект JSON, представляющий тело блока, содержит свойства, соответствующие именам аргументов или типам вложенных блоков.
-
Если свойство соответствует аргументу, который в собственном синтаксисе принимает произвольные выражения, значение свойства преобразуется в выражение, как описано ниже в разделе Преобразование выражений. Для аргументов, которые не принимают произвольные выражения, интерпретация значения свойства зависит от аргумента, как описано далее на этой странице в разделе исключений для отдельных типов блоков.
-
Если имя свойства соответствует ожидаемому типу вложенного блока, значение интерпретируется, как описано ниже в разделе Преобразование вложенных блоков, если далее на этой странице в разделе исключений для отдельных типов блоков не указано иное.
Преобразование выражений
Поскольку грамматика JSON не позволяет представить весь синтаксис выражений языка OpenTofu, значения JSON, интерпретируемые как выражения, преобразуются следующим образом:
| JSON | Интерпретация в языке OpenTofu |
|---|---|
| Логическое значение | Литеральное значение bool. |
| Число | Литеральное значение number. |
| Строка | Разбирается как шаблон строки, а затем вычисляется, как описано ниже. |
| Объект | Каждое значение свойства преобразуется согласно этой таблице, в результате чего получается значение object(...) с подходящими типами атрибутов. |
| Массив | Каждый элемент преобразуется согласно этой таблице, в результате чего получается значение tuple(...) с подходящими типами элементов. |
| Null | Литеральное значение null. |
Если в месте, где ожидаются произвольные выражения, встречается строка JSON, её значение сначала разбирается как [шаблон строки][], а затем вычисляется для получения окончательного результата.
Если данный шаблон состоит только из одной последовательности интерполяции, результат его выражения используется напрямую, без предварительного преобразования в строку. Это позволяет использовать в синтаксисе JSON выражения, результатом которых являются значения нестрокового типа:
{
"output": {
"example": {
"value": "${aws_instance.example}"
}
}
}Объявленное выше output "example" имеет в качестве значения объект, представляющий указанный блок ресурса aws_instance, а не строковое значение. Это особое поведение не применяется, если в шаблоне присутствуют какие-либо литеральные или управляющие последовательности; в таких случаях всегда создаётся строковое значение.
Преобразование вложенных блоков
Если свойство объекта JSON названо в соответствии с типом вложенного блока, его значение представляет один или несколько блоков этого типа. Значением свойства должен быть объект JSON или массив JSON.
Самый простой случай — представление одного блока заданного типа, если для этого типа не требуются метки, как в случае вложенного блока lifecycle внутри блоков resource:
{
"resource": {
"aws_instance": {
"example": {
"lifecycle": {
"create_before_destroy": true
}
}
}
}
}Приведённый выше пример эквивалентен следующей конфигурации в собственном синтаксисе:
resource "aws_instance" "example" {
lifecycle {
create_before_destroy = true
}
}Если для типа вложенного блока требуется одна или несколько меток либо можно указать несколько блоков одного типа, преобразование становится немного сложнее. Например, для вложенного типа блока provisioner внутри блоков resource требуется метка, указывающая используемый исполнитель, а порядок блоков исполнителей важен для определения порядка операций.
В следующем примере на собственном синтаксисе показан блок resource с несколькими исполнителями разных типов:
resource "aws_instance" "example" {
# (resource configuration omitted for brevity)
provisioner "local-exec" {
command = "echo 'Hello World' >example.txt"
}
provisioner "file" {
source = "example.txt"
destination = "/tmp/example.txt"
}
provisioner "remote-exec" {
inline = [
"sudo install-something -f /tmp/example.txt",
]
}
}Чтобы сохранить порядок этих блоков, в качестве непосредственного значения свойства, представляющего этот тип блока, необходимо использовать массив JSON, как в следующем эквиваленте приведённого выше примера на JSON:
{
"resource": {
"aws_instance": {
"example": {
"provisioner": [
{
"local-exec": {
"command": "echo 'Hello World' >example.txt"
}
},
{
"file": {
"source": "example.txt",
"destination": "/tmp/example.txt"
}
},
{
"remote-exec": {
"inline": ["sudo install-something -f /tmp/example.txt"]
}
}
]
}
}
}
}Каждый элемент массива provisioner — это объект с одним свойством, имя которого представляет метку соответствующего блока provisioner. Для типов блоков, которым требуется несколько меток, этот шаблон чередования вложенных массивов и объектов можно использовать для каждого дополнительного уровня.
Если для типа вложенного блока требуются метки, но порядок не имеет значения, массив можно опустить и указать только один объект, имена свойств которого соответствуют уникальным меткам блоков. В простых случаях это допустимо как сокращённая форма приведённой выше записи, однако чередование массивов и объектов — наиболее универсальный подход. При систематическом преобразовании собственного синтаксиса в JSON мы рекомендуем использовать наиболее универсальную форму, чтобы гарантировать точное сохранение смысла конфигурации.
Свойства для комментариев
Хотя мы не рекомендуем вручную редактировать файлы конфигурации с синтаксисом JSON — этот формат в первую очередь предназначен для программной генерации и обработки, — внутри объектов JSON, представляющих тела блоков, допускается ограниченная форма комментариев, задаваемых с помощью специального имени свойства:
{
"resource": {
"aws_instance": {
"example": {
"//": "This instance runs the scheduled tasks for backup",
"instance_type": "t2.micro",
"ami": "ami-abc123"
}
}
}
}Во всех объектах, представляющих тело блока, свойства с именем "//" полностью игнорируются OpenTofu. Это исключение не распространяется на объекты, которые интерпретируются как выражения: в этом случае такое свойство будет интерпретировано как атрибут типа объекта с именем "//".
Это специальное имя свойства также можно использовать в корне файла конфигурации на основе JSON. Так можно, например, указать, какая программа создала файл.
{
"//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!",
"output": {
"example": {
"value": "${aws_instance.example}"
}
}
}Исключения для отдельных типов блоков
Некоторые аргументы в блоках определённых типов обрабатываются OpenTofu особым образом, поэтому их преобразование в синтаксис JSON не соответствует описанным выше общим правилам. В следующих подразделах описаны специальные правила преобразования, применяемые к каждому типу блока верхнего уровня.
Блоки resource и data
Некоторые метааргументы типов блоков resource и data принимают прямые ссылки на объекты или литеральные ключевые слова. В JSON ссылка или ключевое слово задаются строкой JSON без дополнительных пробелов или символов вокруг неё.
Например, метааргумент provider принимает ссылку <PROVIDER>.<ALIAS> на конфигурацию провайдера. В собственном синтаксисе она указывается без кавычек, а в синтаксисе JSON должна быть представлена строкой:
{
"resource": {
"aws_instance": {
"example": {
"provider": "aws.foo"
}
}
}
}Особая обработка применяется к следующим метааргументам:
-
provider: одна строка, как показано выше -
depends_on: массив строк, содержащих ссылки на именованные сущности, например["aws_instance.example"]. -
ignore_changesвнутри блокаlifecycle: если задано значениеall, необходимо указать одну строку"all". В противном случае необходимо использовать массив строк JSON, содержащих ссылки на свойства, например["ami"].
Особая обработка также применяется к аргументу type любого блока connection, расположенного непосредственно внутри блока resource или вложенного в блоки provisioner: заданная строка интерпретируется буквально, а не разбирается и вычисляется как шаблон строки.
Блоки variable
Все аргументы внутри блоков variable преобразуются в JSON нестандартным образом:
-
type: строка с выражением типа, например"string"или"list(string)". -
default: литеральное значение JSON, которое можно преобразовать в указанный тип. Строки в этом значении воспринимаются буквально и не интерпретируются как шаблоны строк. -
description: литеральная строка JSON, которая не интерпретируется как шаблон.
{
"variable": {
"example": {
"type": "string",
"default": "hello"
}
}
}
Блоки output
Аргументы description и sensitive интерпретируются как литеральные значения JSON. Строка description не интерпретируется как шаблон строки.
Аргумент value интерпретируется как выражение.
{
"output": {
"example": {
"value": "${aws_instance.example}"
}
}
}
Блоки locals
Значением свойства объекта JSON, представляющего тип блока locals, должен быть объект JSON, имена свойств которого соответствуют именам объявляемых локальных значений:
{
"locals": {
"greeting": "Hello, ${var.name}"
}
}Значение каждого из этих вложенных свойств интерпретируется как выражение.
Блоки module
Метааргумент providers необходимо задать как объект JSON, свойства которого — это краткие адреса провайдеров, доступных дочернему модулю, а значения — адреса провайдеров, используемых текущим модулем; и те и другие задаются литеральными строками:
{
"module": {
"example": {
"source": "hashicorp/consul/azurerm",
"version": "= 1.0.0",
"providers": {
"aws": "aws.usw1"
}
}
}
}
Блоки provider
Метааргументы alias и version необходимо задавать литеральными строками. Их значения не интерпретируются как шаблоны строк.
{
"provider": {
"aws": [
{
"region": "us-east-1"
},
{
"alias": "usw1",
"region": "us-west-1"
}
]
}
}
Блоки terraform
Параметры внутри блоков terraform обычно интерпретируются буквально, за исключением блоков backend и encryption, которые поддерживают выражения. Другие параметры не принимают ссылки на именованные объекты или вызовы функций, поэтому строковые значения не обрабатываются как шаблоны строк.
Блок backend
Поскольку в каждом блоке terraform допускается только один блок backend, для его представления можно использовать компактное преобразование блока: вложенный объект с одним свойством, имя которого обозначает тип бэкенда.
{
"terraform": {
"required_version": ">= 0.12.0",
"backend": {
"s3": {
"region": "us-west-2",
"bucket": "acme-tofu-states"
}
}
}
}
Блок encryption
Как показано в документации по шифрованию, блок encryption поддерживает следующие дочерние блоки:
key_providermethodstateplanremote_state_data_sources
Блоки key_provider и method могут ссылаться на другие именованные блоки, например переменные, тогда как блоки state, plan и remote_state_data_sources могут ссылаться только на другие блоки внутри блока encryption.
Блок key_provider может ссылаться на переменные, чтобы динамически передавать сведения о ключе:
{
"variable": {
"state_plan_passphrase": {
"type": "string",
"default": "myultrasecretpassphrase1!"
}
},
"terraform": {
"encryption": {
"key_provider": {
"pbkdf2": {
"state_plan": {
"passphrase": "${var.state_plan_passphrase}"
}
}
}
}
}
}Блок method может ссылаться на блоки key_provider с помощью статических строковых ссылок или обычных интерполированных выражений Terraform:
{
"terraform": {
"encryption": {
"key_provider": {
"pbkdf2": {
"state_plan": {
"passphrase": "myultrasecretpassphrase1!"
}
}
},
"method": {
"aes_gcm": {
"my_key_for_state": {
"keys": "${key_provider.pbkdf2.state_plan}"
},
"my_key_for_plan": {
"keys": "key_provider.pbkdf2.state_plan"
}
}
}
}
}
}Блоки state, plan и remote_state_data_sources могут ссылаться только на блоки method с помощью строковых ссылок. Это не выражения, а только ссылки на методы, поэтому они вычисляются не так, как выражения в OpenTofu:
{
"variable": {
"remote_state_passphrase": {
"type": "string",
"default": "mysecrettestpassword!"
},
"state_plan_passphrase": {
"type": "string",
"default": "mysecrettestpassword2!"
}
},
"terraform": {
"encryption": {
"key_provider": {
"pbkdf2": {
"remote_state": {
"passphrase": "remotestateultrasecretpassphrase!1"
},
"state_plan": {
"passphrase": "stateplanultrasecretpassphrase!1"
}
}
},
"method": {
"aes_gcm": {
"remote_state": {
"keys": "${key_provider.pbkdf2.remote_state_key}"
},
"state_plan": {
"keys": "key_provider.pbkdf2.state_plan"
}
},
"unencrypted": {
"unencrypted": {}
}
},
"state": {
"method": "method.aes_gcm.state_plan",
"fallback": {
"method": "method.unencrypted.unencrypted"
}
},
"plan": {
"method": "method.aes_gcm.state_plan",
"fallback": {
"method": "method.unencrypted.unencrypted"
}
},
"remote_state_data_sources": {
"default": {
"method": "method.aes_gcm.remote_state"
},
"remote_state_data_source": {
"another_state": {
"method": "method.aes_gcm.remote_state"
}
}
}
}
},
"data": {
"terraform_remote_state": {
"another_state": {
"backend": "local",
"config": {
"path": "<path to an encrypted state>"
}
}
}
}
}
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.11/language/syntax/json/