Формат вывода JSON
Когда OpenTofu планирует внести изменения, он выводит в терминал понятную человеку сводку. При запуске с -out=<PATH> он также может записать гораздо более подробный двоичный файл плана, который впоследствии можно использовать для применения этих изменений.
Поскольку формат файлов плана не подходит для использования с внешними инструментами (и, скорее всего, никогда не будет подходить), OpenTofu может выводить машиночитаемое представление изменений из файла плана в формате JSON. Он также может преобразовывать файлы состояния в тот же формат, чтобы упростить загрузку данных и обеспечить лучшую долгосрочную совместимость.
Используйте tofu show -json <FILE> для создания представления файла плана или состояния в формате JSON. Подробнее см. в документации по tofu show.
Вывод содержит ключ format_version со значением "1.0". Семантика этой версии такова:
- Мы будем увеличивать минорную версию, например
"1.1", при внесении обратно совместимых изменений или дополнений. Чтобы сохранить прямую совместимость с будущими минорными версиями, игнорируйте свойства объектов с неизвестными именами. - Мы будем увеличивать мажорную версию, например
"2.0", при внесении обратно несовместимых изменений. Отклоняйте любые входные данные, в которых указана неподдерживаемая мажорная версия.
Мы будем вводить новые мажорные версии только в рамках гарантий совместимости OpenTofu 1.0.
Обзор формата
В следующих разделах формат вывода JSON описан на примерах с использованием псевдоформата JSON.
Важные элементы описаны в комментариях, которые начинаются с //.
Чтобы избежать излишних повторов, мы разделили описание полного формата на несколько отдельных подобъектов, которые описаны в отдельных разделах. Ссылки в угловых скобках (например, <values-representation>) — это заполнители, которые в реальном выводе заменяются экземпляром указанного подобъекта.
Формат вывода JSON состоит из следующих объектов и подобъектов:
-
Представление состояния — полный объект верхнего уровня, возвращаемый командой
tofu show -json <STATE FILE>. -
Представление плана — полный объект верхнего уровня, возвращаемый командой
tofu show -json <PLAN FILE>. - Представление значений — подобъект вывода плана и состояния, описывающий текущее или запланированное состояние.
-
Представление конфигурации — подобъект вывода плана, описывающий обработанную конфигурацию OpenTofu.
- Представление выражения — подобъект представления конфигурации, описывающий невычисленное выражение.
- Представление выражений блока — подобъект представления конфигурации, описывающий выражения, вложенные в блок.
- Представление изменения — подобъект вывода плана, описывающий изменения объекта.
- Представление проверок — свойство представлений плана и состояния, описывающее текущий статус всех проверок (например, предварительных условий и постусловий) в конфигурации.
Представление состояния
Состояние не содержит значимых метаданных, которые отсутствовали бы в общем представлении значений, поэтому для <state-representation> используется следующий формат:
{
// "values" is a values representation object derived from the values in the
// state. Because the state is always fully known, this is always complete.
"values": <values-representation>
// The key here is left unchanged in OpenTofu for compatibility reasons.
"terraform_version": "version.string"
}Представление плана
План состоит из исходного состояния, конфигурации, применяемой к этому состоянию, и набора изменений, которые OpenTofu планирует внести для достижения этой цели.
Чтобы вызывающим сторонам было проще работать с данными, представление плана содержит частичное представление значений в конечном состоянии (с использованием представления значений), позволяя легко анализировать запланированный результат с помощью кода, аналогичного коду для анализа исходного состояния.
{
"format_version": "1.0",
// "prior_state" is a representation of the state that the configuration is
// being applied to, using the state representation described above.
"prior_state": <state-representation>,
// "configuration" is a representation of the configuration being applied to the
// prior state, using the configuration representation described above.
"configuration": <configuration-representation>,
// "planned_values" is a description of what is known so far of the outcome in
// the standard value representation, with any as-yet-unknown values omitted.
"planned_values": <values-representation>,
// "proposed_unknown" is a representation of the attributes, including any
// potentially-unknown attributes. Each value is replaced with "true" or
// "false" depending on whether it is known in the proposed plan.
"proposed_unknown": <values-representation>,
// "variables" is a representation of all the variables provided for the given
// plan. This is structured as a map similar to the output map so we can add
// additional fields in later.
"variables": {
"varname": {
"value": "varvalue"
},
},
// "resource_changes" is a description of the individual change actions that
// OpenTofu plans to use to move from the prior state to a new state
// matching the configuration.
"resource_changes": [
// Each element of this array describes the action to take
// for one instance object. All resources in the
// configuration are included in this list.
{
// "address" is the full absolute address of the resource instance this
// change applies to, in the same format as addresses in a value
// representation.
"address": "module.child.aws_instance.foo[0]",
// "previous_address" is the full absolute address of this resource
// instance as it was known after the previous OpenTofu run.
// Included only if the address has changed, e.g. by handling
// a "moved" block in the configuration.
"previous_address": "module.instances.aws_instance.foo[0]",
// "module_address", if set, is the module portion of the above address.
// Omitted if the instance is in the root module.
"module_address": "module.child",
// "mode", "type", "name", and "index" have the same meaning as in a
// value representation.
"mode": "managed",
"type": "aws_instance",
"name": "foo",
"index": 0,
// "deposed", if set, indicates that this action applies to a "deposed"
// object of the given instance rather than to its "current" object.
// Omitted for changes to the current object. "address" and "deposed"
// together form a unique key across all change objects in a particular
// plan. The value is an opaque key representing the specific deposed
// object.
"deposed": "deadbeef",
// "change" describes the change that will be made to the indicated
// object. The <change-representation> is detailed in a section below.
"change": <change-representation>,
// "action_reason" is some optional extra context about why the
// actions given inside "change" were selected. This is the JSON
// equivalent of annotations shown in the normal plan output like
// "is tainted, so it must be replaced" as opposed to just "must be
// replaced".
//
// These reason codes are display hints only and the set of possible
// hints may change over time. Users of this must be prepared to
// encounter unrecognized reasons and treat them as unspecified reasons.
//
// The current set of possible values is:
// - "replace_because_tainted": the object in question is marked as
// "tainted" in the prior state, so OpenTofu planned to replace it.
// - "replace_because_cannot_update": the provider indicated that one
// of the requested changes isn't possible without replacing the
// existing object with a new object.
// - "replace_by_request": the user explicitly called for this object
// to be replaced as an option when creating the plan, which therefore
// overrode what would have been a "no-op" or "update" action otherwise.
// - "delete_because_no_resource_config": OpenTofu found no resource
// configuration corresponding to this instance.
// - "delete_because_no_module": The resource instance belongs to a
// module instance that's no longer declared, perhaps due to changing
// the "count" or "for_each" argument on one of the containing modules.
// - "delete_because_wrong_repetition": The instance key portion of the
// resource address isn't of a suitable type for the corresponding
// resource's configured repetition mode (count, for_each, or neither).
// - "delete_because_count_index": The corresponding resource uses count,
// but the instance key is out of range for the currently-configured
// count value.
// - "delete_because_each_key": The corresponding resource uses for_each,
// but the instance key doesn't match any of the keys in the
// currently-configured for_each value.
// - "read_because_config_unknown": For a data resource, OpenTofu cannot
// read the data during the plan phase because of values in the
// configuration that won't be known until the apply phase.
// - "read_because_dependency_pending": For a data resource, OpenTofu
// cannot read the data during the plan phase because the data
// resource depends on at least one managed resource that also has
// a pending change in the same plan.
//
// If there is no special reason to note, OpenTofu will omit this
// property altogether.
action_reason: "replace_because_tainted"
}
],
// "resource_drift" is a description of the changes OpenTofu detected
// when it compared the most recent state to the prior saved state.
"resource_drift": [
{
// "resource_drift" uses the same object structure as
// "resource_changes".
}
],
// "relevant_attributes" lists the sources of all values contributing to
// changes in the plan. You can use "relevant_attributes" to filter
// "resource_drift" and determine which external changes may have affected the
// plan result.
"relevant_attributes": [
{
"resource": "aws_instance.foo",
"attribute": "attr",
}
]
// "output_changes" describes the planned changes to the output values of the
// root module.
"output_changes": {
// Keys are the defined output value names.
"foo": {
// "change" describes the change that will be made to the indicated output
// value, using the same representation as for resource changes except
// that the only valid actions values are:
// ["create"]
// ["update"]
// ["delete"]
// OpenTofu is not yet fully able to
// track changes to output values, so the actions indicated may not be
// fully accurate, but the "after" value will always be correct.
"change": <change-representation>,
}
},
// "checks" describes the partial results for any checkable objects, such as
// resources with postconditions, with as much information as OpenTofu can
// recognize at plan time. Some objects will have status "unknown" to
// indicate that their status will only be determined after applying the plan.
"checks" <checks-representation>,
// "errored" indicates whether planning failed. An errored plan cannot be applied,
// but the actions planned before failure may help to understand the error.
"errored": false
}Эта общая структура плана в полностью развернутом виде выводится командой tofu show -json <planfile>.
Представление значений
Представление значений используется в выводе состояния и плана для описания текущего состояния (которое всегда приводится целиком) и запланированного состояния (в котором опущены значения, неизвестные до применения).
Следующий пример иллюстрирует структуру <values-representation>:
{
// "outputs" describes the outputs from the root module. Outputs from
// descendent modules are not available because they are not retained in all
// of the underlying structures we will build this values representation from.
"outputs": {
"private_ip": {
"value": "192.168.3.2",
"type": "string",
"sensitive": false
}
},
// "root_module" describes the resources and child modules in the root module.
"root_module": {
"resources": [
{
// "address" is the absolute resource address, which callers must consider
// opaque but may do full string comparisons with other address strings or
// pass this verbatim to other OpenTofu commands that are documented to
// accept absolute resource addresses. The module-local portions of this
// address are extracted in other properties below.
"address": "aws_instance.example[1]",
// "mode" can be "managed", for resources, or "data", for data resources
"mode": "managed",
"type": "aws_instance",
"name": "example",
// If the count or for_each meta-arguments are set for this resource, the
// additional key "index" is present to give the instance index key. This
// is omitted for the single instance of a resource that isn't using count
// or for_each.
"index": 1,
// "provider_name" is the name of the provider that is responsible for
// this resource. This is only the provider name, not a provider
// configuration address, and so no module path nor alias will be
// indicated here. This is included to allow the property "type" to be
// interpreted unambiguously in the unusual situation where a provider
// offers a resource type whose name does not start with its own name,
// such as the "googlebeta" provider offering "google_compute_instance".
"provider_name": "aws",
// "schema_version" indicates which version of the resource type schema
// the "values" property conforms to.
"schema_version": 2,
// "values" is the JSON representation of the attribute values of the
// resource, whose structure depends on the resource type schema. Any
// unknown values are omitted or set to null, making them
// indistinguishable from absent values; callers which need to distinguish
// unknown from unset must use the plan-specific or configuration-specific
// structures described in later sections.
"values": {
"id": "i-abc123",
"instance_type": "t2.micro",
// etc, etc
},
// "sensitive_values" is the JSON representation of the sensitivity of
// the resource's attribute values. Only attributes which are sensitive
// are included in this structure.
"sensitive_values": {
"id": true,
}
}
]
"child_modules": [
// Each entry in "child_modules" has the same structure as the root_module
// object, with the additional "address" property shown below.
{
// "address" is the absolute module address, which callers must treat as
// opaque but may do full string comparisons with other module address
// strings and may pass verbatim to other OpenTofu commands that are
// documented as accepting absolute module addresses.
"address": "module.child",
// "resources" is the same as in "root_module" above
"resources": [
{
"address": "module.child.aws_instance.foo",
// etc, etc
}
],
// Each module object can optionally have its own
// nested "child_modules", recursively describing the
// full module tree.
"child_modules": [ ... ],
}
]
}
}Преобразование значений атрибутов и выходных данных выполняется по тому же интуитивно понятному сопоставлению типов HCL с типами JSON, которое OpenTofu использует в функции jsonencode. При этом часть информации теряется: списки, множества и кортежи преобразуются в массивы JSON, а карты и объекты — в объекты JSON. Неизвестные значения и значения null в обоих случаях рассматриваются как отсутствующие или null.
Выходные значения содержат поле "type", которое представляет собой сериализованное представление типа значения. Для примитивных типов это строковое значение, например "number" или "bool". Составные типы представлены вложенным массивом JSON, например ["map","string"] или ["object",{"a":"number"}]. Это позволяет восстановить выходное значение с правильным типом.
Описывается только объект «current» для каждого экземпляра ресурса. Объекты «deposed» в этой структуре никак не отражаются; в представлениях плана дополнительные сведения можно найти в представлениях изменений.
Эта структура предназначена для предоставления вызывающей стороне доступа к уровню детализации, аналогичному доступному выражениям непосредственно в конфигурации. Это общее представление подходит не для всех случаев использования, поскольку в сравнении с исходными структурами данных оно теряет часть информации. Для более сложных задач используйте более подробные представления изменений и конфигурации.
Представление конфигурации
Конфигурация — самая сложная структура в OpenTofu, поскольку она включает узлы невычисленных выражений и другие сложные элементы.
Модели конфигурации создаются до вычисления выражений, поэтому сформировать для конфигурации представление значений невозможно. Вместо этого мы описываем физическую структуру конфигурации, предоставляя доступ к постоянным значениям, если это возможно, и позволяя вызывающим сторонам анализировать все присутствующие ссылки на другие объекты:
{
// "provider_configs" describes all of the provider configurations throughout
// the configuration tree, flattened into a single map for convenience since
// provider configurations are the one concept in OpenTofu that can span
// across module boundaries.
"provider_config": {
// Keys in the provider_configs map are to be considered opaque by callers,
// and used just for lookups using the "provider_config_key" property in each
// resource object.
"opaque_provider_ref_aws": {
// "name" is the name of the provider without any alias
"name": "aws",
// "full_name" is the fully-qualified provider name
"full_name": "registry.opentofu.org/hashicorp/aws",
// "alias" is the alias set for a non-default configuration, or unset for
// a default configuration.
"alias": "foo",
// "module_address" is included only for provider configurations that are
// declared in a descendent module, and gives the opaque address for the
// module that contains the provider configuration.
"module_address": "module.child",
// "expressions" describes the provider-specific content of the
// configuration block, as a block expressions representation (see section
// below).
"expressions": <block-expressions-representation>
}
},
// "root_module" describes the root module in the configuration, and serves
// as the root of a tree of similar objects describing descendent modules.
"root_module": {
// "outputs" describes the output value configurations in the module.
"outputs": {
// Property names here are the output value names
"example": {
"expression": <expression-representation>,
"sensitive": false
}
},
// "resources" describes the "resource" and "data" blocks in the module
// configuration.
"resources": [
{
// "address" is the opaque absolute address for the resource itself.
"address": "aws_instance.example",
// "mode", "type", and "name" have the same meaning as for the resource
// portion of a value representation.
"mode": "managed",
"type": "aws_instance",
"name": "example",
// "provider_config_key" is the key into "provider_configs" (shown
// above) for the provider configuration that this resource is
// associated with. If the provider configuration was passed into
// this module from the parent module, the key will point to the
// original provider config block.
"provider_config_key": "opaque_provider_ref_aws",
// "provisioners" is an optional field which describes any provisioners.
// Connection info will not be included here.
"provisioners": [
{
"type": "local-exec",
// "expressions" describes the provisioner configuration
"expressions": <block-expressions-representation>
},
],
// "expressions" describes the resource-type-specific content of the
// configuration block.
"expressions": <block-expressions-representation>,
// "schema_version" is the schema version number indicated by the
// provider for the type-specific arguments described in "expressions".
"schema_version": 2,
// "count_expression" and "for_each_expression" describe the expressions
// given for the corresponding meta-arguments in the resource
// configuration block. These are omitted if the corresponding argument
// isn't set.
"count_expression": <expression-representation>,
"for_each_expression": <expression-representation>
},
],
// "module_calls" describes the "module" blocks in the module. During
// evaluation, a module call with count or for_each may expand to multiple
// module instances, but in configuration only the block itself is
// represented.
"module_calls": {
// Key is the module call name chosen in the configuration.
"child": {
// "resolved_source" is the resolved source address of the module, after
// any normalization and expansion. This could be either a
// go-getter-style source address or a local path starting with "./" or
// "../". If the user gave a registry source address then this is the
// final location of the module as returned by the registry, after
// following any redirect indirection.
"resolved_source": "./child"
// "expressions" describes the expressions for the arguments within the
// block that correspond to input variables in the child module.
"expressions": <block-expressions-representation>,
// "count_expression" and "for_each_expression" describe the expressions
// given for the corresponding meta-arguments in the module
// configuration block. These are omitted if the corresponding argument
// isn't set.
"count_expression": <expression-representation>,
"for_each_expression": <expression-representation>,
// "module" is a representation of the configuration of the child module
// itself, using the same structure as the "root_module" object,
// recursively describing the full module tree.
"module": <module-configuration-representation>
}
}
}
}Представление выражения
Каждое невычисленное выражение в конфигурации представлено объектом <expression-representation> со следующей структурой:
{
// "constant_value" is set only if the expression contains no references to
// other objects, in which case it gives the resulting constant value. This is
// mapped as for the individual values in a value representation.
"constant_value": "hello",
// Alternatively, "references" will be set to a list of references in the
// expression. Multi-step references will be unwrapped and duplicated for each
// significant traversal step, allowing callers to more easily recognize the
// objects they care about without attempting to parse the expressions.
// Callers should only use string equality checks here, since the syntax may
// be extended in future releases.
"references": [
"data.template_file.foo[1].vars[\"baz\"]",
"data.template_file.foo[1].vars", // implied by previous
"data.template_file.foo[1]", // implied by previous
"data.template_file.foo", // implied by previous
"module.foo.bar",
"module.foo", // implied by the previous
"var.example[0]",
"var.example", // implied by the previous
// Partial references like "data" and "module" are not included, because
// OpenTofu considers "module.foo" to be an atomic reference, not an
// attribute access.
]
}Выражения в блоках dynamic не включаются в представление конфигурации.
Представление выражений блока
В некоторых случаях необходимо представить всё содержимое блока (возможно, после обработки и удаления некоторых специальных аргументов). Для этого предусмотрена структура <block-expressions-representation>:
{
// Attribute arguments are mapped directly with the attribute name as key and
// an <expression-representation> as value.
"ami": <expression-representation>,
"instance_type": <expression-representation>,
// Nested block arguments are mapped as either a single nested
// <block-expressions-representation> or an array object of these, depending on the
// block nesting mode chosen in the schema.
// - "single" nesting is a direct <block-expressions-representation>
// - "list" and "set" produce arrays
// - "map" produces an object
"root_block_device": <expression-representation>,
"ebs_block_device": [
<expression-representation>
]
}Пока мы ожидаем, что вызывающие стороны будут задавать предположения о схемах определённых типов ресурсов непосредственно в коде, чтобы обрабатывать эти представления выражений. В одном из будущих выпусков мы добавим новые команды для проверки, которые будут возвращать машиночитаемые описания самих схем и позволят более универсально обрабатывать их в таких программах, как инструменты визуализации.
Представление изменения
Объект <change-representation> описывает изменение указанного объекта.
{
// "actions" are the actions that will be taken on the object selected by the
// properties below.
// Valid actions values are:
// ["no-op"]
// ["create"]
// ["read"]
// ["update"]
// ["delete", "create"]
// ["create", "delete"]
// ["delete"]
// ["forget"]
// The two "replace" actions are represented in this way to allow callers to
// e.g. just scan the list for "delete" to recognize all three situations
// where the object will be deleted, allowing for any new deletion
// combinations that might be added in future.
"actions": ["update"],
// Before and After are representations of the object value both before and
// after the action. For ["delete"] and ["forget"] actions, the "after"
// value is unset. For ["create"] the "before" is unset. For ["no-op"], the
// before and after values are identical. The "after" value will be
// incomplete if there are values within it that won't be known until after
// apply.
"before": <value-representation>,
"after": <value-representation>,
// "after_unknown" is an object value with similar structure to "after", but
// with all unknown leaf values replaced with "true", and all known leaf
// values omitted. This can be combined with "after" to reconstruct a full
// value after the action, including values which will only be known after
// apply.
"after_unknown": {
"id": true
},
// "before_sensitive" and "after_sensitive" are object values with similar
// structure to "before" and "after", but with all sensitive leaf values
// replaced with true, and all non-sensitive leaf values omitted. These
// objects should be combined with "before" and "after" to prevent accidental
// display of sensitive values in user interfaces.
"before_sensitive": {},
"after_sensitive": {
"triggers": {
"boop": true
}
},
// "replace_paths" is an array of arrays representing a set of paths into the
// object value which resulted in the action being "replace". This will be
// omitted if the action is not replace, or if no paths caused the
// replacement (for example, if the resource was tainted). Each path
// consists of one or more steps, each of which will be a number or a
// string.
"replace_paths": [["triggers"]]
}Представление проверок
Представление проверок в формате JSON является экспериментальным; некоторые детали могут измениться в будущих версиях OpenTofu с учётом отзывов, в том числе в минорных выпусках OpenTofu CLI.
Объект <checks-representation> описывает текущее состояние проверяемого объекта в конфигурации. Например, ресурс с одним или несколькими предварительными условиями или постусловиями является проверяемым объектом, а его состояние проверки отражает результаты этих условий.
[
{
// "address" describes the address of the checkable object whose status
// this object is describing.
"address": {
// "kind" specifies what kind of checkable object this is. Different
// kinds of object will have different additional properties inside the
// address object, but all kinds include both "kind" and "to_display".
// The two valid kinds are "resource" and "output_value".
"kind": "resource",
// "to_display" contains an opaque string representation of the address
// of the object that is suitable for display in a UI. For consumers that
// have special handling depending on the value of "kind", this property
// is a good fallback to use when the application doesn't recognize the
// "kind" value.
"to_display": "aws_instance.example",
// "mode" is included for kind "resource" only, and specifies the resource
// mode which can either be "managed" (for "resource" blocks) or "data"
// (for "data" blocks).
"mode": "managed",
// "type" is included for kind "resource" only, and specifies the resource
// type.
"type": "aws_instance",
// "name" is the local name of the object. For a resource this is the
// second label in the resource block header, and for an output value
// this is the single label in the output block header.
"name": "example",
// "module" is included if the object belongs to a module other than
// the root module, and provides an opaque string representation of the
// module this object belongs to. This example is of a root module
// resource and so "module" is not included.
}
// "status" is the aggregate status of all of the instances of the object
// being described by this object.
// The possible values are "pass", "fail", "error", and "unknown".
"status": "fail",
// "instances" describes the current status of each of the instances of
// the object being described. An object can have multiple instances if
// it is either a resource which has "count" or "for_each" set, or if
// it's contained within a module that has "count" or "for_each" set.
//
// If "instances" is empty or omitted, that can either mean that the object
// has no instances at all (e.g. count = 0) or that an error blocked
// evaluation of the repetition argument. You can distinguish these cases
// using the "status" property, which will be "pass" or "error" for a
// zero-instance object and "unknown" for situations where an error blocked
// evaluation.
"instances": [
{
// "address" is an object similar to the property of the same name in
// the containing object. Merge the instance-level address into the
// object-level address, overwriting any conflicting property names,
// to create a full description of the instance's address.
"address": {
// "to_display" overrides the property of the same name in the main
// object's address, to include any module instance or resource
// instance keys that uniquely identify this instance.
"to_display": "aws_instance.example[0]",
// "instance_key" is included for resources only and specifies the
// resource-level instance key, which can either be a number or a
// string. Omitted for single-instance resources.
"instance_key": 0,
// "module" is included if the object belongs to a module other than
// the root module, and provides an opaque string representation of the
// module instance this object belongs to.
},
// "status" describes the result of running the configured checks
// against this particular instance of the object, with the same
// possible values as the "status" in the parent object.
//
// "fail" means that the condition evaluated successfully but returned
// false, while "error" means that the condition expression itself
// was invalid.
"status": "fail",
// "problems" might be included for statuses "fail" or "error", in
// which case it describes the individual conditions that failed for
// this instance, if any.
// When a condition expression is invalid, OpenTofu returns that as
// a normal error message rather than as a problem in this list.
"problems": [
{
// "message" is the string that resulted from evaluating the
// error_message argument of the failing condition.
"message": "Server does not have a public IPv6 address."
}
]
},
]
}
]Модель «checks» включает как статические проверяемые объекты, так и экземпляры этих объектов, чтобы набор проверяемых объектов оставался согласованным даже в случае, если ошибка не позволяет полностью вычислить конфигурацию. Любой объект конфигурации, с которым связаны проверки, например ресурс с предварительными условиями или постусловиями, всегда включается в число проверяемых объектов, даже если ошибка времени выполнения не позволяет OpenTofu вычислить его аргумент «count» или «for_each» и, следовательно, определить, какие экземпляры этого объекта существуют динамически.
При отображении сводки проверок в пользовательском интерфейсе мы рекомендуем перечислять только отдельные экземпляры и обычно не показывать объекты верхнего уровня. Однако если у объекта нет ни одного экземпляра, интерфейс должен отображать сам объект верхнего уровня в качестве заполнителя, чтобы пользователь видел, что OpenTofu распознал наличие проверок, даже если во время последнего запуска не смог их вычислить.
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.9/internals/json-format/