From e307d1ab35d45623d6d44f46641a74c990f9e9a4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 12 Apr 2022 08:30:21 +0800 Subject: [PATCH] feat(alerting): Add group-specific WebHook URL for Discord (#271) * feat(alerting): Add group-specific webhook URL for discord Add group-specific webhook URL for discord alert Provides support for paging multiple Discords based on the group selector while keeping backward compatibility to the old Discords configuration manifest integration per team can be specified in the overrides sections in an array form. ref: #96 Signed-off-by: Bo-Yi Wu * docs: update Signed-off-by: Bo-Yi Wu * Update README.md * Update README.md * Update alerting/provider/discord/discord.go Co-authored-by: TwiN * Update README.md Co-authored-by: TwiN * test: revert testing name * Update alerting/provider/discord/discord_test.go Co-authored-by: TwiN Co-authored-by: TwiN --- README.md | 17 ++-- alerting/provider/discord/discord.go | 32 ++++++- alerting/provider/discord/discord_test.go | 100 ++++++++++++++++++++++ 3 files changed, 141 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c5b48027..f6efb43a 100644 --- a/README.md +++ b/README.md @@ -346,13 +346,16 @@ ignored. | `alerting.twilio` | Settings for alerts of type `twilio`.
See [Configuring Twilio alerts](#configuring-twilio-alerts). | `{}` | | `alerting.custom` | Configuration for custom actions on failure or alerts.
See [Configuring Custom alerts](#configuring-custom-alerts). | `{}` | - #### Configuring Discord alerts -| Parameter | Description | Default | -|:---------------------------------|:-------------------------------------------------------------------------------------------|:--------------| -| `alerting.discord` | Configuration for alerts of type `discord` | `{}` | -| `alerting.discord.webhook-url` | Discord Webhook URL | Required `""` | -| `alerting.discord.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +| Parameter | Description | Default | +|:------------------------------------------ |:------------------------------------------------------------------------------------------ |:------------- | +| `alerting.discord` | Configuration for alerts of type `discord` | `{}` | +| `alerting.discord.webhook-url` | Discord Webhook URL | Required `""` | +| `alerting.discord.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.discord.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.discord.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.discord.overrides[].webhook-url` | Discord Webhook URL | `""` | ```yaml alerting: @@ -374,8 +377,8 @@ endpoints: send-on-resolved: true ``` - #### Configuring Email alerts + | Parameter | Description | Default | |:---------------------------------- |:------------------------------------------------------------------------------------------ |:------------- | | `alerting.email` | Configuration for alerts of type `email` | `{}` | diff --git a/alerting/provider/discord/discord.go b/alerting/provider/discord/discord.go index 281933d4..38c898ee 100644 --- a/alerting/provider/discord/discord.go +++ b/alerting/provider/discord/discord.go @@ -17,17 +17,35 @@ type AlertProvider struct { // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + WebhookURL string `yaml:"webhook-url"` } // IsValid returns whether the provider's configuration is valid func (provider *AlertProvider) IsValid() bool { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.WebhookURL) == 0 { + return false + } + registeredGroups[override.Group] = true + } + } return len(provider.WebhookURL) > 0 } // Send an alert using the provider func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved))) - request, err := http.NewRequest(http.MethodPost, provider.WebhookURL, buffer) + request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) if err != nil { return err } @@ -86,6 +104,18 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * }`, message, description, colorCode, results) } +// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group +func (provider *AlertProvider) getWebhookURLForGroup(group string) string { + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + return override.WebhookURL + } + } + } + return provider.WebhookURL +} + // GetDefaultAlert returns the provider's default alert configuration func (provider AlertProvider) GetDefaultAlert() *alert.Alert { return provider.DefaultAlert diff --git a/alerting/provider/discord/discord_test.go b/alerting/provider/discord/discord_test.go index 80f2bff3..d1ecbaf1 100644 --- a/alerting/provider/discord/discord_test.go +++ b/alerting/provider/discord/discord_test.go @@ -22,6 +22,43 @@ func TestAlertProvider_IsValid(t *testing.T) { } } +func TestAlertProvider_IsValidWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + WebhookURL: "http://example.com", + Group: "", + }, + }, + } + if providerWithInvalidOverrideGroup.IsValid() { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + WebhookURL: "", + Group: "group", + }, + }, + } + if providerWithInvalidOverrideTo.IsValid() { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + WebhookURL: "http://example.com", + Overrides: []Override{ + { + WebhookURL: "http://example.com", + Group: "group", + }, + }, + } + if !providerWithValidOverride.IsValid() { + t.Error("provider should've been valid") + } +} + func TestAlertProvider_Send(t *testing.T) { defer client.InjectHTTPClient(nil) firstDescription := "description-1" @@ -156,3 +193,66 @@ func TestAlertProvider_GetDefaultAlert(t *testing.T) { t.Error("expected default alert to be nil") } } + +func TestAlertProvider_getWebhookURLForGroup(t *testing.T) { + tests := []struct { + Name string + Provider AlertProvider + InputGroup string + ExpectedOutput string + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + WebhookURL: "http://example.com", + Overrides: nil, + }, + InputGroup: "", + ExpectedOutput: "http://example.com", + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + WebhookURL: "http://example.com", + Overrides: nil, + }, + InputGroup: "group", + ExpectedOutput: "http://example.com", + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + WebhookURL: "http://example.com", + Overrides: []Override{ + { + Group: "group", + WebhookURL: "http://example01.com", + }, + }, + }, + InputGroup: "", + ExpectedOutput: "http://example.com", + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + WebhookURL: "http://example.com", + Overrides: []Override{ + { + Group: "group", + WebhookURL: "http://example01.com", + }, + }, + }, + InputGroup: "group", + ExpectedOutput: "http://example01.com", + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + if got := tt.Provider.getWebhookURLForGroup(tt.InputGroup); got != tt.ExpectedOutput { + t.Errorf("AlertProvider.getWebhookURLForGroup() = %v, want %v", got, tt.ExpectedOutput) + } + }) + } +}