Failed to extract specific value from map object terraform - terraform

This is my main file
services_list = flatten([
reverse(values(var.host_svc)) ])
}
variable "host_svc" {
type = map(map(any))
}
output "aa" {
value = local.services_list
}
This is terraform tfvars file
host_svc = {
accounts = {
name = "accounts"
pod = "3"
cpu = "512M"
memory = "1024M"
}
analytics = {
name = "analytics"
pod = "3"
cpu = "512M"
memory = "1024M"
}
}
When i am output local.services_list i ma getting whole object. i just want to fetch just name of each map object like accounts,analytics.
i have tried with list data type it works but i want to do with map ojject because i have to use different object values

You can do that much easier than you are trying:
local {
services_list = [for k, v in var.host_svc : k]
}
Or even more easy using the built-in keys function [1]:
local {
services_list = keys(var.host_svc)
}
[1] https://developer.hashicorp.com/terraform/language/functions/keys

If I understood you correctly, you want to read the names from your map. If you want to read your map keys, just use keys(map) function:
variable "host_svc" {
type = map(map(any))
default = {
accounts_k = {
name = "accounts"
pod = "3"
cpu = "512M"
memory = "1024M"
}
analytics_k = {
name = "analytics"
pod = "3"
cpu = "512M"
memory = "1024M"
}
}
}
output "aa" {
value = keys(var.host_svc)
}
Which will output:
+ aa = [
+ "accounts_k",
+ "analytics_k",
]
I used _k suffix just to differentiate between the map keys and your name key inside the internal map
In case you want to read name attribute of that map you can do:
output "aa" {
value = [for svc in local.services_list : svc.name]
}
In case you don't want to have duplicates in that outputted list, use toset():
output "aa" {
value = toset([for svc in local.services_list : svc.name])
}

Related

How do I get a specific key in a nested map in terraform, when running in a for_each/for loop?

I have two sets of locals defined as below:
locals {
env1 = {
a = {
}
b = {
}
}
}
locals {
env2 = {
x = {
}
y = {
}
}
}
Now, I have to run a loop for local.env1 so that loop runs for a & b. But I also need "x" from local.env2 for a resource attribute in the same resource block the loops runs.
There are 2 resource blocks and it would look something like this:
resource_block1
{
for_each = local.env1
scope = each.key
id = something["x"]
}
resource_block2
{
for_each = local.env1
scope = each.key
id = something["y"]
}
For "id" attribute in both the resource blocks I don't want to hardcode "x" or "y" respectively. I want to programmatically fill "x" and "y" for "id" attribute in the respective resource blocks.
I tried to create a map for env1 and env2 I was able to fetch "a" and "b" from local.env1 but I am unable to fetch/filter only "x" or only "y" from it.
Any ideas/solutions please? Thank you for checking.

How can I split out an 'any' variable in terraform?

I'm trying to get multiple values out of an 'any' type variable. I'm new to terraform and open to recommendations. Specifically for this example, I'd like to know how I can output the 'bucket_name' value in my outputs.
variable "replica_config" {
type = any
default = {
role = "role_name"
rules = [
{
id = "full-s3-replication"
status = true
priority = 10
delete_marker_replication = false
destination = {
bucket = "bucket_name"
storage_class = "STANDARD"
replica_kms_key_id = "key_id"
account_id = "account_id"
replication_time = {
status = "Enabled"
minutes = 15
}
}
}
]
}
}
Current Output:
output "output4" {
value = flatten(var.replica_config["rules"])
}
Since you you have a list for rules, you can use a splat expression as such:
output "output4" {
value = var.replica_config.rules[*].destination.bucket
}
Keep in mind, the output of this expression will also be a list. If you want a single item instead of a list, you can use an index.
For example:
output "output4" {
value = var.replica_config.rules[0].destination.bucket
}

Produce single item maps procedurally from nested map in terraform

