1

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)

1 Answer 1

1

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.
Sign up to request clarification or add additional context in comments.

4 Comments

Superb, thanks Rui, its working now. also how to add alert policies using map(object) for every customsrv and custom_request_based_slo in a map which is created above.
@Ayub if my answer helped please consider upvoting it and/or accepting it.
@Ayub That is a different question, and should be asked in a new question. Please accept this answer since it answered your question to help other users seeking assistance.
@RuiJarimba please help regarding new query stackoverflow.com/questions/78941015/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.