Terraform: nested for loop from yaml - terraform

I am trying to run nested for loop on terraform.
I have the following Yaml file:
Employees:
- Department:
- Dev:
- name: "danielf"
role: developer
details:
email : danielf#example.com
firstname : daniel
lastname : folsik
- name: "johnb"
role: developer
details:
email : johnb#example.com
firstname : john
lastname : belk
- Ops:
- name: "benol"
role: devops
details:
email : benol#example.com
firstname : ben
lastname : olkin
- name: "pauld"
role: devops
details:
email : pauld#example.com
firstname : paul
lastname : dempler
I am using locals to get the yaml data:
locals {
ou_config = yamldecode(file("employees.yaml"))
}
I want to run into the list of objects on "Dev" and "Ops" lists using for_each.
for example, I want to run on the "Dev" list to get the following list of objects in the first iteration:
[
{
key = "email"
value = "danielf#example.com"
},
{
key = "firstname"
value = "daniel"
},
{
key = "lastname"
value = "folskin"
}
]
The next run on the for_each will be:
[
{
key = "email"
value = "johnb#example.com"
},
{
key = "firstname"
value = "john"
},
{
key = "lastname"
value = "belk"
}
]
etc.
How can I do it on terraform?

If I understand correctly, all you are trying to extract is the details portion of that yaml file ...
Here is what I would do to get all:
locals {
ou_config = yamldecode(file("employees.yaml"))
expanded_names = flatten([
for e in local.ou_config.Employees : [
for d in e.Department : [
for key, person in d : [
for key, value in person : [
value.details
]
]
]
]
])
}
output "test" {
value = local.expanded_names
}
And if we want to filter we add an if key == "Dev"
locals {
ou_config = yamldecode(file("employees.yaml"))
expanded_names = flatten([
for e in local.ou_config.Employees : [
for d in e.Department : [
for key, person in d : [
for key, value in person : [
value.details
]
] if key == "Dev"
]
]
])
}
output "test" {
value = local.expanded_names
}
A terraform plan on that will look like:
Changes to Outputs:
+ test = [
+ {
+ email = "danielf#example.com"
+ firstname = "daniel"
+ lastname = "folsik"
},
+ {
+ email = "johnb#example.com"
+ firstname = "john"
+ lastname = "belk"
},
]
That format should be easier to loop in the final resource than the key value you suggested

Related

print from json with a given condition in terraform

I'm into the terraform world recently and learning based on the requirements. I've a question on printing the values with a given condition
json file:
{
"team1" : [
{
"engg_name" : "Alex",
"guid" : 1001,
"scope" : "QA"
},
{
"engg_name" : "Trex",
"guid" : 1002,
"scope" : "QA"
},
{
"engg_name" : "Jessica",
"guid" : 1003,
"scope" : "QA"
},
{
"engg_name" : "Tom",
"guid" : 1004,
"scope" : "DEV"
}
],
"team2" : [
{
"engg_name" : "Roger",
"guid" : 2001,
"scope" : "DEV"
},
{
"engg_name" : "Jhonny",
"guid" : 2002,
"scope" : "DEV"
}
]
}
What I'm trying:
print the engg whose scope is DEV from the json file
locals {
teams = jsondecode(file("${path.module}/teams_info.json"))
engg_with_scope_dev = flatten([for i in local.teams : i.teams if keys(local.teams).scope == "DEV"])
}
Error:
engg_with_scope_dev = flatten([for i in local.teams : i.teams if keys(local.teams).scope == "DEV"])
|----------------
| local.teams is object with 2 attributes
This value does not have any attributes.
Can someone suggest me what's the right way to just print based on the condition?
output must be as following:
engg_with_scope_dev = ["Tom", "Roger", "Jhonny"]
You need an embedded for loop for this:
locals {
teams = jsondecode(file("${path.module}/teams_info.json"))
engg_with_scope_dev = flatten([for team in local.teams : [
for engineer in team : engineer.engg_name if engineer.scope == "DEV"
]])
}
Other solution would be to use a concatenation of the lists with ellipsis operator:
locals {
teams = jsondecode(file("${path.module}/teams_info.json"))
engg_with_scope_dev = ([
for engineer in concat(values(local.teams)...) : engineer.engg_name if engineer.scope == "DEV"
])
}
But also, a simple flatten with values would work as well:
locals {
teams = jsondecode(file("${path.module}/teams_info.json"))
engg_with_scope_dev = ([
for engineer in flatten(values(local.teams)) : engineer.engg_name if engineer.scope == "DEV"
])
}

