test ci build

This commit is contained in:
Stuart Nelson 2017-12-09 15:45:29 +01:00
parent ee8ac8e604
commit c7573ef09d
9 changed files with 273 additions and 2 deletions

View File

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

View File

@ -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/",
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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