diff --git a/plugin/notify/flowdock.go b/plugin/notify/flowdock.go
new file mode 100644
index 000000000..27c81e7d7
--- /dev/null
+++ b/plugin/notify/flowdock.go
@@ -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 = "
%s
\nBuild: %s
\nResult: %s
\nAuthor: %s
Commit: %s
\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("%s", 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
+}
diff --git a/plugin/notify/notification.go b/plugin/notify/notification.go
index dab57f43d..d3b2de6ae 100644
--- a/plugin/notify/notification.go
+++ b/plugin/notify/notification.go
@@ -21,12 +21,13 @@ type Sender interface {
// for notifying a user, or group of users,
// when their Build has completed.
type Notification struct {
- Email *email.Email `yaml:"email,omitempty"`
- Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
- Hipchat *Hipchat `yaml:"hipchat,omitempty"`
- Irc *irc.IRC `yaml:"irc,omitempty"`
- Slack *Slack `yaml:"slack,omitempty"`
- Gitter *Gitter `yaml:"gitter,omitempty"`
+ Email *email.Email `yaml:"email,omitempty"`
+ Webhook *webhook.Webhook `yaml:"webhook,omitempty"`
+ Hipchat *Hipchat `yaml:"hipchat,omitempty"`
+ Irc *irc.IRC `yaml:"irc,omitempty"`
+ Slack *Slack `yaml:"slack,omitempty"`
+ Gitter *Gitter `yaml:"gitter,omitempty"`
+ Flowdock *Flowdock `yaml:"flowdock,omitempty"`
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
// TODO (bradrydzewski) need to improve this code
githubStatus := new(github.GitHub)
diff --git a/plugin/publish/github.go b/plugin/publish/github.go
new file mode 100644
index 000000000..9e98ea177
--- /dev/null
+++ b/plugin/publish/github.go
@@ -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
+}
diff --git a/plugin/publish/github_test.go b/plugin/publish/github_test.go
new file mode 100644
index 000000000..33e995b44
--- /dev/null
+++ b/plugin/publish/github_test.go
@@ -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)
+ }
+ }
+}
diff --git a/plugin/publish/publish.go b/plugin/publish/publish.go
index 0b82a2378..19ba9e78a 100644
--- a/plugin/publish/publish.go
+++ b/plugin/publish/publish.go
@@ -16,6 +16,7 @@ type Publish struct {
PyPI *PyPI `yaml:"pypi,omitempty"`
NPM *npm.NPM `yaml:"npm,omitempty"`
Docker *Docker `yaml:"docker,omitempty"`
+ Github *Github `yaml:"github,omitempty"`
}
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)
}
+ // Github
+ if p.Github != nil && match(p.Github.GetCondition(), r) {
+ p.Github.Write(f)
+ }
+
// Docker
if p.Docker != nil && match(p.Docker.GetCondition(), r) {
p.Docker.Write(f)