From 32ea1801aa612687ef834a5fba688748674241af Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Fri, 7 Jun 2024 11:49:49 -0400 Subject: [PATCH] Alerting: Support AWS SNS integration in Grafana (#88867) --- .../ngalert/api/compat_contact_points.go | 12 ++ .../ngalert/api/compat_contact_points_test.go | 5 - .../api/tooling/definitions/contact_points.go | 22 ++++ .../channels_config/available_channels.go | 113 ++++++++++++++++++ 4 files changed, 147 insertions(+), 5 deletions(-) diff --git a/pkg/services/ngalert/api/compat_contact_points.go b/pkg/services/ngalert/api/compat_contact_points.go index b6afc5f83df..26b13fbc5a2 100644 --- a/pkg/services/ngalert/api/compat_contact_points.go +++ b/pkg/services/ngalert/api/compat_contact_points.go @@ -133,6 +133,13 @@ func ContactPointToContactPointExport(cp definitions.ContactPoint) (notify.APIRe errs = append(errs, err) } } + for _, i := range cp.Sns { + el, err := marshallIntegration(j, "sns", i, i.DisableResolveMessage) + integration = append(integration, el) + if err != nil { + errs = append(errs, err) + } + } for _, i := range cp.Slack { el, err := marshallIntegration(j, "slack", i, i.DisableResolveMessage) integration = append(integration, el) @@ -284,6 +291,11 @@ func parseIntegration(json jsoniter.API, result *definitions.ContactPoint, recei if err = json.Unmarshal(data, &integration); err == nil { result.Sensugo = append(result.Sensugo, integration) } + case "sns": + integration := definitions.SnsIntegration{DisableResolveMessage: disable} + if err = json.Unmarshal(data, &integration); err == nil { + result.Sns = append(result.Sns, integration) + } case "slack": integration := definitions.SlackIntegration{DisableResolveMessage: disable} if err = json.Unmarshal(data, &integration); err == nil { diff --git a/pkg/services/ngalert/api/compat_contact_points_test.go b/pkg/services/ngalert/api/compat_contact_points_test.go index ddf74430691..8700212edba 100644 --- a/pkg/services/ngalert/api/compat_contact_points_test.go +++ b/pkg/services/ngalert/api/compat_contact_points_test.go @@ -53,11 +53,6 @@ func TestContactPointFromContactPointExports(t *testing.T) { // use the configs for testing because they have all fields supported by integrations for integrationType, cfg := range notify.AllKnownConfigsForTesting { t.Run(integrationType, func(t *testing.T) { - if integrationType == "sns" { - // TODO: Add code for SNS in grafana/grafana. - // Related grafana/alerting PR: https://github.com/grafana/alerting/pull/173 - t.Skip("sns not supported yet") - } recCfg := ¬ify.APIReceiver{ ConfigReceiver: notify.ConfigReceiver{Name: "test-receiver"}, GrafanaIntegrations: notify.GrafanaIntegrations{ diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 546aacf2730..238e65bce61 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -169,6 +169,27 @@ type SensugoIntegration struct { Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` } +type SigV4Config struct { + Region string `json:"region,omitempty" yaml:"region,omitempty" hcl:"region"` + AccessKey string `json:"access_key,omitempty" yaml:"access_key,omitempty" hcl:"access_key"` + SecretKey string `json:"secret_key,omitempty" yaml:"secret_key,omitempty" hcl:"secret_key"` + Profile string `json:"profile,omitempty" yaml:"profile,omitempty" hcl:"profile"` + RoleARN string `json:"role_arn,omitempty" yaml:"role_arn,omitempty" hcl:"role_arn"` +} + +type SnsIntegration struct { + DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` + + APIUrl *string `yaml:"api_url,omitempty" json:"api_url,omitempty" hcl:"api_url"` + Sigv4 SigV4Config `yaml:"sigv4" json:"sigv4" hcl:"sigv4,block"` + TopicARN *string `yaml:"topic_arn,omitempty" json:"topic_arn,omitempty" hcl:"topic_arn"` + PhoneNumber *string `yaml:"phone_number,omitempty" json:"phone_number,omitempty" hcl:"phone_number"` + TargetARN *string `yaml:"target_arn,omitempty" json:"target_arn,omitempty" hcl:"target_arn"` + Subject *string `yaml:"subject,omitempty" json:"subject,omitempty" hcl:"subject"` + Message *string `yaml:"message,omitempty" json:"message,omitempty" hcl:"message"` + Attributes *map[string]string `yaml:"attributes,omitempty" json:"attributes,omitempty" hcl:"attributes"` +} + type SlackIntegration struct { DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` @@ -284,6 +305,7 @@ type ContactPoint struct { Pushover []PushoverIntegration `json:"pushover" yaml:"pushover" hcl:"pushover,block"` Sensugo []SensugoIntegration `json:"sensugo" yaml:"sensugo" hcl:"sensugo,block"` Slack []SlackIntegration `json:"slack" yaml:"slack" hcl:"slack,block"` + Sns []SnsIntegration `json:"sns" yaml:"sns" hcl:"sns,block"` Teams []TeamsIntegration `json:"teams" yaml:"teams" hcl:"teams,block"` Telegram []TelegramIntegration `json:"telegram" yaml:"telegram" hcl:"telegram,block"` Threema []ThreemaIntegration `json:"threema" yaml:"threema" hcl:"threema,block"` diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 485277265fa..5fc42b809aa 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -1393,6 +1393,119 @@ func GetAvailableNotifiers() []*NotifierPlugin { }, }, }, + { // Since Grafana 11.1 + Type: "sns", + Name: "AWS SNS", + Description: "Sends notifications to Cisco Webex Teams", + Heading: "Webex settings", + Info: "Notifications can be configured for any Cisco Webex Teams", + Options: []NotifierOption{ + { + Label: "The Amazon SNS API URL", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "api_url", + }, + { + Label: "SigV4 Authentication", + Description: "Configures AWS's Signature Verification 4 signing process to sign requests", + Element: ElementTypeSubform, + PropertyName: "sigv4", + SubformOptions: []NotifierOption{ + { + Label: "Region", + Description: "The AWS region. If blank, the region from the default credentials chain is used.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "region", + }, + { + Label: "Access Key", + Description: "The AWS API access key.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "access_key", + Secure: true, + }, + { + Label: "Secret Key", + Description: "The AWS API secret key.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "secret_key", + Secure: true, + }, + { + Label: "Profile", + Description: "Named AWS profile used to authenticate", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "profile", + }, + { + Label: "Role ARN", + Description: "AWS Role ARN, an alternative to using AWS API keys", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "role_arn", + }, + }, + }, + { + Label: "SNS topic ARN", + Description: "If you don't specify this value, you must specify a value for the phone_number or target_arn. If you are using a FIFO SNS topic you should set a message group interval longer than 5 minutes to prevent messages with the same group key being deduplicated by the SNS default deduplication window.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "topic_arn", + }, + { + Label: "Phone number", + Description: "Phone number if message is delivered via SMS in E.164 format. If you don't specify this value, you must specify a value for the topic_arn or target_arn", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: ``, + PropertyName: "phone_number", + Secure: false, + }, + { + Label: "Target ARN", + Description: "The mobile platform endpoint ARN if message is delivered via mobile notifications. If you don't specify this value, you must specify a value for the topic_arn or phone_number", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: ``, + PropertyName: "target_arn", + }, + { + Label: "Subject", + Element: ElementTypeInput, + InputType: InputTypeText, + Description: "Optional subject. You can use templates to customize this field", + PropertyName: "subject", + Placeholder: alertingTemplates.DefaultMessageTitleEmbed, + }, + { + Label: "Message", + Description: "Optional message. You can use templates to customize this field. Using a custom message will replace the default message", + Element: ElementTypeTextArea, + PropertyName: "message", + Placeholder: alertingTemplates.DefaultMessageEmbed, + }, + { + Label: "Attributes", + Description: "SNS message attributes", + Element: ElementTypeKeyValueMap, + InputType: InputTypeText, + PropertyName: "attributes", + }, + }, + }, } }