Terraform - How to modify map keys? - terraform

In Terraform 0.12.xx, is it possible to modify the keys in the map instead of their respective values? Let us assume that we have the following in the module definition:
locals {
task_logging = [
for k, v in var.task_logging_options : {
name = trimprefix(k,"TASK_LOGGING_")
value = v
}
]
}
However, trimprefix here only applies on value.
Then down below I am creating a task definition for ECS service:
{...}
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": [],
"options": ${jsonencode(local.task_logging_options)}
},
{...}
And finally, in the module instantiation, I am passing task_logging_options as follows:
task_logging_options = {
TASK_LOGGING_Name = "es"
TASK_LOGGING_Host = "some.host"
}
Where local function should strip the prefix TASK_LOGGING_ to build a JSON object for Fluentbit configuration.
End result should be an object, similar to snippet from terraform plan:
~ logConfiguration = {
logDriver = "awsfirelens"
~ options = {
- Buffer_Size = "False" -> null
- HTTP_Passwd = "READACTED" -> null
- HTTP_User = "READACTED" -> null
- Host = "READACTEDd" -> null
- Index = "READACTED" -> null
- Name = "es" -> null
- Port = "READACTED" -> null
+ TASK_LOGGING_Host = "some.host"
+ TASK_LOGGING_Name = "es"
- Tls = "On" -> null
- Trace_Output = "On" -> null
}
secretOptions = []
}

Not fully understand what do you want to achieve, but you can use trimprefix(k,"TASK_LOGGING_") as key as well.
For example:
locals {
task_logging2 = [
for k, v in var.task_logging_options : {
trimprefix(k,"TASK_LOGGING_") = v
}
]
}
will result in local.task_logging2 being:
[
{
"Host" = "some.host"
},
{
"Name" = "es"
},
]
Update
If object is required, the following could be used:
locals {
task_logging2 = {
for k, v in var.task_logging_options :
trimprefix(k,"TASK_LOGGING_") => v
}
}
which results in local.task_logging2 being:
{
"Host" = "some.host"
"Name" = "es"
}

Related

How can I get a map using flatten in Terraform?

I have this variable:
applications = {
"app-name" = {
more_stuff = "x"
environments = ["dev", "stg"]
}
"workload" = {
random_thing = "y"
environments = ["dev"]
}
}
I want to create this map from it:
application_envs = {
"app-name-dev" = { more_stuff = "x" }
"app-name-stg" = { more_stuff = "x" }
"workload-dev" = { random_thing = "y" }
}
I've tried using flatten but I didn't have any success:
application_envs = flatten([
for application_key, application in var.applications : [
for env in application.environments : [
{"${application_key}-${env}" = workspace}
]
]
])
The problem is that it creates a list of objects:
result = [
{ "app-name-dev" = { ... } },
{ "app-name-stg" = { ... } },
...
]
How can I achieve the desired result?
You are headed in the correct direction, but for this kind of data transformation the algorithm requires two steps. Your first step is completely correct, but now you need the second step:
result = { for app_env in application_envs : keys(app_env)[0] => values(app_env)[0] }
This transforms the list(object) by iteratively mapping the key of each object element to the value of each object element. Testing the output produces:
result = {
app-name-dev = {
more_stuff = "x"
}
app-name-stg = {
more_stuff = "x"
}
}
as desired. Since the namespaces of the variables have been omitted from the question, I have likewise omitted them from the answer, but you may need to re-add them yourself when converting between the question code and the actual code.

How to use ellipsis (…) in nested for loop

I see this error "If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key."
locals {
key_id = {
for x in var.security_rules :
"${x.type}" => x}
}
Is it possible to use ellipsis in a nested for this loop and how can i do it?
The error message means that var.security_rules has multiple items with the same type. For example:
variable "security_rules" {
default = [
{
type = "a"
},
{
type = "b"
},
{
type = "a"
}
]
}
We can see that there are at least 2 items with the same type, which wont be accepted as key in map. What we can do here is to group the items with the same type. This is exactly what ellipsis (...) will accomplish. So:
locals {
key_id = {
for x in var.security_rules : "${x.type}" => x... }
}
The value of key_id in this example will be:
key_id = {
"a" = [
{
"type" = "a"
},
{
"type" = "a"
},
]
"b" = [
{
"type" = "b"
},
]
}

How to merge two level nested maps in terraform?

