Terraform map transformation - terraform

I have been banging my head for a few days trying to do this. I know I have probably seen it before but google is failing me.
I have created a local from a data source.:
locals {
my_new_map = [for group in data.okta_groups.aws_groups.groups: {
id = group.id
name = group.name
} if contains(local.groups_not_to_check, group.name) == false]
}
the result is
[
{
id = <group1id>
name = <group1name>
},
{
id = <group2id>
name = <group2name>
},
{
...
},
]
What i need is to transform it to this:
[
<group1name> = <group1id>,
<group2name> = <group2id>,
...
]
groupnames and ids are all unique so can be used as keys
Don’t know if it is possible in my for statement or not but am willing to create a local to do it.
Any help would be awesome.
Have tried many combinations of the FOR expression but to no avail.
Maybe i am missing a function of some sort that could help or maybe i just haven't hit the correct way to output the For expression.

The following should do what you want:
locals {
my_new_map = {for group in data.okta_groups.aws_groups.groups:
group.name => group.id if contains(local.groups_not_to_check, group.name) == false }
}

Related

Merging complex types

I'm struggling with merging maps that have duplicate keys, since the built-in merge function will only keep the latest argument that has matching keys or attributes.
My maps are of the following shape:
{mykey = ["item1","item2","item3"]}
{mykey = ["item4","item5","item6"]}
The merge function simply returns {mykey = ["item4","item5","item6"]} (or whatever is the last argument). I'd like to compose a map like
{mykey = ["item1","item2","item3","item4","item5","item6"]}
I don't believe there's a function I can use to achieve this. However, I suspect that a for loop is the right approach, yet my knowledge is failing me.
Hi #MattSchuchard, thanks for your help. The parent map structure (if I am following you correctly), is a local variable. I am using keys and values from this local in order to retrieve object_ids from the azuread_users and azuread_groups data sources. My locals looks like this:
locals {
roles = {
role1 = {
users = ["user1#domain.com","user2#domain.com"]
groups = ["myGroup1","myGroup2"]
}
}
I am referencing this local in the data sources as follows:
data azuread_users.lookup {
for_each = local.roles
user_principal_names = each.values.users
}
data azuread_groups.lookup {
for_each = local.roles
display_names = each.values.groups
}
These data sources export object_ids as an attribute (which I will then use when adding members to groups in a resource block). The exported attributes are in the shape detailed in my original post.
I would like to compose a combined list of all object_ids exported from both data.azuread_users.lookup and data.azuread_groups.lookup which I can then provide as an argument when creating a group:
resource "azuread_group" "my_group" {
members = $myCombinedListOfObjectIDs
}
Depending on whether you want a list/map you can use this:
locals {
input = { mykey = ["item1", "item2", "item3"] }
input2 = { mykey = ["item4", "item5", "item6"] }
merged = concat(local.input.mykey, local.input2.mykey)
merged_mykey = {
mykeys = concat(local.input.mykey, local.input2.mykey)
}
}
The output is
> local.merged_mykey
{
"mykeys" = [
"item1",
"item2",
"item3",
"item4",
"item5",
"item6",
]
}
> local.merged
[
"item1",
"item2",
"item3",
"item4",
"item5",
"item6",
]
If you're worried about duplicates you can wrap it in a distinct call. https://www.terraform.io/language/functions/distinct

Terraform merge values in maps into sinlge using key lookup

I have a locals block which returns data in list of maps
ids = [
{
"d81f6779-031f-4aa6-8e88-3242f65e35c0" = "478da44dc0aa8b0cdce5e7448c9509a5095a0f93"
},
{
"d81f6779-031f-4aa6-8e88-3242f65e35c0" = "131e25de9ffd028fc9dab497f537eecb6d1b0faf"
},
{
"e2e6deb4-8512-4089-b2ec-ef77447dabb7" = "f8ea0d6234a8ee2858a3ae04952c6c974337cf6a"
},
{
"e2e6deb4-8512-4089-b2ec-ef77447dabb7" = "143b826f93e78d5a18ac9276972e80414262755e"
},
{
"e2e6deb4-8512-4089-b2ec-ef77447dabb7" = "e7fe936edcc34c258c35aa19b9c702e553e3b265"
},
]
I am looking for an output like below. any function I can use or have to write some for loops?
ids =
{
"d81f6779-031f-4aa6-8e88-3242f65e35c0" = ["478da44dc0aa8b0cdce5e7448c9509a5095a0f93", "131e25de9ffd028fc9dab497f537eecb6d1b0faf"],
"e2e6deb4-8512-4089-b2ec-ef77447dabb7" = ["f8ea0d6234a8ee2858a3ae04952c6c974337cf6a", "143b826f93e78d5a18ac9276972e80414262755e","e7fe936edcc34c258c35aa19b9c702e553e3b265"]
}
This worked by manipulating local variable with grouping mode as described in documentation. I didn't had to write another block.
locals {
ids = {for vfc in data.vra_fabric_compute.this : vfc.custom_properties.vcUuid => vfc.id... }
}

terraform - how to use variables inside attributes

I am not sure if this is the right approach to do this but I want to use a variable as an attribute.
For example, I have a variable that changes based on user input: os_name = ubuntu.
I want to use this variable name like the following,
resource "aws_instance" "workerNode" {
..................
ami = data.aws_ami.${var.os_name}.image_id
..................
}
Following is an example of the data block,
data "aws_ami" "suse" {
count = "${var.os_name == "suse" ? 1 : 0}"
owners = ["amazon"]
most_recent = true
filter {
name = "name"
values = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
}
}
Which result the following,
"architecture" = "x86_64"
"hypervisor" = "xen"
"id" = "ami-0d3905203a039e3b0"
"image_id" = "ami-0d3905203a039e3b0"
But terraform is not allowing me to do this. Is there any way I can do this or I have to change the workflow?
In situations where it's not appropriate to gather all of your instances under a single resource using for_each (which would implicitly make that resource appear as a map of objects), you can get a similar result explicitly by writing a local value expression to construct an equivalent map:
locals {
amis = {
suse = data.aws_ami.suse
ubuntu = data.aws_ami.ubuntu
}
}
Then you can refer to local.amis["ubuntu"] or local.amis["suse"] (possibly replacing the element key with a variable, if you need to.
With that said, it does seem like there is a possible different approach for your case which would get there with only one data block:
locals {
os_ami_queries = {
suse = {
owners = ["amazon"]
filters = {
name = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
}
}
ubuntu = {
owners = ["amazon"]
filters = {
name = ["ubuntu-${var.os_version}-something-something"]
}
}
}
ami_query = local.os_ami_queries[var.os_name]
}
data "aws_ami" "selected" {
owners = local.ami_query.owners
dynamic "filter" {
for_each = local.ami_query.filters
content {
name = filter.key
values = filter.value
}
}
}
This different permutation does the OS selection before the data "aws_ami" lookup, so it can use the settings associated with whichever OS was selected by the caller. The AMI id would then be in data.aws_ami.selected.id.
With that said, this approach has the disadvantage of being quite indirect and using a dynamic block, so I'd weigh that against the readability of the alternatives to pick the one which seems subjectively easiest to follow for someone who isn't familiar with this configuration. There isn't a single answer to that because to some extent it's a matter of taste, and so if you are working in a team setting this could be something to discuss with colleagues to see which approach best suits tradeoffs like how often you expect to be adding and removing supported operating systems vs. changing the details of how you use the result.
You can make it work by specifying your AMI's with a for_each and thus getting a map which you can access by key.
My data.aws_ami.myamis looks like this:
data "aws_ami" "myamis" {
for_each = toset(["suse", "ubuntu"])
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["${each.value}*"]
}
}
For test purposes I define a variable foo like this:
variable "foo" {
type = string
default = "suse"
}
Now I can access the AMI like this:
$ tf console
> data.aws_ami.myamis[var.foo].image_id
"ami-0ea50c090ba6e85c5"
You can adapt this to suit your needs for os_name and os_version.
I have solved the issue just by using conditional expression.
I am not sure if it is a standard way of doing things but it works for me.
I have tried to emulate if/elif/else with nested conditional expression.
output "name" {
value = "${ var.os_name == "ubuntu" ? data.aws_ami.ubuntu[0].image_id : (var.os_name == "redhat" ? data.aws_ami.redhat[0].image_id : (var.os_name == "centos" ? data.aws_ami.suse[0].image_id : data.aws_ami.suse[0].image_id ))}"
}

Terraform reduce the amount of loops at the moment of generating a JSON

I have this terraform code that is generating me this JSON.
{
host = {
path = "/xxxx/yyyy"
}
name = "NAME"
}
Currently it's working, but I have 3 loops, consider it not efficient, wondering if I can reduce it to 2 or probably 1 loop? Or this isn't possible.
My first loop validates that container_mounts isn't empty. Don't want to generate it, if that comes empty. The second and the third is specific for getting the information as container_mounts is a map of strings.
variable "container_mounts" {
type = map(string)
default = { "app/data" = "/xxxx/yyyyy" }
}
json = jsonencode(
[
for i in range(length(var.container_mounts)) :
{
name = [for sourceVolume in keys(var.container_mounts) :
replace(substr(sourceVolume, 1, length(sourceVolume)), "/", "-")][0]
host = {
sourcePath = [for key, value in var.container_mounts : value][0]
}
}
]
)
Is there a way to improve it? I assume that yes, but running into different scenarios were it's not working.
So after talking with a friend, looks I complicated my life doing what I did.
json = jsonencode(
[
for key, value in var.container_mounts :{
host = { "sourcePath" = value }
name = replace(substr(key, 1, length(key)), "/", "-")
}
]
)
It can be done only with one loop.

Terraform iterate over list

I would like to replace the 3 indepedent variables (dev_id, prod_id, stage_id), for a single list containing all the three variables, and iterate over them, applying them to the policy.
Is this something terraform can do?
data "aws_iam_policy_document" "iam_policy_document_dynamodb" {
statement {
effect = "Allow"
resources = ["arn:aws:dynamodb:${var.region}:${var.account_id}:table:${var.dynamodb_table_name}"]
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${var.dev_id}:root",
"arn:aws:iam::${var.prod_id}:root",
"arn:aws:iam::${var.stage_id}:root"
]
}
}
}
I looked into cycles and interpolation, but It seems that 99% of the time the interpolation is done with "count" which only works for the creation of multiple resources (I hope I am not saying a big lie).
For example, I used
principals {
count = "${length(var.list)}"
identifiers = ["arn:aws:iam::${var.list[count.index]}"]
}
but that was unsuccessful.
Is there some way of achieving the final goal of replacing those 3 variables by a single list (or map) and iterate over them?
Given you have the list of account ids, have you tried this?
var "accounts" {
default = ["123", "456", "789"]
type = "list"
}
locals {
accounts_arn = "${formatlist("arn:aws:iam::%s", var.accounts)}"
}
Then in your policy document:
principals {
type = "AWS"
identifiers = ["${local.accounts_arn}"]
}
I haven't actually tried it, but can't think of a reason it wouldn't work.

Resources