Compare commits

..

8 Commits

Author SHA1 Message Date
lddsb a9e3dbbf4b use help option 2019-02-07 10:42:01 +08:00
lddsb c82a6a8c79 add workspace 2019-02-07 10:39:53 +08:00
lddsb aec957818e migrate golang image to latest 2019-02-07 10:36:35 +08:00
lddsb 6c497aa777 add dep install step 2019-02-07 10:35:15 +08:00
lddsb 9e4a2ae0dc add dep ensure step 2019-02-07 10:32:49 +08:00
lddsb 00b6d66762 fix drone.yml syntax error 2019-02-07 10:31:05 +08:00
lddsb 26933681c4 add drone auto build 2019-02-07 10:30:11 +08:00
lddsb b6dd3953d2 increase retry mechanism 2019-02-06 22:26:52 +08:00
10 changed files with 676 additions and 311 deletions
+24 -36
View File
@@ -7,40 +7,28 @@ workspace:
path: src/github.com/lddsb/drone-dingtalk-message
steps:
- name: build
image: golang
commands:
- go get -u github.com/golang/dep/cmd/dep
- dep ensure
- CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk-message .
- go test -race -coverprofile=coverage.txt -covermode=atomic
- ./drone-dingtalk-message -h
- name: codecov
image: plugins/codecov
when:
status:
- success
settings:
token:
from_secret: codecov_token
- name: publish
image: plugins/docker
when:
status:
- success
event:
- tag
settings:
repo: lddsb/drone-dingtalk-message
dockerfile: Dockerfile
tags:
- latest
- 1.0.0
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: build
image: golang
commands:
- go get -u github.com/golang/dep/cmd/dep
- dep ensure
- CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk-message .
- ./drone-dingtalk-message -h
- name: publish
image: plugins/docker
when:
branch:
- beta
status:
- success
event:
- push
settings:
repo: lddsb/drone-dingtalk-message
dockerfile: Dockerfile
tags: beta
username:
from_secret: docker_username
password:
from_secret: docker_password
trigger:
branch:
- master
+1 -2
View File
@@ -1,5 +1,4 @@
dive.log
drone-dingtalk-message
.idea
vendor
coverage.txt
vendor
Generated
-9
View File
@@ -12,14 +12,6 @@
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"
@@ -33,7 +25,6 @@
analyzer-version = 1
input-imports = [
"github.com/joho/godotenv/autoload",
"github.com/lddsb/dingtalk-webhook",
"github.com/urfave/cli",
]
solver-name = "gps-cdcl"
-4
View File
@@ -36,7 +36,3 @@
[prune]
go-tests = true
unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/lddsb/dingtalk-webhook"
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Dee Luo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-1
View File
@@ -1,5 +1,4 @@
# Drone CI DingTalk Message Plugin
[![Build Status](https://drone.lddsb.com/api/badges/lddsb/drone-dingtalk-message/status.svg)](https://drone.lddsb.com/lddsb/drone-dingtalk-message) [![Go Report Card](https://goreportcard.com/badge/github.com/lddsb/drone-dingtalk-message)](https://goreportcard.com/report/github.com/lddsb/drone-dingtalk-message) [![codecov](https://codecov.io/gh/lddsb/drone-dingtalk-message/branch/master/graph/badge.svg)](https://codecov.io/gh/lddsb/drone-dingtalk-message) [![codebeat badge](https://codebeat.co/badges/23f68b84-1fd2-4f29-8467-9285c1e0facc)](https://codebeat.co/projects/github-com-lddsb-drone-dingtalk-message-master) [![LICENSE: MIT](https://img.shields.io/github/license/lddsb/drone-dingtalk-message.svg?style=flat-square)](LICENSE)
### Drone CI Plugin Config
`0.8.x`
+245 -49
View File
@@ -9,7 +9,6 @@ import (
"github.com/urfave/cli"
)
// Version of cli
var Version = "0.1.1202"
func main() {
@@ -26,11 +25,6 @@ func main() {
app.Action = run
app.Version = Version
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "config.debug",
Usage: "debug mode",
EnvVar: "PLUGIN_DEBUG",
},
cli.StringFlag{
Name: "config.token,access_token,token",
Usage: "dingtalk webhook access token",
@@ -43,7 +37,7 @@ func main() {
EnvVar: "PLUGIN_LANG",
},
cli.StringFlag{
Name: "config.message.type,message_type",
Name: "config.message.type,msg_type,message_type,type",
Usage: "dingtalk message type, like text, markdown, action card, link and feed card...",
EnvVar: "PLUGIN_MSG_TYPE,PLUGIN_TYPE,PLUGIN_MESSAGE_TYPE",
},
@@ -57,6 +51,22 @@ func main() {
Usage: "at someone in a dingtalk group need this guy bind's mobile",
EnvVar: "PLUGIN_MSG_AT_MOBILES",
},
cli.BoolFlag{
Name: "drone",
Usage: "indicates the runtime environment is Drone",
EnvVar: "DRONE",
},
cli.StringFlag{
Name: "branch",
Usage: "providers the branch for the current build",
EnvVar: "DRONE_BRANCH",
},
// commit args start
cli.StringFlag{
Name: "remote.url",
Usage: "git remote url",
EnvVar: "DRONE_REMOTE_URL",
},
cli.StringFlag{
Name: "commit.author.avatar",
Usage: "providers the author avatar url for the current commit",
@@ -88,16 +98,161 @@ func main() {
Usage: "providers the commit message for the current build",
EnvVar: "DRONE_COMMIT_MESSAGE",
},
cli.StringFlag{
Name: "commit.ref",
Usage: "providers the reference for the current build",
EnvVar: "DRONE_COMMIT_REF",
},
cli.StringFlag{
Name: "commit.sha",
Usage: "providers the commit sha for the current build",
EnvVar: "DRONE_COMMIT_SHA",
},
// commit args end
cli.StringFlag{
Name: "git.url.http",
Usage: "providers the repository git+http url",
EnvVar: "DRONE_GIT_HTTP_URL",
},
cli.StringFlag{
Name: "git.url.ssh",
Usage: "providers the repository git+ssh url",
EnvVar: "DRONE_GIT_SSH_URL",
},
cli.StringFlag{
Name: "machine",
Usage: "providers the Drone agent hostname",
EnvVar: "DRONE_MACHINE",
},
cli.StringFlag{
Name: "pull.request",
Usage: "providers the pull request number for the current build.This value is only set if the build event is of type pull request",
EnvVar: "DRONE_PULL_REQUEST",
},
// repo args start
cli.StringFlag{
Name: "repo.fullname",
Usage: "providers the full name of the repository",
EnvVar: "DRONE_REPO",
},
cli.StringFlag{
Name: "repo.owner",
Usage: "repository owner",
EnvVar: "DRONE_REPO_OWNER",
},
cli.StringFlag{
Name: "repo.branch",
Usage: "providers the default repository branch(e.g.master)",
EnvVar: "DRONE_REPO_BRANCH",
},
cli.StringFlag{
Name: "repo.link",
Usage: "providers the repository http link",
EnvVar: "DRONE_REPO_LINK",
},
cli.StringFlag{
Name: "repo.name",
Usage: "providers the repository name",
EnvVar: "DRONE_REPO_NAME",
},
cli.StringFlag{
Name: "repo.avatar",
Usage: "repository avatar",
EnvVar: "DRONE_REPO_AVATAR",
},
cli.StringFlag{
Name: "repo.namespace",
Usage: "providers the repository namespace(e.g. account owner)",
EnvVar: "DRONE_REPO_NAMESPACE",
},
cli.BoolFlag{
Name: "repo.private",
Usage: "indicates the repository is public or private",
EnvVar: "DRONE_REPO_PRIVATE",
},
cli.BoolFlag{
Name: "repo.trusted",
Usage: "repository is trusted",
EnvVar: "DRONE_REPO_TRUSTED",
},
// repo args end
cli.StringFlag{
Name: "runner.host",
Usage: "provider are Drone agent hostname",
EnvVar: "DRONE_RUNNER_HOST",
},
cli.StringFlag{
Name: "runner.hostname",
Usage: "providers the Drone agent hostname",
EnvVar: "DRONE_RUNNER_HOSTNAME",
},
cli.StringFlag{
Name: "runner.platform",
Usage: "providers the Drone agent os and architecture",
EnvVar: "DRONE_RUNNER_PLATFORM",
},
cli.StringFlag{
Name: "runner.label",
Usage: "404 not found",
EnvVar: "DRONE_RUNNER_LABEL",
},
cli.StringFlag{
Name: "source.branch",
Usage: "providers the source branch for a pull request",
EnvVar: "DRONE_SOURCE_BRANCH",
},
cli.StringFlag{
Name: "target.branch",
Usage: "providers the target branch for a pull request",
EnvVar: "DRONE_TARGET_BRANCH",
},
cli.StringFlag{
Name: "system.host",
Usage: "providers the Drone server hostname",
EnvVar: "DRONE_SYSTEM_HOST",
},
cli.StringFlag{
Name: "system.hostname",
Usage: "providers the Drone server hostname",
EnvVar: "DRONE_SYSTEM_HOSTNAME",
},
cli.StringFlag{
Name: "system.version",
Usage: "providers the Drone server version",
EnvVar: "DRONE_SYSTEM_VERSION",
},
cli.StringFlag{
Name: "tag",
Usage: "providers the tag name for the current build.This value is only set if the build event is of type tag",
EnvVar: "DRONE_TAG",
},
// build args start
cli.StringFlag{
Name: "build.event",
Value: "push",
Usage: "build event",
EnvVar: "DRONE_BUILD_EVENT",
},
cli.IntFlag{
Name: "build.number",
Usage: "build number",
EnvVar: "DRONE_BUILD_NUMBER",
},
cli.IntFlag{
Name: "build.created",
Usage: "build created",
EnvVar: "DRONE_BUILD_CREATED",
},
cli.IntFlag{
Name: "build.started",
Usage: "build started",
EnvVar: "DRONE_BUILD_STARTED",
},
cli.IntFlag{
Name: "build.finished",
Usage: "build finished",
EnvVar: "DRONE_BUILD_FINISHED",
},
cli.StringFlag{
Name: "build.status",
Usage: "build status",
@@ -109,6 +264,37 @@ func main() {
Usage: "build link",
EnvVar: "DRONE_BUILD_LINK",
},
cli.StringFlag{
Name: "build.deploy",
Usage: "build deployment target",
EnvVar: "DRONE_DEPLOY_TO",
},
cli.BoolFlag{
Name: "yaml.verified",
Usage: "build yaml is verified",
EnvVar: "DRONE_YAML_VERIFIED",
},
cli.BoolFlag{
Name: "yaml.signed",
Usage: "build yaml is signed",
EnvVar: "DRONE_YAML_SIGNED",
},
// build args end
cli.Float64Flag{
Name: "job.started",
Usage: "job started",
EnvVar: "DRONE_JOB_STARTED",
},
cli.Float64Flag{
Name: "job.finished",
Usage: "job finished",
EnvVar: "DRONE_JOB_FINISHED",
},
cli.StringFlag{
Name: "ci.repo.link",
Usage: "ci repo link",
EnvVar: "CI_REPO_LINK",
},
cli.StringFlag{
Name: "config.success.pic.url",
Usage: "config success picture url",
@@ -144,67 +330,77 @@ func main() {
Usage: "link sha source page or not",
EnvVar: "PLUGIN_SHA_LINK,PLUGIN_MESSAGE_SHA_LINK",
},
cli.IntFlag{
Name: "config.retry.time",
Usage: "time out retry times, default 3",
Value: 3,
EnvVar: "PLUGIN_RETRY_TIME,PLUGIN_RETRY",
},
}
if err := app.Run(os.Args); nil != err {
log.Println(err)
os.Exit(1)
}
}
// run with args
func run(c *cli.Context) {
plugin := Plugin{
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"),
},
// repo info
Repo: Repo{
FullName: c.String("repo.fullname"),
Owner: c.String("repo.owner"),
Name: c.String("repo.name"),
},
// build info
Build: Build{
Action: c.String("build.action"),
Number: c.Int("build.number"),
Started: c.Float64("build.started"),
Created: c.Float64("build.created"),
Event: c.String("build.event"),
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
Config: Config{
AccessToken: c.String("config.token"),
//Lang: c.String("config.lang"),
IsAtALL: c.Bool("config.message.at.all"),
MsgType: c.String("config.message.type"),
Mobiles: c.String("config.message.at.mobiles"),
Debug: c.Bool("config.debug"),
AccessToken: c.String("config.token"),
Lang: c.String("config.lang"),
IsAtALL: c.Bool("config.message.at.all"),
MsgType: c.String("config.message.type"),
Mobiles: c.String("config.message.at.mobiles"),
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"),
RetryTime: c.Int("config.retry.time"),
},
Extra: Extra{
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"),
CI: CI{
RepoLink: c.String("ci.repo.link"),
},
}
if err := plugin.Exec(); nil != err {
fmt.Println(err)
os.Exit(1)
}
}
+165 -130
View File
@@ -4,175 +4,210 @@ import (
"errors"
"fmt"
"log"
"net"
"strings"
webhook "github.com/lddsb/dingtalk-webhook"
)
type (
// Repo `repo base info`
// repo base info
Repo struct {
FullName string // repository full name
Owner string // providers the repository owner name
Name string // providers the repository name
Branch string // providers the default repository branch(e.g.master)
Link string // providers the repository http link
NameSpace string // providers the repository namespace(e.g.account owner)
Private bool // indicates the repository is public or private
Visibility string // providers the repository visibility level.Possible values are public,private and internal
SCM string // providers the repository version control system
FullName string // repository full name
}
// Build `build info`
// build info
Build struct {
Status string // providers the current build status
Link string // providers the current build link
Action string // document description not found
Created float64 // providers the date and time when the build was created in the system
Event string // providers the current build event
Number int // providers the current build number
Started float64 // providers the date and time when the build was started
Status string // providers the current build status
Link string // providers the current build link
}
// Commit `commit info`
// commit info
Commit struct {
After string // providers the commit sha for the current build
Author string // providers the author username for the current commit
Before string // providers the parent commit sha for the current build
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
Ref string // providers the reference for the current build
Sha string // providers the commit sha for the current build
Authors CommitAuthors
// 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
}
}
// 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
// git url info
Git struct {
HttpUrl string // providers the repository git+http url
SSHUrl string // providers the repository git+ssh url
}
// Drone `drone info`
Drone struct {
Repo Repo
Build Build
Commit Commit
// Drone runner info
Runner struct {
Host string // providers the Drone agent hostname
Hostname string // providers the Drone agent hostname
Platform string // providers the Drone agent os and architecture
Label string // document description not found
}
// Config `plugin private config`
// Drone system info
System struct {
Host string // providers the Drone server hostname
Hostname string // providers the Drone server hostname
Version string // providers the Drone server version
}
// Drone CI Info
CI struct {
RepoLink string
}
// 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 {
AccessToken string
Message string
Lang string
IsAtALL bool
Mobiles string
Username string
AvatarURL string
MsgType string
LinkUrls string
LinkTitles string
HideAvatar bool
BtnOrientation bool
PicURL string
MsgURL string
SuccessPicUrl string
FailurePicUrl string
SuccessColor string
FailureColor string
WithColor bool
WithPic bool
LinkSha bool
RetryTime int
}
// Extra `extra variables`
Extra struct {
Color ExtraColor
Pic ExtraPic
LinkSha bool
}
// ExtraPic `extra config for pic`
ExtraPic struct {
WithPic bool
SuccessPicURL string
FailurePicURL string
}
// ExtraColor `extra config for color`
ExtraColor struct {
WithColor bool
SuccessColor string
FailureColor string
}
// Plugin `plugin all config`
// plugin all config
Plugin struct {
Drone Drone
Config Config
Extra Extra
CI CI
Git Git
Runner Runner
System System
Commit Commit
Repo Repo
Build Build
Config Config
WebHook *WebHook
}
)
// Exec `execute webhook`
func (p *Plugin) Exec() error {
var err error
log.Println("start execute sending...")
if 0 == len(p.Config.AccessToken) {
msg := "missing dingtalk access token"
log.Println(msg)
return errors.New(msg)
}
if 6 > len(p.Drone.Commit.Sha) {
return errors.New("commit sha cannot short than 6")
}
newWebhook := webhook.NewWebHook(p.Config.AccessToken)
log.Println("access token pass...")
p.WebHook = NewWebHook(p.Config.AccessToken)
mobiles := strings.Split(p.Config.Mobiles, ",")
switch strings.ToLower(p.Config.MsgType) {
case "markdown":
err = newWebhook.SendMarkdownMsg("You have a new message...", p.baseTpl(), p.Config.IsAtALL, mobiles...)
case "text":
err = newWebhook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
case "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)
}
linkUrls := strings.Split(p.Config.LinkUrls, ",")
linkTitles := strings.Split(p.Config.LinkTitles, ",")
log.Println("sending message type: " + p.Config.MsgType)
var err error
retryTime := 1
for retryTime <= p.Config.RetryTime {
log.Printf("start a %d try", retryTime)
switch strings.ToLower(p.Config.MsgType) {
case "markdown":
err = p.WebHook.SendMarkdownMsg(
"You have a new message...",
p.baseTpl(),
p.Config.IsAtALL,
mobiles...
)
case "text":
err = p.WebHook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...)
case "actioncard":
err = p.WebHook.SendActionCardMsg(
"A actionCard title",
p.baseTpl(),
linkUrls,
linkTitles,
p.Config.HideAvatar,
p.Config.BtnOrientation,
)
case "link":
err = p.WebHook.SendLinkMsg(p.Build.Status, p.baseTpl(), p.Commit.Authors.Avatar, p.Build.Link)
default:
err = errors.New("not support message type")
}
if err == nil {
log.Println("send message success!")
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
retryTime++
} else {
break
}
}
return err
if nil != err {
log.Println(err)
return err
}
log.Println("send " + p.Config.MsgType + " message success!")
return nil
}
// markdownTpl `output the tpl of markdown`
func (p *Plugin) markdownTpl() string {
var tpl string
// title
title := fmt.Sprintf(" %s *Branch Build %s*",
strings.Title(p.Drone.Commit.Branch),
strings.Title(p.Drone.Build.Status))
strings.Title(p.Commit.Branch),
strings.Title(p.Build.Status))
// with color on title
if p.Extra.Color.WithColor {
if p.Config.WithColor {
title = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), title)
}
tpl = fmt.Sprintf("# %s \n", title)
// with pic
if p.Extra.Pic.WithPic {
if p.Config.WithPic {
tpl += fmt.Sprintf("![%s](%s)\n\n",
p.Drone.Build.Status,
p.getPicURL())
p.Build.Status,
p.getPicUrl())
}
// commit message
commitMsg := fmt.Sprintf("%s", p.Drone.Commit.Message)
if p.Extra.Color.WithColor {
commitMsg := fmt.Sprintf("%s", p.Commit.Message)
if p.Config.WithColor {
commitMsg = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), commitMsg)
}
tpl += commitMsg + "\n\n"
// sha info
commitSha := p.Drone.Commit.Sha
if p.Extra.LinkSha {
commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Drone.Commit.Link)
commitSha := p.Commit.Sha
if p.Config.LinkSha {
commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Commit.Link)
}
tpl += commitSha + "\n\n"
// author info
authorInfo := fmt.Sprintf("`%s(%s)`", p.Drone.Commit.Authors.Name, p.Drone.Commit.Authors.Email)
authorInfo := fmt.Sprintf("`%s(%s)`", p.Commit.Authors.Name, p.Commit.Authors.Email)
tpl += authorInfo + "\n\n"
// build detail link
buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)",
p.getEmoticon(),
p.Drone.Build.Link)
p.Build.Link)
tpl += buildDetail
return tpl
}
@@ -188,20 +223,20 @@ func (p *Plugin) baseTpl() string {
@%s
%s (%s)
`,
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)
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)
case "link":
tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`,
p.Drone.Repo.FullName,
p.Drone.Commit.Branch,
p.Drone.Commit.Sha[:6],
p.Drone.Commit.Authors.Name,
p.Drone.Commit.Authors.Email)
p.Repo.FullName,
p.Commit.Branch,
p.Commit.Sha[:6],
p.Commit.Authors.Name,
p.Commit.Authors.Email)
case "actionCard":
// coming soon
@@ -212,13 +247,13 @@ func (p *Plugin) baseTpl() string {
/**
get emoticon
*/
*/
func (p *Plugin) getEmoticon() string {
emoticons := make(map[string]string)
emoticons["success"] = ":)"
emoticons["failure"] = ":("
emoticon, ok := emoticons[p.Drone.Build.Status]
emoticon, ok := emoticons[p.Build.Status]
if ok {
return emoticon
}
@@ -228,21 +263,21 @@ 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.Pic.SuccessPicURL != "" {
pics["success"] = p.Extra.Pic.SuccessPicURL
if p.Config.SuccessPicUrl != "" {
pics["success"] = p.Config.SuccessPicUrl
}
// failure picture url
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
if p.Extra.Pic.FailurePicURL != "" {
pics["failure"] = p.Extra.Pic.FailurePicURL
if p.Config.FailurePicUrl != "" {
pics["failure"] = p.Config.FailurePicUrl
}
url, ok := pics[p.Drone.Build.Status]
url, ok := pics[p.Build.Status]
if ok {
return url
}
@@ -252,21 +287,21 @@ func (p *Plugin) getPicURL() string {
/**
get color for message title
*/
*/
func (p *Plugin) getColor() string {
colors := make(map[string]string)
// success color
colors["success"] = "#008000"
if p.Extra.Color.SuccessColor != "" {
colors["success"] = "#" + p.Extra.Color.SuccessColor
if p.Config.SuccessColor != "" {
colors["success"] = "#" + p.Config.SuccessColor
}
// failure color
colors["failure"] = "#FF0000"
if p.Extra.Color.FailureColor != "" {
colors["failure"] = "#" + p.Extra.Color.FailureColor
if p.Config.FailureColor != "" {
colors["failure"] = "#" + p.Config.FailureColor
}
color, ok := colors[p.Drone.Build.Status]
color, ok := colors[p.Build.Status]
if ok {
return color
}
-59
View File
@@ -1,59 +0,0 @@
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")
}
+241
View File
@@ -0,0 +1,241 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"regexp"
)
// link message struct
type LinkMsg struct {
Title string `json:"title"`
MessageURL string `json:"messageURL"`
PicURL string `json:"picURL"`
}
// 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
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"`
}
// web hook base config
type WebHook struct {
AccessToken string `json:"accessToken"`
}
func NewWebHook(accessToken string) *WebHook {
return &WebHook{AccessToken: accessToken}
}
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
}
// 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,
},
})
}
// with 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,
},
})
}
// send 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,
},
})
}
// send single action card
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,
},
})
}
// 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,
},
})
}