From c060ecd0a520f5b4e2c6a58cead1d6a9cb490c29 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Sun, 13 May 2018 16:36:51 -0700 Subject: [PATCH] website: Document the JSON configuration syntax --- website/docs/configuration/index.html.md | 2 +- .../docs/configuration/syntax-json.html.md | 461 ++++++++++++++++++ 2 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 website/docs/configuration/syntax-json.html.md diff --git a/website/docs/configuration/index.html.md b/website/docs/configuration/index.html.md index a7dc5abf1..f420cc6ff 100644 --- a/website/docs/configuration/index.html.md +++ b/website/docs/configuration/index.html.md @@ -32,7 +32,7 @@ another. ## Code Organization The Terraform language uses configuration files that are named with the `.tf` -file extension. There is also [a JSON-based variant of the language](/docs/configuration/json-syntax.html) +file extension. There is also [a JSON-based variant of the language](/docs/configuration/syntax-json.html) that is named with the `.tf.json` file extension. Configuration files must always use UTF-8 encoding, and by convention are diff --git a/website/docs/configuration/syntax-json.html.md b/website/docs/configuration/syntax-json.html.md new file mode 100644 index 000000000..bb4333344 --- /dev/null +++ b/website/docs/configuration/syntax-json.html.md @@ -0,0 +1,461 @@ +--- +layout: "docs" +page_title: "JSON Configuration Syntax" +sidebar_current: "docs-config-json-syntax" +description: |- + In addition to the native syntax that is most commonly used with Terraform, + the Terraform language can also be expressed in a JSON-compatible syntax. +--- + +# JSON Configuration Syntax + +Most Terraform configurations are written in +[the native Terraform language syntax](./syntax.html), which is designed to be +easy for humans to read and update. + +Terraform also supports an alternative syntax that is JSON-compatible. This +syntax is useful when generating portions of a configuration programmatically, +since existing JSON libraries can be used to prepare the generated +configuration files. + +The JSON syntax is defined in terms of the native syntax. Everything that can +be expressed in native syntax can also be expressed in JSON syntax, but some +constructs are more complex to represent in JSON due to limitations of the +JSON grammar. + +Terraform expects native syntax for files named with a `.tf` suffix, and +JSON syntax for files named with a `.tf.json` suffix. + +The low-level JSON syntax, just as with the native syntax, is defined in terms +of a specification called _HCL_. It is not necessary to know all of the details +of HCL syntax or its JSON mapping in order to use Terraform, and so this page +summarizes the most important differences between native and JSON syntax. +If you are interested, you can find a full definition of HCL's JSON syntax +in [its specification](https://github.com/hashicorp/hcl2/blob/master/hcl/json/spec.md). + +## JSON File Structure + +At the root of any JSON-based Terraform configuration is a JSON object. The +properties of this object correspond to the top-level block types of the +Terraform language. For example: + +```json +{ + "variable": { + "example": { + "default": "hello" + } + } +} +``` + +Each top-level object property must match the name of one of the expected +top-level block types. Block types that expect labels, such as `variable` +shown above, are represented by one nested object value for each level +of label. `resource` blocks expect two labels, so two levels of nesting +are required: + +```json +{ + "resource": { + "aws_instance": { + "example": { + "instance_type": "t2.micro", + "ami": "ami-abc123" + } + } + } +} +``` + +After any nested objects representing the labels, finally one more nested +object represents the body of the block itself. In the above examples, the +`default` argument for `variable "example"` and the `instance_type` and +`ami` arguments for `resource "aws_instance" "example"` are specified. + +Taken together, the above two configuration files are equivalent to the +following blocks in the native syntax: + +```hcl +variable "example" { + default = "hello" +} + +resource "aws_instance" "example" { + instance_type = "t2.micro" + ami = "ami-abc123" +} +``` + +Within each top-level block type the rules for mapping to JSON are slightly +different, but the following general rules apply in most cases: + +* The JSON object representing the block body contains properties that + correspond either to attribute arguments names or to nested block type names. + +* Where a property corresponds to an attribute argument that accepts + [arbitrary expressions](./expressions.html) in the native syntax, the + property value is mapped to an expression as described under + [_Expression Mapping_](#expression-mapping) below. For arguments that + do _not_ accept arbitrary expressions, the interpretation of the property + value depends on the argument, as described in [the block-type-specific exceptions](#block-type-specific-exceptions) + given later in this page. + +* Where a property name corresponds to an expected nested block type name, + the value is interpreted as described under + [_Nested Block Mapping_](#nested-block-mapping) below, unless otherwise + stated in [the block-type-specific exceptions](#block-type-specific-exceptions) + given later in this page. + +## Expression Mapping + +Since JSON grammar is not able to represent all of the Terraform language +[expression syntax](./expressions.html), JSON values interpreted as expressions +are mapped as follows: + +| JSON | Terraform Language Interpretation | +| ------- | ------------------------------------------------------------------------------------------------------------- | +| Boolean | A literal `bool` value. | +| Number | A literal `number` value. | +| String | Parsed as a [string template](./expressions.html#string-templates) and then evaluated as described below. | +| Object | Each property value is mapped per this table, producing an `object(...)` value with suitable attribute types. | +| Array | Each element is mapped per this table, producing a `tuple(...)` value with suitable element types. | +| Null | A literal `null`. | + +When a JSON string is encountered in a location where arbitrary expressions are +expected, its value is first parsed as a [string template](./expressions.html#string-templates) +and then it is evaluated to produce the final result. + +If the given template consists _only_ of a single interpolation sequence, +the result of its expression is taken directly, without first converting it +to a string. This allows non-string expressions to be used within the +JSON syntax: + +```json +{ + "output": { + "example": { + "value": "${aws_instance.example}" + } + } +} +``` + +The `output "example"` declared above has the object value representing the +given `aws_instance` resource block as its value, rather than a string value. +This special behavior does not apply if any literal or control sequences appear +in the template; in these other situations, a string value is always produced. + +## Nested Block Mapping + +When a JSON object property is named after a nested block type, the value +of this property represents one or more blocks of that type. The value of +the property must be either a JSON object or a JSON array. + +The simplest situation is representing only a single block of the given type +when that type expects no labels, as with the `lifecycle` nested block used +within `resource` blocks: + +```json +{ + "resource": { + "aws_instance": { + "example": { + "lifecycle": { + "create_before_destroy": true + } + } + } + } +} +``` + +The above is equivalent to the following native syntax configuration: + +```hcl +resource "aws_instance" "example" { + lifecycle { + create_before_destroy = true + } +} +``` + +When the nested block type requires one or more labels, or when multiple +blocks of the same type must be given, the mapping gets a little more +complicated. For example, the `provisioner` nested block type used +within `resource` blocks expects a label giving the provisioner to use, +and the ordering of provisioner blocks is significant to decide the order +of operations. + +The following native syntax example shows a `resource` block with a number +of provisioners of different types: + +```hcl +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", + ] + } +} +``` + +In order to preserve the order of these blocks, you must use a JSON array +as the direct value of the property representing this block type, as in +this JSON equivalent of the above: + +```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"] + } + } + ] + } + } + } +} +``` + +Each element of the `provisioner` array is an object with a single property +whose name represents the label for each `provisioner` block. For block types +that expect multiple labels, this pattern of alternating array and object +nesting can be used for each additional level. + +If a nested block type requires labels but the order does _not_ matter, you +may omit the array and provide just a single object whose property names +correspond to unique block labels. This is allowed as a shorthand for the above +for simple cases, but the alternating array and object approach is the most +general. We recommend using the most general form if systematically converting +from native syntax to JSON, to ensure that the meaning of the configuration is +preserved exactly. + +### Comment Properties + +Although we do not recommend hand-editing of JSON syntax configuration files +-- this format is primarily intended for programmatic generation and consumption -- +a limited form of _comments_ are allowed inside JSON objects that represent +block bodies using a special property name: + +```json +{ + "resource": { + "aws_instance": { + "example": { + "//": "This instance runs the scheduled tasks for backup", + + "instance_type": "t2.micro", + "ami": "ami-abc123" + } + } + } +} +``` + +In any object that represents a block body, properties named `"//"` are +ignored by Terraform entirely. This exception does _not_ apply to objects +that are being [interpreted as expressions](#expression-mapping), where this +would be interpreted as an object type attribute named `"//"`. + +This special property name can also be used at the root of a JSON-based +configuration file. This can be useful to note which program created the file. + +```json +{ + "//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!", + + "output": { + "example": { + "value": "${aws_instance.example}" + } + } +} +``` + +## Block-type-specific Exceptions + +Certain arguments within specific block types are processed in a special way +by Terraform, and so their mapping to the JSON syntax does not follow the +general rules described above. The following sub-sections describe the special +mapping rules that apply to each top-level block type. + +### `resource` and `data` blocks + +Some meta-arguments for the `resource` and `data` block types take direct +references to objects, or literal keywords. When represented in JSON, the +reference or keyword is given as a JSON string with no additonal surrounding +spaces or symbols. + +For example, the `provider` meta-argument takes a special compact provider +configuration reference, which appears directly in the native syntax but must +be presented as a string in the JSON syntax: + +```json +{ + "resource": { + "aws_instance": { + "example": { + "provider": "aws.foo" + } + } + } +} +``` + +This special processing applies to the following meta-arguments: + +* `provider`: a single string, as shown above +* `depends_on`: an array of strings containing object references, like + `["aws_instance.example"]`. +* `ignore_changes` within the `lifecycle` block: if set to `all`, a single + string `"all"` must be given. Otherwise, an array of JSON strings containing + property references must be used, like `["ami"]`. + +Special processing also applies to the `type` argument of any `connection` +blocks, whether directly inside the `resource` block or nested inside +`provisioner` blocks: the given string is interpreted literally, and not +parsed and evaluated as a string template. + +### `variable` blocks + +All arguments inside `variable` blocks have non-standard mappings to JSON: + +* `type`: a string containing a type expression, like `"string"` or `"list(string)"`. +* `default`: a literal JSON value that can be converted to the given type. + Strings within this value are taken literally and _not_ interpreted as + string templates. +* `description`: a literal JSON string, _not_ interpreted as a template. + +```json +{ + "variable": { + "example": { + "type": "string", + "default": "hello" + } + } +} +``` + +### `output` blocks + +The `description` and `sensitive` arguments are interpreted as literal JSON +values. The `description` string is not interpreted as a string template. + +The `value` argument is [interpreted as an expression](#expression-mapping). + +```json +{ + "output": { + "example": { + "value": "${aws_instance.example}" + } + } +} +``` + +### `locals` blocks + +The value of the JSON object property representing the locals block type +must be a JSON object whose property names are the local value names to +declare: + +```json +{ + "locals": { + "greeting": "Hello, ${var.name}" + } +} +``` + +The value of each of these nested properties is +[interpreted as an expression](#expression-mapping). + +### `module` blocks + +The `source` and `version` meta-arguments must be given as literal strings. The +values are not interpreted as string templates. + +The `providers` meta-argument must be given as a JSON object whose properties +are the compact provider addresses to expose into the child module and whose +values are the provider addresses to use from the current module, both +given as literal strings: + +```json +{ + "module": { + "example": { + "source": "hashicorp/consul/azurerm", + "version": "= 1.0.0", + "providers": { + "aws": "aws.usw1" + } + } + } +} +``` + +### `provider` blocks + +The `alias` and `version` meta-arguments must be given as literal strings. The +values are not interpreted as string templates. + +```json +{ + "provider": { + "aws": { + "alias": "usw1", + "region": "us-west-1" + } + } +} +``` + +### `terraform` blocks + +Since no settings within `terraform` blocks accept named object references or +function calls, all setting values are taken literally. String values are not +interpreted as string templates. + +Since only one `backend` block is allowed per `terraform` block, the compact +block mapping can be used to represent it, with a nested object containing +a single property whose name represents the backend type. + +```json +{ + "terraform": { + "required_version": ">= 0.12.0", + "backend": { + "s3": { + "region": "us-west-2", + "bucket": "acme-terraform-states" + } + } + } +} +```