diff --git a/README.md b/README.md index 3a17210f..e5a9c455 100644 --- a/README.md +++ b/README.md @@ -626,12 +626,14 @@ endpoints: #### Configuring Slack alerts -| Parameter | Description | Default | -|:-------------------------------|:-------------------------------------------------------------------------------------------|:--------------| -| `alerting.slack` | Configuration for alerts of type `slack` | `{}` | -| `alerting.slack.webhook-url` | Slack Webhook URL | Required `""` | -| `alerting.slack.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | - +| Parameter | Description | Default | +|:------------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.slack` | Configuration for alerts of type `slack` | `{}` | +| `alerting.slack.webhook-url` | Slack Webhook URL | Required `""` | +| `alerting.slack.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.slack.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.slack.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.slack.overrides[].webhook-url` | Slack Webhook URL | `""` | ```yaml alerting: slack: diff --git a/alerting/provider/slack/slack.go b/alerting/provider/slack/slack.go index 2de9e5cb..fbde77f2 100644 --- a/alerting/provider/slack/slack.go +++ b/alerting/provider/slack/slack.go @@ -14,20 +14,36 @@ import ( // AlertProvider is the configuration necessary for sending an alert using Slack type AlertProvider struct { WebhookURL string `yaml:"webhook-url"` // Slack webhook URL - // 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 +102,18 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * }`, message, description, color, 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/slack/slack_test.go b/alerting/provider/slack/slack_test.go index 1f60f547..909aafff 100644 --- a/alerting/provider/slack/slack_test.go +++ b/alerting/provider/slack/slack_test.go @@ -11,7 +11,7 @@ import ( "github.com/TwiN/gatus/v3/test" ) -func TestAlertProvider_IsValid(t *testing.T) { +func TestAlertDefaultProvider_IsValid(t *testing.T) { invalidProvider := AlertProvider{WebhookURL: ""} if invalidProvider.IsValid() { t.Error("provider shouldn't have been valid") @@ -20,8 +20,44 @@ func TestAlertProvider_IsValid(t *testing.T) { if !validProvider.IsValid() { t.Error("provider should've been valid") } -} +} +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" @@ -79,7 +115,7 @@ func TestAlertProvider_Send(t *testing.T) { t.Run(scenario.Name, func(t *testing.T) { client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) err := scenario.Provider.Send( - &core.Endpoint{Name: "name"}, + &core.Endpoint{Name: "endpoint-name"}, &scenario.Alert, &core.Result{ ConditionResults: []*core.ConditionResult{ @@ -175,3 +211,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) + } + }) + } +}