Convert Terraform map keys into a list - terraform

I have a Terraform map of accounts like below.
account_map = {
111111111111 = "DEV"
222222222222 = "DEV"
333333333333 = "STG"
333333333333 = "PROD"
}
How can I create a list of Dev accounts IDs as below from the above map?. I tired to use keys function but not sure how to search the DEV value inside the keys function.
dev_accounts = ["111111111111", "222222222222"]

For situations like this without an intrinsic function, and no possibility of a custom function, then you basically must use a for expression:
local {
dev_accounts = [ for number, account in var.account_map : number if account == "DEV" ] # ["111111111111", "222222222222"]
}

There is a built-in Terraform keys function.
> keys({a=1, c=2, d=3})
[
"a",
"c",
"d",
]
So, in your case:
locals {
account_map = {
111111111111 = "DEV"
222222222222 = "DEV"
333333333333 = "STG"
333333333333 = "PROD"
}
dev_accounts = keys(local.account_map)
}
output "dev_accounts" {
value = local.dev_accounts
}
Results to:
% terraform plan
Changes to Outputs:
+ dev_accounts = [
+ "111111111111",
+ "222222222222",
+ "333333333333",
]
...
Terraform docs: https://developer.hashicorp.com/terraform/language/functions/keys

Related

terraform - use count and for_each together

I'm creating multiple Google Cloud Projects using a module which creates a custom service account for each one and provides that as an output eg
module "app_projects" {
count = var.number_application_projects
etc
}
I then have a list of IAM roles I want to assign to each Project Service account like this:
sa_roles = ["roles/run.developer","roles/appengine.deployer"]
resource "google_project_iam_member" "proj_sa_iam_roles" {
count = var.number_application_projects
for_each = toset(var.sa_roles)
project = module.app_projects[count.index].project_id
role = each.value
member = "serviceAccount:${module.app_projects[count.index].service_account_email}"
}
This runs into the error about "count" and "for_each" being mutually-exclusive, and I can't for the life of me figure out what to use instead, any help would be appreciated!
You can probably use setproduct, but the standard method is using flatten.
They even have a section tailored for similar use cases. Flattening nested structures for for_each.
Here is an example, that doesn't use your exact resource, but should be instructive and you can actually run it to test it out.
modules/app-project/variables.tf
variable "name" {}
modules/app-project/outputs.tf
output "name" {
value = var.name
}
modules/member/variables.tf
variable "project" {}
variable "role" {}
modules/member/outputs.tf
output "project_role" {
value = "${var.project}-${var.role}"
}
main.tf
locals {
roles = ["rad", "tubular"]
}
module "app_project" {
source = "./modules/app-project"
count = 2
name = "app-project-${count.index}"
}
module "project_role" {
source = "./modules/member"
for_each = { for pr in flatten([for p in module.app_project[*] : [
for r in local.roles : {
app_project_name = p.name
role = r
}]
]) : "${pr.app_project_name}-${pr.role}" => pr }
project = each.value.app_project_name
role = each.value.role
}
output "project_roles" {
value = values(module.project_role)[*].project_role
}
terraform plan output
Changes to Outputs:
+ project_roles = [
+ "app-project-0-rad",
+ "app-project-0-tubular",
+ "app-project-1-rad",
+ "app-project-1-tubular",
]
In your case specifically, I think something like this would work:
resource "google_project_iam_member" "proj_sa_iam_roles" {
for_each = { for i, pr in flatten([for p in module.app_project[*] : [
for r in var.sa_roles : {
app_project = p
role = r
}]
]) : "${i}-${pr.role}" => pr }
project = each.value.app_project.project_id
role = each.value.role
member = "serviceAccount:${each.value.app_project.service_account_email}"
}

terraform combine 2 objects with 2 attributes for aws instance_ids

