user
user

Reputation: 18529

Terraform : Create multiple metric filters & alarms with variables

I wish to create multiple metric filters with multiple alarms & actions. Here's what I have so far. I keep getting an error when trying to parameterize "alarm_actions" part without repeatedly typing the alarm action ARN manually :

# variables.tf

# 2 Log groups - group1 & group2 
variable "log_groups" {
  type  = list(object({})
  
  default = [
    {
      name = "group1"
      retention_in_days = 10
    },
    {
      name = "group2"
      retention_in_days = 7
    },
  ]
}

# Metric filters - 2 for group1 & 1 for group2
variable "metric_filters" {
  type  = list(object({}))

  default = [
    # Group1 - Metric filters
    {
      name    = "group1-filter1"
      pattern = "abc"
      group   = "group1"
    },
    {
      name    = "group1-filter2"
      pattern = "xyz"
      group   = "group1"
    },

    # Group2 - Metric filters
    {
      name    = "group2-filter1"
      pattern = "abcdef"
      group   = "group2"
    },
  ]
}

# Alarms - with variable number of actions
variable "alarms" {
  type  = list(object({}))

  default = [
    {
      name          = "group1-filter1-alarm"
      period        = 120
      alarm_actions = ["action1"]
    },
    {
      name          = "group1-filter2-alarm"
      period        = 300
      alarm_actions = ["action1", "action2"]
    },
    {
      name          = "group2-filter1-alarm"
      period        = 60
      alarm_actions = []
    },
  ]
}

Now creating the resources for log groups & metric filters is straight forward (although I would wish to avoid repeating the log group names in variables, but I can live with that)

resource "aws_cloudwatch_log_group" "log_groups" {
  for_each          = { for x in var.log_groups : x.name => x }
  name              = each.value.name
  retention_in_days = each.value.retention_in_days
}

resource "aws_cloudwatch_log_metric_filter" "metric_filters" {
  for_each       = { for x in var.metric_filters : x.name => x }
  name           = each.value.name
  pattern        = each.value.pattern
  log_group_name = each.value.group

  metric_transformation {
    name          = each.value.name
    namespace     = "xyz-namespace"
    value         = "1"
  }
}

The problem is with creating alarm_actions without repeating my code. How do I declare the ARN of the SNS notifications once as a variable and then reuse them in variables (I guess terraform doesn't allow variables inside variables). Alternatively, I can use the data functionality to get the SNS by names but a nested for loop for alarm_actions fails inside the alarm resource block :

data "aws_sns_topic" "action1" {
  name = "action1"
}

data "aws_sns_topic" "action2" {
  name = "action2"
}

resource "aws_cloudwatch_metric_alarm" "alarms" {
  for_each            = { for x in var.alarms : x.name => x }
  alarm_name          = each.value.name
  comparison_operator = "GreaterThan"
  evaluation_periods  = "1"
  metric_name         = each.value.name
  namespace           = "xyz-namespace"
  period              = each.value.period
  statistic           = "Sum"
  threshold           = "1"

  alarm_actions = [for a in each.value.alarm_actions : "data.aws_sns_topic.${a}.arn"]
}

The error I'm getting is

Error: "alarm_actions.0" does not match EC2 automation ARN ("^arn:[\\w-]+:automate:[\\w-]+:ec2:(reboot|recover|stop|terminate)$"): "data.aws_sns_topic.action1.arn"

i.e. it looks like the expression data.aws_sns_topic.${a}.arn gets the value of ${a} and then treats it like a string

Upvotes: 1

Views: 2568

Answers (1)

Helder Sepulveda
Helder Sepulveda

Reputation: 17574

You can concatenate the ARN if you like:
"arn:aws:sns:us-east-2:841836440000:action1"
that should be simple, you just need a few more items
"arn:aws:sns:{region}:{account}:{name}"


Another option could be to do the loop on the data, like this:

variable "alarms" {
  type = list(object({
    name          = string
    period        = number
    alarm_actions = list(string)
  }))

  default = [
    {
      name          = "group1-filter1-alarm"
      period        = 120
      alarm_actions = ["action1"]
    },{
      name          = "group1-filter2-alarm"
      period        = 300
      alarm_actions = ["action1", "action2"]
    },
  ]
}

data "aws_sns_topic" "actions" {
  for_each = toset(flatten([for x in var.alarms : x.alarm_actions]))
  name     = each.value
}

resource "aws_cloudwatch_metric_alarm" "alarms" {
  for_each            = { for x in var.alarms : x.name => x }
  alarm_name          = each.value.name
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = each.value.name
  namespace           = "xyz-namespace"
  period              = each.value.period
  statistic           = "Sum"
  threshold           = "1"
  alarm_actions       = [for a in each.value.alarm_actions : data.aws_sns_topic.actions[a].arn]
}

output "actions" {
  value = data.aws_sns_topic.actions
}

Tested to work with:

Terraform v0.12.24
 + provider.aws v2.54.0

Upvotes: 4

Related Questions