mirror of
https://github.com/lddsb/drone-dingtalk-message.git
synced 2026-06-04 18:33:48 +08:00
376 lines
8.2 KiB
Go
376 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
webhook "github.com/lddsb/dingtalk-webhook"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
// Repo repo base info
|
|
Repo struct {
|
|
ShortName string // short name
|
|
GroupName string // group name
|
|
FullName string // repository full name
|
|
OwnerName string // repo owner
|
|
RemoteURL string // repo remote url
|
|
}
|
|
|
|
// Build build info
|
|
Build struct {
|
|
Status string // providers the current build status
|
|
Link string // providers the current build link
|
|
Event string // trigger event
|
|
StartAt int64 // build start at ( unix timestamp )
|
|
FinishedAt int64 // build finish at ( unix timestamp )
|
|
}
|
|
|
|
// 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
|
|
Ref string // commit ref
|
|
Author CommitAuthor
|
|
}
|
|
|
|
// CommitAuthor commit author info
|
|
CommitAuthor 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
|
|
Username string // the author username 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
|
|
}
|
|
|
|
// MessageConfig DingTalk message struct
|
|
MessageConfig struct {
|
|
ActionCard ActionCard
|
|
}
|
|
|
|
// ActionCard action card message struct
|
|
ActionCard struct {
|
|
LinkUrls string
|
|
LinkTitles string
|
|
HideAvatar bool
|
|
BtnOrientation bool
|
|
}
|
|
|
|
// Pic extra config for pic
|
|
Pic struct {
|
|
SuccessPicURL string
|
|
FailurePicURL string
|
|
}
|
|
|
|
// Color extra config for color
|
|
Color struct {
|
|
SuccessColor string
|
|
FailureColor string
|
|
}
|
|
|
|
// Plugin plugin all config
|
|
Plugin struct {
|
|
Tpl Tpl
|
|
Drone Drone
|
|
Config Config
|
|
Custom Custom
|
|
Message MessageConfig
|
|
}
|
|
|
|
Custom struct {
|
|
Tpl string
|
|
Color Color
|
|
Pic Pic
|
|
}
|
|
|
|
Tpl struct {
|
|
Repo TplRepo
|
|
Commit TplCommit
|
|
Build TplBuild
|
|
}
|
|
|
|
TplRepo struct {
|
|
FullName string
|
|
ShortName string
|
|
}
|
|
|
|
TplCommit struct {
|
|
Branch string
|
|
}
|
|
|
|
TplBuild struct {
|
|
Status Status
|
|
}
|
|
|
|
Status struct {
|
|
Success string
|
|
Failure string
|
|
}
|
|
)
|
|
|
|
// Exec execute webhook
|
|
func (p *Plugin) Exec() error {
|
|
var err error
|
|
if 0 == len(p.Config.AccessToken) {
|
|
msg := "missing dingtalk access token"
|
|
return errors.New(msg)
|
|
}
|
|
|
|
tpl, err := p.getMessage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newWebhook := webhook.NewWebHook(p.Config.AccessToken)
|
|
mobiles := strings.Split(p.Config.Mobiles, ",")
|
|
switch strings.ToLower(p.Config.MsgType) {
|
|
case "markdown":
|
|
err = newWebhook.SendMarkdownMsg("new message", tpl, p.Config.IsAtALL, mobiles...)
|
|
case "text":
|
|
err = newWebhook.SendTextMsg(tpl, p.Config.IsAtALL, mobiles...)
|
|
case "link":
|
|
err = newWebhook.SendLinkMsg(p.Drone.Build.Status, tpl, p.Drone.Commit.Author.Avatar, p.Drone.Build.Link)
|
|
default:
|
|
msg := "not support message type"
|
|
err = errors.New(msg)
|
|
}
|
|
|
|
if err == nil {
|
|
log.Println("send message success!")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// fileExists check file is exists
|
|
func fileExists(filePath string) bool {
|
|
_, err := os.Stat(filePath)
|
|
if err != nil {
|
|
if os.IsExist(err) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// getTpl get tpl from local file or remote file
|
|
func (p *Plugin) getTpl() (tpl string, err error) {
|
|
//var tpl string
|
|
tplDir := "/app/tpls"
|
|
if "" == p.Custom.Tpl {
|
|
p.Custom.Tpl = fmt.Sprintf("%s/%s.tpl", tplDir, strings.ToLower(p.Config.MsgType))
|
|
}
|
|
|
|
u, err := url.Parse(p.Custom.Tpl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if u.Scheme != "" {
|
|
resp, err := http.Get(p.Custom.Tpl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// check response
|
|
if u.Path != resp.Request.URL.Path {
|
|
return "", errors.New("cannot get tpl from url")
|
|
}
|
|
|
|
// defer close
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
tpl = string(body)
|
|
} else {
|
|
if !fileExists(p.Custom.Tpl) {
|
|
return "", errors.New("tpl file not exists")
|
|
}
|
|
|
|
tplStr, err := ioutil.ReadFile(p.Custom.Tpl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
tpl = string(tplStr)
|
|
}
|
|
|
|
|
|
return tpl, nil
|
|
}
|
|
|
|
// fillTpl fill the tpl by valid keyword
|
|
func (p *Plugin) fillTpl(tpl string) string {
|
|
envs := p.getEnvs()
|
|
// replace regex
|
|
reg := regexp.MustCompile(`\[([^\[\]]*)]`)
|
|
match := reg.FindAllStringSubmatch(tpl, -1)
|
|
for _, m := range match {
|
|
// check if the keyword is legal
|
|
if _, ok := envs[m[1]]; ok {
|
|
// replace keyword
|
|
tpl = strings.ReplaceAll(tpl, m[0], envs[m[1]].(string))
|
|
}
|
|
}
|
|
|
|
return tpl
|
|
}
|
|
|
|
// getEnvs get available envs
|
|
func (p *Plugin) getEnvs() map[string]interface{} {
|
|
var envs map[string]interface{}
|
|
envs = make(map[string]interface{})
|
|
envs["TPL_REPO_FULL_NAME"] = p.Drone.Repo.FullName
|
|
if p.Tpl.Repo.FullName != "" {
|
|
envs["TPL_REPO_FULL_NAME"] = p.Tpl.Repo.FullName
|
|
}
|
|
envs["TPL_REPO_SHORT_NAME"] = p.Drone.Repo.ShortName
|
|
if p.Tpl.Repo.ShortName != "" {
|
|
envs["TPL_REPO_SHORT_NAME"] = p.Tpl.Repo.ShortName
|
|
}
|
|
envs["TPL_REPO_GROUP_NAME"] = p.Drone.Repo.GroupName
|
|
envs["TPL_REPO_OWNER_NAME"] = p.Drone.Repo.OwnerName
|
|
envs["TPL_REPO_REMOTE_URL"] = p.Drone.Repo.RemoteURL
|
|
|
|
envs["TPL_BUILD_STATUS"] = p.getStatus()
|
|
envs["TPL_BUILD_LINK"] = p.Drone.Build.Link
|
|
envs["TPL_BUILD_EVENT"] = p.Drone.Build.Event
|
|
envs["TPL_BUILD_CONSUMING"] = fmt.Sprintf("%v", p.Drone.Build.FinishedAt-p.Drone.Build.StartAt)
|
|
|
|
envs["TPL_COMMIT_SHA"] = p.Drone.Commit.Sha
|
|
envs["TPL_COMMIT_REF"] = p.Drone.Commit.Ref
|
|
envs["TPL_COMMIT_LINK"] = p.Drone.Commit.Link
|
|
envs["TPL_COMMIT_MSG"] = p.Drone.Commit.Message
|
|
envs["TPL_COMMIT_BRANCH"] = p.Drone.Commit.Branch
|
|
if p.Tpl.Commit.Branch != "" {
|
|
envs["TPL_COMMIT_BRANCH"] = p.Tpl.Commit.Branch
|
|
}
|
|
|
|
envs["TPL_AUTHOR_NAME"] = p.Drone.Commit.Author.Name
|
|
envs["TPL_AUTHOR_USERNAME"] = p.Drone.Commit.Author.Username
|
|
envs["TPL_AUTHOR_EMAIL"] = p.Drone.Commit.Author.Email
|
|
envs["TPL_AUTHOR_AVATAR"] = p.Drone.Commit.Author.Avatar
|
|
|
|
envs["TPL_STATUS_PIC"] = p.getPicURL()
|
|
envs["TPL_STATUS_COLOR"] = p.getColor()
|
|
envs["TPL_STATUS_EMOTICON"] = p.getEmoticon()
|
|
|
|
return envs
|
|
}
|
|
|
|
// getMessage get message tpl
|
|
func (p *Plugin) getMessage() (tpl string, err error) {
|
|
tpl, err = p.getTpl()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return p.fillTpl(tpl), nil
|
|
}
|
|
|
|
// getStatus
|
|
func (p *Plugin) getStatus() string {
|
|
if p.Drone.Build.Status == "success" {
|
|
if p.Tpl.Build.Status.Success != "" {
|
|
return p.Tpl.Build.Status.Success
|
|
}
|
|
|
|
return p.Drone.Build.Status
|
|
}
|
|
|
|
if p.Tpl.Build.Status.Failure != "" {
|
|
return p.Tpl.Build.Status.Failure
|
|
}
|
|
|
|
return p.Drone.Build.Status
|
|
}
|
|
|
|
// get emoticon
|
|
func (p *Plugin) getEmoticon() string {
|
|
emoticons := make(map[string]string)
|
|
emoticons["success"] = ":)"
|
|
emoticons["failure"] = ":("
|
|
|
|
emoticon, ok := emoticons[p.Drone.Build.Status]
|
|
if ok {
|
|
return emoticon
|
|
}
|
|
|
|
return ":("
|
|
}
|
|
|
|
// get picture url
|
|
func (p *Plugin) getPicURL() string {
|
|
pics := make(map[string]string)
|
|
// success picture url
|
|
pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
|
|
if p.Custom.Pic.SuccessPicURL != "" {
|
|
pics["success"] = p.Custom.Pic.SuccessPicURL
|
|
}
|
|
// failure picture url
|
|
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
|
|
if p.Custom.Pic.FailurePicURL != "" {
|
|
pics["failure"] = p.Custom.Pic.FailurePicURL
|
|
}
|
|
|
|
picURL, ok := pics[p.Drone.Build.Status]
|
|
if ok {
|
|
return picURL
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// get color for message title
|
|
func (p *Plugin) getColor() string {
|
|
colors := make(map[string]string)
|
|
// success color
|
|
colors["success"] = "#008000"
|
|
if p.Custom.Color.SuccessColor != "" {
|
|
colors["success"] = "#" + p.Custom.Color.SuccessColor
|
|
}
|
|
|
|
// failure color
|
|
colors["failure"] = "#FF0000"
|
|
if p.Custom.Color.FailureColor != "" {
|
|
colors["failure"] = "#" + p.Custom.Color.FailureColor
|
|
}
|
|
|
|
color, ok := colors[p.Drone.Build.Status]
|
|
if ok {
|
|
return color
|
|
}
|
|
|
|
return ""
|
|
}
|