Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement opsgenie_api_key_file #2728

Merged
merged 1 commit into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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")
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