Nested loop based on json response in Terraform for multiple resource target

Array:
regions = [
{name: "region1"},
{name: "region2"},
{name: "region3"},
{name: "region4"},
{name: "region5"},
{name: "region6"}]
Json:
{
"region1" : ["cluster1"],
"region2" : [],
"region3" : ["cluster1"],
"region4" : ["cluster1","cluster2"]
}
resource "type" "name" {
count = length(regionLength)
name = "region-name/cluster-name"
}
I need resource created with such name output like this
region1/cluster1
region2
region3/cluster1
region4/cluster1
region4/cluster2
Can we achieve this too:
Final = []
For r , cs in arr:
for oc in regions:
if r == oc.name:
for c in cs:
oc[‘cluster’] = r-c
Final.push(oc)
Thanks in advance.
You can achieve that as folllows:
variable "regions" {
default = {
"region1" : ["cluster1"],
"region2" : [],
"region3" : ["cluster1"],
"region4" : ["cluster1","cluster2"]
}
}
locals {
region_list = flatten([for region, clusters in var.regions:
[ for cluster in coalescelist(clusters, [""]):
"${region}/${cluster}"
]
])
}
which gives:
region_list = [
"region1/cluster1",
"region2/",
"region3/cluster1",
"region4/cluster1",
"region4/cluster2",
]

Converting json to yaml in Terraform

How can we generate an yaml out from a json
{
"users":[
{
"name":"rock",
"age":33,
"city":"paris"
},
{
"name":"austin",
"age":45,
"city":"greece"
}
]
}
Expected output with a additional field with random password
users:
- key: 'user[0].name'
value: rock
- key: 'user[0].age'
value: '33'
- key: 'user[0].city'
value: paris
- key: 'user[0].password'
value: '5]L+J7rA*<7+:PO6'
- key: 'user[1].name'
value: austin
- key: 'user[1].age'
value: '45'
- key: 'user[1].city'
value: greece
- key: 'user[1].password'
value: P=x&385YGMI0?!Is
The files are used only on a local machine.
A straight conversion from JSON to YAML would involve just passing the result of jsondecode to yamlencode.
However, the meat of your question seems to not be about conversion between serialization formats but rather about transforming from a typical nested data structure into a flattened structure using a compound key syntax.
Transforming from a multi-level structure into a single-level structure is a good job for flatten:
locals {
users = flatten([
for idx, u in local.users : [
for k, v in u : {
key = "user[${idx}].${v}"
value = v
}
]
])
}
The result would be a Terraform value corresponding to the data structure you illustrated in YAML:
[
{
key = "user[0].name"
value = "rock"
},
{
key = "user[0].age"
value = 33
},
# ...etc...
]
You can then pass the result to yamlencode to produce the YAML serialization of that data structure.
You can use file function of terraform to read the content of your json.
You can then use jsondecode to decode/represent the string as json.
The last step is to use , yamlencode function to encode returned json representation in yaml format.
Please see below terraform config.
locals {
test = yamlencode(jsondecode(file("${path.module}/test.json")))
}
output "test" {
value = local.test
}
The result from the above tf config is as below
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = "users":
- "age": 33
"city": "paris"
"name": "rock"
- "age": 45
"city": "greece"
"name": "austin"
locals {
users = jsondecode(file("${path.module}/user.json"))
}
resource "random_string" "password" {
count = length(local.users.users)
length = 16
special = true
}
output "pass" {
value = random_string.password
}
locals {
user_flat = yamlencode(flatten([for i,j in local.users.users:[
for k,v in j : {
key = "user.[${i}].${k}"
"value" = v
}
]
]
)
)
out = yamlencode({"users"= local.user_flat})
}
output "Desired_output" {
value = local.out
}
Try this code.
Outputs:
Desired_output = <<EOT
"users": |
- "key": "user.[0].age"
"value": 23
- "key": "user.[0].city"
"value": "barcelona"
- "key": "user.[0].name"
"value": "john"
- "key": "user.[1].age"
"value": 29
- "key": "user.[1].city"
"value": "london"
- "key": "user.[1].name"
"value": "bob"
EOT
pass = [
{
"id" = "XEXj*svXm#d&_%Fn"
"keepers" = tomap(null) /* of string */
"length" = 16
"lower" = true
"min_lower" = 0
"min_numeric" = 0
"min_special" = 0
"min_upper" = 0
"number" = true
"override_special" = tostring(null)
"result" = "XEXj*svXm#d&_%Fn"
"special" = true
"upper" = true
},
{
"id" = "Ews%V%Xk[YBd]D_M"
"keepers" = tomap(null) /* of string */
"length" = 16
"lower" = true
"min_lower" = 0
"min_numeric" = 0
"min_special" = 0
"min_upper" = 0
"number" = true
"override_special" = tostring(null)
"result" = "Ews%V%Xk[YBd]D_M"
"special" = true
"upper" = true
},
]

