diff --git a/README.md b/README.md index 489a6cf3..94798c9a 100644 --- a/README.md +++ b/README.md @@ -920,6 +920,7 @@ endpoints: | `alerting.discord` | Configuration for alerts of type `discord` | `{}` | | `alerting.discord.webhook-url` | Discord Webhook URL | Required `""` | | `alerting.discord.title` | Title of the notification | `":helmet_with_white_cross: Gatus"` | +| `alerting.discord.message-content` | Message content to send before the embed (useful for pinging users/roles, e.g. `<@123>`) | `""` | | `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 | `""` | diff --git a/alerting/provider/discord/discord.go b/alerting/provider/discord/discord.go index 97f4e727..ebbded5e 100644 --- a/alerting/provider/discord/discord.go +++ b/alerting/provider/discord/discord.go @@ -20,8 +20,9 @@ var ( ) type Config struct { - WebhookURL string `yaml:"webhook-url"` - Title string `yaml:"title,omitempty"` // Title of the message that will be sent + WebhookURL string `yaml:"webhook-url"` + Title string `yaml:"title,omitempty"` // Title of the message that will be sent + MessageContent string `yaml:"message-content,omitempty"` // Message content for pinging users or groups (e.g. "<@123456789>" or "<@&987654321>") } func (cfg *Config) Validate() error { @@ -38,6 +39,9 @@ func (cfg *Config) Merge(override *Config) { if len(override.Title) > 0 { cfg.Title = override.Title } + if len(override.MessageContent) > 0 { + cfg.MessageContent = override.MessageContent + } } // AlertProvider is the configuration necessary for sending an alert using Discord @@ -142,7 +146,7 @@ func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoi title = cfg.Title } body := Body{ - Content: "", + Content: cfg.MessageContent, Embeds: []Embed{ { Title: title, diff --git a/alerting/provider/discord/discord_test.go b/alerting/provider/discord/discord_test.go index 20aaed6d..5cea1603 100644 --- a/alerting/provider/discord/discord_test.go +++ b/alerting/provider/discord/discord_test.go @@ -134,6 +134,16 @@ func TestAlertProvider_Send(t *testing.T) { }), ExpectedError: false, }, + { + Name: "triggered-with-message-content", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com", MessageContent: "<@123456789>"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { @@ -200,6 +210,27 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { Resolved: false, ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332}]}", }, + { + Name: "triggered-with-message-content-user-mention", + Provider: AlertProvider{DefaultConfig: Config{MessageContent: "<@123456789>"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"content\":\"\\u003c@123456789\\u003e\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, + { + Name: "triggered-with-message-content-role-mention", + Provider: AlertProvider{DefaultConfig: Config{MessageContent: "<@&987654321>"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"content\":\"\\u003c@\\u0026987654321\\u003e\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, + { + Name: "resolved-with-message-content", + Provider: AlertProvider{DefaultConfig: Config{MessageContent: "<@123456789>"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"content\":\"\\u003c@123456789\\u003e\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"color\":3066993,\"fields\":[{\"name\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n:white_check_mark: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { @@ -313,6 +344,39 @@ func TestAlertProvider_GetConfig(t *testing.T) { InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, }, + { + Name: "provider-with-message-content-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com", MessageContent: "<@123456789>"}, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com", MessageContent: "<@123456789>"}, + }, + { + Name: "provider-with-message-content-group-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com", MessageContent: "<@123456789>"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com", MessageContent: "<@&987654321>"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com", MessageContent: "<@&987654321>"}, + }, + { + Name: "provider-with-message-content-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com", MessageContent: "<@123456789>"}, + }, + InputGroup: "", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"message-content": "<@999999999>"}}, + ExpectedOutput: Config{WebhookURL: "http://example.com", MessageContent: "<@999999999>"}, + }, } for _, scenario := range scenarios { t.Run(scenario.Name, func(t *testing.T) { @@ -323,6 +387,9 @@ func TestAlertProvider_GetConfig(t *testing.T) { if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) } + if got.MessageContent != scenario.ExpectedOutput.MessageContent { + t.Errorf("expected message content to be %s, got %s", scenario.ExpectedOutput.MessageContent, got.MessageContent) + } // Test ValidateOverrides as well, since it really just calls GetConfig if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { t.Errorf("unexpected error: %s", err)