Ayub
Ayub

Reputation: 21

How to call the value of a map (object) resource in another map (object) resource


resource "google_monitoring_custom_service" "customsrv" {
  for_each = var.custom_service_level
  display_name = each.value.service_display_name
  service_id = each.value.service_id
  project = var.project_id
}

// Google Monitoring SLO objects can support many different metric types, for more info see our documenation. 
resource "google_monitoring_slo" "custom_request_based_slo" {
   for_each = var.custom_sli
   service = google_monitoring_custom_service.customsrv[each.key].service_id
  display_name = each.value.metric_display_name
  goal = each.value.goal
  rolling_period_days = each.value.rolling_period_days // Replacable with calendar_period = "DAY", "WEEK", "FORTNIGHT", or "MONTH"
  request_based_sli {
    // Alternate implementation could use distribution_cut instead of good_total_ratio. 
    good_total_ratio {
      // Any combination of two elements: good_service_filter, bad_service_filter, total_service_filter. 
      good_service_filter = join(" AND ", each.value.good_service_filter)
      total_service_filter = join(" AND ", each.value.total_service_filter)
    }
  }
  depends_on = [ google_monitoring_custom_service.customsrv ]
}

Using .tfvars values:

custom_service_level = {
  "composer-service" = {
    service_id = "custom-srv-slos"
    service_display_name = "My Custom SLO"    
  }
}

custom_sli = {
  "composer-health" = { 
    metric_display_name = "test slo with service based SLI"
    goal = 0.9
    rolling_period_days = 28
    window_period = "300s"
    good_service_filter = [
          "metric.type=\"composer.googleapis.com/workflow/run_count\"",
          "resource.type=\"cloud_composer_workflow\"",
          "metric.labels.state=\"success\"",
      ],
    total_service_filter = [
          "metric.type=\"composer.googleapis.com/workflow/run_count\"",
          "resource.type=\"cloud_composer_workflow\"",
      ]
  },
}

Variables used:

variable "custom_service_level" {
  type = map(object({
    service_id = string,
    service_display_name = string,
  })) 
} 

variable "custom_sli" {
  type = map(object({
     metric_display_name = string,
     goal = number,
     rolling_period_days = number,
     good_service_filter = list(string),
     total_service_filter = list(string)
    # service = string   
  }))
}

Getting this error on plan:

│ Error: Invalid index
│
│   on main.tf line 600, in resource "google_monitoring_slo" "custom_request_based_slo":
│  600:    service = google_monitoring_custom_service.customsrv[each.key].service_id
│     ├────────────────
│     │ each.key is "composer-health"
│     │ google_monitoring_custom_service.customsrv is object with 1 attribute "composer-service"
│
│ The given key does not identify an element in this collection value.
╵

getting errors while creating alerts for above every above created map(object) both resource(1:1 relationship) using workaround1

resource "google_monitoring_alert_policy" "slo_alerts" {
  project = var.project_id
  display_name = var.slo_alert_display_name
  combiner     = "OR"

  conditions {
    display_name = var.slo_alert_display_name
    condition_threshold {
    filter = "select_slo_burn_rate(\"projects/${var.project_id}/services/${google_monitoring_custom_service.customsrv.service_id}/serviceLevelObjectives/${google_monitoring_slo.custom_request_based_slo.slo_id[]}\", \"3600s\")"
      duration   = var.duration
      comparison = "COMPARISON_GT"
      threshold_value = var.threshold_value
    }
  }
  notification_channels =  [
    for channel in google_monitoring_notification_channel.basic : channel.name
  ]

  documentation {
    content = var.content
  }
}

variables used:-

variable "enabled" {
  type = bool
  default = "true"
}

variable "slo_alert_display_name" {
type = string
default = "SLO Burn Rate Alert"
}

variable "content" {
type = string
default = "SLO burn for the past 60min exceeded x10 times the acceptable budget burn rate. Please verify from the console and take necessary action."
}

variable "threshold_value" {
type = number
default = 10
}

variable "duration" {
  type = string
default = "0s"
}

error getting on plan

PS C:\tf-workspace testrepo\testrepo1> terraform plan
Acquiring state lock. This may take a few moments...
╷
│ Error: Reference to "each" in context without for_each
│
│   on logging-alert-policy.tf line 11, in resource "google_monitoring_alert_policy" "slo_alerts":
│   11:       filter = "select_slo_burn_rate(\"projects/${var.project_id}/services/${google_monitoring_custom_service.customsrv[each.key].service_id}/serviceLevelObjectives/${google_monitoring_slo.custom_request_based_slo[each.key].slo_id}\", \"3600s\")"
│
│ The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.
╵
╷
│ Error: Reference to "each" in context without for_each
│
│   on logging-alert-policy.tf line 11, in resource "google_monitoring_alert_policy" "slo_alerts":
│   11:       filter = "select_slo_burn_rate(\"projects/${var.project_id}/services/${google_monitoring_custom_service.customsrv[each.key].service_id}/serviceLevelObjectives/${google_monitoring_slo.custom_request_based_slo[each.key].slo_id}\", \"3600s\")"
│
│ The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.

how to use map(objects) for alert policy resource for each (customsrv and ustom_request_based_slo)

Upvotes: 1

Views: 69

Answers (1)

Rui Jarimba
Rui Jarimba