Following hashicorp doc to leverage for_each to provision multiple instance using local var map.
I am unable to get the instance_ids into a single lists for output:
output "instance_ids" {
description = "IDs of EC2 instances"
value = { for p in sort(keys(var.project)) : p => module.ec2_instances[p].instance_ids }
}
This is the output:
instance_ids = {
"client-webapp" = [
"i-0e11fcc341e6ce292",
"i-0b7ddd178c0590116",
"i-0c570628d3997874b",
"i-0a1642d7cc173f329",
]
"internal-webapp" = [
"i-0e65c8569f2d2c6f5",
"i-0c62e911e9446c53b",
]
}
Looking to get both objects lists of instance_ids into single list. Any good recommendation? Attempt to use merge, flatten, concat fail with various errors.
The var context for ids in above output loops thru the KEYs 'client-webapp' & 'internal-webapp'
variable "project" {
description = "Map of project names to configuration."
type = map
default = {
client-webapp = {
public_subnets_per_vpc = 2,
private_subnets_per_vpc = 2,
instances_per_subnet = 2,
instance_type = "t2.micro",
environment = "dev"
},
internal-webapp = {
public_subnets_per_vpc = 1,
private_subnets_per_vpc = 1,
instances_per_subnet = 2,
instance_type = "t2.nano",
environment = "test"
}
}
}
Any suggestions?
You can concatenate both lists together.
locals {
instance_ids = {
"client-webapp" = [
"i-0e11fcc341e6ce292",
"i-0b7ddd178c0590116",
"i-0c570628d3997874b",
"i-0a1642d7cc173f329",
]
"internal-webapp" = [
"i-0e65c8569f2d2c6f5",
"i-0c62e911e9446c53b",
]
}
new-list = concat(local.instance_ids["client-webapp"], local.instance_ids["internal-webapp"])
}
output "new-list" {
description = "my new list"
value = local.new-list
}
Here is the output
Changes to Outputs:
+ new-list = [
+ "i-0e11fcc341e6ce292",
+ "i-0b7ddd178c0590116",
+ "i-0c570628d3997874b",
+ "i-0a1642d7cc173f329",
+ "i-0e65c8569f2d2c6f5",
+ "i-0c62e911e9446c53b",
]
Instead of creating a map with the p =>, can you just return the array? And flatten.
Something like...
output "instance_ids" {
description = "IDs of EC2 instances"
value = flatten({ for p in sort(keys(var.project)) : module.ec2_instances[p].instance_ids })
}

Splitting of a map, based on validation - Terraform

