Extract Maps from List in Terraform - terraform

I have a list of complex maps, like so (I've simplified the maps, but really they're made up more maps):
[
{
"hooha" = {
"foo" = { "something" = "this" }
}
},
{
"woot" = {
"bar" = { "other_things" = "that" }
}
},
]
I need to feed this into jsonencode(), but first have it look like this:
{
"hooha" = {
"foo" = { "something" = "this" }
},
"woot" = {
"bar" = { "other_things" = "that" }
}
}
So from a list of maps, to a map of the maps.
This seemed trivial, but for the life of me, I can't figure it out!

You can use the function merge with a combination of with ... operator.
locals {
my_list = [
{
"hooha" = {
"foo" = { "something" = "this" }
},
"www" = {
"aa" = {"x" = "y"}
}
},
{
"woot" = {
"bar" = { "other_things" = "that" }
}
},
]
}
output "prepare" {
value = merge(local.my_list...)
}
The output will be something like this:
prepare = {
"hooha" = {
"foo" = {
"something" = "this"
}
}
"woot" = {
"bar" = {
"other_things" = "that"
}
}
"www" = {
"aa" = {
"x" = "y"
}
}
}

Related

Invert nested maps and list in Terraform

I have this structure in Terraform:
{
"us1" : {
"0": "192.168.1.1"
},
"europe2" : {
"0": "192.168.10.1",
"1": "10.0.0.1"
}
}
I want to transform it into this structure:
{
"0": {
"europe2": "192.168.1.1",
"us1": "192.168.10.1"
},
"1": {
"europe2": "10.0.0.1"
}
}
I used maps with 0 and 1 as indexing for clarity, but lists can be used instead in both starting and ending structure. Anyone has an idea how to do this ?
I saw operators like ellipsis, or functions like setproduct() but I didn't managed to sort this out.
This construction is what you are looking for:
variable "test" {
default = {
"us1" : {
"0": "192.168.1.1"
},
"europe2" : {
"0": "192.168.10.1",
"1": "10.0.0.1"
}
}
}
locals {
# in three steps
flatten_map = merge([
for key, val in var.test :
{ for idx, sub_val in val : "${key}-${idx}" => sub_val }
]...)
pre_final_map = {
for k, v in local.flatten_map :
"${split("-", k)[1]}" => { "${split("-", k)[0]}" = v }...
}
final_map = {
for k, v in local.pre_final_map:
k => merge(v...)
}
# all in one
all_in_one_map = {
for k0, v0 in {
for k, v in merge([
for key, val in var.test :
{ for idx, sub_val in val : "${key}-${idx}" => sub_val }
]...) : "${split("-", k)[1]}" => { "${split("-", k)[0]}" = v }...
} : "${k0}" => merge(v0...)
}
# convert to list
as_list = [
for k0, v0 in {
for k, v in merge([
for key, val in var.test :
{ for idx, sub_val in val : "${key}-${idx}" => sub_val }
]...) : "${split("-", k)[1]}" => { "${split("-", k)[0]}" = v }...
} : merge(v0...)
]
}
output in_three_steps {
value = local.final_map
}
output in_one_step {
value = local.all_in_one_map
}
output list_representation {
value = local.as_list
}
output:
in_one_step = {
"0" = {
"europe2" = "192.168.10.1"
"us1" = "192.168.1.1"
}
"1" = {
"europe2" = "10.0.0.1"
}
}
in_three_steps = {
"0" = {
"europe2" = "192.168.10.1"
"us1" = "192.168.1.1"
}
"1" = {
"europe2" = "10.0.0.1"
}
}
list_representation = [
{
"europe2" = "192.168.10.1"
"us1" = "192.168.1.1"
},
{
"europe2" = "10.0.0.1"
},
]
i have a much faster example:
locals {
original_map = {
"us1" : {
"0" : "192.168.1.1"
},
"europe2" : {
"0" : "192.168.10.1",
"1" : "10.0.0.1"
}
}
}
locals {
original_maps_values = [for key, value in local.original_map : [for idx, sub_value in value : [key, sub_value, idx]]]
transformed_map = { for v in chunklist(flatten(local.original_maps_values), 3) : v[2] => { "${v[0]}" = v[1] }... }
}
output "transformed_map" {
value = { for k, v in local.transformed_map : k => merge(v...) }
}
references:
expanding-function-arguments

Terraform: Nested for expression with no duplicity

