Looping over tags/aliases instead of whole resource - terraform

I'm trying to figure out a way to loop over a list of aliases for Datadog dashboards.
In 1 single dashboard I want to have multiple servers involved that will be fetched from variables.
For example:
variable "africa" {
type = map(any)
default = {
"server1" = {
alias = "server1"
hostname = "hostname1"
name = "query1"
formula_expression = "query1 * 8"
}
"server2" = {
name = "server2"
hostname = "hostname2"
name = "query2"
formula_expression = "query2 * 8"
}
"server3" = {
name = "server3
hostname = "hostname3"
name = "query3"
formula_expression = "query3 * 8"
}
}
}
resource "datadog_dashboard" "global_dashboards_africa" {
title = "Terraform - Africa"
description = "Dashboards for Global"
layout_type = "ordered"
widget {
group_definition {
title = "Quick Overview"
show_title = "true"
layout_type = "ordered"
widget {
timeseries_definition {
title = "Traffic"
title_size = "16"
title_align = "left"
show_legend = false
legend_layout = "auto"
legend_columns = ["avg", "min", "max", "value", "sum"]
request {
for server in var.africa:
formula {
alias = ${alias}
formula_expression = ${formula_expression}
}
query {
metric_query {
name = ${name}
query = "max:system.net.bytes_sent{device:bond0,host:${hostname}}"
}
}
style {
palette = "warm"
line_type = "solid"
line_width = "normal"
}
display_type = "area"
}
yaxis {
include_zero = "true"
scale = "linear"
min = "auto"
max = "auto"
}
}
widget_layout {
x = "0"
y = "5"
width = "12"
height = "5"
}
}
}
}
}
I've added for_each at the top, right after resource but it creates only 3 resources. I want to create 1 resource and in aliases - add like 50 servers with hostnames and names in specific locations.
I've tried multiple solutions but Terraform cannot help me with this.

Related

How to create a map by switching keys and values from another map in Terraform?

Given the map of objects that contains a list of repeating strings:
variable "input_var" = {
type = object(map(
name = string
tags = optional(list(string))
)),
default = {
"one" = {
name = "nameone"
tags = ["tag1", "tag2"]
}
"two" = {
name = "nametwo"
tags = ["tag1", "tag3"]
}
"three" = {
name = "namethree"
tags = ["tag2"]
}
"four" = {
name = "namefour"
}
}
}
I want to create a map with keys being the tags from all elements of var.input_var.
Taking the above defaults, a sample of output is:
{
"tag1": [
{
name = "nameone"
key = "one"
},
{
name = "nametwo"
key = "two"
},
],
"tag2": [
{
name = "namethree"
key = "three"
}
],
"tag3": [
{
name = "nametwo"
key = "two"
}
]
}
I was able to achieve this in a two step steps.
First is to create a unique set of tags:
locals{
tags_set = distinct(flatten([
for k, v in var.input_var: coalesce(v.tags, [])
]))
}
followed by creating the (local) map out of local.tags_set:
tags_map = { for key_tag in local.tags_set:
key_tag => flatten([for k, v in var.input_var: [
for tag in coalesce(v.tags, []) : {
key = k
name = v.name
} if tag == key_tag
]]) }
Is there a better implementation for this, in a single step and/or more efficient?

In terraform, how do you sort a list of objects and grab the last value?

Given the following list of objects where order is not guaranteed. How do I use terraform to sort a list and grab the latest value?
locals {
list_of_objects = [
{
name = "0.25388.50855"
sort_versions_by_semver = false
tags = {
"baseosimg" = "windows2022datacenter"
}
},
{
name = "0.25424.21095"
sort_versions_by_semver = false
tags = {
"baseosimg" = "windows2022datacenter"
}
},
{
name = "0.25399.6325"
sort_versions_by_semver = false
tags = {
"baseosimg" = "windows2022datacenter"
}
},
]
}
In terraform if you convert the list of objects to a map, it will automatically sort it. From there you can grab the last value (or first).
locals {
map_of_sorted_objects = { for a in local.list_of_objects : a.name => a }
}
output "test" {
value = lookup(
local.map_of_sorted_objects,
element(
sort(keys(local.map_of_sorted_objects)),
length(local.map_of_sorted_objects) - 1
)
)
}
Output
test = {
"name" = "0.25424.21095"
"sort_versions_by_semver" = false
"tags" = {
"baseosimg" = "windows2022datacenter"
}
}

