Skip to content

Commit

Permalink
add dingding action implemention
Browse files Browse the repository at this point in the history
  • Loading branch information
mylxsw committed Oct 27, 2019
1 parent 50448c6 commit 22e1a57
Show file tree
Hide file tree
Showing 18 changed files with 648 additions and 19 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/swaggo/swag v1.6.2
github.com/tidwall/pretty v1.0.0 // indirect
github.com/urfave/cli v1.22.1
github.com/vjeantet/grok v1.0.0
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.0.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vjeantet/grok v1.0.0 h1:uxMqatJP6MOFXsj6C1tZBnqqAThQEeqnizUZ48gSJQQ=
github.com/vjeantet/grok v1.0.0/go.mod h1:/FWYEVYekkm+2VjcFmO9PufDU5FgXHUz9oy2EGqmQBo=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
Expand Down
10 changes: 8 additions & 2 deletions internal/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

type Action interface {
Handle(trigger repository.Trigger, grp repository.MessageGroup) error
Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error
}

type Manager struct {
Expand All @@ -28,6 +28,10 @@ func (manager *Manager) Resolve(f interface{}) error {
return manager.cc.ResolveWithError(f)
}

func (manager *Manager) MustResolve(f interface{}) {
manager.cc.MustResolve(f)
}

// Dispatch dispatch a action to queue
func (manager *Manager) Dispatch(action string) Action {
return &QueueAction{
Expand Down Expand Up @@ -59,6 +63,7 @@ type QueueAction struct {

type Payload struct {
Action string `json:"action"`
Rule repository.Rule `json:"rule"`
Trigger repository.Trigger `json:"trigger"`
Group repository.MessageGroup `json:"group"`
}
Expand All @@ -72,12 +77,13 @@ func (payload *Payload) Decode(data []byte) error {
return json.Unmarshal(data, payload)
}

func (q *QueueAction) Handle(trigger repository.Trigger, grp repository.MessageGroup) error {
func (q *QueueAction) Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error {
return q.manager.Resolve(func(queueManager queue.Manager) error {
payload := Payload{
Action: q.action,
Trigger: trigger,
Group: grp,
Rule: rule,
}

id, err := queueManager.Enqueue(repository.QueueItem{
Expand Down
90 changes: 85 additions & 5 deletions internal/action/dingding.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,98 @@
package action

import (
"fmt"
"strings"

"github.com/mylxsw/adanos-alert/internal/repository"
"github.com/mylxsw/adanos-alert/pkg/messager/dingding"
"github.com/mylxsw/adanos-alert/pkg/template"
"github.com/mylxsw/asteria/log"
"github.com/mylxsw/coll"
"go.mongodb.org/mongo-driver/bson"
)

type DingdingAction struct {
manager *Manager
manager *Manager
userRepo repository.UserRepo
}

func NewDingdingAction(manager *Manager) *DingdingAction {
return &DingdingAction{manager:manager}
dingdingAction := DingdingAction{manager: manager}
manager.MustResolve(func(userRepo repository.UserRepo) {
dingdingAction.userRepo = userRepo
})
return &dingdingAction
}

func (d DingdingAction) Handle(trigger repository.Trigger, grp repository.MessageGroup) error {
panic("implement me")
}
func (d DingdingAction) Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error {
payload := Payload{
Action: "dingding",
Rule: rule,
Trigger: trigger,
Group: grp,
}

res, err := template.Parse(rule.Template, payload)
if err != nil {
res = fmt.Sprintf("Template parse failed: %s", err)
log.WithFields(log.Fields{
"err": err.Error(),
"template": rule.Template,
"payload": payload,
}).Errorf("template parse failed: %v", err)
}

mobiles := make([]string, 0)
if len(trigger.UserRefs) > 0 {
users, err := d.userRepo.Find(bson.M{"_id": bson.M{"$in": trigger.UserRefs}})
if err != nil {
log.WithFields(log.Fields{
"err": err.Error(),
"trigger": trigger,
}).Errorf("load user from repo failed: %s", err)
} else {
if err := coll.MustNew(users).Filter(func(user repository.User) bool {
for _, m := range user.Metas {
if strings.ToLower(m.Key) == "phone" {
return true
}
}

return false
}).Map(func(user repository.User) string {
for _, m := range user.Metas {
if strings.ToLower(m.Key) == "phone" {
return m.Value
}
}
return ""
}).All(&mobiles); err != nil {
log.WithFields(log.Fields{
"err": err.Error(),
"trigger": trigger,
"users": users,
}).Errorf("convert user's phone to array failed: %v", err)
}
}
}

msg := dingding.NewMarkdownMessage(rule.Name, res, mobiles)
if err := dingding.NewDingding(trigger.Meta).Send(msg); err != nil {
log.WithFields(log.Fields{
"title": rule.Name,
"content": res,
"mobiles": mobiles,
"err": err,
}).Errorf("send message to dingding failed: %v", err)
return err
}

log.WithFields(log.Fields{
"title": rule.Name,
"content": res,
"mobiles": mobiles,
}).Debug("send message to dingding succeed")

return nil
}
2 changes: 1 addition & 1 deletion internal/action/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func NewEmailAction(manager *Manager) *EmailAction {
return &EmailAction{manager:manager}
}

func (e EmailAction) Handle(trigger repository.Trigger, grp repository.MessageGroup) error {
func (e EmailAction) Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error {
panic("implement me")
}

2 changes: 1 addition & 1 deletion internal/action/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ func NewHttpAction(manager *Manager) *HttpAction {
return &HttpAction{manager: manager}
}

func (act HttpAction) Handle(trigger repository.Trigger, grp repository.MessageGroup) error {
func (act HttpAction) Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error {
panic("implement me")
}
2 changes: 1 addition & 1 deletion internal/action/wechat.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ func NewWechatAction(manager *Manager) *WechatAction {
return &WechatAction{manager:manager}
}

func (w WechatAction) Handle(trigger repository.Trigger, grp repository.MessageGroup) error {
func (w WechatAction) Handle(rule repository.Rule, trigger repository.Trigger, grp repository.MessageGroup) error {
panic("implement me")
}
2 changes: 1 addition & 1 deletion internal/job/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (a TriggerJob) processMessageGroups(groupRepo repository.MessageGroupRepo,
}

if matched {
if err := manager.Dispatch(trigger.Action).Handle(trigger, grp); err != nil {
if err := manager.Dispatch(trigger.Action).Handle(rule, trigger, grp); err != nil {
trigger.Status = repository.TriggerStatusFailed
trigger.FailedCount = trigger.FailedCount + 1
trigger.FailedReason = err.Error()
Expand Down
15 changes: 8 additions & 7 deletions internal/repository/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ const (

// Trigger is a action trigger for matched rules
type Trigger struct {
ID primitive.ObjectID `bson:"id" json:"id"`
PreCondition string `bson:"pre_condition" json:"pre_condition"`
Action string `bson:"action" json:"action"`
Meta string `bson:"meta" json:"meta"`
Status TriggerStatus `bson:"trigger_status,omitempty" json:"trigger_status,omitempty"`
FailedCount int `bson:"failed_count" json:"failed_count"`
FailedReason string `bson:"failed_reason" json:"failed_reason"`
ID primitive.ObjectID `bson:"id" json:"id"`
PreCondition string `bson:"pre_condition" json:"pre_condition"`
Action string `bson:"action" json:"action"`
Meta string `bson:"meta" json:"meta"`
UserRefs []primitive.ObjectID `bson:"user_refs" json:"user_refs"`
Status TriggerStatus `bson:"trigger_status,omitempty" json:"trigger_status,omitempty"`
FailedCount int `bson:"failed_count" json:"failed_count"`
FailedReason string `bson:"failed_reason" json:"failed_reason"`
}
120 changes: 120 additions & 0 deletions pkg/messager/dingding/dinding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package dingding

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

// DingdingMessage is a message holds all informations for a dingding sender
type DingdingMessage struct {
Message MarkdownMessage `json:"message"`
Token string `json:"token"`
}

func (dm *DingdingMessage) Encode() []byte {
data, _ := json.Marshal(dm)
return data
}

func (dm *DingdingMessage) Decode(data []byte) error {
return json.Unmarshal(data, &dm)
}

// MarkdownMessage is a markdown message for dingding
type MarkdownMessage struct {
Type string `json:"msgtype,omitempty"`
Markdown MarkdownMessageBody `json:"markdown,omitempty"`
At MessageAtSomebody `json:"at,omitempty"`
}

// Encode encode markdown message to json bytes
func (m MarkdownMessage) Encode() ([]byte, error) {
return json.Marshal(m)
}

// NewMarkdownMessage create a new MarkdownMessage
func NewMarkdownMessage(title string, body string, mobiles []string) MarkdownMessage {
return MarkdownMessage{
Type: "markdown",
Markdown: MarkdownMessageBody{
Title: title,
Text: body,
},
At: MessageAtSomebody{
Mobiles: mobiles,
},
}
}

// MarkdownMessageBody is markdown body
type MarkdownMessageBody struct {
Title string `json:"title,omitempty"`
Text string `json:"text,omitempty"`
MessageURL string `json:"messageUrl,omitempty"`
}

// MessageAtSomebody @ someone
type MessageAtSomebody struct {
Mobiles []string `json:"atMobiles"`
ToAll bool `json:"isAtAll"`
}

type Dingding struct {
Endpoint string
Token string
}

func NewDingding(token string) *Dingding {
return &Dingding{Endpoint: "https://oapi.dingtalk.com/robot/send?access_token=", Token: token}
}

type Message interface {
Encode() ([]byte, error)
}

// dingResponse 钉钉响应
type dingResponse struct {
ErrorCode int `json:"errcode"`
ErrorMessage string `json:"errmsg"`
}

func (ding *Dingding) Send(msg Message) error {
url := ding.Endpoint + ding.Token

msgEncoded, err := msg.Encode()
if err != nil {
return fmt.Errorf("dingding message encode failed: %s", err.Error())
}

reader := bytes.NewReader(msgEncoded)
request, err := http.NewRequest("POST", url, reader)
if err != nil {
return fmt.Errorf("dingding create request failed: %w", err)
}

request.Header.Set("Content-Type", "application/json;charset=UTF-8")
client := http.Client{}
resp, err := client.Do(request)
if err != nil {
return fmt.Errorf("dingding send msg failed: %w", err)
}

respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("dingding read response failed: %w", err)
}

var dresp dingResponse
if err := json.Unmarshal(respBytes, &dresp); err != nil {
return fmt.Errorf("send finished, response: %s", string(respBytes))
}

if dresp.ErrorCode > 0 {
return fmt.Errorf("[%d] %s", dresp.ErrorCode, dresp.ErrorMessage)
}

return nil
}
54 changes: 54 additions & 0 deletions pkg/template/open_falcon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package template

import (
"strconv"
"strings"

"github.com/mylxsw/asteria/log"
)

type OpenFalconIM struct {
Priority int
Status string
Endpoint string
Body string
CurrentStep int
FormatTime string
}

// ParseOpenFalconImMessage 解析 Open-Falcon IM 消息
// https://github.com/open-falcon/falcon-plus/blob/2648553f82dd3986a91239d590461c0d795f63a4/modules/alarm/cron/builder.go#L43:6
func ParseOpenFalconImMessage(msg string) OpenFalconIM {
defer func() {
if err := recover(); err != nil {
log.Errorf("parse open falcon message failed")
}
}()

res := OpenFalconIM{}
// [P3][PROBLEM][192.168.200.4][][ all(#1) agent.alive 1==1][O1 2019-07-08 23:35:00]
segs := strings.Split(strings.TrimRight(strings.TrimLeft(msg, "["), "]"), "][")
if len(segs) > 0 {
res.Priority, _ = strconv.Atoi(strings.TrimLeft(segs[0], "P"))
}

if len(segs) > 1 {
res.Status = segs[1]
}

if len(segs) > 2 {
res.Endpoint = segs[2]
}

if len(segs) > 4 {
res.Body = segs[4]
}

if len(segs) > 5 {
ss := strings.SplitN(segs[5], " ", 2)
res.CurrentStep, _ = strconv.Atoi(ss[0][1:])
res.FormatTime = ss[1]
}

return res
}
Loading

0 comments on commit 22e1a57

Please sign in to comment.