Is it possible to perform nested iterations in HCL resulting in a flat list without calling flatten?

Is it possible with HCL to have nested iterations returning a flat list(map) without resorting to flatten?
I have this:
locals {
mappings = flatten([
for record_type in var.record_types : [
for host in var.hosts : {
type = record_type,
host = host
}
]
])
}
I would like to remove the need for flatten like this:
locals {
mappings = [
for record_type in var.record_types :
for host in var.hosts : {
type = record_type,
host = host
}
]
}
But it seems like each for .. in must return data.
One alternative I could think of to only have a single for-loop is using setproduct():
variable "record_types" {
default = ["type1", "type2"]
}
variable "hosts" {
default = ["host1", "host2"]
}
locals {
mappings = [
for i in setproduct(var.record_types, var.hosts) : {
type = i[0],
host = i[1],
}
]
}
output "mappings" {
value = local.mappings
}
after terraform apply resulting in:
Outputs:
mappings = [
{
"host" = "host1"
"type" = "type1"
},
{
"host" = "host2"
"type" = "type1"
},
{
"host" = "host1"
"type" = "type2"
},
{
"host" = "host2"
"type" = "type2"
},
]
Of course, the two variables need to be independent sets here.
If you want to support duplicates or have dependent inputs, flatten() with two loops is the way.

terraform output map value from lists of maps

I want to get a specific map element from a lists of maps in Terraform's output.
For example, how would I access account in properties.json for each map item
and print a list as output conditionally for type also.
I tried for loops and splat expressions as below but it does not return exact value.
It seems that the props below is also a list.
output "resources_by_name" {
description = "Resource name of all machine type resources from a vRA deployment"
value = [
for props in deployment.deploy[*].resources.*.properties_json:
jsondecode(props).account
if jsondecode(props).type == "vsphere"
]
}
I am not sure how to use nested for loops or access map items within the lists.
properties.json
[
[
{
"id" = "b5336bf7-07fb-4026-aa3d-479bd974ca45"
"name" = "test1"
"properties_json" = "{"account":"test0","constraints":"anothertest4"}"
"type" = "vsphere"
},
{
"id" = "67a3380b-8008-4f9c-9c13-2a1a935d5820"
"name" = "test2"
"properties_json" = "{"account":"test1","constraints":"anothertest3"}"
"type" = "gcp"
},
],
[
{
"id" = "eeddd127-cba2-4b34-a2d7-e56dda5d2974"
"name" = "test3"
"properties_json" = "{"account":"test2","constraints":"anothertest2"}"
"type" = "aws"
},
{
"id" = "81de1857-c0c9-4c9e-8fbd-d8a1da64fa3c"
"name" = "test4"
"properties_json" = "{"account":"test3","constraints":"anothertest1"}"
"type" = "az"
},
],
]
Here is working example. I had to fill out the blanks missing from your question, thus you may need to modify it to suit your needs:
locals {
properties = [
[
{
"id" = "b5336bf7-07fb-4026-aa3d-479bd974ca45"
"name" = "test1"
"properties_json" = "{\"account\":\"test0\",\"constraints\":\"anothertest4\"}"
"type" = "vsphere"
},
{
"id" = "67a3380b-8008-4f9c-9c13-2a1a935d5820"
"name" = "test2"
"properties_json" = "{\"account\":\"test1\",\"constraints\":\"anothertest3\"}"
"type" = "gcp"
},
],
[
{
"id" = "eeddd127-cba2-4b34-a2d7-e56dda5d2974"
"name" = "test3"
"properties_json" = "{\"account\":\"test2\",\"constraints\":\"anothertest2\"}"
"type" = "aws"
},
{
"id" = "81de1857-c0c9-4c9e-8fbd-d8a1da64fa3c"
"name" = "test4"
"properties_json" = "{\"account\":\"test3\",\"constraints\":\"anothertest1\"}"
"type" = "az"
},
],
]
}
output "resources_by_name" {
value = [for props in flatten(local.properties):
jsondecode(props.properties_json).account
if props.type == "vsphere"
]
}
Outcome:
resources_by_name = [
"test0",
]

Resources