mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-27 12:21:03 +00:00
merged flowdock notifications & github publishing into exp
This commit is contained in:
parent
76f76c0bf6
commit
4f3f2875f6
5 changed files with 339 additions and 6 deletions
84
plugin/notify/flowdock.go
Normal file
84
plugin/notify/flowdock.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
|
"github.com/stvp/flowdock"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flowdockStartedSubject = "Building %s (%s)"
|
||||||
|
flowdockSuccessSubject = "Build: %s (%s) is SUCCESS"
|
||||||
|
flowdockFailureSubject = "Build: %s (%s) is FAILED"
|
||||||
|
flowdockMessage = "<h2>%s </h2>\nBuild: %s <br/>\nResult: %s <br/>\nAuthor: %s <br/>Commit: <span class=\"commit-message\">%s</span> <br/>\nRepository Url: %s"
|
||||||
|
flowdockBuildOkEmail = "build+ok@flowdock.com"
|
||||||
|
flowdockBuildFailEmail = "build+fail@flowdock.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flowdock struct {
|
||||||
|
Token string `yaml:"token,omitempty"`
|
||||||
|
Source string `yaml:"source,omitempty"`
|
||||||
|
Tags string `yaml:"tags,omitempty"`
|
||||||
|
Started bool `yaml:"on_started,omitempty"`
|
||||||
|
Success bool `yaml:"on_success,omitempty"`
|
||||||
|
Failure bool `yaml:"on_failure,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) Send(context *model.Request) error {
|
||||||
|
switch {
|
||||||
|
case context.Commit.Status == "Started" && f.Started:
|
||||||
|
return f.sendStarted(context)
|
||||||
|
case context.Commit.Status == "Success" && f.Success:
|
||||||
|
return f.sendSuccess(context)
|
||||||
|
case context.Commit.Status == "Failure" && f.Failure:
|
||||||
|
return f.sendFailure(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) getBuildUrl(context *model.Request) string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name, context.Commit.Branch, context.Commit.Sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) getRepoUrl(context *model.Request) string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) getMessage(context *model.Request) string {
|
||||||
|
buildUrl := fmt.Sprintf("<a href=\"%s\"><span class=\"commit-sha\">%s</span></a>", f.getBuildUrl(context), context.Commit.ShaShort())
|
||||||
|
return fmt.Sprintf(flowdockMessage, context.Repo.Name, buildUrl, context.Commit.Status, context.Commit.Author, context.Commit.Message, f.getRepoUrl(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) sendStarted(context *model.Request) error {
|
||||||
|
fromAddress := context.Commit.Author
|
||||||
|
subject := fmt.Sprintf(flowdockStartedSubject, context.Repo.Name, context.Commit.Branch)
|
||||||
|
msg := f.getMessage(context)
|
||||||
|
tags := strings.Split(f.Tags, ",")
|
||||||
|
return f.send(fromAddress, subject, msg, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) sendFailure(context *model.Request) error {
|
||||||
|
fromAddress := flowdockBuildFailEmail
|
||||||
|
tags := strings.Split(f.Tags, ",")
|
||||||
|
subject := fmt.Sprintf(flowdockFailureSubject, context.Repo.Name, context.Commit.Branch)
|
||||||
|
msg := f.getMessage(context)
|
||||||
|
return f.send(fromAddress, subject, msg, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flowdock) sendSuccess(context *model.Request) error {
|
||||||
|
fromAddress := flowdockBuildOkEmail
|
||||||
|
tags := strings.Split(f.Tags, ",")
|
||||||
|
subject := fmt.Sprintf(flowdockSuccessSubject, context.Repo.Name, context.Commit.Branch)
|
||||||
|
msg := f.getMessage(context)
|
||||||
|
return f.send(fromAddress, subject, msg, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to send Flowdock requests
|
||||||
|
func (f *Flowdock) send(fromAddress, subject, message string, tags []string) error {
|
||||||
|
c := flowdock.Client{Token: f.Token, Source: f.Source, FromName: "drone.io", FromAddress: fromAddress, Tags: tags}
|
||||||
|
go c.Inbox(subject, message)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -21,12 +21,13 @@ type Sender interface {
|
||||||
// for notifying a user, or group of users,
|
// for notifying a user, or group of users,
|
||||||
// when their Build has completed.
|
// when their Build has completed.
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
Email *email.Email `yaml:"email,omitempty"`
|
Email *email.Email `yaml:"email,omitempty"`
|
||||||
Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
|
Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
|
||||||
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
|
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
|
||||||
Irc *irc.IRC `yaml:"irc,omitempty"`
|
Irc *irc.IRC `yaml:"irc,omitempty"`
|
||||||
Slack *Slack `yaml:"slack,omitempty"`
|
Slack *Slack `yaml:"slack,omitempty"`
|
||||||
Gitter *Gitter `yaml:"gitter,omitempty"`
|
Gitter *Gitter `yaml:"gitter,omitempty"`
|
||||||
|
Flowdock *Flowdock `yaml:"flowdock,omitempty"`
|
||||||
|
|
||||||
GitHub github.GitHub `yaml:"--"`
|
GitHub github.GitHub `yaml:"--"`
|
||||||
}
|
}
|
||||||
|
@ -80,6 +81,14 @@ func (n *Notification) Send(context *model.Request) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send gitter notifications
|
||||||
|
if n.Flowdock != nil {
|
||||||
|
err := n.Flowdock.Send(context)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// send email notifications
|
// send email notifications
|
||||||
// TODO (bradrydzewski) need to improve this code
|
// TODO (bradrydzewski) need to improve this code
|
||||||
githubStatus := new(github.GitHub)
|
githubStatus := new(github.GitHub)
|
||||||
|
|
131
plugin/publish/github.go
Normal file
131
plugin/publish/github.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package publish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/condition"
|
||||||
|
"github.com/drone/drone/shared/build/buildfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
type Github struct {
|
||||||
|
// Script is an optional list of commands to run to prepare for a release.
|
||||||
|
Script []string `yaml:"script"`
|
||||||
|
|
||||||
|
// Artifacts is a list of files or directories to release.
|
||||||
|
Artifacts []string `yaml:"artifacts"`
|
||||||
|
|
||||||
|
// Tag is the name of the tag to create for this release.
|
||||||
|
Tag string `yaml:"tag"`
|
||||||
|
|
||||||
|
// Name is the name of the release. Defaults to tag.
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
|
||||||
|
// Description describes the release. Defaults to empty string.
|
||||||
|
Description string `yaml:"description"`
|
||||||
|
|
||||||
|
// Draft is an identifier on a Github release.
|
||||||
|
Draft bool `yaml:"draft"`
|
||||||
|
|
||||||
|
// Prerelease is an identifier on a Github release.
|
||||||
|
Prerelease bool `yaml:"prerelease"`
|
||||||
|
|
||||||
|
// Token is the Github token to use when publishing the release.
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
|
||||||
|
// User is the Github user for the repository you'd like to publish to.
|
||||||
|
User string `yaml:"user"`
|
||||||
|
|
||||||
|
// Repo is the name of the Github repostiory you like to publish to.
|
||||||
|
Repo string `yaml:"repo"`
|
||||||
|
|
||||||
|
Condition *condition.Condition `yaml:"when,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write adds commands to run that will publish a Github release.
|
||||||
|
func (g *Github) Write(f *buildfile.Buildfile) {
|
||||||
|
if len(g.Artifacts) == 0 || g.Tag == "" || g.Token == "" || g.User == "" || g.Repo == "" {
|
||||||
|
f.WriteCmdSilent(`echo -e "Github Plugin: Missing argument(s)"\n\n`)
|
||||||
|
if len(g.Artifacts) == 0 {
|
||||||
|
f.WriteCmdSilent(`echo -e "\tartifacts not defined in yaml config" && false`)
|
||||||
|
}
|
||||||
|
if g.Tag == "" {
|
||||||
|
f.WriteCmdSilent(`echo -e "\ttag not defined in yaml config" && false`)
|
||||||
|
}
|
||||||
|
if g.Token == "" {
|
||||||
|
f.WriteCmdSilent(`echo -e "\ttoken not defined in yaml config" && false`)
|
||||||
|
}
|
||||||
|
if g.User == "" {
|
||||||
|
f.WriteCmdSilent(`echo -e "\tuser not defined in yaml config" && false`)
|
||||||
|
}
|
||||||
|
if g.Repo == "" {
|
||||||
|
f.WriteCmdSilent(`echo -e "\trepo not defined in yaml config" && false`)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default name is tag
|
||||||
|
if g.Name == "" {
|
||||||
|
g.Name = g.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range g.Script {
|
||||||
|
f.WriteCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.WriteEnv("GITHUB_TOKEN", g.Token)
|
||||||
|
|
||||||
|
// Install github-release
|
||||||
|
f.WriteCmd("curl -L -o /tmp/github-release.tar.bz2 https://github.com/aktau/github-release/releases/download/v0.5.2/linux-amd64-github-release.tar.bz2")
|
||||||
|
f.WriteCmd("tar jxf /tmp/github-release.tar.bz2 -C /tmp/ && sudo mv /tmp/bin/linux/amd64/github-release /usr/local/bin/github-release")
|
||||||
|
|
||||||
|
// Create the release. Ignore 422 errors, which indicate the tag has already been created.
|
||||||
|
// Doing otherwise would create the expectation that every commit should be tagged and released,
|
||||||
|
// which is not the norm.
|
||||||
|
draftStr := ""
|
||||||
|
if g.Draft {
|
||||||
|
draftStr = "--draft"
|
||||||
|
}
|
||||||
|
prereleaseStr := ""
|
||||||
|
if g.Prerelease {
|
||||||
|
prereleaseStr = "--pre-release"
|
||||||
|
}
|
||||||
|
f.WriteCmd(fmt.Sprintf(`
|
||||||
|
result=$(github-release release -u %s -r %s -t %s -n "%s" -d "%s" %s %s || true)
|
||||||
|
if [[ $result == *422* ]]; then
|
||||||
|
echo -e "Release already exists for this tag.";
|
||||||
|
exit 0
|
||||||
|
elif [[ $result == "" ]]; then
|
||||||
|
echo -e "Release created.";
|
||||||
|
else
|
||||||
|
echo -e "Error creating release: $result"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
`, g.User, g.Repo, g.Tag, g.Name, g.Description, draftStr, prereleaseStr))
|
||||||
|
|
||||||
|
// Upload files
|
||||||
|
artifactStr := strings.Join(g.Artifacts, " ")
|
||||||
|
f.WriteCmd(fmt.Sprintf(`
|
||||||
|
for f in %s; do
|
||||||
|
# treat directories and files differently
|
||||||
|
if [ -d $f ]; then
|
||||||
|
for ff in $(ls $f); do
|
||||||
|
echo -e "uploading $ff"
|
||||||
|
github-release upload -u %s -r %s -t %s -n $ff -f $f/$ff
|
||||||
|
done
|
||||||
|
elif [ -f $f ]; then
|
||||||
|
echo -e "uploading $f"
|
||||||
|
github-release upload -u %s -r %s -t %s -n $f -f $f
|
||||||
|
else
|
||||||
|
echo -e "$f is not a file or directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
`, artifactStr, g.User, g.Repo, g.Tag, g.User, g.Repo, g.Tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Github) GetCondition() *condition.Condition {
|
||||||
|
return g.Condition
|
||||||
|
}
|
103
plugin/publish/github_test.go
Normal file
103
plugin/publish/github_test.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package publish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/v1/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validcfg = map[string]interface{}{
|
||||||
|
"artifacts": []string{"release/"},
|
||||||
|
"tag": "v1.0",
|
||||||
|
"token": "github-token",
|
||||||
|
"user": "drone",
|
||||||
|
"repo": "drone",
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildfileForConfig(config map[string]interface{}) (string, error) {
|
||||||
|
yml, err := yaml.Marshal(map[string]interface{}{
|
||||||
|
"publish": config,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return setUpWithDrone(string(yml))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiredConfig(t *testing.T) {
|
||||||
|
for _, required := range []string{"artifacts", "tag", "token", "user", "repo"} {
|
||||||
|
invalidcfg := make(map[string]interface{})
|
||||||
|
for k, v := range validcfg {
|
||||||
|
if k != required {
|
||||||
|
invalidcfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": invalidcfg})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
contains := fmt.Sprintf("%s not defined", required)
|
||||||
|
if !strings.Contains(buildfilestr, contains) {
|
||||||
|
t.Fatalf("Expected buildfile to contain error '%s': %s", contains, buildfilestr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScript(t *testing.T) {
|
||||||
|
cmd := "echo run me!"
|
||||||
|
scriptcfg := make(map[string]interface{})
|
||||||
|
scriptcfg["script"] = []string{cmd}
|
||||||
|
for k, v := range validcfg {
|
||||||
|
scriptcfg[k] = v
|
||||||
|
}
|
||||||
|
buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": scriptcfg})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(buildfilestr, cmd) {
|
||||||
|
t.Fatalf("Expected buildfile to contain command '%s': %s", cmd, buildfilestr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultBehavior(t *testing.T) {
|
||||||
|
buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": validcfg})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defaultname := fmt.Sprintf(`-n "%s"`, validcfg["tag"].(string))
|
||||||
|
if !strings.Contains(buildfilestr, defaultname) {
|
||||||
|
t.Fatalf("Expected buildfile to contain name default to tag '%s': %s", defaultname, buildfilestr)
|
||||||
|
}
|
||||||
|
if strings.Contains(buildfilestr, "--draft") {
|
||||||
|
t.Fatalf("Should not create a draft release by default: %s", buildfilestr)
|
||||||
|
}
|
||||||
|
if strings.Contains(buildfilestr, "--pre-release") {
|
||||||
|
t.Fatalf("Should not create a pre-release release by default: %s", buildfilestr)
|
||||||
|
}
|
||||||
|
if !strings.Contains(buildfilestr, "github-release release") {
|
||||||
|
t.Fatalf("Should create a release: %s", buildfilestr)
|
||||||
|
}
|
||||||
|
if !strings.Contains(buildfilestr, "github-release upload") {
|
||||||
|
t.Fatalf("Should upload a file: %s", buildfilestr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpts(t *testing.T) {
|
||||||
|
optscfg := make(map[string]interface{})
|
||||||
|
optscfg["draft"] = true
|
||||||
|
optscfg["prerelease"] = true
|
||||||
|
for k, v := range validcfg {
|
||||||
|
optscfg[k] = v
|
||||||
|
}
|
||||||
|
buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": optscfg})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, flag := range []string{"--draft", "--pre-release"} {
|
||||||
|
if !strings.Contains(buildfilestr, flag) {
|
||||||
|
t.Fatalf("Expected buildfile to contain flag '%s': %s", flag, buildfilestr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ type Publish struct {
|
||||||
PyPI *PyPI `yaml:"pypi,omitempty"`
|
PyPI *PyPI `yaml:"pypi,omitempty"`
|
||||||
NPM *npm.NPM `yaml:"npm,omitempty"`
|
NPM *npm.NPM `yaml:"npm,omitempty"`
|
||||||
Docker *Docker `yaml:"docker,omitempty"`
|
Docker *Docker `yaml:"docker,omitempty"`
|
||||||
|
Github *Github `yaml:"github,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
||||||
|
@ -39,6 +40,11 @@ func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
||||||
p.NPM.Write(f)
|
p.NPM.Write(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Github
|
||||||
|
if p.Github != nil && match(p.Github.GetCondition(), r) {
|
||||||
|
p.Github.Write(f)
|
||||||
|
}
|
||||||
|
|
||||||
// Docker
|
// Docker
|
||||||
if p.Docker != nil && match(p.Docker.GetCondition(), r) {
|
if p.Docker != nil && match(p.Docker.GetCondition(), r) {
|
||||||
p.Docker.Write(f)
|
p.Docker.Write(f)
|
||||||
|
|
Loading…
Reference in a new issue