I know there is an open feature request for deepmerge but I just wanted to see if there is any work around for my use case. lets consider the following local variables:
locals {
default = {
class = "class1"
options = {
option1 = "1"
option2 = "2"
}
}
configs = {
configA = {
name = "A"
max = 10
min = 5
enabled = true
options = {
option3 = "3"
}
}
configB = {
name = "B"
max = 20
min = 10
enabled = false
}
}
}
so I can merge the configs with default like this:
for key, config in local.configs : key => merge(local.default, config)
and the result will be:
configs = {
configA = {
name = "A"
class = "class1"
max = 10
min = 5
enabled = true
options = {
option3 = "3"
}
}
configB = {
name = "B"
class = "class1"
max = 20
min = 10
enabled = false
options = {
option1 = "1"
option2 = "2"
}
}
}
The problem is the nested map (options property) gets completely replaced by configA since merge cannot handle nested merge. Is there any work around for it in terraform 1.1.3 ?
If you know the structure of the map, you can merge the included elements separately as you wish.
In this case this should work:
merged = {
for key, config in local.configs : key =>
merge(
local.default,
config,
{ options = merge(local.default.options, lookup(config, "options", {})) }
)
}
So first merge the top-level elements, and then handle the options separately.
There are various ways to do this including using this deepmerge provider:
https://registry.terraform.io/modules/cloudposse/config/yaml/0.5.0/submodules/deepmerge
Here is a way that assumes only that /usr/bin/jq exists. I am not saying it is pretty, but it does work and ensures that you get the same semantics as a jq * operator.
locals {
left = {...}
right = {...}
merged = {
for k, v in data.external.merge.result : k => jsondecode(v)
}
}
data "external" "merge" {
program = [
"/usr/bin/jq",
"((.left|fromjson) * (.right|fromjson))|with_entries(.value|=tojson)"
]
query = {
left = jsonencode(local.left)
right = jsonencode(local.right)
}
}

How to create a set by loop over a nested map

I have a map that I want to read in locals and generate a new map from. One field in the new map will be a set containing the values from the nested data structure. I can't figure out the syntax to do this.
//I want to generate a set of all zones from the nested zone fields
variable "my_var" {
type = object({
name = string
google_bigtable_clusters = any
})
default = {
app_name = "sdfsdfds"
instances = {
instance01 = [
{
zone = "asia-east1-a"
num_nodes = 1
},
{
zone = "asia-east1-b"
num_nodes = 1
},
{
zone = "asia-east1-c"
num_nodes = 1
},
{
zone = "asia-east2-a"
num_nodes = 1
},
],
instance02 = [
{
zone = "europe-west2-a"
num_nodes = 1
},
{
zone = "europe-west2-b"
num_nodes = 1
},
{
zone = "europe-west2-c"
num_nodes = 1
},
{
zone = "europe-west3-a"
num_nodes = 1
},
]
}
}
}
This throws The key expression produced an invalid result: string required.
// locals
new_map = {
some_field = "arbitrary string"
set_of_zones = {
for item in var.my_var.instances : item => {
for subitem in item : subitem.zone => {
zone = subitem.zone
}
}
}
}
I also tried to get the key name but that didn't work: for item in var.my_var.instances : item.key => {
Edit
I was able to do this but I don't understand why I don't have access to the key name here. I want to use the instance01, instance02, etc key name here: for item in var.my_var.instances : item[0].zone => {.
First, your type for your variable is all messed up. You have:
type = object({
name = string
google_bigtable_clusters = any
})
This means that Terraform will accept a value for the variable only if it has these two fields: name (a string) and google_bigtable_clusters (can be anything).
Your default value has neither of these fields. Instead, it only contains app_name and instances, so that's likely the cause of the first issue.
Regarding why you can't access the key name in your for loop, you need to specify both the key and value:
set_of_zones = {
for key, val in var.my_var.instances :
key => {
for subval in val:
subval.zone => {
zone = subval.zone
}
}
}
This is a really odd thing to want to do though, because you're going to end up with a map that looks like:
set_of_zones = {
instance01 = {
"asia-east1-a" => {
zone = "asia-east1-a"
}
}
}
Which doesn't seem super helpful since there is only one attribute in each map, and that attribute's value is the same as the key for that map.

terraform how to describe variable type with changing keys in object

I've got an ever changing list of objects as variable and wanted to know how to properly describe its type
variable "lifecycle_rules" {
type = set(object({
# set(object({
# action = map(string)
# condition = map(string)
# }))
}))
default = [
{
first = [
{
condition = {
age = "1"
}
action = {
type = "Delete"
}
},
{
condition = {
age = "2"
}
action = {
type = "Delete"
}
}
]},
{
second = [
{
condition = {
age = "3"
}
action = {
type = "Delete"
}
},
{
condition = {
age = "4"
}
action = {
type = "Delete"
}
}
]
}
]
}
Here should be line with smth like this string = set(object({...
the first and second are always changing, so key value should be
string but can't really set it - any other thoguhts, how to write
type for the default below ?
You are almost there. I think the correct one is:
type = set(
map(
set(
object({condition = map(string),
action = map(string)})
)
)
)
In the map you don't specify attributes, as they can be different. In the most inner one you have object as condition and action are constant.

Resources