Синтаксис конфигурации 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, требуется метка с указанием используемого provisioner, а порядок блоков provisioner важен для определения порядка выполнения операций.
В следующем примере на собственном синтаксисе показан блок resource с несколькими provisioner разных типов:
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, для его представления можно использовать компактное преобразование блока: вложенный объект с одним свойством, имя которого обозначает тип 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.12/language/syntax/json/