I have a nested map variable of account name and ID by OU, like:
variable "aws_accounts" {
type = map(map(any))
default = {
first_ou = {
first_account = "111111111"
second_account = "222222222"
}
second_ou = {
third_account = "333333333"
fourth_account = "444444444"
}
}
}
This is great for passing a map of account_name to account_id as a sub-variable to do things by ou and the modules in question are constructed to accept a map input.
I would like to also render a local so that I can also reference single accounts but get a map value for them without having to maintain a separate list of variables, like
local.first_account = {
first_account = "111111111"
}
local.second_account = {
second_account = "222222222"
}
local.third_account = {
third_account = "33333333"
}
etc.
I have tried various techniques but without success:
I cannot work out how to refer to each map in the array iteratively- most documentation seems to be based on lists and when I try to do a for_each I get
The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.
Based on your example, it seems like you want to take your two-level map and turn it into a single-level map where the keys are the account names and the "OU names" are just discarded.
Here's one way to achieve that:
locals {
account_ids = merge(values(var.aws_accounts)...)
}
This first uses values to take the values from the top-level map, producing a list of maps.
It then uses merge to take all of the elements from each of the maps and combine them into a single new map. I used the ... symbol to tell Terraform that it should treat each element of the list as a separate argument to merge, rather than just passing the whole list as a single argument.
After merging these together you could potentially split them apart again, creating a separate map each, using a for expression.
locals {
account_maps = tomap({
for k, id in local.account_ids :
k => { (k) = id }
})
}
Maybe not exactly what you're looking for, but could be helpful:
locals {
accounts = merge(var.aws_accounts["first_ou"], var.aws_accounts["second_ou"])
}
If you need to do this in a more dynamic way:
locals {
accounts = zipmap(
flatten([for item in var.aws_accounts : keys(item)]),
flatten([for item in var.aws_accounts : values(item)])
)
}
Now you can access each account with local.accounts["first_account"] etc.
OK So with help from Martin Atkins and Bryan Heden I have found an answer to this. It isn't exactly pretty but it does work:
variable "aws_accounts" {
type = map(map(any))
default = {
first_ou = {
first_account = "111111111"
second_account = "222222222"
}
second_ou = {
third_account = "333333333"
fourth_account = "444444444"
}
}
}
locals {
# gives single map from nested map
account_ids = merge(values(var.aws_accounts)...)
# gives separate structured map for each key
single_accounts_maps = {
for account, id in local.account_ids :
account => {
account = account
id = id
}
}
# gives map where values = keys plus values
single_accounts_maps_joined = zipmap(
flatten([for item in var.aws_accounts : keys(item)]),
[for item in local.single_accounts_maps :
join(" = ", values(item))]
)
# gives nested map by key = {key = "value"}
single_accounts_maps_keys_values = {
for item in local.single_accounts_maps_joined :
(split(" = ", item)[0]) => {
(split(" = ", item)[0]) = (split(" = ", item)[1])
}
}
}
Output that I wanted:
terraform console
> local.single_accounts_maps_keys_values
{
"first_account" = {
"first_account" = "111111111"
}
"fourth_account" = {
"fourth_account" = "444444444"
}
"second_account" = {
"second_account" = "222222222"
}
"third_account" = {
"third_account" = "333333333"
}
}
After the discussion with Martin Atkins and his subsequent edits below, I am recommending his answer instead as simpler, more legible and more graceful, although the tomap() nesting appears to be unneeded, i.e. do
locals {
account_ids = merge(values(var.aws_accounts)...)
account_maps = {
for k, id in local.account_ids :
k => { (k) = id }
}
}

Terraform: How to store a map in a single ssm-parameter and get back a value pair?

let's assume i have a map like this:
variable "test_parameters" {
type = map
default = {
"A" = "subnet-73e35d3e",
"B" = "subnet-7e00d503",
"C" = "subnet-d9d446b2",
}
}
What is the terraform-code
to store the values of the map in a single aws_ssm_parameter ?
get a single value from the parameter like: B = subnet-7e00d503 or B:subnet-7e00d503 ?
Many thanks for help ;)
You can store it as json, and then get json back.
resource "aws_ssm_parameter" "foo" {
name = "myparam"
type = "String"
value = jsonencode(var.test_parameters)
}
To read it:
data "aws_ssm_parameter" "foo" {
name = "myparam"
}
# to use
locals {
myparam_values = jsondecode(data.aws_ssm_parameter.foo.value)
}

Optional list element processing in Terraform