Reputation: 18169

The problem

You are defining 2 maps, each one with different keys - composer-service and composer-health:

custom_service_level = {
  "composer-service" = { # <--------------------- map key
    # ...
  }
}

custom_sli = {
  "composer-health" = { # <--------------------- map key
    # ...
  }
}

The following line will throw an error because there is no google_monitoring_custom_service.customsrv resource with key composer-health:

service = google_monitoring_custom_service.customsrv[each.key].service_id

Workaround 1

If there is a 1:1 relationship between custom_service_level and custom_sli you can use the same keys in both maps.

Working example using null_resource:

variable "custom_service_level" {
  type = map(object({
    service_id           = string,
    service_display_name = string,
  }))
  default = {
    "composer-001" = { // <-------------------- map key
      service_id           = "custom-srv-slos"
      service_display_name = "My Custom SLO"
    }
  }
}

variable "custom_sli" {
  type = map(object({
    metric_display_name  = string,
    goal                 = number,
    rolling_period_days  = number,
    good_service_filter  = list(string),
    total_service_filter = list(string)
  }))
  default = {
    "composer-001" = { // <-------------------- same map key used in var.custom_service_level
      metric_display_name = "test slo with service based SLI"
      goal                = 0.9
      rolling_period_days = 28
      window_period       = "300s"
      good_service_filter = [
        "metric.type=\"composer.googleapis.com/workflow/run_count\"",
        "resource.type=\"cloud_composer_workflow\"",
        "metric.labels.state=\"success\"",
      ],
      total_service_filter = [
        "metric.type=\"composer.googleapis.com/workflow/run_count\"",
        "resource.type=\"cloud_composer_workflow\"",
      ]
    },
  }
}

resource "null_resource" "customsrv" {
  for_each = var.custom_service_level

  triggers = {
    display_name = each.value.service_display_name
    service_id   = each.value.service_id
  }
}

resource "null_resource" "custom_request_based_slo" {
  for_each = var.custom_sli

  triggers = {
    service             = null_resource.customsrv[each.key].triggers.service_id
    display_name        = each.value.metric_display_name
    goal                = each.value.goal
    rolling_period_days = each.value.rolling_period_days
  }
}

Running terraform plan:

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # null_resource.custom_request_based_slo["composer-001"] will be created
  + resource "null_resource" "custom_request_based_slo" {
      + id       = (known after apply)
      + triggers = {
          + "display_name"        = "test slo with service based SLI"
          + "goal"                = "0.9"
          + "rolling_period_days" = "28"
          + "service"             = "custom-srv-slos"
        }
    }

  # null_resource.customsrv["composer-001"] will be created
  + resource "null_resource" "customsrv" {
      + id       = (known after apply)
      + triggers = {
          + "display_name" = "My Custom SLO"
          + "service_id"   = "custom-srv-slos"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Workaround 2

Add a custom_service_level property to var.custom_sli. This property must contain one of the map keys of var.custom_service_level.

Working example using null_resource:

variable "custom_service_level" {
  type = map(object({
    service_id           = string,
    service_display_name = string,
  }))
  default = {
    "composer-service" = { // <-------------------- map key
      service_id           = "custom-srv-slos"
      service_display_name = "My Custom SLO"
    }
  }
}

variable "custom_sli" {
  type = map(object({
    custom_service_level = string, // <------------------ new property
    metric_display_name  = string,
    goal                 = number,
    rolling_period_days  = number,
    good_service_filter  = list(string),
    total_service_filter = list(string)
  }))
  default = {
    "composer-health" = {
      custom_service_level = "composer-service" // <-------------------- map key from var.custom_service_level
      metric_display_name  = "test slo with service based SLI"
      goal                 = 0.9
      rolling_period_days  = 28
      window_period        = "300s"
      good_service_filter = [
        "metric.type=\"composer.googleapis.com/workflow/run_count\"",
        "resource.type=\"cloud_composer_workflow\"",
        "metric.labels.state=\"success\"",
      ],
      total_service_filter = [
        "metric.type=\"composer.googleapis.com/workflow/run_count\"",
        "resource.type=\"cloud_composer_workflow\"",
      ]
    },
  }
}

resource "null_resource" "customsrv" {
  for_each = var.custom_service_level

  triggers = {
    display_name = each.value.service_display_name
    service_id   = each.value.service_id
  }
}

resource "null_resource" "custom_request_based_slo" {
  for_each = var.custom_sli

  triggers = {
    service             = null_resource.customsrv[each.value.custom_service_level].triggers.service_id
    display_name        = each.value.metric_display_name
    goal                = each.value.goal
    rolling_period_days = each.value.rolling_period_days
  }
}

Running terraform plan:

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # null_resource.custom_request_based_slo["composer-health"] will be created
  + resource "null_resource" "custom_request_based_slo" {
      + id       = (known after apply)
      + triggers = {
          + "display_name"        = "test slo with service based SLI"
          + "goal"                = "0.9"
          + "rolling_period_days" = "28"
          + "service"             = "custom-srv-slos"
        }
    }

  # null_resource.customsrv["composer-service"] will be created
  + resource "null_resource" "customsrv" {
      + id       = (known after apply)
      + triggers = {
          + "display_name" = "My Custom SLO"
          + "service_id"   = "custom-srv-slos"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Upvotes: 1

Related Questions