Implement opsgenie_api_key_file (#2728)

Signed-off-by: Jan-Otto Kröpke <joe@adorsys.de>
This commit is contained in:
Jan-Otto Kröpke 2021-10-21 09:29:03 +02:00 committed by GitHub
parent b9ce1d1e99
commit 186362cef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 152 additions and 24 deletions

View File

@ -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 {
@ -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 {
@ -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.

View File

@ -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())
}
}

View File

@ -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"`
@ -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)

View File

@ -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

View File

@ -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

View File

@ -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> ]
@ -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 ]

View File

@ -18,6 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -255,7 +256,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")

View File

@ -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 {