Filtering variables map+list - list

I have a data structure and I need to extract a list out of a map of lists based on the maps key. Here is the sample data:
locals {
firwall_rules = ["first", "third"] # this is the filter used on firewall_rules_lookup
firewall_rules_lookup = {
type = map
"first" = [ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
],
"second"= [ { name ="rule2.1", start_ip="0.0.1.1" , end_ip = "0.0.1.2" } ],
"third" = [ { name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" },
]
}
fw_rules = flatten([
for rule_name in local.firewall_rules : {
for r in local.firewall_rules_lookup[rule_name] : {
name = r.name
start_ip = r.start_ip
end_ip = r.end_ip
}
}
])
}
Expected result:
fw_rules=
[ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
{ name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" }
]
The inner for loop is not working. Terraform gives me an error. I think the for loops
work only with maps. Is there a different solution to this problem?

It should be as follows:
locals {
firewall_rules = ["first", "third"] # this is the filter used on firewall_rules_lookup
firewall_rules_lookup = {
"first" = [ { name ="rule1.1", start_ip="0.0.0.1" , end_ip = "0.0.0.2" },
{ name ="rule1.2", start_ip="0.0.0.4" , end_ip = "0.0.0.5" },
],
"second"= [ { name ="rule2.1", start_ip="0.0.1.1" , end_ip = "0.0.1.2" } ],
"third" = [ { name ="rule3.1", start_ip="0.0.3.1" , end_ip = "0.0.3.2" },
{ name ="rule3.2", start_ip="0.0.3.4" , end_ip = "0.0.3.5" },
]
}
fw_rules = flatten([
for rule_name in local.firewall_rules : [
for r in local.firewall_rules_lookup[rule_name] : {
name = r.name
start_ip = r.start_ip
end_ip = r.end_ip
}
]
])
}

Related

Terraform for_each nested solution

I need to access the elements of a nested map but I can't do it and I get an error Can't access attributes on a primitive-typed value (string).
# GLUE
locals {
glue_catalog_resources = {
uni = {
name = "mst_business_units",
description = "Unidades de negocios"
columns = [
{
name = "codigouni"
type = "int"
comment = "Code"
},{
name = "descuni"
type = "varchar(256)"
comment = "Description"
},{
name = "estado"
type = "varchar(256)"
comment = "Current status"
}
]
}
}
}
resource "aws_glue_catalog_table" "glue_catalogs_redshift" {
for_each = local.glue_catalog_resources
name = each.value.name
database_name = aws_glue_catalog_database.cl_sales.name
description = each.value.description
retention = 0
table_type = "EXTERNAL_TABLE"
parameters = {
EXTERNAL = "TRUE"
"classification" = "parquet"
}
storage_descriptor {
location = ""
input_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat"
output_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat"
number_of_buckets = 0
compressed = "false"
stored_as_sub_directories = "false"
ser_de_info {
serialization_library = "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"
parameters = {
"serialization.format" = 1
}
}
parameters = {
"typeOfData": "kinesis"
}
columns {
name = [ for column in each.value.columns: [ for row in column: row.name ] ]
type = [ for column in each.value.columns: [ for row in column: row.type ] ]
comment = [ for column in each.value.columns: [ for row in column: row.comment ] ]
}
}
}
I need to include in the columns tag columns name, type and comment reading it from the map above and I can't do it, what would be the correct way to read it
columns {
name = [ for column in each.column.value: [ for row in column: row.name ] [ for row in column: row.name ] [ for row in column: row.name ] [ for row in column: row.name
type = [ for column in each.value.columns: [ for row in column: row.type ]
comment = [ for column in each.value.columns: [ for row in column: row.comment ] ] ]
}
In this case, columns is a list of key value pairs, so you need to adjust the second for a bit:
name = [for column in local.columns : [for k, v in column : v if k == "name"]]
type = [for column in local.columns : [for k, v in column : v if k == "type"]]
comment = [for column in local.columns : [for k, v in column : v if k == "comment"]]
EDIT: The first part of the answer resolves the issue with accessing the list elements. However, in order for this to work as you would want to, I would suggest a couple of changes. In the local variable, you can change columns to be a map of objects:
locals {
glue_catalog_resources = {
uni = {
name = "mst_business_units",
description = "Unidades de negocios"
columns = {
"codigouni" = {
name = "codigouni"
type = "int"
comment = "Code"
},
"descuni" = {
name = "descuni"
type = "varchar(256)"
comment = "Description"
},
"estado " = {
name = "estado"
type = "varchar(256)"
comment = "Current status"
}
}
}
}
}
Then, in the columns block you would do the following:
resource "aws_glue_catalog_table" "glue_catalogs_redshift" {
for_each = local.glue_catalog_resources
name = each.value.name
database_name = aws_glue_catalog_database.cl_sales.name
description = each.value.description
retention = 0
table_type = "EXTERNAL_TABLE"
parameters = {
EXTERNAL = "TRUE"
"classification" = "parquet"
}
storage_descriptor {
location = ""
input_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat"
output_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat"
number_of_buckets = 0
compressed = "false"
stored_as_sub_directories = "false"
ser_de_info {
serialization_library = "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"
parameters = {
"serialization.format" = 1
}
}
parameters = {
"typeOfData" : "kinesis"
}
dynamic "columns" {
for_each = each.value.columns
content {
name = columns.value.name
type = columns.value.type
comment = columns.value.comment
}
}
}
}
This approach is using dynamic [1] and for_each [2] to iterate over all of the columns and assign the wanted values to arguments.
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each

Value from list of string is adding sqaure brackets to the value

I am creating dashboard for ecs but loadbalancer name is getting [] in "[ "mis-app-cluster" ]" because of list of string type in variable.
Is there any by which we can remove [] from the value.
List of string is mandatory.
data -
{
my-dashboard-name = "MIS-Dev-Cloudwatch-Dashboard-ECS"
aws-region = "eu-west-1"
targets = ["mis-app-cluster"]
metrics = ["CPUUtilization", "MemoryUtilization"]
service_name = ["mis-fileserver", "mis-nginx", "mis-odi", "mis-oraclebi", "mis-ords", "mis-restdataservices"]
aws-namespace = "AWS/ECS"
dim = "ClusterName"
stat = "Average"
period = 300
}
module (I have only added for ALB module below)
locals {
widget-def-alb = [for metric in var.metrics :
{
type = "metric",
x = 0,
y = 0
width = 18,
height = 8,
properties = {
metrics = [for targetgroup in var.target_groups : [var.aws-namespace, metric,"TargetGroup", targetgroup, var.dim, var.targets]],
title = "${var.aws-namespace}: ${metric}",
region = var.aws-region,
period = var.period,
stat = var.stat,
view = "timeSeries",
legend = {
position = "right"
}
}
}
]
}
resource "aws_cloudwatch_dashboard" "cw-dashboard-alb" {
count = var.aws-namespace == "AWS/ApplicationELB" ? 1 : 0
dashboard_name = var.dashboard-name
dashboard_body = jsonencode({
start = "-PT9H"
widgets = local.widget-def-alb
})
}
Code -
module "create-dashboard" {
source = "../"
for_each = { for service in local.dashboards : service.my-dashboard-name => service if length(regexall(".*ALB.*", service.my-dashboard-name))
> 0 }
dashboard-name = each.value.my-dashboard-name
aws-region = each.value.aws-region
targets = each.value.targets
metrics = each.value.metrics
aws-namespace = each.value.aws-namespace
dim = each.value.dim
target_groups = each.value.target_groups
stat = each.value.stat
period = each.value.period
}
output
{
"legend": {
"position": "right"
},
"metrics": [
[ "AWS/ECS", "CPUUtilization", "ServiceName", "mis-fileserver", "ClusterName", [ "mis-app-cluster" ] ],
[ ".", "MemoryUtilization", ".", ".", ".", [ "mis-app-cluster" ] ]
],
"period": 300,
"region": "eu-west-1",
"stat": "Average",
"title": "mis-fileserver",
"view": "timeSeries",
"timezone": "UTC"
}

Tricky conversion of variable using locals in terraform

looking for possible solution/approach for the below req.
I have a var as below
nodes= {
"type1" = {
max_size = 4
tags = { "key1" = "value1"
"key2" = "value2" } # structure of tags can be changed as needed
}
and then I have a locals that will ref this tag to use in an asg
locals {
for nodetype, nodeconfig in var.nodes
..
..
node_tags = nodeconfig.tags
}
local.node_tags must take the final form as below
{
"key" = "key1"
"value" = "value1"
"propagate_at_launch" = "true"
},
{
"key" = "key2"
...
...
}
I need to use these in an asg
resource "aws_autoscaling_group" "nodes" {
for_each = { for i in local.nodes ....
...
...
tags = each.value.node_tags
}
Similar use case - https://github.com/terraform-aws-modules/terraform-aws-autoscaling#tags
I tried something below.. looks ugly and doesn't work.
locals {
for nodetype, nodeconfig in var.nodes
..
..
node_tags = {for i in nodeconfig.tags : "{i}" => {
"key" = keys(i),
"value" = values(i),
"propagate_at_launch" = "true"
}
}
}
What about this one:
variable "nodes" {
default = {
"type1" = {
max_size = 4
tags = { "key1" = "value1"
"key2" = "value2"
}
},
"type2" = {
max_size = 4
tags = { "key11" = "value11"
"key22" = "value22"
}
},
}
}
locals {
node_tags = values(merge([for nodetype, nodeconfig in var.nodes:
{for tag_key , tag_value in nodeconfig.tags:
"${nodetype}-${tag_key}-${tag_value}" =>
{key = tag_key
value = tag_value
"propagate_at_launch" = "true"}
}
]...))
}
output "test" {
value = local.node_tags
}
which gives:
test = [
{
"key" = "key1"
"propagate_at_launch" = "true"
"value" = "value1"
},
{
"key" = "key2"
"propagate_at_launch" = "true"
"value" = "value2"
},
{
"key" = "key11"
"propagate_at_launch" = "true"
"value" = "value11"
},
{
"key" = "key22"
"propagate_at_launch" = "true"
"value" = "value22"
},
]

Terraform merge list of maps

My input is as below:
list_groups = [
{
dev-api = {
envs = [
"dev-eu-1",
"dev-eu-2",
]
hosts = [
"dev-api-eu1",
"dev-api-eu2",
]
}
},
{
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
},
]
I am using merge as below:
output "map_groups" {
value = merge(var.list_groups...)
}
And the output I get is :
map_groups = {
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
}
But I need the output to be:
map_groups = {
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
"dev-eu-1",
"dev-eu-2"
]
hosts = [
"dev-api-us1",
"dev-api-us2",
"dev-api-eu1",
"dev-api-eu2"
]
}
}
I understand the merge is eating up one of the entries because the key is the same but if it could somehow merge the entries/elements, that'll be great.
This is a rather complex issue. I think you should concentrate on simplifying your input data, rather than trying to come out with some convoluted TF code to post-fix your input data structures.
Nevertheless, the expansion symbol ... can be used to solve the issue in terraform.
variable "list_groups" {
default = [
{
dev-api = {
envs = [
"dev-eu-1",
"dev-eu-2",
]
hosts = [
"dev-api-eu1",
"dev-api-eu2",
]
}
},
{
dev-api = {
envs = [
"dev-us-1",
"dev-us-2",
]
hosts = [
"dev-api-us1",
"dev-api-us2",
]
}
},
]
}
locals {
api_names = distinct([for api in var.list_groups: keys(api)[0]])
}
output "test" {
value = {
for key, val in {
for api_name in local.api_names:
api_name => {
envs = flatten([
for api in var.list_groups:
api[api_name].envs
])
hosts = flatten([
for api in var.list_groups:
api[api_name].hosts
])
}...
}:
key => val[0]
}
}
Output is:
test = {
"dev-api" = {
"envs" = [
"dev-eu-1",
"dev-eu-2",
"dev-us-1",
"dev-us-2",
]
"hosts" = [
"dev-api-eu1",
"dev-api-eu2",
"dev-api-us1",
"dev-api-us2",
]
}
}

How to define GCP log based metric with multiple labels using terraform , not able to define label_extractors block

resource "google_logging_metric" "logging_metric" {
for_each = { for inst in var.log_based_metrics : inst.name => inst }
name = each.value.name
filter = each.value.filter
metric_descriptor {
metric_kind = each.value.metric_kind
value_type = each.value.value_type
dynamic "labels" {
for_each = each.value.labels
content {
key = labels.value["label_key"]
value_type = labels.value["label_value_type"]
description = labels.value["label_description"]
}
}
display_name = each.value.display_name
}
label_extractors = {
<< How to define multiple label_keys with label extractors >>
}
}
I have tried for_each but it was referring to first for_each in the resource and we cant use dynamic as it creates multiple label_extractors blocks, which is not intended.
My variables file :
log_based_metrics = [
{
name = "name1",
filter = "something"
metric_kind = "DELTA",
value_type = "INT64",
labels = [
{
label_key = "deployment",
label_value_type = "STRING",
label_description = "deployment",
label_extractor = "REGEXP_EXTRACT(jsonPayload.involvedObject.name, \"(.*)-[^-]*-[^-]*$\")"
}
]
},
{
name = "name2",
filter = "something",
metric_kind = "DELTA",
value_type = "INT64",
labels = [
{
label_key = "deployment",
label_value_type = "STRING",
label_description = "deployment",
label_extractor = "REGEXP_EXTRACT(jsonPayload.involvedObject.name, \"(.*)-[^-]*-[^-]*$\")"
}
]
},
{
name = "name3",
filter = "something",
metric_kind = "DELTA",
value_type = "INT64",
labels = [
{
label_key = "deployment",
label_value_type = "STRING",
label_description = "deployment",
label_extractor = "REGEXP_EXTRACT(jsonPayload.involvedObject.name, \"(.*)-[^-]*-[^-]*$\")"
}
]
}
]
label_extractor should be something like this :(label_extractor ,label_key from each label block)
for example :
labels {
key = "mass"
value_type = "STRING"
description = "amount of matter"
}
labels {
key = "sku"
value_type = "INT64"
description = "Identifying number for item"
}
display_name = "My metric"
}
value_extractor = "EXTRACT(jsonPayload.request)"
label_extractors = {
"mass" = "EXTRACT(jsonPayload.request)"
"sku" = "EXTRACT(jsonPayload.id)"
}
label_extractors is an attribute, not a block. So you have to just create a map that you want. I don't know exact structure of your input data (not shown in the question), but it would be something as follows (rough example):
label_extractors = { for val in each.value.labels: val.label_key => val.label_extractor }