Terraform interpolation for creating a map - terraform

I'm struggling with this interpolation, I have variables below like.
primary = ["foo.dev","all.bar.com"]
secondary = ["module.foo.dev","*.foo.dev","all.bar.com"]
I want my output to be
{
"foo.dev" = ["module.foo.dev","*foo.dev"]
"all.bar.com" = ["all.bar.com"]
}
Using Terraform 0.12.20
I tried in terraform console , I'm not able to achieve my desired output. Is there any easy solution?
[for i in ["foo.dev","all.bar.com"]: map(i,[for j in ["module.foo.dev","*foo.dev","all.bar.com"]: replace(j,i,"")!=j == true ? j : 0])]
[
{
"foo.dev" = [
"module.foo.dev",
"*foo.dev",
"0",
]
},
{
"all.bar.com" = [
"0",
"0",
"all.bar.com",
]
},
]

You can do this with the following expression:
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j if can(regex(i, j))] }
This results in the following value:
{
"all.bar.com" = [
"all.bar.com",
]
"foo.dev" = [
"module.foo.dev",
"*foo.dev",
]
}
which is exactly what you requested.
The iterative explanation follows:
First, specify the type is a map:
{}
Now, we should iterate over the elements in the first list:
{ for i in ["foo.dev", "all.bar.com"] : }
Next, we assign the i value to the key of the map, and initialize a list for the value:
{ for i in ["foo.dev", "all.bar.com"] : i => [] }
However, we need to assign the values from the elements in the second list to the list for the key of the resulting map (the below example is sub-optimal because we do not need a for expression, but it is illustrative for the next step):
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j] }
Finally, we want to filter the elements in the list value based on the key. We can use the can function outside the regex function to return a boolean based on whether there is a match. We use this for the conditional on whether the element of the second list should be added to the list in the value:
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j if can(regex(i, j))] }
and we have arrived at the solution.

Related

Terraform construct a list from map

I have a terraform map like below.
org_sub_accounts = [
{
"id" = "11111111111"
"type" = "test"
},
{
"id" = "22222222222"
"type" = "prod"
},
{
"id" = "33333333333"
"type" = "prod"
}
]
I want to create a list from this map by including the account id where the type is prod. So the output should be something like the below.
prod_accounts = ["22222222222","33333333333"]
Can someone please help? Was trying to figure this out for some time now.
Assuming that org_sub_accounts is defined as a local (modify answer accordingly if otherwise), then this can be constructed from a for expression lambda inside the list constructor:
[for account in local.org_sub_accounts : account.id if account.type == "prod"]
which returns the value:
[
"22222222222",
"33333333333",
]
which can be assigned to a prod_accounts as desired. Note this assumes your original structure is a list(object) as shown in the question, and therefore always contains the keys id and type.

Transform nested list values to include value from parent map

Is it possible to construct a data structure given the input variable below, where you append the name value to each value in the stages list;
environments = [
{
name = "preprod"
stages = ["blue", "green"]
},
{
name = "qat"
stages = ["blue", "green"]
}
]
so that I end up with a data structure that looks like this once flattened:
local.transformed = [
"preprod-blue",
"preprod-green",
"prod-blue",
"prod-green"
]
You can use flatten:
locals {
transformed = flatten([for val in var.environments: [
for color in val.stages:
"${val.name}-${color}"
]
])
}

Create a set from a list of sets

I am trying to create a predefined set of IAM roles.
locals {
default_iam_roles = {
group1 = {
name = "group:group1-group#mydomain.com"
roles = toset([
"roles/viewer"
])
}
group2 = {
name = "group:group2-group#mydomain.com"
roles = toset([
"roles/owner"
])
}
}
formatted_iam_roles = [ for member in local.default_iam_roles : setproduct([member.name], member.roles) ]
}
If I print local.formatted_iam_roles I get the following:
[
toset([
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
]),
toset([
[
"group:group2-group#mydomain.com",
"roles/owner",
],
]),
]
Now I want to create a single set which contains all of the combinations contained in the list, so that I can feed it to a resource on a for_each statement, but I am not able to find the logic for this.
The expected output would be:
toset([
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
[
"group:group2-group#mydomain.com",
"roles/owner",
]
])
The operation of taking multiple sets and deriving a new set which contains all of the elements across all of the input sets is called union and so Terraform's function for it is called setunion to follow that.
locals {
all_iam_roles = setunion(local.formatted_iam_roles...)
}
The ... symbol after the argument tells Terraform that it should use each element of local.formatted_iam_roles as a separate argument to setunion, because that function is defined as taking an arbitrary number of arguments that are all sets, rather than as taking a list of sets.
It might be helpful to think of setunion as being a similar sort of function as concat. The concat function joins multiple lists together, preserving each list's element order and the order the lists are given. Set elements don't have any particular order and contain each distinct value only once, and so the behavior of setunion is different but its purpose is related.
Your solution in the question was close, you just have to apply a flatten function to the setproduct output.
locals {
# ...
formatted_iam_roles = [for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))]
}
The output will be the following:
formatted_iam_roles = [
[
"group:group1-group#mydomain.com",
"roles/viewer",
],
[
"group:group2-group#mydomain.com",
"roles/owner",
],
]
Now, if you really want it to make a set for the final result, you can use toset, most like it will be pointless:
formatted_iam_roles = toset([for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))])
I finally faced the problem using maps instead of lists, because I needed to use a for_each argument and it doesn't accept a set of tuples as value.
If anyone knows a better approach, post it and I will be pleased of marking it as correct.
locals {
formatted_iam_roles = merge([ for member in local.default_iam_roles :
{ for pair in setproduct([member.name], member.roles) :
"${pair[0]}${pair[1]}" => { "name": pair[0], "role": pair[1] }
}
]...)
}
resource "google_project_iam_member" "team_access" {
for_each = local.formatted_iam_roles
project = var.project_id
member = each.value["name"]
role = each.value["role"]
}

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",
]

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.

Resources