From a8e4c166a86df10acca7612c9db462aa4e663bfe Mon Sep 17 00:00:00 2001 From: Simon Rozet Date: Wed, 18 Jan 2023 23:33:42 +0100 Subject: [PATCH] support loading pushover secrets from files (#3200) * support loading pushover secrets from files Add the user_key_file and token_file keys to the pushover config. /cc https://github.com/prometheus/alertmanager/issues/2498 Signed-off-by: Simon Rozet --- config/notifiers.go | 38 ++++++++++++++++++++-------------- config/notifiers_test.go | 41 +++++++++++++++++++++++++++++++++++-- docs/configuration.md | 6 +++++- notify/pushover/pushover.go | 28 +++++++++++++++++++++++-- 4 files changed, 93 insertions(+), 20 deletions(-) diff --git a/config/notifiers.go b/config/notifiers.go index e0abdcd5..d84fa184 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -666,17 +666,19 @@ type PushoverConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` - UserKey Secret `yaml:"user_key,omitempty" json:"user_key,omitempty"` - Token Secret `yaml:"token,omitempty" json:"token,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - Message string `yaml:"message,omitempty" json:"message,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` - URLTitle string `yaml:"url_title,omitempty" json:"url_title,omitempty"` - Sound string `yaml:"sound,omitempty" json:"sound,omitempty"` - Priority string `yaml:"priority,omitempty" json:"priority,omitempty"` - Retry duration `yaml:"retry,omitempty" json:"retry,omitempty"` - Expire duration `yaml:"expire,omitempty" json:"expire,omitempty"` - HTML bool `yaml:"html" json:"html,omitempty"` + UserKey Secret `yaml:"user_key,omitempty" json:"user_key,omitempty"` + UserKeyFile string `yaml:"user_key_file,omitempty" json:"user_key_file,omitempty"` + Token Secret `yaml:"token,omitempty" json:"token,omitempty"` + TokenFile string `yaml:"token_file,omitempty" json:"token_file,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` + URLTitle string `yaml:"url_title,omitempty" json:"url_title,omitempty"` + Sound string `yaml:"sound,omitempty" json:"sound,omitempty"` + Priority string `yaml:"priority,omitempty" json:"priority,omitempty"` + Retry duration `yaml:"retry,omitempty" json:"retry,omitempty"` + Expire duration `yaml:"expire,omitempty" json:"expire,omitempty"` + HTML bool `yaml:"html" json:"html,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. @@ -686,11 +688,17 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error if err := unmarshal((*plain)(c)); err != nil { return err } - if c.UserKey == "" { - return fmt.Errorf("missing user key in Pushover config") + if c.UserKey == "" && c.UserKeyFile == "" { + return fmt.Errorf("one of user_key or user_key_file must be configured") } - if c.Token == "" { - return fmt.Errorf("missing token in Pushover config") + if c.UserKey != "" && c.UserKeyFile != "" { + return fmt.Errorf("at most one of user_key & user_key_file must be configured") + } + if c.Token == "" && c.TokenFile == "" { + return fmt.Errorf("one of token or token_file must be configured") + } + if c.Token != "" && c.TokenFile != "" { + return fmt.Errorf("at most one of token & token_file must be configured") } return nil } diff --git a/config/notifiers_test.go b/config/notifiers_test.go index 5efb7803..a1947832 100644 --- a/config/notifiers_test.go +++ b/config/notifiers_test.go @@ -391,7 +391,25 @@ user_key: '' var cfg PushoverConfig err := yaml.UnmarshalStrict([]byte(in), &cfg) - expected := "missing user key in Pushover config" + expected := "one of user_key or user_key_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 TestPushoverUserKeyOrUserKeyFile(t *testing.T) { + in := ` +user_key: 'user key' +user_key_file: /pushover/user_key +` + var cfg PushoverConfig + err := yaml.UnmarshalStrict([]byte(in), &cfg) + + expected := "at most one of user_key & user_key_file must be configured" if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) @@ -409,7 +427,26 @@ token: '' var cfg PushoverConfig err := yaml.UnmarshalStrict([]byte(in), &cfg) - expected := "missing token in Pushover config" + expected := "one of token or token_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 TestPushoverTokenOrTokenFile(t *testing.T) { + in := ` +token: 'pushover token' +token_file: /pushover/token +user_key: 'user key' +` + var cfg PushoverConfig + err := yaml.UnmarshalStrict([]byte(in), &cfg) + + expected := "at most one of token & token_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 d9da5aec..e128afad 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -862,13 +862,17 @@ 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 user key. +# The recipient user's key. +# user_key and user_key_file are mutually exclusive. user_key: +user_key_file: # Your registered application's API token, see https://pushover.net/apps # You can also register a token by cloning this Prometheus app: # https://pushover.net/apps/clone/prometheus +# token and token_file are mutually exclusive. token: +token_file: # Notification title. [ title: | default = '{{ template "pushover.default.title" . }}' ] diff --git a/notify/pushover/pushover.go b/notify/pushover/pushover.go index c5fc287a..d1c43174 100644 --- a/notify/pushover/pushover.go +++ b/notify/pushover/pushover.go @@ -18,6 +18,7 @@ import ( "fmt" "net/http" "net/url" + "os" "strings" "time" @@ -83,9 +84,32 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) tmpl := notify.TmplText(n.tmpl, data, &err) tmplHTML := notify.TmplHTML(n.tmpl, data, &err) + var ( + token string + userKey string + ) + if n.conf.Token != "" { + token = string(n.conf.Token) + } else { + content, err := os.ReadFile(n.conf.TokenFile) + if err != nil { + return false, fmt.Errorf("read token_file: %w", err) + } + token = string(content) + } + if n.conf.UserKey != "" { + userKey = string(n.conf.UserKey) + } else { + content, err := os.ReadFile(n.conf.UserKeyFile) + if err != nil { + return false, fmt.Errorf("read user_key_file: %w", err) + } + userKey = string(content) + } + parameters := url.Values{} - parameters.Add("token", tmpl(string(n.conf.Token))) - parameters.Add("user", tmpl(string(n.conf.UserKey))) + parameters.Add("token", tmpl(token)) + parameters.Add("user", tmpl(userKey)) title, truncated := notify.TruncateInRunes(tmpl(n.conf.Title), maxTitleLenRunes) if truncated {