diff --git a/.drone.yml b/.drone.yml
index ea54770..7f0e349 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -21,13 +21,19 @@ steps:
- master
status:
- success
- event:
- - push
settings:
repo: lddsb/drone-dingtalk-message
dockerfile: Dockerfile
- tags: latest
+ tags:
+ - latest
+ - 1.0.0
username:
from_secret: docker_username
password:
from_secret: docker_password
+
+trigger:
+ branch:
+ - master
+ event:
+ - tags
diff --git a/Gopkg.lock b/Gopkg.lock
index 5896f27..0a0ee9a 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -12,6 +12,14 @@
revision = "23d116af351c84513e1946b527c88823e476be13"
version = "v1.3.0"
+[[projects]]
+ branch = "master"
+ digest = "1:9142979c770f3d0f3c42c2eec532048bbbe2571134da91e8946cd8610c85c04b"
+ name = "github.com/lddsb/dingtalk-webhook"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "b4abe34b5fa9af8ea7d5f28c02bd314558b21f7f"
+
[[projects]]
digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679"
name = "github.com/urfave/cli"
@@ -25,6 +33,7 @@
analyzer-version = 1
input-imports = [
"github.com/joho/godotenv/autoload",
+ "github.com/lddsb/dingtalk-webhook",
"github.com/urfave/cli",
]
solver-name = "gps-cdcl"
diff --git a/Gopkg.toml b/Gopkg.toml
index 744b8fa..857f0f2 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -36,3 +36,7 @@
[prune]
go-tests = true
unused-packages = true
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/lddsb/dingtalk-webhook"
diff --git a/main.go b/main.go
index ff736e2..09fdf91 100644
--- a/main.go
+++ b/main.go
@@ -148,35 +148,36 @@ func main() {
if err := app.Run(os.Args); nil != err {
log.Println(err)
- os.Exit(1)
}
}
// run with args
func run(c *cli.Context) {
plugin := Plugin{
- // repo info
- Repo: Repo{
- FullName: c.String("repo.fullname"),
- },
- // build info
- Build: Build{
- Status: c.String("build.status"),
- Link: c.String("build.link"),
- },
- Commit: Commit{
- Sha: c.String("commit.sha"),
- Branch: c.String("commit.branch"),
- Message: c.String("commit.message"),
- Link: c.String("commit.link"),
- Authors: struct {
- Avatar string
- Email string
- Name string
- }{
- Avatar: c.String("commit.author.avatar"),
- Email: c.String("commit.author.email"),
- Name: c.String("commit.author.name"),
+ Drone: Drone{
+ // repo info
+ Repo: Repo{
+ FullName: c.String("repo.fullname"),
+ },
+ // build info
+ Build: Build{
+ Status: c.String("build.status"),
+ Link: c.String("build.link"),
+ },
+ Commit: Commit{
+ Sha: c.String("commit.sha"),
+ Branch: c.String("commit.branch"),
+ Message: c.String("commit.message"),
+ Link: c.String("commit.link"),
+ Authors: struct {
+ Avatar string
+ Email string
+ Name string
+ }{
+ Avatar: c.String("commit.author.avatar"),
+ Email: c.String("commit.author.email"),
+ Name: c.String("commit.author.name"),
+ },
},
},
// custom config
@@ -189,18 +190,21 @@ func run(c *cli.Context) {
Debug: c.Bool("config.debug"),
},
Extra: Extra{
- SuccessPicUrl: c.String("config.success.pic.url"),
- FailurePicUrl: c.String("config.failure.pic.url"),
- SuccessColor: c.String("config.success.color"),
- FailureColor: c.String("config.failure.color"),
- WithColor: c.Bool("config.message.color"),
- WithPic: c.Bool("config.message.pic"),
- LinkSha: c.Bool("config.message.sha.link"),
+ Pic: ExtraPic{
+ WithPic: c.Bool("config.message.pic"),
+ SuccessPicURL: c.String("config.success.pic.url"),
+ FailurePicURL: c.String("config.failure.pic.url"),
+ },
+ Color: ExtraColor{
+ SuccessColor: c.String("config.success.color"),
+ FailureColor: c.String("config.failure.color"),
+ WithColor: c.Bool("config.message.color"),
+ },
+ LinkSha: c.Bool("config.message.sha.link"),
},
}
if err := plugin.Exec(); nil != err {
fmt.Println(err)
- os.Exit(1)
}
}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..d884907
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestMain(t *testing.T) {
+ main()
+ t.Log("main testing finished")
+}
diff --git a/plugin.go b/plugin.go
index 701fb0a..a888d87 100644
--- a/plugin.go
+++ b/plugin.go
@@ -5,6 +5,8 @@ import (
"fmt"
"log"
"strings"
+
+ webhook "github.com/lddsb/dingtalk-webhook"
)
type (
@@ -12,58 +14,85 @@ type (
Repo struct {
FullName string // repository full name
}
+
// Build `build info`
Build struct {
Status string // providers the current build status
Link string // providers the current build link
}
+
// Commit `commit info`
Commit struct {
Branch string // providers the branch for the current commit
Link string // providers the http link to the current commit in the remote source code management system(e.g.GitHub)
Message string // providers the commit message for the current build
Sha string // providers the commit sha for the current build
- // repo author info
- Authors struct {
- Avatar string // providers the author avatar for the current commit
- Email string // providers the author email for the current commit
- Name string // providers the author name for the current commit
- }
+ Authors CommitAuthors
}
+
+ // CommitAuthors `commit author info`
+ CommitAuthors struct {
+ Avatar string // providers the author avatar for the current commit
+ Email string // providers the author email for the current commit
+ Name string // providers the author name for the current commit
+ }
+
+ // Drone `drone info`
+ Drone struct {
+ Repo Repo
+ Build Build
+ Commit Commit
+ }
+
// Config `plugin private config`
Config struct {
- Debug bool
- AccessToken string
- IsAtALL bool
- Mobiles string
- Username string
- MsgType string
+ Debug bool
+ AccessToken string
+ IsAtALL bool
+ Mobiles string
+ Username string
+ MsgType string
+ }
+
+ // MessageConfig `DingTalk message struct`
+ MessageConfig struct {
+ ActionCard ActionCard
+ }
+
+ // ActionCard `action card message struct`
+ ActionCard struct {
LinkUrls string
LinkTitles string
HideAvatar bool
BtnOrientation bool
}
+
// Extra `extra variables`
Extra struct {
- PicURL string
- MsgURL string
- SuccessPicUrl string
- FailurePicUrl string
- SuccessColor string
- FailureColor string
- WithColor bool
+ Color ExtraColor
+ Pic ExtraPic
+ LinkSha bool
+ }
+
+ // ExtraPic `extra config for pic`
+ ExtraPic struct {
WithPic bool
- LinkSha bool
+ SuccessPicURL string
+ FailurePicURL string
+ }
+
+ // ExtraColor `extra config for color`
+ ExtraColor struct {
+ WithColor bool
+ SuccessColor string
+ FailureColor string
}
// Plugin `plugin all config`
Plugin struct {
- Commit Commit
- Repo Repo
- Build Build
- Config Config
- Extra Extra
- WebHook *WebHook
+ Drone Drone
+ Config Config
+ Extra Extra
}
)
@@ -74,25 +103,30 @@ func (p *Plugin) Exec() error {
msg := "missing dingtalk access token"
return errors.New(msg)
}
- p.WebHook = NewWebHook(p.Config.AccessToken)
+
+ if 6 > len(p.Drone.Commit.Sha) {
+ return errors.New("commit sha cannot short than 6")
+ }
+
+ newWebhook := webhook.NewWebHook(p.Config.AccessToken)
mobiles := strings.Split(p.Config.Mobiles, ",")
switch strings.ToLower(p.Config.MsgType) {
case "markdown":
- err = p.WebHook.SendMarkdownMsg("You have a new message...", p.baseTpl(), p.Config.IsAtALL, mobiles...)
+ err = newWebhook.SendMarkdownMsg("You have a new message...", p.baseTpl(), p.Config.IsAtALL, mobiles...)
case "text":
- err = p.WebHook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
+ err = newWebhook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
case "link":
- err = p.WebHook.SendLinkMsg(p.Build.Status, p.baseTpl(), p.Commit.Authors.Avatar, p.Build.Link)
+ err = newWebhook.SendLinkMsg(p.Drone.Build.Status, p.baseTpl(), p.Drone.Commit.Authors.Avatar, p.Drone.Build.Link)
default:
msg := "not support message type"
err = errors.New(msg)
}
- if err != nil {
- return err
+ if err == nil {
+ log.Println("send message success!")
}
- log.Println("send message success!")
- return nil
+
+ return err
}
// markdownTpl `output the tpl of markdown`
@@ -101,44 +135,44 @@ func (p *Plugin) markdownTpl() string {
// title
title := fmt.Sprintf(" %s *Branch Build %s*",
- strings.Title(p.Commit.Branch),
- strings.Title(p.Build.Status))
+ strings.Title(p.Drone.Commit.Branch),
+ strings.Title(p.Drone.Build.Status))
// with color on title
- if p.Extra.WithColor {
+ if p.Extra.Color.WithColor {
title = fmt.Sprintf("%s", p.getColor(), title)
}
tpl = fmt.Sprintf("# %s \n", title)
// with pic
- if p.Extra.WithPic {
+ if p.Extra.Pic.WithPic {
tpl += fmt.Sprintf("\n\n",
- p.Build.Status,
- p.getPicUrl())
+ p.Drone.Build.Status,
+ p.getPicURL())
}
// commit message
- commitMsg := fmt.Sprintf("%s", p.Commit.Message)
- if p.Extra.WithColor {
+ commitMsg := fmt.Sprintf("%s", p.Drone.Commit.Message)
+ if p.Extra.Color.WithColor {
commitMsg = fmt.Sprintf("%s", p.getColor(), commitMsg)
}
tpl += commitMsg + "\n\n"
// sha info
- commitSha := p.Commit.Sha
+ commitSha := p.Drone.Commit.Sha
if p.Extra.LinkSha {
- commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Commit.Link)
+ commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Drone.Commit.Link)
}
tpl += commitSha + "\n\n"
// author info
- authorInfo := fmt.Sprintf("`%s(%s)`", p.Commit.Authors.Name, p.Commit.Authors.Email)
+ authorInfo := fmt.Sprintf("`%s(%s)`", p.Drone.Commit.Authors.Name, p.Drone.Commit.Authors.Email)
tpl += authorInfo + "\n\n"
// build detail link
buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)",
p.getEmoticon(),
- p.Build.Link)
+ p.Drone.Build.Link)
tpl += buildDetail
return tpl
}
@@ -154,20 +188,20 @@ func (p *Plugin) baseTpl() string {
@%s
%s (%s)
`,
- p.Build.Status,
- strings.TrimSpace(p.Commit.Message),
- p.Repo.FullName,
- p.Commit.Branch,
- p.Commit.Sha,
- p.Commit.Authors.Name,
- p.Commit.Authors.Email)
+ p.Drone.Build.Status,
+ strings.TrimSpace(p.Drone.Commit.Message),
+ p.Drone.Repo.FullName,
+ p.Drone.Commit.Branch,
+ p.Drone.Commit.Sha,
+ p.Drone.Commit.Authors.Name,
+ p.Drone.Commit.Authors.Email)
case "link":
tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`,
- p.Repo.FullName,
- p.Commit.Branch,
- p.Commit.Sha[:6],
- p.Commit.Authors.Name,
- p.Commit.Authors.Email)
+ p.Drone.Repo.FullName,
+ p.Drone.Commit.Branch,
+ p.Drone.Commit.Sha[:6],
+ p.Drone.Commit.Authors.Name,
+ p.Drone.Commit.Authors.Email)
case "actionCard":
// coming soon
@@ -184,7 +218,7 @@ func (p *Plugin) getEmoticon() string {
emoticons["success"] = ":)"
emoticons["failure"] = ":("
- emoticon, ok := emoticons[p.Build.Status]
+ emoticon, ok := emoticons[p.Drone.Build.Status]
if ok {
return emoticon
}
@@ -195,20 +229,20 @@ func (p *Plugin) getEmoticon() string {
/**
get picture url
*/
-func (p *Plugin) getPicUrl() string {
+func (p *Plugin) getPicURL() string {
pics := make(map[string]string)
// success picture url
pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
- if p.Extra.SuccessPicUrl != "" {
- pics["success"] = p.Extra.SuccessPicUrl
+ if p.Extra.Pic.SuccessPicURL != "" {
+ pics["success"] = p.Extra.Pic.SuccessPicURL
}
// failure picture url
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
- if p.Extra.FailurePicUrl != "" {
- pics["failure"] = p.Extra.FailurePicUrl
+ if p.Extra.Pic.FailurePicURL != "" {
+ pics["failure"] = p.Extra.Pic.FailurePicURL
}
- url, ok := pics[p.Build.Status]
+ url, ok := pics[p.Drone.Build.Status]
if ok {
return url
}
@@ -223,16 +257,16 @@ func (p *Plugin) getColor() string {
colors := make(map[string]string)
// success color
colors["success"] = "#008000"
- if p.Extra.SuccessColor != "" {
- colors["success"] = "#" + p.Extra.SuccessColor
+ if p.Extra.Color.SuccessColor != "" {
+ colors["success"] = "#" + p.Extra.Color.SuccessColor
}
// failure color
colors["failure"] = "#FF0000"
- if p.Extra.FailureColor != "" {
- colors["failure"] = "#" + p.Extra.FailureColor
+ if p.Extra.Color.FailureColor != "" {
+ colors["failure"] = "#" + p.Extra.Color.FailureColor
}
- color, ok := colors[p.Build.Status]
+ color, ok := colors[p.Drone.Build.Status]
if ok {
return color
}
diff --git a/plugin_test.go b/plugin_test.go
new file mode 100644
index 0000000..5ea7675
--- /dev/null
+++ b/plugin_test.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestPlugin(t *testing.T) {
+ p := Plugin{}
+ err := p.Exec()
+ if nil == err {
+ t.Error("access token empty error should be catch!")
+ }
+
+ p.Config.AccessToken = "example-access-token"
+ err = p.Exec()
+ if nil == err {
+ t.Error("commit sha length error should be catch!")
+ }
+
+ p.Drone.Commit.Sha = "53729847dfksj"
+ err = p.Exec()
+ if nil == err {
+ t.Error("not support message type error should be catch!")
+ }
+
+ p.Config.MsgType = "text"
+ err = p.Exec()
+ if nil == err {
+ t.Error("access token invalid error should be catch!")
+ }
+
+ p.Config.MsgType = "link"
+ err = p.Exec()
+ if nil == err {
+ t.Error("access token invalid error should be catch!")
+ }
+
+ p.Extra.Color.WithColor = true
+ p.Extra.Color.FailureColor = "#555555"
+ p.Extra.Color.SuccessColor = "#222222"
+ p.Extra.Pic.WithPic = true
+ p.Extra.Pic.FailurePicURL = "https://www.baidu.com"
+ p.Extra.Pic.SuccessPicURL = "https://www.baidu.com"
+ p.Extra.LinkSha = true
+ // p.Drone.Build.Status = "failure"
+ p.Config.MsgType = "markdown"
+ err = p.Exec()
+ if nil == err {
+ t.Error("access token invalid error should be catch!")
+ }
+
+ p.Drone.Build.Status = "failure"
+ err = p.Exec()
+ if nil == err {
+ t.Error("access token invalid error should be catch!")
+ }
+
+ t.Log("plugin testing finished")
+}
diff --git a/webhook.go b/webhook.go
deleted file mode 100644
index 8ecfa09..0000000
--- a/webhook.go
+++ /dev/null
@@ -1,243 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "regexp"
-)
-
-// LinkMsg `link message struct`
-type LinkMsg struct {
- Title string `json:"title"`
- MessageURL string `json:"messageURL"`
- PicURL string `json:"picURL"`
-}
-
-// ActionCard `action card message struct`
-type ActionCard struct {
- Text string `json:"text"`
- Title string `json:"title"`
- SingleTitle string `json:"singleTitle"`
- SingleURL string `json:"singleURL"`
- BtnOrientation string `json:"btnOrientation"`
- HideAvatar string `json:"hideAvatar"` // robot message avatar
- Buttons []struct {
- Title string `json:"title"`
- ActionURL string `json:"actionURL"`
- } `json:"btns"`
-}
-
-// PayLoad payload
-type PayLoad struct {
- MsgType string `json:"msgtype"`
- Text struct {
- Content string `json:"content"`
- } `json:"text"`
- Link struct {
- Title string `json:"title"`
- Text string `json:"text"`
- PicUrl string `json:"picUrl"`
- MessageUrl string `json:"messageUrl"`
- } `json:"link"`
- Markdown struct {
- Title string `json:"title"`
- Text string `json:"text"`
- } `json:"markdown"`
- ActionCard ActionCard `json:"actionCard"`
- FeedCard struct {
- Links []LinkMsg `json:"links"`
- } `json:"feedCard"`
- At struct {
- AtMobiles []string `json:"atMobiles"`
- IsAtAll bool `json:"isAtAll"`
- } `json:"at"`
-}
-
-// WebHook `web hook base config`
-type WebHook struct {
- AccessToken string `json:"accessToken"`
-}
-
-// NewWebHook `new a webhook`
-func NewWebHook(accessToken string) *WebHook {
- return &WebHook{AccessToken: accessToken}
-}
-
-// Response `dingtalk webhook response struct`
-type Response struct {
- ErrorCode int `json:"errcode"`
- ErrorMessage string `json:"errmsg"`
-}
-
-var baseApi = "https://oapi.dingtalk.com/robot/send?access_token="
-var reg = `^1([38][0-9]|14[57]|5[^4])\d{8}$`
-var regx = regexp.MustCompile(reg)
-
-// real send request to api
-func (w *WebHook) sendPayload(payload *PayLoad) error {
- // get config
- bs, err := json.Marshal(payload)
- if nil != err {
- return err
- }
- // request api
- resp, err := http.Post(baseApi+w.AccessToken, "application/json", bytes.NewReader(bs))
- if nil != err {
- return err
- }
- // read response body
- body, err := ioutil.ReadAll(resp.Body)
- if nil != err {
- return err
- }
- // api unusual
- if 200 != resp.StatusCode {
- return fmt.Errorf("%d: %s", resp.StatusCode, string(body))
- }
-
- var result Response
- // json decode
- err = json.Unmarshal(body, &result)
- if nil != err {
- return err
- }
- if 0 != result.ErrorCode {
- return fmt.Errorf("%d: %s", result.ErrorCode, result.ErrorMessage)
- }
-
- return nil
-}
-
-// SendTextMsg `send a text message`
-func (w *WebHook) SendTextMsg(content string, isAtAll bool, mobiles ...string) error {
- // send request
- return w.sendPayload(&PayLoad{
- MsgType: "text",
- Text: struct {
- Content string `json:"content"`
- }{
- Content: content,
- },
- At: struct {
- AtMobiles []string `json:"atMobiles"`
- IsAtAll bool `json:"isAtAll"`
- }{
- AtMobiles: mobiles,
- IsAtAll: isAtAll,
- },
- })
-}
-
-// SendLinkMsg `send a link message`
-func (w *WebHook) SendLinkMsg(title, content, picURL, msgURL string) error {
- return w.sendPayload(&PayLoad{
- MsgType: "link",
- Link: struct {
- Title string `json:"title"`
- Text string `json:"text"`
- PicUrl string `json:"picUrl"`
- MessageUrl string `json:"messageUrl"`
- }{
- Title: title,
- Text: content,
- PicUrl: picURL,
- MessageUrl: msgURL,
- },
- })
-}
-
-// SendMarkdownMsg `send a markdown msg`
-func (w *WebHook) SendMarkdownMsg(title, content string, isAtAll bool, mobiles ...string) error {
- firstLine := false
- for _, mobile := range mobiles {
- if regx.MatchString(mobile) {
- if false == firstLine {
- content += "#####"
- }
- content += " @" + mobile
- firstLine = true
- }
- }
- // send request
- return w.sendPayload(&PayLoad{
- MsgType: "markdown",
- Markdown: struct {
- Title string `json:"title"`
- Text string `json:"text"`
- }{
- Title: title,
- Text: content,
- },
- At: struct {
- AtMobiles []string `json:"atMobiles"`
- IsAtAll bool `json:"isAtAll"`
- }{
- AtMobiles: mobiles,
- IsAtAll: isAtAll,
- },
- })
-}
-
-// SendActionCardMsg `send single action card message`
-func (w *WebHook) SendActionCardMsg(title, content string, linkTitles, linkUrls []string, hideAvatar, btnOrientation bool) error {
- // validation is empty
- if 0 == len(linkTitles) || 0 == len(linkUrls) {
- return errors.New("links or titles is empty!")
- }
- // validation is equal
- if len(linkUrls) != len(linkTitles) {
- return errors.New("links length and titles length is not equal!")
- }
- // hide robot avatar
- var strHideAvatar = "0"
- if hideAvatar {
- strHideAvatar = "1"
- }
- // button sort
- var strBtnOrientation = "0"
- if btnOrientation {
- strBtnOrientation = "1"
- }
- // button struct
- var buttons []struct {
- Title string `json:"title"`
- ActionURL string `json:"actionURL"`
- }
- // inject to button
- for i := 0; i < len(linkTitles); i++ {
- buttons = append(buttons, struct {
- Title string `json:"title"`
- ActionURL string `json:"actionURL"`
- }{
- Title: linkTitles[i],
- ActionURL: linkUrls[i],
- })
- }
- // send request
- return w.sendPayload(&PayLoad{
- MsgType: "actionCard",
- ActionCard: ActionCard{
- Title: title,
- Text: content,
- HideAvatar: strHideAvatar,
- BtnOrientation: strBtnOrientation,
- Buttons: buttons,
- },
- })
-}
-
-// SendLinkCardMsg `send link card message`
-func (w *WebHook) SendLinkCardMsg(messages []LinkMsg) error {
- return w.sendPayload(&PayLoad{
- MsgType: "feedCard",
- FeedCard: struct {
- Links []LinkMsg `json:"links"`
- }{
- Links: messages,
- },
- })
-}