How to refer to variables in a JSON configuration file

Is it possible to use variables within the json file example
“security_groups”: “module.nsg.security_groups.id”

When I try, it just passes the string module.nsg.security_groups.id I also tried ${module.nsg.security_groupds.id)

Hi @brandootr! You bumped a very old topic and so I’ve moved your question into a separate topic so we can discuss it without creating notification noise for those who participated in the original topic.

I assume you are talking about the JSON variant of the Terraform language, which is to say that you are writing or generating .tf.json files to define your infrastructure objects.

In any situation where the native syntax would expect a general value expression (which includes all normal resource arguments), the Expression Mapping describes how Terraform translates your JSON input into corresponding concepts from the native syntax.

Notice that the description of how Terraform interprets a string value says:

Parsed as a string template and then evaluated as described below.

The subsequent paragraphs then go on to show an example of referring to another object using template interpolation inside a JSON string:

{
  "output": {
    "example": {
      "value": "${aws_instance.example}"
    }
  }
}

The above is showing a JSON declaration of an output block, whose native syntax equivalent would be the following:

output "example" {
  value = aws_instance.example
}

From your question it sounds like you already tried something like this and encountered an error. If the above doesn’t answer your question, it would be helpful if you could show the full, exact configuration you tried (the one where you tried ${module.nsg.security_groupds.id) and the full error message Terraform returned in response to it, and then I can hopefully understand and explain why your particular attempt didn’t work.

My main.tf looks like this

data "template_file" "instance" {
  template = "${file("${path.module}/instance.json")}"

}
locals {
  instance = data.template_file.instance
}

My instance.json looks like this

{
    "UATDB01": {
        "AZ": "$${data.oci_identity_availability_domain.AD-1}",
        "env_number": "1",
        "instance_shape_config_memory_in_gbs": "60",
        "subnet_id": "$${data.oci_core_subnets.internal1.subnets[0].id}",
        "fault_domain": "FAULT-DOMAIN-1",
        "image_id": "$${data.oci_core_images.this.images[0].id}",
        "ocpu_count": "4",
        "shape": "VM.Standard.E3.Flex",
        "public_ip": "false",
        "security_groups": "$${module.nsg.security_groups.id}"
    },
    "UATDB02": {
        "AZ": "$${data.oci_identity_availability_domain.AD-1}",
        "env_number": "1",
        "instance_shape_config_memory_in_gbs": "60",
        "subnet_id": "$${data.oci_core_subnets.internal1.subnets[0].id}",
        "fault_domain": "FAULT-DOMAIN-1",
        "image_id": "$${data.oci_core_images.this.images[0].id}",
        "ocpu_count": "4",
        "shape": "VM.Standard.E3.Flex",
        "public_ip": "false",
        "security_groups": "$${module.nsg.security_groups.id}"
    }
}

My Module looks like this

module "instance" {
  source          = “”
  for_each        = data.template_file.instance
  compartment_id  = oci_identity_compartment.this.id
  fault_domain    = each.value.fault_domain
  AZ              = "${each.value.AZ}"
  shape           = each.value.shape
  security_groups = each.value.security_groups
  tags                                = module.environment.tags
  name                                = each.key
  instance_shape_config_memory_in_gbs = each.value.instance_shape_config_memory_in_gbs
  user_data                           = module.cloud_init.windows
  subnet_id                           = each.value.subnet_id
  image_id                            = each.value.image_id
  ocpu_count                          = each.value.ocpu_count
}

When I execute a Terraform plan I get this

168:   ocpu_count                          = each.value.ocpu_count
    ├────────────────
    │ each.value is "{\r\n    \"UATDB01\": {\r\n        \"AZ\": \"$${data.oci_identity_availability_domain.AD-1}\",\r\n        \"env_number\": \"1\",\r\n        \"instance_shape_config_memory_in_gbs\": \"60\",\r\n        \"subnet_id\": \"$${data.oci_core_subnets.internal1.subnets[0].id}\",\r\n        \"fault_domain\": \"FAULT-DOMAIN-1\",\r\n     
   \"image_id\": \"$${data.oci_core_images.this.images[0].id}\",\r\n        \"ocpu_count\": \"4\",\r\n        \"shape\": \"VM.Standard.E3.Flex\",\r\n        \"public_ip\": \"false\",\r\n        \"security_groups\": \"$${module.nsg.security_groups.id}\"\r\n    },\r\n    \"UATDB02\": {\r\n        \"AZ\": \"$${data.oci_identity_availability_domain.AD-1}\",\r\n        \"env_number\": \"1\",\r\n        \"instance_shape_config_memory_in_gbs\": \"60\",\r\n        \"subnet_id\": \"$${data.oci_core_subnets.internal1.subnets[0].id}\",\r\n        \"fault_domain\": \"FAULT-DOMAIN-1\",\r\n        \"image_id\": \"$${data.oci_core_images.this.images[0].id}\",\r\n        \"ocpu_count\": \"4\",\r\n      
  \"shape\": \"VM.Standard.E3.Flex\",\r\n        \"public_ip\": \"false\",\r\n        \"security_groups\": \"$${module.nsg.security_groups.id}\"\r\n    }\r\n}"

Can't access attributes on a primitive-typed value (string).

Also, I am using Terraform version v1.0.9 for Windows.

Hi @brandootr,

What you have here is not a JSON Configuration File, but rather a Terraform template that happens to generate JSON. That might seem like a pedantic distinction to make, but it’s particularly important here because Terraform has no awareness that this template is generating JSON, and is instead just treating it like any other string template.

The relevant documentation is therefore string template syntax, rather than the JSON Configuration Syntax I linked before.

With that said, I see that you are using the deprecated template_file data source here, rather than the current templatefile function. The documentation for the current function includes a section specifically about generating JSON or YAML from a template, which shows our recommended way to do it, which allows you to describe the data structure using Terraform’s expression syntax rather than Terraform’s template syntax, and can therefore avoid annoying escaping, quoting, and delimiter-related problems.

First let’s see what it looks like to remove the deprecated template_file data source and use the templatefile function instead. The following configuration replaces what you showed as being in your main.tf file:

locals {
  instance = templatefile("${path.module}/instance.json", {})
}

If you just change the above then I expect you’ll see just a slightly different version of the same error message you saw before, because Terraform is still parsing that file with the same template syntax, but now the parsing is happening inside Terraform itself rather than in the deprecated hashicorp/template provider.

From here there are two more problems to solve:

  1. Passing the necessary data into the template rendering, so you can refer to it. Terraform templates are evaluated in their own separate namespace and so by default there are no variables available, but you can pass in extra variables using the second argument to templatefile, which I set to just {} (an empty object) in the above example.
  2. Making the template use the recommended pattern so you can use Terraform expression syntax to generate the data structure and then have Terraform itself encode it as JSON for you, so that the JSON syntax will be guaranteed correct.

For the first of those, this template seems to need a few different values and so the goal is to pass each of those values into that second argument of templatefile using names that will make sense in the context of the template. I don’t know enough about your underlying system to know what names to pick here and so I’m going to make a guess here but you should of course feel free to rename and adjust here as needed. Again, this replaces my previous example, which in turn replaced your main.tf example:

locals {
  instance = templatefile("${path.module}/instance.json", {
    az                 = data.oci_identity_availability_domain.AD-1
    subnet_id          = data.oci_core_subnets.internal1.subnets[0].id
    image_id           = data.oci_core_images.this.images[0].id
    security_group_ids = module.nsg.security_groups.ids
  })
}

Notice that because the template namespace is separate from the Terraform module namespace, we can assign shorter meaningful names to each of the values passed in, which is what we’ll then use inside the template.

The instance.json file would then look something like the following, based on the advice on the templatefile documentation page about generating JSON:

${jsonencode({
  UATDB01 = {
    AZ                                  = az
    env_number                          = "1"
    instance_shape_config_memory_in_gbs = "60"
    subnet_id                           = subnet_id
    fault_domain                        = "FAULT-DOMAIN-1"
    image_id                            = image_id
    ocpu_count                          = "4"
    shape                               = "VM.Standard.E3.Flex"
    public_ip                           = "false"
    security_groups                     = security_group_ids
  }
  UATDB02 = {
    AZ                                  = az
    env_number                          = "1"
    instance_shape_config_memory_in_gbs = "60"
    subnet_id                           = subnet_id
    fault_domain                        = "FAULT-DOMAIN-1"
    image_id                            = image_id
    ocpu_count                          = "4"
    shape                               = "VM.Standard.E3.Flex"
    public_ip                           = "false"
    security_groups                     = security_group_ids
  }
})}

Notice that this template is really just one big interpolation sequence ${ ... }, and so the result of the template is just the result if the expression inside. In this case it immediately calls jsonencode, and so the result is a JSON string.

The argument to jsonencode is any valid Terraform expression, whose result Terraform will convert to JSON using the mapping table in the jsonencode documentation, and so the result should be the same JSON data structure as described in your original example.

This is awesome information. Thank you so much. Let me make some modifications and see if I can get this to work.