Reputation: 22333
To provision tag policies in an AWS organization, I need to build the JSON content
from variables. Management of tag policies, scp, etc. shall be centralized, so changes can be applied everywhere: Renaming, adding, removing tags, etc.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-1"
}
The problem at hand I am facing is: How would I build the JSON object?
Example variable/ tag map:
# tag_policies.tf
variable "resource_tags" {
description = "Central resource tags"
type = list( object( {
name = string
tags = map(string)
} ) )
default = [
{
name = "Environment"
tags = {
prod = "crn::env:prod"
lab = "crn::env:lab"
dev = "crn::env:dev"
}
}
]
}
What I have tried so far is to use HCL template tags, but I end up with one ,
comma too much when iterating through the map of tag names. This works fine for the join()
with the sub-map of tag names, but does not workout if I try to wrap the template markup. Why did I try this? Because I ran out of ideas.
# vars.tf
resource "aws_organizations_policy" "root-tag-policy" {
name = "RootTagPolicy"
type = "TAG_POLICY"
content = <<CONTENT
{
"tags": {
%{ for tag in var.resource_tags_env ~}
"${tag.name}": {
"tag_key": {
"@@assign": "${tag.name}",
"@@operators_allowed_for_child_policies": [ "@@none" ]
},
"tag_value": { "@@assign": [ "${join( ", ", values( tag.tags ) )}" ] }
},
%{ endfor ~}
}
}
CONTENT
}
Upvotes: 0
Views: 847
Reputation: 22333
After reading @martin-atkins answer, I finally understood how the for
works for objects
and maps
. The var before the =>
arrow actually is part of the resulting object. (This highly confused me as I compared it to other languages arrow functions and arguments.)
The first part of the process is to build a map of maps. The main reason is that I don't want to have a convention of a name
key in a map of variables. This might lead to handling of conventions later on, what should be avoided at all costs as it is a possible trap if one does not pay close attention or is aware of it. So the key
actually is the name
now.
variable "resource_tags" {
description = "Central resource tags"
type = map(
map(string)
)
default = {
Environment = {
common = "grn::env:common"
prod = "grn::env:prod"
stage = "grn::env:stage"
dev = "grn::env:dev"
demo = "grn::env:demo"
lab = "grn::env:lab"
},
Foo = {
bar = "baz"
}
}
}
content
as JSONAfter understanding that the key in { "tags": { … } }
is just the part before the =>
, I could reduce the final resource to the following block.
resource "aws_organizations_policy" "root-tag-policy" {
name = "RootTagPolicy"
description = "Tag policies, assigned to the root org."
type = "TAG_POLICY"
content = jsonencode({
tags = {
for key, tags in var.resource_tags : key => {
tag_key = {
"@@assign" = key
"@@operators_allowed_for_child_policies" = ["@@none"]
}
tag_value = {
"@@assign" = values( tags )
}
}
}
})
}
Add the following output
statement after the resource
block:
output "debug" {
value = aws_organizations_policy.tp_root-tag-policy.content
}
Now apply
(or plan
or refresh
) just this resource. It's faster this way. Then output the built debug
from the apply
or refresh
run.
$ terraform apply -target=aws_organizations_policy.root-tag-policy
…things happening…
$ terraform output debug | json_pp
ProTips:
output
directly into json_pp
or jq
so you can read it.jq .
if you want validation on top. If you see the output, it means it's valid. Else you should receive 0
as response.Upvotes: 0
Reputation: 74054
kaiser's answer shows a good general approach: build a suitable data structure and then pass it to jsonencode
to get a valid JSON string from it.
Here's an example that I think matches what the string template in the original question would've produced:
content = jsonencode({
tags = {
for tag in var.resource_tags_env : tag.name => {
tag_key = {
"@@assign" = tag.name
"@@operators_allowed_for_child_policies" = ["@@none"]
}
tag_value = {
"@@assign" = values(tag.tags)
}
}
}
})
I'm not familiar with the aws_organizations_policy
resource type so I'm sorry if I got some details wrong here, but hopefully you can adapt the above example to generate the JSON data structure you need.
Upvotes: 1
Reputation: 22333
The solution actually was quite simple: Iterate of the tags using a for
expression and enclose it with curly braces { … }
to return an object (=>
returns tuples).
Finally jsonencode()
cares about converting the HCL key = value
syntax to proper JSON.
resource "aws_organizations_policy" "root-tag-policy" {
name = "RootTagPolicy"
type = "TAG_POLICY"
content = jsonencode( [ for key, tag in var.resource_tags: {
"${tag.name}" = {
"tag_key" = {
"@@assign" = tag.name,
"@@operators_allowed_for_child_policies" = [ "@@none" ]
},
"tag_value" = { "@@assign" = [ join( ", ", values( tag.tags ) ) ] }
}
} ] )
}
EDIT This still does not work, as I forgot that the whole JSON object needs to get wrapped inside a tags: {}
.
Upvotes: 1