I am trying to add redrive policies to existing queues.
I have managed to define a list like this:
variable "sqsq_primary" {
type = "list"
default = [
{
name = "PrimaryQueue1"
maxReceiveCount = -1
deadLetterQueue = ""
},
{
name = "PrimaryQueue2"
maxReceiveCount = 5
deadLetterQueue = "PrimaryQueue2_DL"
},
{
name = "PrimaryQueue3"
maxReceiveCount = 20
deadLetterQueue = "PrimaryQueue3_DL"
}
]
}
I have defined a list of DL queues like this:
variable "sqsq_primary_dl" {
type = "list"
default = [
"PrimaryQueue2_DL",
"PrimaryQueue3_DL"
]
}
In my module I define resources like this:
resource "aws_sqs_queue" "q" {
count = "${length(var.sqsq_primary)}"
name = "${lookup(var.sqsq_primary[count.index], "name")}-${var.environment}"
## Conditionally Sets A Redrive Policy ##
redrive_policy = "${lookup(var.sqsq_primary[count.index], "deadLetterQueue") != "" ? "{\"deadLetterTargetArn\":\"arn:aws:sqs:${var.region}:${var.acc_number}:${lookup(var.sqsq_primary[count.index], "deadLetterQueue")}-${var.environment}\",\"maxReceiveCount\":${lookup(var.sqsq_primary[count.index], "maxReceiveCount")}}" : ""}"
depends_on = ["aws_sqs_queue.qdl"]
}
resource "aws_sqs_queue" "qdl" {
count = "${length(var.sqsq_primary_dl)}"
name = "${element(var.sqsq_primary_dl, count.index)}-${var.environment}"
}
This works. However, I don't like the duplicated information which is the names of the DL queues.
So the question is, how could I get rid of the second list? How could I iterate in the second resource over the first list instead and only create a DL queue if deadLetterQueue != "" ?
Thanks for your help!
I think you may have encountered a limitation of terraform interpolation. Unless you deconstruct your list of maps to separate maps, the best is probably below.
If you keep your definitions for queues with no dl at the bottom and use a static value for minus maths on the dl resource count, the plan stays the same as before.
As a side note, it's dead letter not dead leater.
variable "sqsq_primary" {
type = "list"
default = [
{
name = "PrimaryQueue2"
maxReceiveCount = 5
deadLeaterQueue = "PrimaryQueue2_DL"
},
{
name = "PrimaryQueue3"
maxReceiveCount = 20
deadLeaterQueue = "PrimaryQueue3_DL"
},
{
name = "PrimaryQueue1"
maxReceiveCount = -1
deadLeaterQueue = ""
}
]
}
resource "aws_sqs_queue" "q" {
count = "${length(var.sqsq_primary)}"
name = "${lookup(var.sqsq_primary[count.index], "name")}-${var.environment}"
## Conditionally Sets A Redrive Policy ##
redrive_policy = "${lookup(var.sqsq_primary[count.index], "deadLeaterQueue") != "" ? "{\"deadLetterTargetArn\":\"arn:aws:sqs:${var.region}:${var.acc_number}:${lookup(var.sqsq_primary[count.index], "deadLeaterQueue")}-${var.environment}\",\"maxReceiveCount\":${lookup(var.sqsq_primary[count.index], "maxReceiveCount")}}" : ""}"
depends_on = ["aws_sqs_queue.qdl"]
}
resource "aws_sqs_queue" "qdl" {
count = "${length(var.sqsq_primary) - 1}"
name = "${lookup(var.sqsq_primary[count.index], "deadLeaterQueue")-var.environment}"
}
My colleague has come up with a solution that seems slightly more flexible than the one provided by #henry-dobson.
We have also refactored it so now it doesn't require the deadLeaterQueue value - we conform to a naming standard now, so the resulting names of the DL queues are different from the ones in the question.
variable "sqsq_primary" {
type = "list"
default = [
{
name = "PrimaryQueue1"
maxReceiveCount = 0
},
{
name = "PrimaryQueue2"
maxReceiveCount = 5
},
{
name = "PrimaryQueue3"
maxReceiveCount = 20
}
]
}
data "empty_data_source" "deadletterq" {
count = "${length(var.sqsq_primary)}"
inputs = {
dl = "${lookup(var.sqsq_primary[count.index], "maxReceiveCount", "") > 0 ? "${replace(lookup(var.sqsq_primary[count.index], "name"),"Queue","DeadLetterQueue")}" : ""}"
}
}
resource "aws_sqs_queue" "q" {
count = "${length(var.sqsq_primary)}"
name = "${lookup(var.sqsq_primary[count.index], "name")}-${var.environment}"
## Conditionally Sets A Redrive Policy ##
redrive_policy = "${lookup(var.sqsq_primary[count.index], "maxReceiveCount") > 0 ? "{\"deadLetterTargetArn\":\"arn:aws:sqs:${var.region}:${var.acc_number}:${replace(lookup(var.sqsq_primary[count.index], "name"),"Queue","DeadLetterQueue")}-${var.environment}\",\"maxReceiveCount\":${lookup(var.sqsq_primary[count.index], "maxReceiveCount")}}" : ""}"
depends_on = ["aws_sqs_queue.qdl"]
}
resource "aws_sqs_queue" "qdl" {
count = "${length(compact(data.empty_data_source.deadletterq.*.outputs.dl))}"
name = "${element(compact(data.empty_data_source.deadletterq.*.outputs.dl), count.index)}-${var.environment}"
}

Resources