Search code examples
terraformamazon-iamhclterraform-template-file

terraform template if condition to add comma to iam statement


❯ terraform version
Terraform v1.6.3
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v5.29.0

I working on terraforming AWS IAM trust policies and am tempalating the trust IAM policy which looks like this

{

    "Statement": [
        %{ for oidc in oidcs ~}
        {
                    "${oidc}:aud": "sts.amazon.com",
                    "${oidc}:sub": "system:serviceaccount:${sa}"
        },
        %{ endfor ~}
    ]
}

I am using this template with the terraform template function which looks like

locals {
  backend_config_content = templatefile("${path.module}/json/template.tpl", {
    sa     = "kube-system:ebs-csi-controller-sa",
    account = "804616344559",
    oidcs = [
        "id/4ECE95AF53127CE866BA", 
        "id/9173553CA62864"
        ]
  })
}

output "generated_trust_policy" {
  value = local.backend_config_content
}

only thing is i am trying to figure out how to remove the comma just before the end of the loop if it is the last item in the list, otherwise it is not a validy policy that can be added to AWS IAM.

I tried adding the following if statement


{

    "Statement": [
        %{ for oidc in oidcs ~}
        {
                    "${oidc}:aud": "sts.amazon.com",
                    "${oidc}:sub": "system:serviceaccount:${sa}"
        } %{ "," if not loop.last ~}
        %{ endfor ~}
    ]
}

but doing it this way i get an error saying

 Call to function "templatefile" failed: ./json/template.tpl:17,14-15: Invalid template directive; A
│ template directive keyword ("if", "for", etc) is expected at the beginning of a %{ sequence..

also tried the following way


{

    "Statement": [
        %{ for oidc in oidcs ~}
        {
                    "${oidc}:aud": "sts.amazon.com",
                    "${oidc}:sub": "system:serviceaccount:${sa}"
        }%{ if not loop.last ~}
            ,
        %{ endif ~}
        %{ endfor ~}
    ]
}

gives me the error

Call to function "templatefile" failed: ./json/template.tpl:8,21-25: Extra characters in if marker;
│ Expected a closing brace to end the sequence, but found extra characters..

@martinatkins I have attempted to do as you suggested, have copied and pasted exactly and still am getting the the final comma

below is the output from the terraform plan

❯ terraform plan

Changes to Outputs:
  + iam = jsonencode(
        {
          + Statement = [
              + {
                  + "id/4ECE95AF53127CE866BA:aud" = "sts.amazon.com"
                  + "id/4ECE95AF53127CE866BA:sub" = "system:serviceaccount:kube-system:ebs-csi-controller-sa"
                },
              + {
                  + "id/9173553CA62864:aud" = "sts.amazon.com"
                  + "id/9173553CA62864:sub" = "system:serviceaccount:kube-system:ebs-csi-controller-sa"
                },
            ]
        }
    )

Solution

  • You seem to have encountered the problem that is warned about in Generating JSON or YAML from a template:

    If the string you want to generate will be in JSON or YAML syntax, it's often tricky and tedious to write a template that will generate valid JSON or YAML that will be interpreted correctly when using lots of individual interpolation sequences and directives.

    Instead, you can [...]

    I would suggest abandoning your attempts to construct JSON by string concatenation and instead following the advice in that section of using Terraform's jsonencode function, which guarantees that the result will always be valid JSON without you having to worry about pesky details like trailing commas.

    The template you included in your question is not a complete and valid IAM policy, so I can't show a complete and valid example, but here's a template which should generate a result similar to the subset you showed in your example, and hopefully from here you can see how to extend it to produce a fully-specified IAM policy document:

    ${jsonencode({
      Statement = [
        for oidc in oidcs : {
          "${oidc}:aud" = "sts.amazon.com"
          "${oidc}:sub" = "system:serviceaccount:${sa}"
        }
      ]
    })}
    

    This works by constructing a value using normal Terraform expressions and then encoding that value using jsonencode. The jsonencode function knows how to insert commas correctly between object properties and array elements, and so the result should be valid as long as the value you've described has the same structure as a valid IAM policy document.


    For AWS IAM policy documents in particular, there's also a higher-level alternative in the form of the aws_iam_policy_document data source. This performs a similar job of performing the JSON encoding for you automatically, but it also has awareness of the IAM policy document schema and so it can also guarantee to produce a valid IAM policy document structure or return an error explaining why your given arguments are unsuitable for that purpose.