diff --git a/config/notifiers.go b/config/notifiers.go index 645fb92b..cac63c5c 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -473,7 +473,8 @@ type WebhookConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` // URL to send POST request to. - URL *SecretURL `yaml:"url" json:"url"` + URL *SecretURL `yaml:"url" json:"url"` + URLFile string `yaml:"url_file" json:"url_file"` // MaxAlerts is the maximum number of alerts to be sent per webhook message. // Alerts exceeding this threshold will be truncated. Setting this to 0 @@ -488,11 +489,16 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*plain)(c)); err != nil { return err } - if c.URL == nil { - return fmt.Errorf("missing URL in webhook config") + if c.URL == nil && c.URLFile == "" { + return fmt.Errorf("one of url or url_file must be configured") } - if c.URL.Scheme != "https" && c.URL.Scheme != "http" { - return fmt.Errorf("scheme required for webhook url") + if c.URL != nil && c.URLFile != "" { + return fmt.Errorf("at most one of url & url_file must be configured") + } + if c.URL != nil { + if c.URL.Scheme != "https" && c.URL.Scheme != "http" { + return fmt.Errorf("scheme required for webhook url") + } } return nil } diff --git a/config/notifiers_test.go b/config/notifiers_test.go index a1947832..2b4e0835 100644 --- a/config/notifiers_test.go +++ b/config/notifiers_test.go @@ -228,7 +228,25 @@ func TestWebhookURLIsPresent(t *testing.T) { var cfg WebhookConfig err := yaml.UnmarshalStrict([]byte(in), &cfg) - expected := "missing URL in webhook config" + expected := "one of url or url_file must be configured" + + if err == nil { + t.Fatalf("no error returned, expected:\n%v", expected) + } + if err.Error() != expected { + t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) + } +} + +func TestWebhookURLOrURLFile(t *testing.T) { + in := ` +url: 'http://example.com' +url_file: 'http://example.com' +` + var cfg WebhookConfig + err := yaml.UnmarshalStrict([]byte(in), &cfg) + + expected := "at most one of url & url_file must be configured" if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) diff --git a/docs/configuration.md b/docs/configuration.md index 371fe56c..fcadfdb1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -862,7 +862,7 @@ Pushover notifications are sent via the [Pushover API](https://pushover.net/api) # Whether to notify about resolved alerts. [ send_resolved: | default = true ] -# The recipient user's key. +# The recipient user's key. # user_key and user_key_file are mutually exclusive. user_key: user_key_file: @@ -1116,7 +1116,9 @@ The webhook receiver allows configuring a generic receiver. [ send_resolved: | default = true ] # The endpoint to send HTTP POST requests to. +# url and url_file are mutually exclusive. url: +url_file: # The HTTP client's configuration. [ http_config: | default = global.http_config ] diff --git a/notify/pushover/pushover_test.go b/notify/pushover/pushover_test.go index 87a5f125..487c428e 100644 --- a/notify/pushover/pushover_test.go +++ b/notify/pushover/pushover_test.go @@ -15,6 +15,7 @@ package pushover import ( "fmt" + "os" "testing" "github.com/go-kit/log" @@ -59,3 +60,53 @@ func TestPushoverRedactedURL(t *testing.T) { test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key, token) } + +func TestPushoverReadingUserKeyFromFile(t *testing.T) { + ctx, apiURL, fn := test.GetContextWithCancelingURL() + defer fn() + + const userKey = "user key" + f, err := os.CreateTemp("", "pushover_user_key") + require.NoError(t, err, "creating temp file failed") + _, err = f.WriteString(userKey) + require.NoError(t, err, "writing to temp file failed") + + notifier, err := New( + &config.PushoverConfig{ + UserKeyFile: f.Name(), + Token: config.Secret("token"), + HTTPConfig: &commoncfg.HTTPClientConfig{}, + }, + test.CreateTmpl(t), + log.NewNopLogger(), + ) + notifier.apiURL = apiURL.String() + require.NoError(t, err) + + test.AssertNotifyLeaksNoSecret(ctx, t, notifier, userKey) +} + +func TestPushoverReadingTokenFromFile(t *testing.T) { + ctx, apiURL, fn := test.GetContextWithCancelingURL() + defer fn() + + const token = "token" + f, err := os.CreateTemp("", "pushover_token") + require.NoError(t, err, "creating temp file failed") + _, err = f.WriteString(token) + require.NoError(t, err, "writing to temp file failed") + + notifier, err := New( + &config.PushoverConfig{ + UserKey: config.Secret("user key"), + TokenFile: f.Name(), + HTTPConfig: &commoncfg.HTTPClientConfig{}, + }, + test.CreateTmpl(t), + log.NewNopLogger(), + ) + notifier.apiURL = apiURL.String() + require.NoError(t, err) + + test.AssertNotifyLeaksNoSecret(ctx, t, notifier, token) +} diff --git a/notify/webhook/webhook.go b/notify/webhook/webhook.go index c229eeb9..d361d09c 100644 --- a/notify/webhook/webhook.go +++ b/notify/webhook/webhook.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "net/http" + "os" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -101,7 +102,18 @@ func (n *Notifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, er return false, err } - resp, err := notify.PostJSON(ctx, n.client, n.conf.URL.String(), &buf) + var url string + if n.conf.URL != nil { + url = n.conf.URL.String() + } else { + content, err := os.ReadFile(n.conf.URLFile) + if err != nil { + return false, fmt.Errorf("read url_file: %w", err) + } + url = string(content) + } + + resp, err := notify.PostJSON(ctx, n.client, url, &buf) if err != nil { return true, notify.RedactURL(err) } diff --git a/notify/webhook/webhook_test.go b/notify/webhook/webhook_test.go index 0bb5806c..682b5114 100644 --- a/notify/webhook/webhook_test.go +++ b/notify/webhook/webhook_test.go @@ -19,6 +19,7 @@ import ( "io" "net/http" "net/url" + "os" "testing" "github.com/go-kit/log" @@ -116,3 +117,25 @@ func TestWebhookRedactedURL(t *testing.T) { test.AssertNotifyLeaksNoSecret(ctx, t, notifier, secret) } + +func TestWebhookReadingURLFromFile(t *testing.T) { + ctx, u, fn := test.GetContextWithCancelingURL() + defer fn() + + f, err := os.CreateTemp("", "webhook_url") + require.NoError(t, err, "creating temp file failed") + _, err = f.WriteString(u.String()) + require.NoError(t, err, "writing to temp file failed") + + notifier, err := New( + &config.WebhookConfig{ + URLFile: f.Name(), + HTTPConfig: &commoncfg.HTTPClientConfig{}, + }, + test.CreateTmpl(t), + log.NewNopLogger(), + ) + require.NoError(t, err) + + test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String()) +}