Skip to content

Commit

Permalink
Implement opsgenie_api_key_file (prometheus#2728)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan-Otto Kröpke <joe@adorsys.de>
  • Loading branch information
jkroepke authored and Marko Posavec committed Nov 4, 2021
1 parent b104c6c commit 0ff0186
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 24 deletions.
48 changes: 27 additions & 21 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("at most one of slack_api_url & slack_api_url_file must be configured")
}

if c.Global.OpsGenieAPIKey != "" && len(c.Global.OpsGenieAPIKeyFile) > 0 {
return fmt.Errorf("at most one of opsgenie_api_key & opsgenie_api_key_file must be configured")
}

names := map[string]struct{}{}

for _, rcv := range c.Receivers {
Expand Down Expand Up @@ -393,11 +397,12 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if !strings.HasSuffix(ogc.APIURL.Path, "/") {
ogc.APIURL.Path += "/"
}
if ogc.APIKey == "" {
if c.Global.OpsGenieAPIKey == "" {
return fmt.Errorf("no global OpsGenie API Key set")
if ogc.APIKey == "" && len(ogc.APIKeyFile) == 0 {
if c.Global.OpsGenieAPIKey == "" && len(c.Global.OpsGenieAPIKeyFile) == 0 {
return fmt.Errorf("no global OpsGenie API Key set either inline or in a file")
}
ogc.APIKey = c.Global.OpsGenieAPIKey
ogc.APIKeyFile = c.Global.OpsGenieAPIKeyFile
}
}
for _, wcc := range rcv.WechatConfigs {
Expand Down Expand Up @@ -636,24 +641,25 @@ type GlobalConfig struct {

HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"`
SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"`
PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"`
SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"`
PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"`
WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig.
Expand Down
29 changes: 27 additions & 2 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -952,13 +952,38 @@ func TestOpsGenieDefaultAPIKey(t *testing.T) {
}
}

func TestOpsGenieDefaultAPIKeyFile(t *testing.T) {
conf, err := LoadFile("testdata/conf.opsgenie-default-apikey-file.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.opsgenie-default-apikey-file.yml", err)
}

var defaultKey = conf.Global.OpsGenieAPIKeyFile
if defaultKey != conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile {
t.Fatalf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, defaultKey)
}
if defaultKey == conf.Receivers[1].OpsGenieConfigs[0].APIKeyFile {
t.Errorf("Invalid OpsGenie key_file: %s\nExpected: %s", conf.Receivers[0].OpsGenieConfigs[0].APIKeyFile, "/override_file")
}
}

func TestOpsGenieBothAPIKeyAndFile(t *testing.T) {
_, err := LoadFile("testdata/conf.opsgenie-both-file-and-apikey.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-both-file-and-apikey.yml", err)
}
if err.Error() != "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured" {
t.Errorf("Expected: %s\nGot: %s", "at most one of opsgenie_api_key & opsgenie_api_key_file must be configured", err.Error())
}
}

func TestOpsGenieNoAPIKey(t *testing.T) {
_, err := LoadFile("testdata/conf.opsgenie-no-apikey.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.opsgenie-no-apikey.yml", err)
}
if err.Error() != "no global OpsGenie API Key set" {
t.Errorf("Expected: %s\nGot: %s", "no global OpsGenie API Key set", err.Error())
if err.Error() != "no global OpsGenie API Key set either inline or in a file" {
t.Errorf("Expected: %s\nGot: %s", "no global OpsGenie API Key set either inline or in a file", err.Error())
}
}

Expand Down
5 changes: 5 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ type OpsGenieConfig struct {
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
APIKeyFile string `yaml:"api_key_file,omitempty" json:"api_key_file,omitempty"`
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Expand All @@ -480,6 +481,10 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
return err
}

if c.APIURL != nil && len(c.APIKeyFile) > 0 {
return fmt.Errorf("at most one of api_key & api_key_file must be configured")
}