Terraform dynamic block module

I am trying to create dynamic block for the below datadog custom pipeline however I can see only verbose category but not Debug category, is there any way to get both debug & Verbose.
Resource:
dynamic "processor" {
for_each = var.processor
content {
dynamic "category_processor" {
for_each = length(keys(lookup(processor.value, "category_processor", {}))) == 0 ? [] : [lookup(processor.value, "category_processor", {})]
content {
target = category_processor.value["target"]
name = lookup(category_processor.value, "name", null)
is_enabled = lookup(category_processor.value, "is_enabled", null)
dynamic "category" {
for_each = length(keys(lookup(category_processor.value, "category", {}))) == 0 ? [] : [lookup( category_processor.value, "category", {})]
content {
name = category.value.name
dynamic "filter" {
for_each = length(keys(lookup(category.value, "filter", {}))) == 0 ? [] : [lookup( category.value, "filter", {})]
content{
query=filter.value.query
}
}
}
}
}
}
}
}
Variable:
variable "processor" {
description = "One or more processors (multiples allowed)."
type = any
default = {
processor {
category_processor = {
target = "foo.severity"
category = {
name = "debug"
filter {
query = "#severity: \".\""
}
}
category = {
name = "verbose"
filter {
query = "#severity: \"-\""
}
}
name = "sample category processor"
is_enabled = true
}
}
}

Terraform variables and count from CSV

I'm making myself virtual machines from the CSV list. Currently it works for me to create virtual machines for all entries. I only need to create them for those that have in CSV in the maxview table have = 1. Is it possible? I try also with
for_each = { for inst in local.instances : inst.node => inst } ? 1:0
but not results.
.csv
"node","node_ip","ipmi_ip","vmsystem","vmhost","node_id","maxview"
"s156","127.0.0.1","127.0.0.1","vmware","vmhost.example.com","15","1"
"s101","127.0.0.1","127.0.0.1","Proxmox","vmhost.example.com","","0"
main.tf
locals {
instances = csvdecode(file("./data/sys-${local.environment}.csv"))
vsphere_hn = {
domain = "hn"
dev1 = {
app_datastores = ["sata_ssd_storage_140-1"]
}
eu1 = {
app_datastores = ["sata_ssd_storage_140-1"]
}
}
}
// VMs
module "vm-hn-maxview-maxview_vm" {
count = length(local.instances)
providers = {
vsphere = vsphere.vcadmin
}
name = "${local.instances[count.index].node}.${local.vsphere_hn.domain}.${var.dns_local_name[local.environment]}"
annotation = "Managed by Terraform"
dc = var.vsphere_core_datacenter_name[local.environment]
datastore = local.vsphere_hn[local.environment].app_datastores[count.index]
template = var.centos7_vsphere_template[local.environment].template
resource_pool_vapp = vsphere_vapp_container.vm-hn-maxview-vapp.id
customize_name = "maxview"
customize_domain = "${local.instances[count.index].node}.${local.vsphere_hn.domain}.${var.dns_local_name[local.environment]}"
cpu_number = 2
num_cores_per_socket = 1
ram_size = 2048
network = {
"${var.network_vlan_maxview_name[local.environment]}" = ["${var.network_address_prefix[local.environment]}.8.${local.instances[count.index].node_id}"]
}
network_gateway = "${var.network_address_prefix[local.environment]}.9.254"
network_submask = ["${var.network_address_submask_maxview[local.environment]}"]
data_disk = {
disk1 = {
size_gb = 10,
thin_provisioned = false,
data_disk_scsi_controller = 0,
}
}
}
It is possible?
Your for expression lambda is close, but you need to add a conditional for the maxview:
# number to number comparison
for_each = { for inst in local.instances : inst.node => inst if tonumber(inst.maxview) == 1 }
# string to string comparison
for_each = { for inst in local.instances : inst.node => inst if inst.maxview == "1" }
This will filter your map(object) down to only the entries where maxview equals 1, and then the for_each meta-argument will cause iteration over only those entries. You can read more about this in the documentation.

How can I do dimensions block "dynamic"?

Would like to know how can I make dimensions block in aws_cloudwatch_metric_alarm resource "dynamic".
So far have code which I'm sure won't work... but wouldl like to ask how it should be written to achive the goal.
locals {
backend_tg_name = data.terraform_remote_state.network.outputs.backend_tg_name
frontend_tg_name = data.terraform_remote_state.network.outputs.frontend_tg_name
webadmin_tg_name = data.terraform_remote_state.network.outputs.webadmin_tg_name
dimensions = [
{
LoadBalancer = data.terraform_remote_state.network.outputs.alb_suffix
TargetGroup = data.terraform_remote_state.network.outputs.backend_tg_suffix
},
{
LoadBalancer = data.terraform_remote_state.network.outputs.alb_suffix
TargetGroup = data.terraform_remote_state.network.outputs.frontend_tg_name
},
{
LoadBalancer = data.terraform_remote_state.network.outputs.alb_suffix
TargetGroup = data.terraform_remote_state.network.outputs.webadmin_tg_suffix
}
]
}
resource "aws_cloudwatch_metric_alarm" "httpcode_target_5xx_count" {
for_each = {
backend_tg_name = local.backend_tg_name
frontend_tg_name = local.frontend_tg_name
webadmin_tg_name = local.webadmin_tg_name
}
alarm_name = format("ALB: High amount of 5XX errors on target group %s", each.value)
comparison_operator = "GreaterThanThreshold"
evaluation_periods = var.tg_evaluation_periods
metric_name = "HTTP_Code_Target_5XX_Count"
namespace = "AWS/ApplicationELB"
period = var.tg_period
statistic = "Sum"
threshold = var.tg_5xx_threshhold
alarm_description = "Average API 5XX target group error code count is too high"
alarm_actions = aws_sns_topic.infra_monitoring.arn
ok_actions = aws_sns_topic.infra_monitoring.arn
treat_missing_data = "notBreaching"
dimensions = {
"LoadBalancer" = ???
"TargetGroup" = ???
}
}
How I have to change dimensions and/or locals block? I would like to iterate three times and create three same alarms for three different target groups behind one and the same ALB.
Help please.
Seems this is working, hope it will be helpfull:
locals {
alb_suffix = data.terraform_remote_state.network.outputs.alb_suffix
tg_alarms = {
"backend_tg" = {
tg_name = data.terraform_remote_state.network.outputs.backend_tg_name
tg_suffix = data.terraform_remote_state.network.outputs.backend_tg_suffix
},
"frontend_tg" = {
tg_name = data.terraform_remote_state.network.outputs.frontend_tg_name
tg_suffix = data.terraform_remote_state.network.outputs.frontend_tg_name
},
"webadmin_tg" = {
tg_name = data.terraform_remote_state.network.outputs.webadmin_tg_name
tg_suffix = data.terraform_remote_state.network.outputs.webadmin_tg_suffix
}
}
}
resource "aws_cloudwatch_metric_alarm" "httpcode_target_5xx_count" {
for_each = local.tg_alarms
alarm_name = format("ALB: High amount of 5XX errors on target group %s", each.value["tg_name"])
comparison_operator = "GreaterThanThreshold"
evaluation_periods = var.tg_evaluation_periods
datapoints_to_alarm = var.tg_datapoints_to_alarm
metric_name = "HTTPCode_Target_5XX_Count"
namespace = "AWS/ApplicationELB"
period = var.tg_period
statistic = "Sum"
threshold = var.tg_5xx_threshold
alarm_description = "Average API 5XX target group error code count is too high"
alarm_actions = [aws_sns_topic.infra_monitoring.arn]
ok_actions = [aws_sns_topic.infra_monitoring.arn]
treat_missing_data = "notBreaching"
dimensions = {
"LoadBalancer" = local.alb_suffix
"TargetGroup" = each.value["tg_suffix"]
}
}

Resources