From 4e8aa22cc1b65ffb81f966fe96bf2613f0910b08 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Wed, 1 Nov 2023 15:11:41 -0400 Subject: [PATCH] Alerting: Introduce support of responders for OpsGenie integration (#1100) * introduce support of responders for OpsGenie integration * Run tests in cloud instance --------- Co-authored-by: Julien Duchesne --- docs/resources/contact_point.md | 15 +++++ .../_acc_receiver_types_10_3.tf | 21 +++++++ ...source_alerting_contact_point_notifiers.go | 59 +++++++++++++++++++ .../resource_alerting_contact_point_test.go | 34 +++++++++++ 4 files changed, 129 insertions(+) create mode 100644 examples/resources/grafana_contact_point/_acc_receiver_types_10_3.tf diff --git a/docs/resources/contact_point.md b/docs/resources/contact_point.md index 9285c9335..6a3f3defa 100644 --- a/docs/resources/contact_point.md +++ b/docs/resources/contact_point.md @@ -248,6 +248,7 @@ Optional: - `disable_resolve_message` (Boolean) Whether to disable sending resolve messages. Defaults to `false`. - `message` (String) The templated content of the message. - `override_priority` (Boolean) Whether to allow the alert priority to be configured via the value of the `og_priority` annotation on the alert. +- `responders` (Block List) Teams, users, escalations and schedules that the alert will be routed to send notifications. If the API Key belongs to a team integration, this field will be overwritten with the owner team. This feature is available from Grafana 10.3+. (see [below for nested schema](#nestedblock--opsgenie--responders)) - `send_tags_as` (String) Whether to send annotations to OpsGenie as Tags, Details, or both. Supported values are `tags`, `details`, `both`, or empty to use the default behavior of Tags. - `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`. - `url` (String) Allows customization of the OpsGenie API URL. @@ -256,6 +257,20 @@ Read-Only: - `uid` (String) The UID of the contact point. + +### Nested Schema for `opsgenie.responders` + +Required: + +- `type` (String) Type of the responder. Supported: team, teams, user, escalation, schedule or a template that is expanded to one of these values. + +Optional: + +- `id` (String) ID of the responder. Must be specified if name and username are empty. +- `name` (String) Name of the responder. Must be specified if username and id are empty. +- `username` (String) User name of the responder. Must be specified if name and id are empty. + + ### Nested Schema for `pagerduty` diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types_10_3.tf b/examples/resources/grafana_contact_point/_acc_receiver_types_10_3.tf new file mode 100644 index 000000000..081828210 --- /dev/null +++ b/examples/resources/grafana_contact_point/_acc_receiver_types_10_3.tf @@ -0,0 +1,21 @@ +resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v10.3" + + opsgenie { + url = "http://opsgenie-api" + api_key = "token" + message = "message" + description = "description" + auto_close = true + override_priority = true + send_tags_as = "both" + responders { + type = "user" + id = "803f87e1a7f848b0a0779810bee5d1d3" + } + responders { + type = "team" + name = "Test team" + } + } +} diff --git a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go index 601424d4c..cab0d1824 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go +++ b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go @@ -797,6 +797,36 @@ func (o opsGenieNotifier) schema() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"tags", "details", "both"}, false), Description: "Whether to send annotations to OpsGenie as Tags, Details, or both. Supported values are `tags`, `details`, `both`, or empty to use the default behavior of Tags.", } + r.Schema["responders"] = &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Description: "Teams, users, escalations and schedules that the alert will be routed to send notifications. If the API Key belongs to a team integration, this field will be overwritten with the owner team. This feature is available from Grafana 10.3+.", + Elem: &schema.Resource{ + Description: "Defines a responder. Either id, name or username must be specified", + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: "Type of the responder. Supported: team, teams, user, escalation, schedule or a template that is expanded to one of these values.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the responder. Must be specified if username and id are empty.", + }, + "username": { + Type: schema.TypeString, + Optional: true, + Description: "User name of the responder. Must be specified if name and id are empty.", + }, + "id": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the responder. Must be specified if name and username are empty.", + }, + }, + }, + } return r } @@ -830,6 +860,21 @@ func (o opsGenieNotifier) pack(p gapi.ContactPoint, data *schema.ResourceData) ( notifier["send_tags_as"] = v.(string) delete(p.Settings, "sendTagsAs") } + if v, ok := p.Settings["responders"]; ok && v != nil { + items := v.([]any) + responders := make([]map[string]interface{}, 0, len(items)) + for _, item := range items { + itemMap := item.(map[string]interface{}) + responder := make(map[string]interface{}, 4) + packNotifierStringField(&itemMap, &responder, "type", "type") + packNotifierStringField(&itemMap, &responder, "id", "id") + packNotifierStringField(&itemMap, &responder, "name", "name") + packNotifierStringField(&itemMap, &responder, "username", "username") + responders = append(responders, responder) + } + notifier["responders"] = responders + delete(p.Settings, "responders") + } packSecureFields(notifier, getNotifierConfigFromStateWithUID(data, o, p.UID), o.meta().secureFields) @@ -862,6 +907,20 @@ func (o opsGenieNotifier) unpack(raw interface{}, name string) gapi.ContactPoint if v, ok := json["send_tags_as"]; ok && v != nil { settings["sendTagsAs"] = v.(string) } + if v, ok := json["responders"]; ok && v != nil { + items := v.([]any) + responders := make([]map[string]interface{}, 0, len(items)) + for _, item := range items { + tfResponder := item.(map[string]interface{}) + responder := make(map[string]interface{}, 4) + unpackNotifierStringField(&tfResponder, &responder, "type", "type") + unpackNotifierStringField(&tfResponder, &responder, "id", "id") + unpackNotifierStringField(&tfResponder, &responder, "name", "name") + unpackNotifierStringField(&tfResponder, &responder, "username", "username") + responders = append(responders, responder) + } + settings["responders"] = responders + } return gapi.ContactPoint{ UID: uid, Name: name, diff --git a/internal/resources/grafana/resource_alerting_contact_point_test.go b/internal/resources/grafana/resource_alerting_contact_point_test.go index 76c6a90b2..45919717c 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_test.go +++ b/internal/resources/grafana/resource_alerting_contact_point_test.go @@ -370,6 +370,40 @@ func TestAccContactPoint_notifiers10_2(t *testing.T) { }) } +func TestAccContactPoint_notifiers10_3(t *testing.T) { + testutils.CheckCloudInstanceTestsEnabled(t) // TODO: Switch to `testutils.CheckOSSTestsEnabled(t, ">=10.3.0")` once 10.3 is released. + + var points []gapi.ContactPoint + + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + // Implicitly tests deletion. + CheckDestroy: testContactPointCheckDestroy(points), + Steps: []resource.TestStep{ + // Test creation. + { + Config: testutils.TestAccExample(t, "resources/grafana_contact_point/_acc_receiver_types_10_3.tf"), + Check: resource.ComposeTestCheckFunc( + testContactPointCheckExists("grafana_contact_point.receiver_types", &points, 1), + // opsgenie + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.url", "http://opsgenie-api"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.api_key", "token"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.message", "message"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.description", "description"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.auto_close", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.override_priority", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.send_tags_as", "both"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.responders.0.type", "user"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.responders.0.id", "803f87e1a7f848b0a0779810bee5d1d3"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.responders.1.type", "team"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "opsgenie.0.responders.1.name", "Test team"), + ), + }, + }, + }) +} + func TestAccContactPoint_empty(t *testing.T) { testutils.CheckOSSTestsEnabled(t, ">=9.1.0")