1

I'm looking for a solution for this template to get the correct JSON file. As you may see in the sections, aws:RequestTag/*** lacks commas at the end. If I use the comma in the template, then I will have an unnecessary comma at the end of the last string.

I wonder the ${jsonencode()} should help, but I'm still not realizing how it's using with %{ for key in key_tag ~} together.

I would be appreciated for any help.

Terraform:

resource "local_file" "enforcetags" {
  content = templatefile("${path.module}/enforcetags.tpl",
    {
      key_tag = ["development_prod", "production_prod", "rnd_prod"]
    }
  )
  filename = "./enforce_tags.json"
}

Template:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": "ec2:*",
                "Resource": "*",
                "Condition": {
                    "ForAllValues:StringEquals": {
                        "aws:TagKeys": ${jsonencode([for key in key_tag : "${key}"])}
                    },
                    "StringEqualsIfExists": {
%{ for key in key_tag ~}
                    "aws:RequestTag/${key}": ${jsonencode([for key in key_tag : "${key}"])}
%{ endfor ~}
                    }
                }
            }
        ]
    }

Output:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": "ec2:*",
                "Resource": "*",
                "Condition": {
                    "ForAllValues:StringEquals": {
                        "aws:TagKeys": ["development_prod","production_prod","rnd_prod"]
                    },
                    "StringEqualsIfExists": {

                       "aws:RequestTag/development_prod": ["development_prod","production_prod","rnd_prod"]
                       "aws:RequestTag/production_prod": ["development_prod","production_prod","rnd_prod"]
                       "aws:RequestTag/rnd_prod": ["development_prod","production_prod","rnd_prod"]
                    
                    }
                }
            }
        ]
    }
Marcin
  • 215,873
  • 14
  • 235
  • 294
  • What's wrong with the code and your output? – Marcin Nov 25 '21 at 22:31
  • This doesn't work for me: ${jsonencode( %{ for key in key_tag ~} somecode..... I.e. I can't encode to JSON the block generated from %{ for key in key_tag ~} – Rostyslav Malenko Nov 26 '21 at 07:52
  • Sorry, its not clear. Why you can't? You haven't showed any TF code, nor provided any error messages which could clarify what you are doing and why it does not work. – Marcin Nov 26 '21 at 07:55
  • Sorry, I have added terraform code. JSON file generated but with error in block "StringEqualsIfExists": {. Strings should have a comma at the end, except the last string. My problem is these strings don't have commas or all strings with a comma. – Rostyslav Malenko Nov 26 '21 at 08:08

2 Answers2

2

To have a comma at the end, except the last string it should be:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": "ec2:*",
                "Resource": "*",
                "Condition": {
                    "ForAllValues:StringEquals": {
                        "aws:TagKeys": ${jsonencode([for key in key_tag : "${key}"])}
                    },
                    "StringEqualsIfExists": ${jsonencode(
                                {for key in key_tag: "aws:RequestTag/${key}" => key_tag})}
                }
            }
        ]
    }
Marcin
  • 215,873
  • 14
  • 235
  • 294
2

Here you seem to have run into the typical challenges of trying to generate valid JSON using string concatenation, which is what template interpolation effectively is. This problem is common enough to have a dedicated section in the templatefile documentation page: Generating JSON or YAML from a template.

You can avoid this sort of friction by following the advice in that section and generating the entire JSON data structure with jsonencode, rather than just some sub-sections of it as you did in the example you shared. You can use Terraform's normal expression operators to generate dynamic parts as needed. For example:

${jsonencode({
  Version = "2012-10-17"
  Statement = [
    {
      Sid      = "VisualEditor0"
      Effect   = "Allow"
      Action   = "ec2:*"
      Resource = "*"
      Condition = {
        "ForAllValues:StringEquals" = {
          "aws:TagKeys" = key_tag
        },
        "StringEqualsIfExists": {
          for key in key_tag : "aws:RequestTag/${key}" => key_tag
        }
      }
    }
  ],
})}

With this approach you define the data structure using Terraform syntax instead of JSON syntax, and let jsonencode be responsible for encoding it to the equivalent valid JSON. jsonencode is guaranteed to always produce valid JSON, including commas in all of the expected places, and Terraform's object syntax doesn't require commas between attribute definitions anyway so you don't need to worry about writing them yourself.

(I also simplified your for expressions a little in the above, although that wasn't directly related to your question. In particular, if key_tag is already a sequence type anyway then [for key in key_tag : "${key}"] is just the same as writing key_tag directly, because there's no actual transformation occurring there.)

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138