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

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.

Related

Failed to extract specific value from map object 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])
}

Terraform Lookup returning a block

I am attempting to only assign a log_config to a subnet, if the subnet name is in the "file_one.tf sample" below. To accomplish the conditional logic I am using the lookup function to only return a "log_config" block if the name matches a name in the variable referenced in "file_one.tf".
The error I am getting is:
An argument named "log_config" is not expected here. Did you mean to define a block type of "log_config"
Would someone please help out with this? Or present a more elegant approach to assigning blocks to resources with conditional logic?
Thank you
# file_one.tf sample
locals {
subnets = {
"one" = {
"name" = "one"
local_config = {
aggregation_interval = "INTERVAL_10_SEC"
metadata = "INCLUDE_ALL_METADATA"
}
}
}
# file_two.tf
resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" {
foreach = local.subnets
name = each_value.name
ip_cidr_range = "10.2.0.0/16"
region = "us-central1"
network = google_compute_network.custom-test.id
secondary_ip_range {
range_name = "tf-test-secondary-range-update1"
ip_cidr_range = "192.168.10.0/24"
}
log_config = lookup(each.value, log_config, null)
}
You are attempting to pass a map type as a value for an argument, and the resource schema is expecting a block type and not an argument with a map. You would need a dynamic block for this situation:
dynamic "log_config" {
for_each = lookup(each.value, log_config, [])
content {
aggregation_interval = log_config.value["aggregation_interval"]
metadata = log_config.value["metadata"]
}
}
The key that you want in said map is called local_config, no? When lookup() returns its default value is null, which is also not a block. But here I believe you want to pass in a string key to the lookup function:
# file_two.tf
{
# ...
name = each.value.name
# ...
log_config = lookup(each.value, "local_config", null)
}

For_each loop with for expression based on value in map

Since the title is not descriptive enough let me introduce my problem.
I'm creating DRY module code for CDN that contains profile/endpoint/custom_domain.
Variable cdn_config would hold all necessary/optional parameters and these are created based on the for_each loop.
Variable looks like this:
variable "cdn_config" {
profiles = {
"profile_1" = {}
}
endpoints = {
"endpoint_1" = {
custom_domain = {
}
}
}
}
Core of this module is working - in the means that it would create cdn_profile "profile_1" then cdn_endpoint "endpoint_1" will be created and assigned to this profile then cdn_custom_domain will be created and assigned to "endpoint_1" since it's the part of "endpoint_1" map.
Then I realize, what in case I want to create "cdn_custom_domain" only and specify resource ID manually?
I was thinking that adding the optional parameter "standalone" could help, so it would look like this:
variable "cdn_config" {
profiles = {
"profile_1" = {}
}
endpoints = {
"endpoint_1" = {
custom_domain = {
}
}
"endpoint_standalone" = {
custom_domain = {
standalone = true
cdn_endpoint_id = "xxxxx"
}
}
}
}
Having this "standalone" parameter eq true "endpoint_standalone" map should be totally ignored from looping in the azurerm_cdn_endpoint resource creation.
So far this direction is my only guess, clearly, it's not working - if I add "endpoint_standalone" it complains that not all required parameters are specified so it's surely finding it.
resource "azurerm_cdn_endpoint" "this" {
for_each = {for k in keys(var.cdn_config.endpoints) : k => var.cdn_config.endpoints[k] if lookup(var.cdn_config.endpoints[k],"standalone",null) != "true"}
I would be grateful if you have a solution for this problem.
You are comparing a bool type to a string type, so the logical comparison will always return false:
for_each = {for k in keys(var.cdn_config.endpoints) : k => var.cdn_config.endpoints[k] if lookup(var.cdn_config.endpoints[k],"standalone",null) != true }
While we are here, we can also improve this for expression:
for_each = { for endpoint, params in var.cdn_config.endpoints : endpoint => params if lookup(params.custom_domain, "standalone", null) != true }

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