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 path: src/github.com/lddsb/drone-dingtalk-message
steps: steps:
- name: build - name: build
image: golang image: golang
commands: commands:
- go get -u github.com/golang/dep/cmd/dep - go get -u github.com/golang/dep/cmd/dep
- dep ensure - dep ensure
- CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk-message . - CGO_ENABLED=0 GOOS=linux go build -a -o drone-dingtalk-message .
- go test -race -coverprofile=coverage.txt -covermode=atomic - ./drone-dingtalk-message -h
- ./drone-dingtalk-message -h - name: publish
- name: codecov image: plugins/docker
image: plugins/codecov when:
when: branch:
status: - beta
- success status:
settings: - success
token: event:
from_secret: codecov_token - push
- name: publish settings:
image: plugins/docker repo: lddsb/drone-dingtalk-message
when: dockerfile: Dockerfile
status: tags: beta
- success username:
event: from_secret: docker_username
- tag password:
settings: from_secret: docker_password
repo: lddsb/drone-dingtalk-message
dockerfile: Dockerfile
tags:
- latest
- 1.0.0
username:
from_secret: docker_username
password:
from_secret: docker_password
trigger:
branch:
- master
+1 -2
View File
@@ -1,5 +1,4 @@
dive.log dive.log
drone-dingtalk-message drone-dingtalk-message
.idea .idea
vendor vendor
coverage.txt
Generated
-9
View File
@@ -12,14 +12,6 @@
revision = "23d116af351c84513e1946b527c88823e476be13" revision = "23d116af351c84513e1946b527c88823e476be13"
version = "v1.3.0" version = "v1.3.0"
[[projects]]
branch = "master"
digest = "1:9142979c770f3d0f3c42c2eec532048bbbe2571134da91e8946cd8610c85c04b"
name = "github.com/lddsb/dingtalk-webhook"
packages = ["."]
pruneopts = "UT"
revision = "b4abe34b5fa9af8ea7d5f28c02bd314558b21f7f"
[[projects]] [[projects]]
digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679"
name = "github.com/urfave/cli" name = "github.com/urfave/cli"
@@ -33,7 +25,6 @@
analyzer-version = 1 analyzer-version = 1
input-imports = [ input-imports = [
"github.com/joho/godotenv/autoload", "github.com/joho/godotenv/autoload",
"github.com/lddsb/dingtalk-webhook",
"github.com/urfave/cli", "github.com/urfave/cli",
] ]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
-4
View File
@@ -36,7 +36,3 @@
[prune] [prune]
go-tests = true go-tests = true
unused-packages = 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 # 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 ### Drone CI Plugin Config
`0.8.x` `0.8.x`
+245 -49
View File
@@ -9,7 +9,6 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// Version of cli
var Version = "0.1.1202" var Version = "0.1.1202"
func main() { func main() {
@@ -26,11 +25,6 @@ func main() {
app.Action = run app.Action = run
app.Version = Version app.Version = Version
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "config.debug",
Usage: "debug mode",
EnvVar: "PLUGIN_DEBUG",
},
cli.StringFlag{ cli.StringFlag{
Name: "config.token,access_token,token", Name: "config.token,access_token,token",
Usage: "dingtalk webhook access token", Usage: "dingtalk webhook access token",
@@ -43,7 +37,7 @@ func main() {
EnvVar: "PLUGIN_LANG", EnvVar: "PLUGIN_LANG",
}, },
cli.StringFlag{ 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...", Usage: "dingtalk message type, like text, markdown, action card, link and feed card...",
EnvVar: "PLUGIN_MSG_TYPE,PLUGIN_TYPE,PLUGIN_MESSAGE_TYPE", 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", Usage: "at someone in a dingtalk group need this guy bind's mobile",
EnvVar: "PLUGIN_MSG_AT_MOBILES", 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{ cli.StringFlag{
Name: "commit.author.avatar", Name: "commit.author.avatar",
Usage: "providers the author avatar url for the current commit", 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", Usage: "providers the commit message for the current build",
EnvVar: "DRONE_COMMIT_MESSAGE", EnvVar: "DRONE_COMMIT_MESSAGE",
}, },
cli.StringFlag{
Name: "commit.ref",
Usage: "providers the reference for the current build",
EnvVar: "DRONE_COMMIT_REF",
},
cli.StringFlag{ cli.StringFlag{
Name: "commit.sha", Name: "commit.sha",
Usage: "providers the commit sha for the current build", Usage: "providers the commit sha for the current build",
EnvVar: "DRONE_COMMIT_SHA", 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{ cli.StringFlag{
Name: "repo.fullname", Name: "repo.fullname",
Usage: "providers the full name of the repository", Usage: "providers the full name of the repository",
EnvVar: "DRONE_REPO", 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{ cli.StringFlag{
Name: "build.status", Name: "build.status",
Usage: "build status", Usage: "build status",
@@ -109,6 +264,37 @@ func main() {
Usage: "build link", Usage: "build link",
EnvVar: "DRONE_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{ cli.StringFlag{
Name: "config.success.pic.url", Name: "config.success.pic.url",
Usage: "config success picture url", Usage: "config success picture url",
@@ -144,67 +330,77 @@ func main() {
Usage: "link sha source page or not", Usage: "link sha source page or not",
EnvVar: "PLUGIN_SHA_LINK,PLUGIN_MESSAGE_SHA_LINK", 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 { if err := app.Run(os.Args); nil != err {
log.Println(err) log.Println(err)
os.Exit(1)
} }
} }
// run with args // run with args
func run(c *cli.Context) { func run(c *cli.Context) {
plugin := Plugin{ plugin := Plugin{
Drone: Drone{ // repo info
// repo info Repo: Repo{
Repo: Repo{ FullName: c.String("repo.fullname"),
FullName: c.String("repo.fullname"), Owner: c.String("repo.owner"),
}, Name: c.String("repo.name"),
// build info },
Build: Build{ // build info
Status: c.String("build.status"), Build: Build{
Link: c.String("build.link"), Action: c.String("build.action"),
}, Number: c.Int("build.number"),
Commit: Commit{ Started: c.Float64("build.started"),
Sha: c.String("commit.sha"), Created: c.Float64("build.created"),
Branch: c.String("commit.branch"), Event: c.String("build.event"),
Message: c.String("commit.message"), Status: c.String("build.status"),
Link: c.String("commit.link"), Link: c.String("build.link"),
Authors: struct { },
Avatar string Commit: Commit{
Email string Sha: c.String("commit.sha"),
Name string Branch: c.String("commit.branch"),
}{ Message: c.String("commit.message"),
Avatar: c.String("commit.author.avatar"), Link: c.String("commit.link"),
Email: c.String("commit.author.email"), Authors: struct {
Name: c.String("commit.author.name"), 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 // custom config
Config: Config{ Config: Config{
AccessToken: c.String("config.token"), AccessToken: c.String("config.token"),
//Lang: c.String("config.lang"), Lang: c.String("config.lang"),
IsAtALL: c.Bool("config.message.at.all"), IsAtALL: c.Bool("config.message.at.all"),
MsgType: c.String("config.message.type"), MsgType: c.String("config.message.type"),
Mobiles: c.String("config.message.at.mobiles"), Mobiles: c.String("config.message.at.mobiles"),
Debug: c.Bool("config.debug"), 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{ CI: CI{
Pic: ExtraPic{ RepoLink: c.String("ci.repo.link"),
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 { if err := plugin.Exec(); nil != err {
fmt.Println(err) fmt.Println(err)
os.Exit(1)
} }
} }
+165 -130
View File
@@ -4,175 +4,210 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"net"
"strings" "strings"
webhook "github.com/lddsb/dingtalk-webhook"
) )
type ( type (
// Repo `repo base info` // repo base info
Repo struct { 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 info
// Build `build info`
Build struct { Build struct {
Status string // providers the current build status Action string // document description not found
Link string // providers the current build link 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 info
// Commit `commit info`
Commit struct { 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 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) 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 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 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
}
} }
// git url info
// CommitAuthors `commit author info` Git struct {
CommitAuthors struct { HttpUrl string // providers the repository git+http url
Avatar string // providers the author avatar for the current commit SSHUrl string // providers the repository git+ssh url
Email string // providers the author email for the current commit
Name string // providers the author name for the current commit
} }
// Drone runner info
// Drone `drone info` Runner struct {
Drone struct { Host string // providers the Drone agent hostname
Repo Repo Hostname string // providers the Drone agent hostname
Build Build Platform string // providers the Drone agent os and architecture
Commit Commit Label string // document description not found
} }
// Drone system info
// Config `plugin private config` 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 { Config struct {
Debug bool AccessToken string
AccessToken string Message string
IsAtALL bool Lang string
Mobiles string IsAtALL bool
Username string Mobiles string
MsgType string Username string
} AvatarURL string
MsgType string
// MessageConfig `DingTalk message struct`
MessageConfig struct {
ActionCard ActionCard
}
// ActionCard `action card message struct`
ActionCard struct {
LinkUrls string LinkUrls string
LinkTitles string LinkTitles string
HideAvatar bool HideAvatar bool
BtnOrientation bool BtnOrientation bool
PicURL string
MsgURL string
SuccessPicUrl string
FailurePicUrl string
SuccessColor string
FailureColor string
WithColor bool
WithPic bool
LinkSha bool
RetryTime int
} }
// plugin all config
// 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 struct { Plugin struct {
Drone Drone CI CI
Config Config Git Git
Extra Extra Runner Runner
System System
Commit Commit
Repo Repo
Build Build
Config Config
WebHook *WebHook
} }
) )
// Exec `execute webhook`
func (p *Plugin) Exec() error { func (p *Plugin) Exec() error {
var err error log.Println("start execute sending...")
if 0 == len(p.Config.AccessToken) { if 0 == len(p.Config.AccessToken) {
msg := "missing dingtalk access token" msg := "missing dingtalk access token"
log.Println(msg)
return errors.New(msg) return errors.New(msg)
} }
log.Println("access token pass...")
if 6 > len(p.Drone.Commit.Sha) { p.WebHook = NewWebHook(p.Config.AccessToken)
return errors.New("commit sha cannot short than 6")
}
newWebhook := webhook.NewWebHook(p.Config.AccessToken)
mobiles := strings.Split(p.Config.Mobiles, ",") mobiles := strings.Split(p.Config.Mobiles, ",")
switch strings.ToLower(p.Config.MsgType) { linkUrls := strings.Split(p.Config.LinkUrls, ",")
case "markdown": linkTitles := strings.Split(p.Config.LinkTitles, ",")
err = newWebhook.SendMarkdownMsg("You have a new message...", p.baseTpl(), p.Config.IsAtALL, mobiles...) log.Println("sending message type: " + p.Config.MsgType)
case "text": var err error
err = newWebhook.SendTextMsg(p.baseTpl(), p.Config.IsAtALL, mobiles...) retryTime := 1
case "link": for retryTime <= p.Config.RetryTime {
err = newWebhook.SendLinkMsg(p.Drone.Build.Status, p.baseTpl(), p.Drone.Commit.Authors.Avatar, p.Drone.Build.Link) log.Printf("start a %d try", retryTime)
default: switch strings.ToLower(p.Config.MsgType) {
msg := "not support message type" case "markdown":
err = errors.New(msg) 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 { if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
log.Println("send message success!") retryTime++
} else {
break
}
} }
if nil != err {
return 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 { func (p *Plugin) markdownTpl() string {
var tpl string var tpl string
// title // title
title := fmt.Sprintf(" %s *Branch Build %s*", title := fmt.Sprintf(" %s *Branch Build %s*",
strings.Title(p.Drone.Commit.Branch), strings.Title(p.Commit.Branch),
strings.Title(p.Drone.Build.Status)) strings.Title(p.Build.Status))
// with color on title // with color on title
if p.Extra.Color.WithColor { if p.Config.WithColor {
title = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), title) title = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), title)
} }
tpl = fmt.Sprintf("# %s \n", title) tpl = fmt.Sprintf("# %s \n", title)
// with pic // with pic
if p.Extra.Pic.WithPic { if p.Config.WithPic {
tpl += fmt.Sprintf("![%s](%s)\n\n", tpl += fmt.Sprintf("![%s](%s)\n\n",
p.Drone.Build.Status, p.Build.Status,
p.getPicURL()) p.getPicUrl())
} }
// commit message // commit message
commitMsg := fmt.Sprintf("%s", p.Drone.Commit.Message) commitMsg := fmt.Sprintf("%s", p.Commit.Message)
if p.Extra.Color.WithColor { if p.Config.WithColor {
commitMsg = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), commitMsg) commitMsg = fmt.Sprintf("<font color=%s>%s</font>", p.getColor(), commitMsg)
} }
tpl += commitMsg + "\n\n" tpl += commitMsg + "\n\n"
// sha info // sha info
commitSha := p.Drone.Commit.Sha commitSha := p.Commit.Sha
if p.Extra.LinkSha { if p.Config.LinkSha {
commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Drone.Commit.Link) commitSha = fmt.Sprintf("[Click To %s Commit Detail Page](%s)", commitSha[:6], p.Commit.Link)
} }
tpl += commitSha + "\n\n" tpl += commitSha + "\n\n"
// author info // 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" tpl += authorInfo + "\n\n"
// build detail link // build detail link
buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)", buildDetail := fmt.Sprintf("[Click To The Build Detail Page %s](%s)",
p.getEmoticon(), p.getEmoticon(),
p.Drone.Build.Link) p.Build.Link)
tpl += buildDetail tpl += buildDetail
return tpl return tpl
} }
@@ -188,20 +223,20 @@ func (p *Plugin) baseTpl() string {
@%s @%s
%s (%s) %s (%s)
`, `,
p.Drone.Build.Status, p.Build.Status,
strings.TrimSpace(p.Drone.Commit.Message), strings.TrimSpace(p.Commit.Message),
p.Drone.Repo.FullName, p.Repo.FullName,
p.Drone.Commit.Branch, p.Commit.Branch,
p.Drone.Commit.Sha, p.Commit.Sha,
p.Drone.Commit.Authors.Name, p.Commit.Authors.Name,
p.Drone.Commit.Authors.Email) p.Commit.Authors.Email)
case "link": case "link":
tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`, tpl = fmt.Sprintf(`%s(%s) @%s %s(%s)`,
p.Drone.Repo.FullName, p.Repo.FullName,
p.Drone.Commit.Branch, p.Commit.Branch,
p.Drone.Commit.Sha[:6], p.Commit.Sha[:6],
p.Drone.Commit.Authors.Name, p.Commit.Authors.Name,
p.Drone.Commit.Authors.Email) p.Commit.Authors.Email)
case "actionCard": case "actionCard":
// coming soon // coming soon
@@ -212,13 +247,13 @@ func (p *Plugin) baseTpl() string {
/** /**
get emoticon get emoticon
*/ */
func (p *Plugin) getEmoticon() string { func (p *Plugin) getEmoticon() string {
emoticons := make(map[string]string) emoticons := make(map[string]string)
emoticons["success"] = ":)" emoticons["success"] = ":)"
emoticons["failure"] = ":(" emoticons["failure"] = ":("
emoticon, ok := emoticons[p.Drone.Build.Status] emoticon, ok := emoticons[p.Build.Status]
if ok { if ok {
return emoticon return emoticon
} }
@@ -228,21 +263,21 @@ func (p *Plugin) getEmoticon() string {
/** /**
get picture url get picture url
*/ */
func (p *Plugin) getPicURL() string { func (p *Plugin) getPicUrl() string {
pics := make(map[string]string) pics := make(map[string]string)
// success picture url // success picture url
pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg" pics["success"] = "https://ws4.sinaimg.cn/large/006tNc79gy1fz05g5a7utj30he0bfjry.jpg"
if p.Extra.Pic.SuccessPicURL != "" { if p.Config.SuccessPicUrl != "" {
pics["success"] = p.Extra.Pic.SuccessPicURL pics["success"] = p.Config.SuccessPicUrl
} }
// failure picture url // failure picture url
pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg" pics["failure"] = "https://ws1.sinaimg.cn/large/006tNc79gy1fz0b4fghpnj30hd0bdmxn.jpg"
if p.Extra.Pic.FailurePicURL != "" { if p.Config.FailurePicUrl != "" {
pics["failure"] = p.Extra.Pic.FailurePicURL pics["failure"] = p.Config.FailurePicUrl
} }
url, ok := pics[p.Drone.Build.Status] url, ok := pics[p.Build.Status]
if ok { if ok {
return url return url
} }
@@ -252,21 +287,21 @@ func (p *Plugin) getPicURL() string {
/** /**
get color for message title get color for message title
*/ */
func (p *Plugin) getColor() string { func (p *Plugin) getColor() string {
colors := make(map[string]string) colors := make(map[string]string)
// success color // success color
colors["success"] = "#008000" colors["success"] = "#008000"
if p.Extra.Color.SuccessColor != "" { if p.Config.SuccessColor != "" {
colors["success"] = "#" + p.Extra.Color.SuccessColor colors["success"] = "#" + p.Config.SuccessColor
} }
// failure color // failure color
colors["failure"] = "#FF0000" colors["failure"] = "#FF0000"
if p.Extra.Color.FailureColor != "" { if p.Config.FailureColor != "" {
colors["failure"] = "#" + p.Extra.Color.FailureColor colors["failure"] = "#" + p.Config.FailureColor
} }
color, ok := colors[p.Drone.Build.Status] color, ok := colors[p.Build.Status]
if ok { if ok {
return color 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,
},
})
}