test ci build
This commit is contained in:
parent
ee8ac8e604
commit
c7573ef09d
|
@ -236,6 +236,29 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
ogc.APIURL += "/"
|
||||
}
|
||||
}
|
||||
for _, wcc := range rcv.WechatConfigs {
|
||||
wcc.APIURL = c.Global.WeChatAPIURL
|
||||
if wcc.APIURL == "" {
|
||||
if c.Global.WeChatAPIURL == "" {
|
||||
return fmt.Errorf("no global Wechat URL set")
|
||||
}
|
||||
}
|
||||
wcc.APISecret = c.Global.WeChatAPISecret
|
||||
if wcc.APISecret == "" {
|
||||
if c.Global.WeChatAPISecret == "" {
|
||||
return fmt.Errorf("no global Wechat ApiSecret set")
|
||||
}
|
||||
}
|
||||
if wcc.CorpID == "" {
|
||||
if c.Global.WeChatAPICorpID == "" {
|
||||
return fmt.Errorf("no global Wechat CorpID set")
|
||||
}
|
||||
wcc.CorpID = c.Global.WeChatAPICorpID
|
||||
}
|
||||
if !strings.HasSuffix(wcc.APIURL, "/") {
|
||||
wcc.APIURL += "/"
|
||||
}
|
||||
}
|
||||
for _, voc := range rcv.VictorOpsConfigs {
|
||||
if voc.APIURL == "" {
|
||||
if c.Global.VictorOpsAPIURL == "" {
|
||||
|
@ -301,6 +324,7 @@ var DefaultGlobalConfig = GlobalConfig{
|
|||
PagerdutyURL: "https://events.pagerduty.com/v2/enqueue",
|
||||
HipchatAPIURL: "https://api.hipchat.com/",
|
||||
OpsGenieAPIURL: "https://api.opsgenie.com/",
|
||||
WeChatAPIURL: "https://qyapi.weixin.qq.com/cgi-bin/",
|
||||
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
|
||||
}
|
||||
|
||||
|
@ -324,6 +348,9 @@ type GlobalConfig struct {
|
|||
HipchatAPIURL string `yaml:"hipchat_api_url,omitempty" json:"hipchat_api_url,omitempty"`
|
||||
HipchatAuthToken Secret `yaml:"hipchat_auth_token,omitempty" json:"hipchat_auth_token,omitempty"`
|
||||
OpsGenieAPIURL string `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
|
||||
WeChatAPIURL string `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
|
||||
WeChatAPISecret string `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 string `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
|
||||
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
|
||||
|
||||
|
@ -459,6 +486,7 @@ type Receiver struct {
|
|||
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
|
||||
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
|
||||
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
|
||||
WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
|
||||
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
||||
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
||||
|
||||
|
|
|
@ -285,6 +285,7 @@ func TestEmptyFieldsAndRegex(t *testing.T) {
|
|||
SMTPRequireTLS: true,
|
||||
PagerdutyURL: "https://events.pagerduty.com/v2/enqueue",
|
||||
OpsGenieAPIURL: "https://api.opsgenie.com/",
|
||||
WeChatAPIURL: "https://qyapi.weixin.qq.com/cgi-bin/",
|
||||
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
|
||||
},
|
||||
|
||||
|
|
|
@ -94,6 +94,21 @@ var (
|
|||
// TODO: Add a details field with all the alerts.
|
||||
}
|
||||
|
||||
// DefaultWechatConfig defines default values for wechat configurations.
|
||||
DefaultWechatConfig = WechatConfig{
|
||||
NotifierConfig: NotifierConfig{
|
||||
VSendResolved: true,
|
||||
},
|
||||
Message: `{{ template "wechat.default.message" . }}`,
|
||||
APIURL: `{{ template "wechat.default.api_url" . }}`,
|
||||
APISecret: `{{ template "wechat.default.api_secret" . }}`,
|
||||
ToUser: `{{ template "wechat.default.to_user" . }}`,
|
||||
ToParty: `{{ template "wechat.default.to_party" . }}`,
|
||||
ToTag: `{{ template "wechat.default.to_tag" . }}`,
|
||||
AgentID: `{{ template "wechat.default.agent_id" . }}`,
|
||||
// TODO: Add a details field with all the alerts.
|
||||
}
|
||||
|
||||
// DefaultVictorOpsConfig defines default values for VictorOps configurations.
|
||||
DefaultVictorOpsConfig = VictorOpsConfig{
|
||||
NotifierConfig: NotifierConfig{
|
||||
|
@ -295,6 +310,39 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
return checkOverflow(c.XXX, "webhook config")
|
||||
}
|
||||
|
||||
// WechatConfig configures notifications via Wechat.
|
||||
type WechatConfig struct {
|
||||
NotifierConfig `yaml:",inline" json:",inline"`
|
||||
|
||||
APISecret string `yaml:"api_secret,omitempty" json:"api_secret,omitempty"`
|
||||
CorpID string `yaml:"corp_id,omitempty" json:"corp_id,omitempty"`
|
||||
Message string `yaml:"message,omitempty" json:"message,omitempty"`
|
||||
APIURL string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
|
||||
ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"`
|
||||
ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"`
|
||||
ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"`
|
||||
AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline" json:"-"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *WechatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultWechatConfig
|
||||
type plain WechatConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.APISecret == "" {
|
||||
return fmt.Errorf("missing Wechat APISecret in Wechat config")
|
||||
}
|
||||
if c.CorpID == "" {
|
||||
return fmt.Errorf("missing Wechat CorpID in Wechat config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "Wechat config")
|
||||
}
|
||||
|
||||
// OpsGenieConfig configures notifications via OpsGenie.
|
||||
type OpsGenieConfig struct {
|
||||
NotifierConfig `yaml:",inline" json:",inline"`
|
||||
|
|
|
@ -110,6 +110,39 @@ url: ''
|
|||
}
|
||||
}
|
||||
|
||||
func TestWechatAPIKeyIsPresent(t *testing.T) {
|
||||
in := `
|
||||
api_secret: ''
|
||||
`
|
||||
var cfg WechatConfig
|
||||
err := yaml.Unmarshal([]byte(in), &cfg)
|
||||
|
||||
expected := "missing Wechat APISecret in Wechat config"
|
||||
|
||||
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 TestWechatCorpIDIsPresent(t *testing.T) {
|
||||
in := `
|
||||
corp_id: ''
|
||||
`
|
||||
var cfg WechatConfig
|
||||
err := yaml.Unmarshal([]byte(in), &cfg)
|
||||
|
||||
expected := "missing Wechat CorpID in Wechat config"
|
||||
|
||||
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 TestOpsGenieAPIKeyIsPresent(t *testing.T) {
|
||||
in := `
|
||||
api_key: ''
|
||||
|
|
135
notify/impl.go
135
notify/impl.go
|
@ -20,6 +20,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
|
@ -116,6 +117,10 @@ func BuildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log
|
|||
n := NewOpsGenie(c, tmpl, logger)
|
||||
add("opsgenie", i, n, c)
|
||||
}
|
||||
for i, c := range nc.WechatConfigs {
|
||||
n := NewWechat(c, tmpl, logger)
|
||||
add("wechat", i, n, c)
|
||||
}
|
||||
for i, c := range nc.SlackConfigs {
|
||||
n := NewSlack(c, tmpl, logger)
|
||||
add("slack", i, n, c)
|
||||
|
@ -771,6 +776,136 @@ func (n *Hipchat) retry(statusCode int) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// Wechat implements a Notfier for wechat notifications
|
||||
type Wechat struct {
|
||||
conf *config.WechatConfig
|
||||
tmpl *template.Template
|
||||
logger log.Logger
|
||||
}
|
||||
type WechatToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `json:"-"`
|
||||
}
|
||||
type weChatMessage struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
type weChatCreateMessage struct {
|
||||
Text weChatMessage `yaml:"text,omitempty" json:"text,omitempty"`
|
||||
ToUser string `yaml:"touser,omitempty" json:"touser,omitempty"`
|
||||
ToParty string `yaml:"toparty,omitempty" json:"toparty,omitempty"`
|
||||
Totag string `yaml:"totag,omitempty" json:"totag,omitempty"`
|
||||
AgentID string `yaml:"agentid,omitempty" json:"agentid,omitempty"`
|
||||
Safe string `yaml:"safe,omitempty" json:"safe,omitempty"`
|
||||
Type string `yaml:"msgtype,omitempty" json:"msgtype,omitempty"`
|
||||
}
|
||||
|
||||
type weChatCloseMessage struct {
|
||||
Text weChatMessage `yaml:"text,omitempty" json:"text,omitempty"`
|
||||
ToUser string `yaml:"touser,omitempty" json:"touser,omitempty"`
|
||||
ToParty string `yaml:"toparty,omitempty" json:"toparty,omitempty"`
|
||||
Totag string `yaml:"totag,omitempty" json:"totag,omitempty"`
|
||||
AgentID string `yaml:"agentid,omitempty" json:"agentid,omitempty"`
|
||||
Safe string `yaml:"safe,omitempty" json:"safe,omitempty"`
|
||||
Type string `yaml:"msgtype,omitempty" json:"msgtype,omitempty"`
|
||||
}
|
||||
|
||||
type weChatErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// NewWechat returns a new Wechat notifier.
|
||||
func NewWechat(c *config.WechatConfig, t *template.Template, l log.Logger) *Wechat {
|
||||
return &Wechat{conf: c, tmpl: t, logger: l}
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Wechat) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
key, ok := GroupKey(ctx)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("group key missing")
|
||||
}
|
||||
data := n.tmpl.Data(receiverName(ctx, n.logger), groupLabels(ctx, n.logger), as...)
|
||||
level.Debug(n.logger).Log("msg", "Notifying Wechat", "incident", key)
|
||||
|
||||
var err error
|
||||
tmpl := tmplText(n.tmpl, data, &err)
|
||||
|
||||
var (
|
||||
msg interface{}
|
||||
apiURL string
|
||||
apiMsg = weChatMessage{
|
||||
Content: tmpl(n.conf.Message),
|
||||
}
|
||||
alerts = types.Alerts(as...)
|
||||
)
|
||||
parameters := url.Values{}
|
||||
parameters.Add("corpsecret", tmpl(string(n.conf.APISecret)))
|
||||
parameters.Add("corpid", tmpl(string(n.conf.CorpID)))
|
||||
apiURL = n.conf.APIURL + "gettoken"
|
||||
u, err := url.Parse(apiURL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
u.RawQuery = parameters.Encode()
|
||||
level.Debug(n.logger).Log("msg", "Sending Wechat message", "incident", key, "url", u.String())
|
||||
resp, err := ctxhttp.Get(ctx, http.DefaultClient, u.String())
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var wechatToken WechatToken
|
||||
if err := json.NewDecoder(resp.Body).Decode(&wechatToken); err != nil {
|
||||
return false, err
|
||||
}
|
||||
postMessageURL := n.conf.APIURL + "message/send?access_token=" + wechatToken.AccessToken
|
||||
switch alerts.Status() {
|
||||
case model.AlertResolved:
|
||||
msg = &weChatCloseMessage{Text: apiMsg,
|
||||
ToUser: tmpl(n.conf.ToUser),
|
||||
ToParty: tmpl(n.conf.ToParty),
|
||||
Totag: tmpl(n.conf.ToTag),
|
||||
AgentID: tmpl(n.conf.AgentID),
|
||||
Type: "text",
|
||||
Safe: "0"}
|
||||
default:
|
||||
msg = &weChatCreateMessage{
|
||||
Text: weChatMessage{
|
||||
Content: tmpl(n.conf.Message),
|
||||
},
|
||||
ToUser: tmpl(n.conf.ToUser),
|
||||
ToParty: tmpl(n.conf.ToParty),
|
||||
Totag: tmpl(n.conf.ToTag),
|
||||
AgentID: tmpl(n.conf.AgentID),
|
||||
Type: "text",
|
||||
Safe: "0",
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
resp, err = ctxhttp.Post(ctx, http.DefaultClient, postMessageURL, contentTypeJSON, &buf)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
level.Debug(n.logger).Log("msg", "response: "+string(body), "incident", key)
|
||||
defer resp.Body.Close()
|
||||
return n.retry(resp.StatusCode)
|
||||
}
|
||||
func (n *Wechat) retry(statusCode int) (bool, error) {
|
||||
// https://work.weixin.qq.com/api/doc#10649
|
||||
if statusCode/100 == 5 || statusCode == 429 {
|
||||
return true, fmt.Errorf("unexpected status code %v", statusCode)
|
||||
} else if statusCode/100 != 2 {
|
||||
return false, fmt.Errorf("unexpected status code %v", statusCode)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// OpsGenie implements a Notifier for OpsGenie notifications.
|
||||
type OpsGenie struct {
|
||||
conf *config.OpsGenieConfig
|
||||
|
|
|
@ -53,6 +53,14 @@ func TestHipchatRetry(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWechatRetry(t *testing.T) {
|
||||
notifier := new(Wechat)
|
||||
retryCodes := append(defaultRetryCodes(), http.StatusTooManyRequests)
|
||||
for statusCode, expected := range retryTests(retryCodes) {
|
||||
actual, _ := notifier.retry(statusCode)
|
||||
require.Equal(t, expected, actual, fmt.Sprintf("error on status %d", statusCode))
|
||||
}
|
||||
}
|
||||
func TestOpsGenieRetry(t *testing.T) {
|
||||
notifier := new(OpsGenie)
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ func init() {
|
|||
numNotifications.WithLabelValues("email")
|
||||
numNotifications.WithLabelValues("hipchat")
|
||||
numNotifications.WithLabelValues("pagerduty")
|
||||
numNotifications.WithLabelValues("wechat")
|
||||
numNotifications.WithLabelValues("pushover")
|
||||
numNotifications.WithLabelValues("slack")
|
||||
numNotifications.WithLabelValues("opsgenie")
|
||||
|
@ -62,6 +63,7 @@ func init() {
|
|||
numFailedNotifications.WithLabelValues("email")
|
||||
numFailedNotifications.WithLabelValues("hipchat")
|
||||
numFailedNotifications.WithLabelValues("pagerduty")
|
||||
numFailedNotifications.WithLabelValues("wechat")
|
||||
numFailedNotifications.WithLabelValues("pushover")
|
||||
numFailedNotifications.WithLabelValues("slack")
|
||||
numFailedNotifications.WithLabelValues("opsgenie")
|
||||
|
|
|
@ -46,6 +46,22 @@ Alerts Resolved:
|
|||
{{ define "opsgenie.default.source" }}{{ template "__alertmanagerURL" . }}{{ end }}
|
||||
|
||||
|
||||
{{ define "wechat.default.message" }}{{ template "__subject" . }}
|
||||
{{ .CommonAnnotations.SortedPairs.Values | join " " }}
|
||||
{{ if gt (len .Alerts.Firing) 0 -}}
|
||||
Alerts Firing:
|
||||
{{ template "__text_alert_list" .Alerts.Firing }}
|
||||
{{- end }}
|
||||
{{ if gt (len .Alerts.Resolved) 0 -}}
|
||||
Alerts Resolved:
|
||||
{{ template "__text_alert_list" .Alerts.Resolved }}
|
||||
{{- end }}
|
||||
AlertmanagerUrl:
|
||||
{{ template "__alertmanagerURL" . }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
|
||||
{{ define "victorops.default.state_message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }}
|
||||
{{ if gt (len .Alerts.Firing) 0 -}}
|
||||
Alerts Firing:
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue