From 615d5ffa3034feceec3711e9b4f0cdcdd3c9b5ef Mon Sep 17 00:00:00 2001 From: gotjosh Date: Thu, 24 Oct 2024 20:55:35 +0100 Subject: [PATCH 1/2] Add a test for `Notify` of the Discord integration (#4082) Just to ensure this works correclty as expected, I originally thought there was a bug with the shadowing of the `content` varible but there isn't - to avoid further confusion I have followed up on this document left by George: https://github.com/prometheus/alertmanager/pull/3555#discussion_r1398448423 Signed-off-by: gotjosh --- notify/discord/discord.go | 4 +-- notify/discord/discord_test.go | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/notify/discord/discord.go b/notify/discord/discord.go index 1202a9caaf..7ab60bb868 100644 --- a/notify/discord/discord.go +++ b/notify/discord/discord.go @@ -137,11 +137,11 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) if n.conf.WebhookURL != nil { url = n.conf.WebhookURL.String() } else { - content, err := os.ReadFile(n.conf.WebhookURLFile) + b, err := os.ReadFile(n.conf.WebhookURLFile) if err != nil { return false, fmt.Errorf("read webhook_url_file: %w", err) } - url = strings.TrimSpace(string(content)) + url = strings.TrimSpace(string(b)) } w := webhook{ diff --git a/notify/discord/discord_test.go b/notify/discord/discord_test.go index a571ffffe0..7025109435 100644 --- a/notify/discord/discord_test.go +++ b/notify/discord/discord_test.go @@ -17,6 +17,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "net/http/httptest" "net/url" @@ -168,3 +169,63 @@ func TestDiscordReadingURLFromFile(t *testing.T) { test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String()) } + +func TestDiscord_Notify(t *testing.T) { + // Create a fake HTTP server to simulate the Discord webhook + var resp string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Read the request as a string + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "reading request body failed") + // Store the request body in the response + resp = string(body) + + w.WriteHeader(http.StatusOK) + })) + + // Create a temporary file to simulate the WebhookURLFile + tempFile, err := os.CreateTemp("", "webhook_url") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.Remove(tempFile.Name())) + }) + + // Write the fake webhook URL to the temp file + _, err = tempFile.WriteString(srv.URL) + require.NoError(t, err) + + // Create a DiscordConfig with the WebhookURLFile set + cfg := &config.DiscordConfig{ + WebhookURLFile: tempFile.Name(), + HTTPConfig: &commoncfg.HTTPClientConfig{}, + Title: "Test Title", + Message: "Test Message", + Content: "Test Content", + } + + // Create a new Discord notifier + notifier, err := New(cfg, test.CreateTmpl(t), log.NewNopLogger()) + require.NoError(t, err) + + // Create a context and alerts + ctx := context.Background() + ctx = notify.WithGroupKey(ctx, "1") + alerts := []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "lbl1": "val1", + }, + StartsAt: time.Now(), + EndsAt: time.Now().Add(time.Hour), + }, + }, + } + + // Call the Notify method + ok, err := notifier.Notify(ctx, alerts...) + require.NoError(t, err) + require.False(t, ok) + + require.Equal(t, "{\"content\":\"Test Content\",\"embeds\":[{\"title\":\"Test Title\",\"description\":\"Test Message\",\"color\":10038562}]}\n", resp) +} From 95655dccb87f3316934f88f679190d30060f5e47 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Thu, 24 Oct 2024 13:12:05 -0700 Subject: [PATCH 2/2] Discord notifier: Add username and avatar_url (#4081) * Feat(discord): Allow for custom username and avatar URLs to be set in discord notifications. Add `username` and `avatar_url` to discord configuration, default empty string. Re-implement #3821 Signed-off-by: Jeff Wong * Test the new fields Signed-off-by: gotjosh * These are not templeatable strings Signed-off-by: gotjosh --------- Signed-off-by: Jeff Wong Signed-off-by: gotjosh Co-authored-by: gotjosh --- config/notifiers.go | 8 +++++--- docs/configuration.md | 6 ++++++ notify/discord/discord.go | 18 +++++++++++++++--- notify/discord/discord_test.go | 4 +++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index ac437096e8..fe28ca05c4 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -251,9 +251,11 @@ type DiscordConfig struct { WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"` WebhookURLFile string `yaml:"webhook_url_file,omitempty" json:"webhook_url_file,omitempty"` - Content string `yaml:"content,omitempty" json:"content,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - Message string `yaml:"message,omitempty" json:"message,omitempty"` + Content string `yaml:"content,omitempty" json:"content,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + Username string `yaml:"username,omitempty" json:"username,omitempty"` + AvatarURL string `yaml:"avatar_url,omitempty" json:"avatar_url,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/docs/configuration.md b/docs/configuration.md index 696548658c..4f20a1ef68 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -886,6 +886,12 @@ webhook_url_file: # Message content template. Limited to 2000 characters. [ content: | default = '{{ template "discord.default.content" . }}' ] +# Message username. +[ username: | default = '' ] + +# Message avatar URL. +[ avatar_url: | default = '' ] + # The HTTP client's configuration. [ http_config: | default = global.http_config ] ``` diff --git a/notify/discord/discord.go b/notify/discord/discord.go index 7ab60bb868..a63137c6af 100644 --- a/notify/discord/discord.go +++ b/notify/discord/discord.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + netUrl "net/url" "os" "strings" @@ -76,8 +77,10 @@ func New(c *config.DiscordConfig, t *template.Template, l log.Logger, httpOpts . } type webhook struct { - Content string `json:"content"` - Embeds []webhookEmbed `json:"embeds"` + Content string `json:"content"` + Embeds []webhookEmbed `json:"embeds"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` } type webhookEmbed struct { @@ -145,7 +148,8 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } w := webhook{ - Content: content, + Content: content, + Username: n.conf.Username, Embeds: []webhookEmbed{{ Title: title, Description: description, @@ -153,6 +157,14 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) }}, } + if len(n.conf.AvatarURL) != 0 { + if _, err := netUrl.Parse(n.conf.AvatarURL); err == nil { + w.AvatarURL = n.conf.AvatarURL + } else { + level.Warn(n.logger).Log("msg", "Bad avatar url", "key", key) + } + } + var payload bytes.Buffer if err = json.NewEncoder(&payload).Encode(w); err != nil { return false, err diff --git a/notify/discord/discord_test.go b/notify/discord/discord_test.go index 7025109435..f19c8b625f 100644 --- a/notify/discord/discord_test.go +++ b/notify/discord/discord_test.go @@ -201,6 +201,8 @@ func TestDiscord_Notify(t *testing.T) { Title: "Test Title", Message: "Test Message", Content: "Test Content", + Username: "Test Username", + AvatarURL: "http://example.com/avatar.png", } // Create a new Discord notifier @@ -227,5 +229,5 @@ func TestDiscord_Notify(t *testing.T) { require.NoError(t, err) require.False(t, ok) - require.Equal(t, "{\"content\":\"Test Content\",\"embeds\":[{\"title\":\"Test Title\",\"description\":\"Test Message\",\"color\":10038562}]}\n", resp) + require.Equal(t, "{\"content\":\"Test Content\",\"embeds\":[{\"title\":\"Test Title\",\"description\":\"Test Message\",\"color\":10038562}],\"username\":\"Test Username\",\"avatar_url\":\"http://example.com/avatar.png\"}\n", resp) }