From 0054ed593992ae64994c05a633b6eaae80c05d72 Mon Sep 17 00:00:00 2001 From: songjiayang Date: Wed, 7 Feb 2018 20:23:17 +0800 Subject: [PATCH] fix wechat issue --- config/config.go | 11 ++-- notify/impl.go | 142 +++++++++++++++++++++++--------------------- notify/impl_test.go | 40 ++++++------- 3 files changed, 100 insertions(+), 93 deletions(-) diff --git a/config/config.go b/config/config.go index c25416f54c..d397324348 100644 --- a/config/config.go +++ b/config/config.go @@ -245,22 +245,21 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 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") - } + 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") - } + 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 += "/" } diff --git a/notify/impl.go b/notify/impl.go index 8f66088096..65bb29e63a 100644 --- a/notify/impl.go +++ b/notify/impl.go @@ -797,33 +797,30 @@ type Wechat struct { conf *config.WechatConfig tmpl *template.Template logger log.Logger + + accessToken string + accessTokenAt time.Time } + +// Wechat AccessToken with corpid and corpsecret. 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"` + Text weChatMessageContent `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 weChatMessageContent struct { + Content string `json:"content"` } type weChatErrorResponse struct { @@ -842,75 +839,86 @@ func (n *Wechat) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { 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) + data := n.tmpl.Data(receiverName(ctx, n.logger), groupLabels(ctx, n.logger), as...) 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 accessToken string + // Refresh AccessToken over 2 hours + if n.accessToken != "" && time.Now().Sub(n.accessTokenAt) < 110*time.Minute { + accessToken = n.accessToken + } else { + 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 } + + accessToken = wechatToken.AccessToken + + // Cache accessToken + n.accessToken = accessToken + n.accessTokenAt = time.Now() } + + msg := &weChatMessage{ + Text: weChatMessageContent{ + Content: tmpl(n.conf.Message), + }, + ToUser: n.conf.ToUser, + ToParty: n.conf.ToParty, + Totag: n.conf.ToTag, + AgentID: 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) + + postMessageURL := n.conf.APIURL + "message/send?access_token=" + accessToken + + resp, err := ctxhttp.Post(ctx, http.DefaultClient, postMessageURL, contentTypeJSON, &buf) if err != nil { return true, err } + + defer resp.Body.Close() + 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 { diff --git a/notify/impl_test.go b/notify/impl_test.go index 5867afd2b1..171816dd72 100644 --- a/notify/impl_test.go +++ b/notify/impl_test.go @@ -10,12 +10,12 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/net/context" - "github.com/prometheus/common/model" - "github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/template" - "net/url" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" "io/ioutil" + "net/url" ) func TestWebhookRetry(t *testing.T) { @@ -200,7 +200,7 @@ func createTmpl(t *testing.T) *template.Template { return tmpl } -func readBody(t *testing.T, r *http.Request) string { +func readBody(t *testing.T, r *http.Request) string { body, err := ioutil.ReadAll(r.Body) require.NoError(t, err) return string(body) @@ -216,7 +216,7 @@ func TestOpsGenie(t *testing.T) { Message: `{{ .CommonLabels.Message }}`, Description: `{{ .CommonLabels.Description }}`, Source: `{{ .CommonLabels.Source }}`, - Teams: `{{ .CommonLabels.Teams }}`, + Teams: `{{ .CommonLabels.Teams }}`, Tags: `{{ .CommonLabels.Tags }}`, Note: `{{ .CommonLabels.Note }}`, Priority: `{{ .CommonLabels.Priority }}`, @@ -231,11 +231,11 @@ func TestOpsGenie(t *testing.T) { expectedUrl, _ := url.Parse("https://opsgenie/apiv2/alerts") // Empty alert. - alert1:= &types.Alert{ - Alert: model.Alert{ - StartsAt: time.Now(), - EndsAt: time.Now().Add(time.Hour), - }, + alert1 := &types.Alert{ + Alert: model.Alert{ + StartsAt: time.Now(), + EndsAt: time.Now().Add(time.Hour), + }, } expectedBody := `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""} ` @@ -247,23 +247,23 @@ func TestOpsGenie(t *testing.T) { require.Equal(t, expectedBody, readBody(t, req)) // Fully defined alert. - alert2:= &types.Alert{ + alert2 := &types.Alert{ Alert: model.Alert{ Labels: model.LabelSet{ - "Message": "message", + "Message": "message", "Description": "description", - "Source": "http://prometheus", - "Teams": "TeamA,TeamB,", - "Tags": "tag1,tag2", - "Note": "this is a note", - "Priotity": "P1", - }, + "Source": "http://prometheus", + "Teams": "TeamA,TeamB,", + "Tags": "tag1,tag2", + "Note": "this is a note", + "Priotity": "P1", + }, StartsAt: time.Now(), - EndsAt: time.Now().Add(time.Hour), + EndsAt: time.Now().Add(time.Hour), }, } expectedBody = `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{},"source":"http://prometheus","teams":[{"name":"TeamA"},{"name":"TeamB"}],"tags":["tag1","tag2"],"note":"this is a note"} ` req, retry, err = notifier.createRequest(ctx, alert2) require.Equal(t, expectedBody, readBody(t, req)) -} \ No newline at end of file +}