I am newbie to Terraform, apologies in advance in case inappropriate logic is used.
There is nested map, want to create new map using existing one.
terraform.tfvars
vpcs_info= {
"devops" = {
main = {
cidr = "10.14.0.0/16"
region = "ap-south-1"
peering = {
creator = {
devops = "poc",
uat = "main"
}
}
},
poc = {
cidr = "10.9.0.0/16"
region = "ap-south-1"
peering = {
creator = {
dev = "main"
}
}
}
}
}
locas.tf
locals {
vpcs_info = {
for vpc, properties in var.vpcs_info.devops:
vpc => {for dst_env, dst_vpc in properties.peering.creator : vpc => {"name": "${dst_env}-${dst_vpc}-vpc", "id": "${timestamp()}" }...}
}
}
Actual output:
{
"main" = {
"main" = [
{
"id" = "2023-02-05T21:23:24Z"
"name" = "devops-poc-vpc"
},
{
"id" = "2023-02-05T21:23:24Z"
"name" = "uat-main-vpc"
},
]
}
"poc" = {
"poc" = [
{
"id" = "2023-02-05T21:23:24Z"
"name" = "dev-main-vpc"
},
]
}
}
If we observe, in the output there is duplicity in KEYS. Need to get below desired output using for expression:
Desired output:
{
"main" = {
{
"id" = "2023-02-05T21:23:24Z"
"name" = "devops-poc-vpc"
},
{
"id" = "2023-02-05T21:23:24Z"
"name" = "uat-main-vpc"
}
}
"poc" = {
{
"id" = "2023-02-05T21:23:24Z"
"name" = "dev-main-vpc"
}
}
}
Note: "id" is sensitive information in actual requirement, to avoid security challenges, "${timestamp()}" is being used here.
You can do that with merge:
vpcs_info = merge([
for vpc, properties in var.vpcs_info.devops: {
for dst_env, dst_vpc in properties.peering.creator:
vpc => {
"name" = "${dst_env}-${dst_vpc}-vpc"
"id" = "${timestamp()}"
}...
}
]...)

Dynamically Rebuilding a Terraform Complex Type

I could be going about this the wrong way, but I'm trying to copy a complex type and inject an object at specific point in a list of complex types. I have a working solution but it is not dynamic and I am forced to repeat myself for each point that I'm modifying.
The below works, but is not dynamic. I am required to repeat a block in the conditional merge statement for each property I need to manipulate.
locals {
map_to_merge = {
foo = "bar"
}
original_list = [
{
property_1 = "value"
property_2 = {
"nested" = {
string_property = "string"
map_property_1 = {
a = "b"
list = []
}
map_property_2 = {
x = "y"
}
}
}
}
]
modified_list = [
for item in local.original_list : merge(item, {property_2 = {
for key, value in item.property_2 : key => merge(value,
contains(keys(value), "map_property_1") ? {
map_property_1 = merge(value.map_property_1, local.map_to_merge)
} : {},
contains(keys(value), "map_property_2") ? {
map_property_2 = merge(value.map_property_2, local.map_to_merge)
} : {}
)
}})
]
}
output "test_original" {
value = local.original_list
}
output "test_modified" {
value = local.modified_list
}
The output is,
+ test_modified = [
+ {
+ property_1 = "value"
+ property_2 = {
+ nested = {
+ map_property_1 = {
+ a = "b"
+ foo = "bar"
+ list = []
}
+ map_property_2 = {
+ foo = "bar"
+ x = "y"
}
+ string_property = "string"
}
}
},
]
+ test_original = [
+ {
+ property_1 = "value"
+ property_2 = {
+ nested = {
+ map_property_1 = {
+ a = "b"
+ list = []
}
+ map_property_2 = {
+ x = "y"
}
+ string_property = "string"
}
}
},
]
What I'd like to do is something like below as it allows me not to repeat the conditional merge statement for each property I want to merge with.
locals {
map_to_merge = {
foo = "bar"
}
original_list = [
{
property_1 = "value"
property_2 = {
"nested" = {
string_property = "string"
map_property_1 = {
a = "b"
list = []
}
map_property_2 = {
x = "y"
}
}
}
}
]
modified_list = [
for item in local.original_list : merge(item, {property_2 = {
for nested, definition in item.property_2 : nested => merge(definition, {
for key, value in definition : key => can(tomap(value))
? merge(
value,
key == "map_property_1" || key == "map_property_2"
? local.map_to_merge
: {}
)
: value
})
}})
]
}
output "test_original" {
value = local.original_list
}
output "test_modified" {
value = local.modified_list
}
But I am getting an error,
Error: Inconsistent conditional result types
on test.tf line 41, in locals:
40: for key, value in definition : key => can(tomap(value))
41: ? merge(
42: value,
43: key == "map_property_1" || key == "map_property_2"
44: ? { foo = "bar" }
45: : {}
46: )
47: : value
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
Is there a dynamic/better way to do this or do I need to repeat the block for each property that needs to be manipulated?
Turns out that switching from the use of can to try enables this to run as I'd expect.
locals {
map_to_merge = {
foo = "bar"
}
original_list = [
{
property_1 = "value"
property_2 = {
"nested" = {
string_property = "string"
map_property_1 = {
a = "b"
list = []
}
map_property_2 = {
x = "y"
}
}
}
}
]
modified_list = [
for item in local.original_list : merge(item, {property_2 = {
for nested, definition in item.property_2 : nested => merge(definition, {
for key, value in definition : key => try(
merge(
value,
key == "map_property_1" || key == "map_property_2"
? local.map_to_merge
: {}
),
value
)
})
}})
]
}
output "test_original" {
value = local.original_list
}
output "test_modified" {
value = local.modified_list
}
Here is the output,
+ test_modified = [
+ {
+ property_1 = "value"
+ property_2 = {
+ nested = {
+ map_property_1 = {
+ a = "b"
+ foo = "bar"
+ list = []
}
+ map_property_2 = {
+ foo = "bar"
+ x = "y"
}
+ string_property = "string"
}
}
},
]
+ test_original = [
+ {
+ property_1 = "value"
+ property_2 = {
+ nested = {
+ map_property_1 = {
+ a = "b"
+ list = []
}
+ map_property_2 = {
+ x = "y"
}
+ string_property = "string"
}
}
},
]

