Terraform concatenate sublists - terraform

I would like to get combined list from sublists of an object list.
I have a list object like:
users: [
{
name: "a"
cars: [
"z1",
"z2"
]
},
{
name: "b"
cars: [
"x1",
"x2"
]
}
]
and result should be:
cars = ["z1", "z2", "x1", "x2"]
The main reason is that I need to execute resource on each element:
resource "some_resource" "some_resource_name" {
for_each = var.cars
car_name = each.value
}
If there is a way to do it directly in resource, perfect. Or if I need to pre-build the list in locals that also is not a problem.
How do I do that?

This can be achieved easily with combining a splat expression with flatten function:
locals {
cars = flatten([
var.users[*].cars
])
}
Other solution would be to use a for expression:
locals {
cars = flatten([
for user in var.users: user.cars
])
}
The result for cars in both cases will be:
cars = [
"z1",
"z2",
"x1",
"x2",
]

local {
cars = flatten([
for user, cars in var.users : [
for car in cars: {
user = user
car = car
}
]
])
}
will not produce the perfect result but is a working solution
result will be:
[
{
user = "a",
car = "z1"
},
{
user = "a",
car = "z2"
},
{
user = "b",
car = "x1"
},
{
user = "b",
car = "x2"
}
]

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

Looking for a more concise way of doing a nested loop

Looking for a cleaner/more-readable way to achieve a nested loop in Terraform. I will illustrate with an example.
Let's say we have variable for roles that looks like this:
variable "roles" {
type = "list"
default = [
{
name = "LOADER"
schemas = {
RAW = ["USAGE", "ALL"]
SRC = ["ALL"]
}
},
{
name = "USER"
schemas = {
RAW = ["DELETE", "OBJECT"]
SRC = ["USE"]
}
}
]
}
From this, I want to end up with a List of dictionaries that looks something like:
output = [
{
"privilege" = "USAGE"
"role" = "LOADER"
"schema" = "RAW"
},
{
"privilege" = "ALL"
"role" = "LOADER"
"schema" = "RAW"
},
{
"privilege" = "ALL"
"role" = "LOADER"
"schema" = "SRC"
},
{
"privilege" = "DELETE"
"role" = "USER"
"schema" = "RAW"
},
{
"privilege" = "OBJECT"
"role" = "USER"
"schema" = "RAW"
},
{
"privilege" = "USE"
"role" = "USER"
"schema" = "SRC"
},
]
What I have tried so far (seems to work but I am looking for a more concise/readable way to do it):
locals {
# FlatMapping to a list of dictionaries. Each dict in the form of {schema=<schema>, role=<role>, privilege=<privilege>}
key_val = [for role in var.roles : [for schema, privilege in role["schemas"]: {
role = role["name"]
schema = schema
privilege = privilege
}]]
other_key_val = [for dict in flatten(local.key_val): [for priv in dict["privilege"]: {
role = dict["role"]
schema = dict["schema"]
privilege = priv
}]]
}
output "output" {
value = flatten(local.other_key_val)
}
My main objective is to have readable code that can be understood better by others. Given that I am using loops in Terraform for the first time, I can't judge if my implementation is considered readable.
Maybe this would be a little bit simpler way to achieve the same result:
locals {
roles = [
{
name = "LOADER"
schemas = {
RAW = ["USAGE", "ALL"]
SRC = ["ALL"]
}
},
{
name = "USER"
schemas = {
RAW = ["DELETE", "OBJECT"]
SRC = ["USE"]
}
}
]
out = flatten([
for item in local.roles: [
for schema, privileges in item.schemas: [
for privilege in privileges: {
role = item.name
privilege = privilege
schema = schema
}
]
]
])
}

Terraform extract account id from aws_organizations_organization.main.accounts

Given an account name, is it possible to extract the account id from
resource "aws_organizations_organization" "main" {
}
So something like:
output "account_id" {
value = "aws_organizations_organization.main.accounts[name == 'account1']"
}
account_id = 012345678901
accounts = [
{
"arn" = "arn:aws:organizations::012345678901:account/o-abc123/012345678901"
"email" = "account1#email.com"
"id" = "012345678901"
"name" = "account1"
},
{
"arn" = "arn:aws:organizations::012345678902:account/o-abc123/012345678902"
"email" = "account2#email.com"
"id" = "012345678902"
"name" = "account2"
},
{
"arn" = "arn:aws:organizations::012345678903:account/o-abc123/012345678903"
"email" = "account3#email.com"
"id" = "320413348752"
"name" = "account3"
}
]
In case anyone else stumbles upon this, I was able to solve with the following:
data "aws_organizations_organization" "main" {}
locals {
account-name = "account1"
account-index = index(data.aws_organizations_organization.main.accounts[*].name, local.account-name)
account-id = data.aws_organizations_organization.main.accounts[local.account-index].id
}
output "account_id" {
value = local.account-id
}
Theoretically, if you get version 2.21.0 or newer, you should be able to use the new aws_organizations_organization data source and filter it based on the account name. For example, though not tested:
data "aws_organizations_organization" "org" {
filter = {
name = "name"
values = ["account1"]
}
}
And then where you need the account id use data.aws_organizations_organization.org.id
You can use null_data_source for creating an email list. and then extract accounts using matchkeys.
data "aws_organizations_organization" "main" {}
data "null_data_source" "main" {
count = length(data.aws_organizations_organization.main.accounts)
inputs = {
emails = data.aws_organizations_organization.main.accounts[count.index]["email"]
}
}
output "accounts" {
value = matchkeys(data.aws_organizations_organization.main.accounts, data.null_data_source.main.*.outputs.emails, list("account1"))
}

Return object with dynamic keys in AQL

Can I return something like:
{
"c/12313" = 1,
"c/24223" = 2,
"c/43423" = 3,
...
}
from an AQL query? The idea is something like (this non-working code):
for c in my_collection
return { c._id : c.sortOrder }
where sortOrder is some property on my documents.
Yes, it is possible to have dynamic attribute names:
LET key = "foo"
LET value = "bar"
RETURN { [ key ]: value } // { "foo": "bar" }
An expression to compute the attribute key has to be wrapped in [ square brackets ], like in JavaScript.
This doesn't return quite the desired result however:
FOR c IN my_collection
RETURN { [ c._id ]: c.sortOrder }
[
{ "c/12313": 1 },
{ "c/24223": 2 },
{ "c/43423": 3 },
...
]
To not return separate objects for every key, MERGE() and a subquery are required:
RETURN MERGE(
FOR c IN my_collection
RETURN { [ c._id ]: c.sortOrder }
)
[
{
"c/12313": 1,
"c/24223": 2,
"c/43423": 3,
...
}
]

Resources