Templatefile JSON input for aws tag policy

I’m trying to create a tag policy of using “aws_organizations_policy” resource. I’m trying to create this structure:

    {
        "tags": {
            "tag1": {
                "tag_key": {
                    "@@assign": "tag1",
                    "@@operators_allowed_for_child_policies": ["@@none"]
                }
            },
            "tag2": {
                "tag_key": {
                    "@@assign": "tag2",
                    "@@operators_allowed_for_child_policies": ["@@none"]
                }
            }
        }
    }

I want to create this tag policy structure based on a list of tags as input, and I’ve tried using the templatefile function with a for directive to create new “tag-blocks” for each tag in the list.
this is my tags.tmpl file:

    {
        "tags": {
            %{ for tag in tags ~}
            "${tag}": {
                "tag_key": {
                    "@@assign": "${tag}",
                    "@@operators_allowed_for_child_policies": ["@@none"]
                }
            },
            %{ endfor ~}
        }
    }

In the configuration I’m calling the function like this:

standard_tags_policy = templatefile("${path.module}/templates/tags.tmpl", { tags = var.standard_tags })

standard_tags is a list of tag keys. the “standard_tags_policy” is a local variable which I place in the resource configuration like this:

content = <<CONTENT
${local.standard_tags_policy}
CONTENT

I get this error:
Error: “content” contains an invalid JSON: invalid character ‘}’ looking for beginning of object key string

One error I’ve found in the .tmpl I’ve written is that the last “tag-black” will end with a comma which is incorrect json. I’ve read jsonencode in the .tmpl file might be easier to use for this scenario, but I don’t understand how I can use the “for expression” as the example shows rather than the “for directive” which I’ve made use of? Hope this makes sense, and I really hope someone can help me out:)

Hi @hansarh,

As you’ve seen, generating JSON from string concatenation (like templates) is hard because JSON requires exact handling of delimiters like commas.

I see you’ve read the recommendation to use Terraform’s jsonencode function, which (because it’s specialized for JSON in particular) can automatically handle the JSON syntax details for you, leaving you just to construct a suitable data structure.

The jsonencode function is available in templates, so you can still keep the input in a separate template file if you like, by calling jsonencode as the whole content of the template in this way:

${jsonencode({
  tags = {
    for tag in tags : tag => {
      tag_key = {
        "@@assign" = tag
        "@@operators_allowed_for_child_policies" = ["@@none"]
      }
    }
  }
})}

The above uses the Terraform language’s for expressions to construct a new object value based on your given var.standard_tags list value. That produces a Terraform value which jsonencode can then convert into the equivalent JSON value using the type mapping table shown on the jsonencode page: Terraform maps and objects become JSON objects, ann Terraform lists, tuples, and sets become JSON arrays. That should therefore produce the result you were looking for.

A for expression follows a similar principle than the template for directive you used. The difference is that the template for always produces a sequence of strings and concatenates them together to produce a string result, whereas the for expression is more general and can produce maps or lists with element values of any type.

1 Like

Thank you @apparentlymart ! this was very helpful! I think it would also be helpful if the jsonencode documentation showed some more advanced examples. I see the for expression documentation shows some examples, but I struggled a bit to connect the two.

@hansarh did this actually work for you? I followed your exact steps and I still receive a

Error: error creating Organizations Policy (tag-policy): MalformedPolicyDocumentException: The provided policy document does not meet the requirements of the specified policy type. on 06_tag_policies.tf line 7, in resource "aws_organizations_policy" "tag-policy": 7: resource "aws_organizations_policy" "tag-policy" {

Ok I found out that the issue was simply that I didn’t set

type = “TAG_POLICY”

That did the trick, since the default type is SCP_POLICY, which has a different syntax then TAG_POLICY