I'm looking for a way to split up my config list/map. Reason for this is that we are looking for a way to share resources with other AWS Accounts, by using the aws_ram_principal.
resource "aws_ram_principal_association" "vpc" {
count = length(module.custom_local.accounts)
principal = lookup(module.custom_local.accounts[count.index], "shared") == true ? lookup(module.custom_local.accounts[count.index], "id") : null
resource_share_arn = aws_ram_resource_share.vpc.arn
}
The module.custom_local.accounts variable looks as followed.
accounts = [
{
"name" = "account_a",
"id" = "111111111111",
"shared" = false
},
{
"name" = "account_b"
"id" = "222222222222"
"shared" = true
},
{
"name" = "account_c"
"id" = "333333333333"
"shared" = true
}
]
The problem in this is that it works as long as all shared values are true, in case of a false the principal is invalid and complains: The argument "principal" is required, but no definition was found.
Now I was wondering if I can create a local variable that only contains the account ids of the accounts that are true.
Something in the lines of
locals {
share_accounts = ....
}
Im not sure if this is even possible but my attempts using below didn't get me anywhere.
share_accounts = { for s in module.custom_local.accounts : s => ... }
- or -
share_accounts = [ for index in range(0, length(module.custom_local.accounts) ... ]
Any help is much appreciated - ty.
For statements take a conditional. You could do it like so:
share_accounts = [ for s in module.custom_local.accounts : s if s.shared ]

terraform flatten loop for 3 times

Has any one tried to get flatten for 3 loops working? I keep getting an error when i try the 3rd one:
I am trying to loop through a list within a data resource - data.instances.sg.ids
Variable example:
alarms = [
{
instances = data.instances.ids ## a list of instance ids
config = [
metric_name = "disk_free"
threshold = "GreaterThan"
]
}
]
locals {
configs = flatten([
for config_key, config_list in var.alarms : [
for instance in config_list.instances : {
for config in config_list.configs : {
instance_id = instance
metric_name = config.name
threshold = config.threshold
}
}
]
])
}
how can i properly loop through and flatten this list with data instances list.
Thanks
Not sure what exactly you want to construct, but I think it should be:
locals {
configs = flatten([
for config_key, config_list in var.alarms : [
for instance in config_list.instances : [
for config in config_list.configs :
{
instance_id = instance
metric_name = config.name
threshold = config.threshold
}
]
]
])
}

Iterate over list of list of maps in terraform

Consider I have a variable that is a list of list of maps.
Example:
processes = [
[
{start_cmd: "a-server-start", attribute2:"type_a"},
{start_cmd: "a-worker-start", attribute2:"type_b"}
{start_cmd: "a--different-worker-start", attribute2:"type_c"}
],
[
{start_cmd: "b-server-start", attribute2:"type_a"},
{start_cmd: "b-worker-start", attribute2:"type_b"}
]
]
In each iteration, I need to take out the array of maps, then iterate over that array and take out the values of the map. How do I achieve this in terraform?
I have considered having two counts and doing some arithmetic to trick terraform into performing a lookalike nested iteration Check reference here. But in our case the number of maps in the inner array can vary.
Also we are currently using the 0.11 terraform version but dont mind using the alpha 0.12 version of terraform if it is possible to achieve this in that version.
Edit:
Added how I would use this variable:
resource “create_application” “applications” {
// Create a resource for every array in the variable processes. 2 in this case
name = ""
migration_command = ""
proc {
// For every map create this attribute for the resource.
name = ““
init_command = “a-server-start”
type = “server”
}
}
Not sure if this clears up the requirement. Please do ask if it is still not clear.
Using terraform 0.12.x
locals {
processes = [
[
{ start_cmd: "a-server-start", type: "type_a", name: "inglorious bastards" },
{ start_cmd: "a-worker-start", type: "type_b", name: "kill bill" },
{ start_cmd: "a--different-worker-start", type: "type_c", name: "pulp fiction" },
],
[
{ start_cmd: "b-server-start", type: "type_a", name: "inglorious bastards" },
{ start_cmd: "b-worker-start", type: "type_b", name: "kill bill" },
]
]
}
# just an example
data "archive_file" "applications" {
count = length(local.processes)
type = "zip"
output_path = "applications.zip"
dynamic "source" {
for_each = local.processes[count.index]
content {
content = source.value.type
filename = source.value.name
}
}
}
$ terraform apply
data.archive_file.applications[0]: Refreshing state...
data.archive_file.applications[1]: Refreshing state...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
If a create_application resource existed, it can be modeled like so
resource "create_application" "applications" {
count = length(local.processes)
name = ""
migration_command = ""
dynamic "proc" {
for_each = local.processes[count.index]
content {
name = proc.value.name
init_command = proc.value.start_cmd
type = proc.value.type
}
}
}
Here is my solution that work like charm. Just note the tricks google_service_account.purpose[each.value["name"]].name where I can retrieve the named array element by using its name.
variable "my_envs" {
type = map(object({
name = string
bucket = string
}))
default = {
"dev" = {
name = "dev"
bucket = "my-bucket-fezfezfez"
}
"prod" = {
name = "prod"
bucket = "my-bucket-ezaeazeaz"
}
}
}
resource "google_service_account" "purpose" {
for_each = var.my_envs
display_name = "blablabla (terraform)"
project = each.value["name"]
account_id = "purpose-${each.value["name"]}"
}
resource "google_service_account_iam_binding" "purpose_workload_identity_binding" {
for_each = var.my_envs
service_account_id = google_service_account.purpose[each.value["name"]].name
role = "roles/iam.whatever"
members = [
"serviceAccount:${each.value["name"]}.svc.id.goog[purpose/purpose]",
]
}
resource "google_storage_bucket_iam_member" "purpose_artifacts" {
for_each = var.my_envs
bucket = each.value["bucket"]
role = "roles/storage.whatever"
member = "serviceAccount:${google_service_account.purpose[each.value["name"]].email}"
}

Resources