for _, r := range c.Responders {
if r.ID == "" && r.Username == "" && r.Name == "" {
return errors.Errorf("OpsGenieConfig responder %v has to have at least one of id, username or name specified", r)
Expand Down
27 changes: 27 additions & 0 deletions config/testdata/conf.opsgenie-both-file-and-apikey.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
global:
opsgenie_api_key: asd132
opsgenie_api_key_file: '/global_file'

route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: escalation-Y-opsgenie
routes:
- match:
service: foo
receiver: team-X-opsgenie

receivers:
- name: 'team-X-opsgenie'
opsgenie_configs:
- responders:
- name: 'team-X'
type: 'team'
- name: 'escalation-Y-opsgenie'
opsgenie_configs:
- responders:
- name: 'escalation-Y'
type: 'escalation'
api_key: qwe456
26 changes: 26 additions & 0 deletions config/testdata/conf.opsgenie-default-apikey-file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
global:
opsgenie_api_key_file: '/global_file'

route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: escalation-Y-opsgenie
routes:
- match:
service: foo
receiver: team-X-opsgenie

receivers:
- name: 'team-X-opsgenie'
opsgenie_configs:
- responders:
- name: 'team-X'
type: 'team'
- name: 'escalation-Y-opsgenie'
opsgenie_configs:
- responders:
- name: 'escalation-Y'
type: 'escalation'
api_key_file: /override_file
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ global:
[ victorops_api_url: <string> | default = "https://alert.victorops.com/integrations/generic/20131114/alert/" ]
[ pagerduty_url: <string> | default = "https://events.pagerduty.com/v2/enqueue" ]
[ opsgenie_api_key: <secret> ]
[ opsgenie_api_key_file: <filepath> ]
[ opsgenie_api_url: <string> | default = "https://api.opsgenie.com/" ]
[ wechat_api_url: <string> | default = "https://qyapi.weixin.qq.com/cgi-bin/" ]
[ wechat_api_secret: <secret> ]
Expand Down Expand Up @@ -506,6 +507,9 @@ OpsGenie notifications are sent via the [OpsGenie API](https://docs.opsgenie.com
# The API key to use when talking to the OpsGenie API.
[ api_key: <secret> | default = global.opsgenie_api_key ]
# The filepath to API key to use when talking to the OpsGenie API. Conflicts with api_key.
[ api_key_file: <filepath> | default = global.opsgenie_api_key_file ]
# The host to send OpsGenie API requests to.
[ api_url: <string> | default = global.opsgenie_api_url ]
Expand Down
12 changes: 11 additions & 1 deletion notify/opsgenie/opsgenie.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"

Expand Down Expand Up @@ -267,7 +268,16 @@ func (n *Notifier) createRequests(ctx context.Context, as ...*types.Alert) ([]*h
}
}

apiKey := tmpl(string(n.conf.APIKey))
var apiKey string
if n.conf.APIKey != "" {
apiKey = tmpl(string(n.conf.APIKey))
} else {
content, err := ioutil.ReadFile(n.conf.APIKeyFile)
if err != nil {
return nil, false, errors.Wrap(err, "read key_file error")
}
apiKey = tmpl(string(content))
}

if err != nil {
return nil, false, errors.Wrap(err, "templating error")
Expand Down
25 changes: 25 additions & 0 deletions notify/opsgenie/opsgenie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ func TestOpsGenieRedactedURL(t *testing.T) {
test.AssertNotifyLeaksNoSecret(t, ctx, notifier, key)
}

func TestGettingOpsGegineApikeyFromFile(t *testing.T) {
ctx, u, fn := test.GetContextWithCancelingURL()
defer fn()

key := "key"

f, err := ioutil.TempFile("", "opsgenie_test")
require.NoError(t, err, "creating temp file failed")
_, err = f.WriteString(key)
require.NoError(t, err, "writing to temp file failed")

notifier, err := New(
&config.OpsGenieConfig{
APIURL: &config.URL{URL: u},
APIKeyFile: f.Name(),
HTTPConfig: &commoncfg.HTTPClientConfig{},
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
require.NoError(t, err)

test.AssertNotifyLeaksNoSecret(t, ctx, notifier, key)
}

func TestOpsGenie(t *testing.T) {
u, err := url.Parse("https://opsgenie/api")
if err != nil {
Expand Down

0 comments on commit 0ff0186

Please sign in to comment.