is there a way to concat tuple in terraform?

variable "query_param" {
type = list(string)
default = [
"hello",
"one",
"two"
]
}
locals {
common_tags = flatten([
for i in var.query_param: {
"method.request.querystring.${i}" = false
}
])
}
output name {
value = local.common_tags
description = "description"
}
I have this code to iterate the variable(list) to locals (map). I got the below output which is not desired.
name = [
{
"method.request.querystring.hello" = false
},
{
"method.request.querystring.one" = false
},
{
"method.request.querystring.two" = false
},
]
The desired output is getting all the values in a single array. As shown below.
name = [
{
"method.request.querystring.hello" = false,
"method.request.querystring.one" = false,
"method.request.querystring.two" = false
}
]
Is there any way to achieve this?
I think you would want something like this:
locals {
common_tags = {
for i in var.query_param :
"method.request.querystring.${i}" => false
}
}
The output of the code above will be as follows:
name = {
"method.request.querystring.hello" = false
"method.request.querystring.one" = false
"method.request.querystring.two" = false
}
Please note, the name itself is not an array in this case. If you want it to be, you can do the following:
locals {
common_tags = [{
for i in var.query_param :
"method.request.querystring.${i}" => false
}]
}
The output will be:
name = [
{
"method.request.querystring.hello" = false
"method.request.querystring.one" = false
"method.request.querystring.two" = false
}
]

How to Recreate Terraform map with duplicate keys?

I'm getting terraform Local variable output as below. I want to reconstruct it to use in deferent argument. I have tried it many way , Still no luck, Appreciate community support on this.
snap_id_list = [
{
"3" = {
"/x01" = "snap-01397a07b574e781f"
}
},
{
"2" = {
"/x04" = "snap-035d6c4165d34041a"
}
},
{
"1" = {
"/x04" = "snap-07bc61be86cb0b3a2"
}
},
{
"3" = {
"/x04" = "snap-0aa3a4d1cbd87531b"
}
},
{
"1" = {
"/x01" = "snap-0e501f7d2798eeb70"
}
},
{
"2" = {
"/x01" = "snap-0fe2fe503e433fdbd"
}
},
]
I want to recreate it as below using common keys.
snap_id_list = [
{
"3" = {
"/x01" = "snap-01397a07b574e781f"
"/x04" = "snap-0aa3a4d1cbd87531b"
},
{
"2" = {
"/x04" = "snap-035d6c4165d34041a"
"/x01" = "snap-0fe2fe503e433fdbd"
},
{
"1" = {
"/x04" = "snap-07bc61be86cb0b3a2"
"/x01" = "snap-0e501f7d2798eeb70"
}
},
]

Resources