Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
9fcec0025d
26 changed files with 731 additions and 27 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
|||
[submodule "vendor/src/github.com/GeertJohan/go.rice"]
|
||||
path = vendor/src/github.com/GeertJohan/go.rice
|
||||
url = https://github.com/GeertJohan/go.rice.git
|
||||
[submodule "vendor/src/github.com/GeertJohan/rsrc"]
|
||||
path = vendor/src/github.com/GeertJohan/rsrc
|
||||
url = https://github.com/GeertJohan/rsrc.git
|
||||
|
|
|
@ -10,13 +10,15 @@ RUN wget https://go.googlecode.com/files/go1.2.src.tar.gz && tar zxvf go1.2.src.
|
|||
ENV PATH $PATH:/go/bin:/gocode/bin
|
||||
ENV GOPATH /gocode
|
||||
|
||||
RUN go get github.com/tools/godep
|
||||
|
||||
RUN mkdir -p /gocode/src/github.com/drone
|
||||
|
||||
ADD . /gocode/src/github.com/drone/drone
|
||||
|
||||
WORKDIR /gocode/src/github.com/drone/drone
|
||||
|
||||
RUN make deps
|
||||
RUN godep restore
|
||||
RUN make
|
||||
RUN make install
|
||||
|
||||
|
@ -24,4 +26,4 @@ EXPOSE 80
|
|||
|
||||
ENTRYPOINT ["/usr/local/bin/droned"]
|
||||
|
||||
CMD ["--port=:80", "--path=/var/lib/drone/drone.sqlite"]
|
||||
CMD ["--port=:80", "--datasource=/var/lib/drone/drone.sqlite"]
|
||||
|
|
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
|
@ -174,6 +174,10 @@
|
|||
"Comment": "1.5.0-168-g2abea07",
|
||||
"Rev": "2abea075ec076abf0572d29bdb28ae7da64cfb7a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stvp/flowdock",
|
||||
"Rev": "50362abeabebf40b0f56a326d62f17e61a59302b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "launchpad.net/goyaml",
|
||||
"Comment": "51",
|
||||
|
|
53
Godeps/_workspace/src/github.com/stvp/flowdock/README.md
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/stvp/flowdock/README.md
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
flowdock
|
||||
========
|
||||
|
||||
A Flowdock client for Golang. See the [Flowdock API docs][api_docs] for more
|
||||
information on the individual attributes.
|
||||
|
||||
[Go API Documentation][godocs]
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
You can set global defaults and use `flowdock.Inbox()` directly:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/stvp/flowdock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flowdock.Token = "732da505d0284d5b1d909b1a32426345"
|
||||
flowdock.Source = "My Cool App"
|
||||
flowdock.FromAddress = "cool@dudes.com"
|
||||
// See API docs for more variables
|
||||
|
||||
go flowdock.Inbox("My subject", "My content goes here.")
|
||||
}
|
||||
```
|
||||
|
||||
Or you can create a `flowdock.Client` to use different Flowdock message
|
||||
settings in the same app:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/stvp/flowdock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := flowdock.Client{
|
||||
Token: "732da505d0284d5b1d909b1a32426345",
|
||||
Source: "App A",
|
||||
FromAddress: "email@stovepipestudios.com",
|
||||
FromName: "Client A",
|
||||
ReplyTo: "app_a@stovepipestudios.com",
|
||||
Tags: []string{"app_a"},
|
||||
}
|
||||
|
||||
go client.Inbox("Subject", "Content")
|
||||
}
|
||||
```
|
||||
|
||||
[api_docs]: https://www.flowdock.com/api/team-inbox
|
||||
[godocs]: http://godoc.org/github.com/stvp/flowdock
|
||||
|
105
Godeps/_workspace/src/github.com/stvp/flowdock/flowdock.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/stvp/flowdock/flowdock.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
package flowdock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
ENDPOINT = "https://api.flowdock.com/v1/messages/team_inbox/"
|
||||
)
|
||||
|
||||
var (
|
||||
// Required default client settings
|
||||
Token = ""
|
||||
Source = ""
|
||||
FromAddress = ""
|
||||
|
||||
// Optional default client settings
|
||||
FromName = ""
|
||||
ReplyTo = ""
|
||||
Project = ""
|
||||
Link = ""
|
||||
Tags = []string{}
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
// Required
|
||||
Token string
|
||||
Source string
|
||||
FromAddress string
|
||||
Subject string
|
||||
Content string
|
||||
|
||||
// Optional
|
||||
FromName string
|
||||
ReplyTo string
|
||||
Project string
|
||||
Link string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func (c *Client) Inbox(subject, content string) error {
|
||||
return send(c.Token, c.Source, c.FromAddress, subject, content, c.FromName, c.ReplyTo, c.Project, c.Link, c.Tags)
|
||||
}
|
||||
|
||||
func Inbox(subject, content string) error {
|
||||
return send(Token, Source, FromAddress, subject, content, FromName, ReplyTo, Project, Link, Tags)
|
||||
}
|
||||
|
||||
func send(token, source, fromAddress, subject, content, fromName, replyTo, project, link string, tags []string) error {
|
||||
// Required validation
|
||||
if len(token) == 0 {
|
||||
return fmt.Errorf(`"Token" is required`)
|
||||
}
|
||||
if len(source) == 0 {
|
||||
return fmt.Errorf(`"Source" is required`)
|
||||
}
|
||||
if len(fromAddress) == 0 {
|
||||
return fmt.Errorf(`"FromAddress" is required`)
|
||||
}
|
||||
if len(subject) == 0 {
|
||||
return fmt.Errorf(`"Subject" is required`)
|
||||
}
|
||||
|
||||
// Build payload
|
||||
payload := map[string]interface{}{
|
||||
"source": source,
|
||||
"from_address": fromAddress,
|
||||
"subject": subject,
|
||||
"content": content,
|
||||
}
|
||||
if len(fromName) > 0 {
|
||||
payload["from_name"] = fromName
|
||||
}
|
||||
if len(replyTo) > 0 {
|
||||
payload["reply_to"] = replyTo
|
||||
}
|
||||
if len(project) > 0 {
|
||||
payload["project"] = project
|
||||
}
|
||||
if len(link) > 0 {
|
||||
payload["link"] = link
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
payload["tags"] = tags
|
||||
}
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send to Flowdock
|
||||
resp, err := http.Post(ENDPOINT+token, "application/json", bytes.NewReader(jsonPayload))
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
} else {
|
||||
bodyBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
return fmt.Errorf("Unexpected response from Flowdock: %s %s", resp.Status, string(bodyBytes))
|
||||
}
|
||||
}
|
3
Makefile
3
Makefile
|
@ -94,4 +94,5 @@ godep:
|
|||
go get github.com/tools/godep
|
||||
|
||||
rice:
|
||||
go install github.com/GeertJohan/go.rice/rice
|
||||
go get github.com/GeertJohan/go.rice/rice
|
||||
go build github.com/GeertJohan/go.rice/rice
|
||||
|
|
|
@ -227,6 +227,7 @@ func setupHandlers() {
|
|||
|
||||
// handlers for repository, commits and build details
|
||||
m.Get("/:host/:owner/:name/commit/:commit/build/:label/out.txt", handler.RepoHandler(handler.BuildOut))
|
||||
m.Get("/:host/:owner/:name/commit/:commit/build/:label/status.json", handler.PublicHandler(handler.BuildStatus))
|
||||
m.Post("/:host/:owner/:name/commit/:commit/build/:label/rebuild", handler.RepoAdminHandler(rebuild.CommitRebuild))
|
||||
m.Get("/:host/:owner/:name/commit/:commit/build/:label", handler.RepoHandler(handler.CommitShow))
|
||||
m.Post("/:host/:owner/:name/commit/:commit/rebuild", handler.RepoAdminHandler(rebuild.CommitRebuild))
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# -datasource="drone.sqlite":
|
||||
# -driver="sqlite3":
|
||||
# -path="":
|
||||
# -port=":8080":
|
||||
# -workers="4":
|
||||
#
|
||||
|
|
|
@ -244,6 +244,8 @@ func (b *Builder) setup() error {
|
|||
if err := b.dockerClient.Images.Pull(b.Build.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Errf("failed to inspect image %s", b.Build.Image)
|
||||
}
|
||||
|
||||
// create the Docker image
|
||||
|
@ -439,7 +441,7 @@ func (b *Builder) writeDockerfile(dir string) error {
|
|||
switch {
|
||||
case strings.HasPrefix(b.Build.Image, "bradrydzewski/"),
|
||||
strings.HasPrefix(b.Build.Image, "drone/"):
|
||||
// the default user for all official Drone imnage
|
||||
// the default user for all official Drone images
|
||||
// is the "ubuntu" user, since all build images
|
||||
// inherit from the ubuntu cloud ISO
|
||||
dockerfile.WriteUser("ubuntu")
|
||||
|
|
|
@ -13,10 +13,7 @@ type Git struct {
|
|||
Depth *int `yaml:"depth,omitempty"`
|
||||
|
||||
// The name of a directory to clone into.
|
||||
// TODO this still needs to be implemented. this field is
|
||||
// critical for forked Go projects, that need to clone
|
||||
// to a specific repository.
|
||||
Path string `yaml:"path,omitempty"`
|
||||
Path *string `yaml:"path,omitempty"`
|
||||
}
|
||||
|
||||
// GitDepth returns GitDefaultDepth
|
||||
|
@ -29,3 +26,14 @@ func GitDepth(g *Git) int {
|
|||
}
|
||||
return *g.Depth
|
||||
}
|
||||
|
||||
// GitPath returns the given default path
|
||||
// when Git.Path is empty.
|
||||
// GitPath returns Git.Path
|
||||
// when it is not empty.
|
||||
func GitPath(g *Git, defaultPath string) string {
|
||||
if g == nil || g.Path == nil {
|
||||
return defaultPath
|
||||
}
|
||||
return *g.Path
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ import (
|
|||
. "github.com/drone/drone/pkg/model"
|
||||
)
|
||||
|
||||
type BuildResult struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
// Returns the combined stdout / stderr for an individual Build.
|
||||
func BuildOut(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error {
|
||||
branch := r.FormValue("branch")
|
||||
|
@ -32,6 +36,33 @@ func BuildOut(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error
|
|||
return RenderText(w, build.Stdout, http.StatusOK)
|
||||
}
|
||||
|
||||
// Returns the combined stdout / stderr for an individual Build.
|
||||
func BuildStatus(w http.ResponseWriter, r *http.Request, repo *Repo) error {
|
||||
branch := r.FormValue("branch")
|
||||
if branch == "" {
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
hash := r.FormValue(":commit")
|
||||
labl := r.FormValue(":label")
|
||||
|
||||
// get the commit from the database
|
||||
commit, err := database.GetCommitBranchHash(branch, hash, repo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the build from the database
|
||||
build, err := database.GetBuildSlug(labl, commit.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
build_result := BuildResult{build.Status}
|
||||
|
||||
return RenderJson(w, build_result)
|
||||
}
|
||||
|
||||
// Returns the gzipped stdout / stderr for an individual Build
|
||||
func BuildOutGzip(w http.ResponseWriter, r *http.Request, u *User) error {
|
||||
// TODO
|
||||
|
|
|
@ -64,10 +64,33 @@ func (h AdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// PublicHandler wraps the default http.HandlerFunc to include
|
||||
// requested Repository in the method signature, in addition
|
||||
// to handling an error as the return value.
|
||||
type PublicHandler func(w http.ResponseWriter, r *http.Request, repo *Repo) error
|
||||
|
||||
func (h PublicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// repository name from the URL parameters
|
||||
hostParam := r.FormValue(":host")
|
||||
userParam := r.FormValue(":owner")
|
||||
nameParam := r.FormValue(":name")
|
||||
repoName := fmt.Sprintf("%s/%s/%s", hostParam, userParam, nameParam)
|
||||
|
||||
repo, err := database.GetRepoSlug(repoName)
|
||||
if err != nil || repo == nil {
|
||||
RenderNotFound(w)
|
||||
return
|
||||
}
|
||||
|
||||
h(w, r, repo)
|
||||
return
|
||||
}
|
||||
|
||||
// RepoHandler wraps the default http.HandlerFunc to include
|
||||
// the currently authenticated User and requested Repository
|
||||
// in the method signature, in addition to handling an error
|
||||
// as the return value.
|
||||
|
||||
type RepoHandler func(w http.ResponseWriter, r *http.Request, user *User, repo *Repo) error
|
||||
|
||||
func (h RepoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -326,6 +326,7 @@ func RepoUpdate(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) err
|
|||
return err
|
||||
}
|
||||
default:
|
||||
repo.URL = r.FormValue("URL")
|
||||
repo.Disabled = len(r.FormValue("Disabled")) == 0
|
||||
repo.DisabledPullRequest = len(r.FormValue("DisabledPullRequest")) == 0
|
||||
repo.Private = len(r.FormValue("Private")) > 0
|
||||
|
|
|
@ -35,7 +35,7 @@ func (g *Git) Write(f *buildfile.Buildfile) {
|
|||
// that need to be deployed to git remote.
|
||||
f.WriteCmd(fmt.Sprintf("git add -A"))
|
||||
f.WriteCmd(fmt.Sprintf("git commit -m 'add build artifacts'"))
|
||||
f.WriteCmd(fmt.Sprintf("git push deploy $COMMIT:%s --force", destinationBranch))
|
||||
f.WriteCmd(fmt.Sprintf("git push deploy HEAD:%s --force", destinationBranch))
|
||||
case false:
|
||||
// otherwise we just do a standard git push
|
||||
f.WriteCmd(fmt.Sprintf("git push deploy $COMMIT:%s", destinationBranch))
|
||||
|
|
|
@ -30,7 +30,7 @@ func (h *Heroku) Write(f *buildfile.Buildfile) {
|
|||
// that need to be deployed to Heroku.
|
||||
f.WriteCmd(fmt.Sprintf("git add -A"))
|
||||
f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'"))
|
||||
f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master --force"))
|
||||
f.WriteCmd(fmt.Sprintf("git push heroku HEAD:master --force"))
|
||||
case false:
|
||||
// otherwise we just do a standard git push
|
||||
f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master"))
|
||||
|
|
|
@ -30,7 +30,7 @@ func (h *Tsuru) Write(f *buildfile.Buildfile) {
|
|||
// that need to be deployed to Tsuru.
|
||||
f.WriteCmd(fmt.Sprintf("git add -A"))
|
||||
f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'"))
|
||||
f.WriteCmd(fmt.Sprintf("git push tsuru $COMMIT:master --force"))
|
||||
f.WriteCmd(fmt.Sprintf("git push tsuru HEAD:master --force"))
|
||||
case false:
|
||||
// otherwise we just do a standard git push
|
||||
f.WriteCmd(fmt.Sprintf("git push tsuru $COMMIT:master"))
|
||||
|
|
90
pkg/plugin/notify/flowdock.go
Normal file
90
pkg/plugin/notify/flowdock.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"net/url"
|
||||
"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 *Context) 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 *Context) string {
|
||||
branchQuery := url.Values{}
|
||||
if context.Commit.Branch != "" {
|
||||
branchQuery.Set("branch", context.Commit.Branch)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/commit/%s?%s", context.Host, context.Repo.Slug, context.Commit.Hash, branchQuery.Encode())
|
||||
}
|
||||
|
||||
func (f *Flowdock) getRepoUrl(context *Context) string {
|
||||
return fmt.Sprintf("%s/%s", context.Host, context.Repo.Slug)
|
||||
}
|
||||
|
||||
func (f *Flowdock) getMessage(context *Context) string {
|
||||
buildUrl := fmt.Sprintf("<a href=\"%s\"><span class=\"commit-sha\">%s</span></a>", f.getBuildUrl(context), context.Commit.HashShort())
|
||||
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 *Context) 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 *Context) 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 *Context) 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
|
||||
}
|
|
@ -28,11 +28,12 @@ type Sender interface {
|
|||
// for notifying a user, or group of users,
|
||||
// when their Build has completed.
|
||||
type Notification struct {
|
||||
Email *Email `yaml:"email,omitempty"`
|
||||
Webhook *Webhook `yaml:"webhook,omitempty"`
|
||||
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
|
||||
Irc *IRC `yaml:"irc,omitempty"`
|
||||
Slack *Slack `yaml:"slack,omitempty"`
|
||||
Email *Email `yaml:"email,omitempty"`
|
||||
Webhook *Webhook `yaml:"webhook,omitempty"`
|
||||
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
|
||||
Irc *IRC `yaml:"irc,omitempty"`
|
||||
Slack *Slack `yaml:"slack,omitempty"`
|
||||
Flowdock *Flowdock `yaml:"flowdock,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Notification) Send(context *Context) error {
|
||||
|
@ -61,5 +62,10 @@ func (n *Notification) Send(context *Context) error {
|
|||
n.Slack.Send(context)
|
||||
}
|
||||
|
||||
// send flowdock notifications
|
||||
if n.Flowdock != nil {
|
||||
n.Flowdock.Send(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
|
||||
const (
|
||||
slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s"
|
||||
slackStartedMessage = "*Building* %s, commit <%s|%s>, author %s"
|
||||
slackSuccessMessage = "*Success* %s, commit <%s|%s>, author %s"
|
||||
slackFailureMessage = "*Failed* %s, commit <%s|%s>, author %s"
|
||||
slackStartedMessage = "*Building* %s <%s|%s>, by %s:\n> %s"
|
||||
slackSuccessMessage = "*Success* %s <%s|%s>, by %s:\n> %s"
|
||||
slackFailureMessage = "*Failed* %s <%s|%s>, by %s:\n> %s"
|
||||
)
|
||||
|
||||
type Slack struct {
|
||||
|
@ -47,7 +47,13 @@ func getBuildUrl(context *Context) string {
|
|||
|
||||
func getMessage(context *Context, message string) string {
|
||||
url := getBuildUrl(context)
|
||||
return fmt.Sprintf(message, context.Repo.Name, url, context.Commit.HashShort(), context.Commit.Author)
|
||||
return fmt.Sprintf(
|
||||
message,
|
||||
context.Repo.Name,
|
||||
url,
|
||||
context.Commit.HashShort(),
|
||||
context.Commit.Author,
|
||||
context.Commit.Message)
|
||||
}
|
||||
|
||||
func (s *Slack) sendStarted(context *Context) error {
|
||||
|
|
118
pkg/plugin/publish/docker.go
Normal file
118
pkg/plugin/publish/docker.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package publish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
"github.com/drone/drone/pkg/build/repo"
|
||||
)
|
||||
|
||||
type Docker struct {
|
||||
// The path to the dockerfile to create the image from. If the path is empty or no
|
||||
// path is specified then the docker file will be built from the base directory.
|
||||
Dockerfile string `yaml:"docker_file"`
|
||||
|
||||
// Connection information for the docker server that will build the image
|
||||
DockerServer string `yaml:"docker_server"`
|
||||
DockerServerPort int `yaml:"docker_port"`
|
||||
// The Docker client version to download. This must match the docker version on the server
|
||||
DockerVersion string `yaml:"docker_version"`
|
||||
|
||||
// Optional Arguments to allow finer-grained control of registry
|
||||
// endpoints
|
||||
RegistryLoginUrl string `yaml:"registry_login_url"`
|
||||
ImageName string `yaml:"image_name"`
|
||||
RegistryLogin bool `yaml:"registry_login"`
|
||||
|
||||
// Authentication credentials for index.docker.io
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Email string `yaml:"email"`
|
||||
|
||||
// Keep the build on the Docker host after pushing?
|
||||
KeepBuild bool `yaml:"keep_build"`
|
||||
// Do we want to override "latest" automatically with this build?
|
||||
PushLatest bool `yaml:"push_latest"`
|
||||
CustomTag string `yaml:"custom_tag"`
|
||||
Branch string `yaml:"branch"`
|
||||
}
|
||||
|
||||
// Write adds commands to the buildfile to do the following:
|
||||
// 1. Install the docker client in the Drone container.
|
||||
// 2. Build a docker image based on the dockerfile defined in the config.
|
||||
// 3. Push that docker image to index.docker.io.
|
||||
// 4. Delete the docker image on the server it was build on so we conserve disk space.
|
||||
func (d *Docker) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
||||
if len(d.DockerServer) == 0 || d.DockerServerPort == 0 || len(d.DockerVersion) == 0 ||
|
||||
len(d.ImageName) == 0 {
|
||||
f.WriteCmdSilent(`echo -e "Docker Plugin: Missing argument(s)"\n\n`)
|
||||
if len(d.DockerServer) == 0 { f.WriteCmdSilent(`echo -e "\tdocker_server not defined in yaml`) }
|
||||
if d.DockerServerPort == 0 { f.WriteCmdSilent(`echo -e "\tdocker_port not defined in yaml`) }
|
||||
if len(d.DockerVersion) == 0 { f.WriteCmdSilent(`echo -e "\tdocker_version not defined in yaml`) }
|
||||
if len(d.ImageName) == 0 { f.WriteCmdSilent(`echo -e "\timage_name not defined in yaml`) }
|
||||
return
|
||||
}
|
||||
|
||||
f.WriteCmd("sudo apt-get update")
|
||||
|
||||
// Ensure correct apt-get has the https method-driver as per (http://askubuntu.com/questions/165676/)
|
||||
f.WriteCmd("sudo apt-get install apt-transport-https")
|
||||
|
||||
// Install Docker on the container
|
||||
f.WriteCmd("sudo sh -c \"echo deb https://get.docker.io/ubuntu docker main\\ > " +
|
||||
"/etc/apt/sources.list.d/docker.list\"")
|
||||
f.WriteCmd("sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys " +
|
||||
"36A1D7869245C8950F966E92D8576A8BA88D21E9")
|
||||
f.WriteCmd("sudo apt-get update")
|
||||
f.WriteCmd("sudo apt-get --yes install lxc-docker-" + d.DockerVersion)
|
||||
|
||||
// Format our Build Server Endpoint
|
||||
dockerServerUrl := d.DockerServer + ":" + strconv.Itoa(d.DockerServerPort)
|
||||
|
||||
dockerPath := "."
|
||||
if len(d.Dockerfile) != 0 {
|
||||
dockerPath = fmt.Sprintf("- < %s", d.Dockerfile)
|
||||
}
|
||||
|
||||
// Run the command commands to build and deploy the image.
|
||||
// Are we setting a custom tag, or do we use the git hash?
|
||||
imageTag := ""
|
||||
if len(d.CustomTag) > 0 {
|
||||
imageTag = d.CustomTag
|
||||
} else {
|
||||
imageTag = "$(git rev-parse --short HEAD)"
|
||||
}
|
||||
f.WriteCmd(fmt.Sprintf("docker -H %s build -t %s:%s %s", dockerServerUrl, d.ImageName, imageTag, dockerPath))
|
||||
|
||||
// Login?
|
||||
if d.RegistryLogin == true {
|
||||
// Are we logging in to a custom Registry?
|
||||
if len(d.RegistryLoginUrl) > 0 {
|
||||
f.WriteCmdSilent(fmt.Sprintf("docker -H %s login -u %s -p %s -e %s %s",
|
||||
dockerServerUrl, d.Username, d.Password, d.Email, d.RegistryLoginUrl))
|
||||
} else {
|
||||
// Assume index.docker.io
|
||||
f.WriteCmdSilent(fmt.Sprintf("docker -H %s login -u %s -p %s -e %s",
|
||||
dockerServerUrl, d.Username, d.Password, d.Email))
|
||||
}
|
||||
}
|
||||
|
||||
// Are we overriding the "latest" tag?
|
||||
if d.PushLatest {
|
||||
f.WriteCmd(fmt.Sprintf("docker -H %s tag %s:%s %s:latest",
|
||||
dockerServerUrl, d.ImageName, imageTag, d.ImageName))
|
||||
}
|
||||
|
||||
f.WriteCmd(fmt.Sprintf("docker -H %s push %s", dockerServerUrl, d.ImageName))
|
||||
|
||||
// Delete the image from the docker server we built on.
|
||||
if ! d.KeepBuild {
|
||||
f.WriteCmd(fmt.Sprintf("docker -H %s rmi %s:%s",
|
||||
dockerServerUrl, d.ImageName, imageTag))
|
||||
if d.PushLatest {
|
||||
f.WriteCmd(fmt.Sprintf("docker -H %s rmi %s:latest",
|
||||
dockerServerUrl, d.ImageName))
|
||||
}
|
||||
}
|
||||
}
|
240
pkg/plugin/publish/docker_test.go
Normal file
240
pkg/plugin/publish/docker_test.go
Normal file
|
@ -0,0 +1,240 @@
|
|||
package publish
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/v1/yaml"
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
"github.com/drone/drone/pkg/build/repo"
|
||||
)
|
||||
|
||||
type PublishToDrone struct {
|
||||
Publish *Publish `yaml:"publish,omitempty"`
|
||||
}
|
||||
|
||||
func setUpWithDrone(input string) (string, error) {
|
||||
var buildStruct PublishToDrone
|
||||
err := yaml.Unmarshal([]byte(input), &buildStruct)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bf := buildfile.New()
|
||||
buildStruct.Publish.Write(bf, &repo.Repo{Name: "name"})
|
||||
return bf.String(), err
|
||||
}
|
||||
|
||||
// Private Registry Test (no auth)
|
||||
var privateRegistryNoAuthYaml = `
|
||||
publish:
|
||||
docker:
|
||||
dockerfile: file_path
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
registry_login: false
|
||||
image_name: registry/image
|
||||
`
|
||||
func TestPrivateRegistryNoAuth(t *testing.T) {
|
||||
response, err := setUpWithDrone(privateRegistryNoAuthYaml)
|
||||
t.Log(privateRegistryNoAuthYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t registry/image:$(git rev-parse --short HEAD)") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain registry in image-names: expected registry/image\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Private Registry Test (with auth)
|
||||
var privateRegistryAuthYaml = `
|
||||
publish:
|
||||
docker:
|
||||
dockerfile: file_path
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
registry_login_url: https://registry:8000/v1/
|
||||
registry_login: true
|
||||
username: username
|
||||
password: password
|
||||
email: email@example.com
|
||||
image_name: registry/image
|
||||
`
|
||||
func TestPrivateRegistryAuth(t *testing.T) {
|
||||
response, err := setUpWithDrone(privateRegistryAuthYaml)
|
||||
t.Log(privateRegistryAuthYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 login -u username -p password -e email@example.com https://registry:8000/v1/") {
|
||||
t.Log("\n\n\n\ndocker -H server:1000 login -u username -p xxxxxxxx -e email@example.com https://registry:8000/v1/\n\n\n\n")
|
||||
t.Fatalf("Response: " + response + " doesn't contain private registry login\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t registry/image:$(git rev-parse --short HEAD) .") {
|
||||
t.Log("docker -H server:1000 build -t registry/image:$(git rev-parse --short HEAD) .")
|
||||
t.Fatalf("Response: " + response + " doesn't contain registry in image-names\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Override "latest" Test
|
||||
var overrideLatestTagYaml = `
|
||||
publish:
|
||||
docker:
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
username: username
|
||||
password: password
|
||||
email: email@example.com
|
||||
image_name: username/image
|
||||
push_latest: true
|
||||
`
|
||||
func TestOverrideLatestTag(t *testing.T) {
|
||||
response, err := setUpWithDrone(overrideLatestTagYaml)
|
||||
t.Log(overrideLatestTagYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t username/image:$(git rev-parse --short HEAD) .") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain the git-ref tagged image\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 tag username/image:$(git rev-parse --short HEAD) username/image:latest") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain 'latest' tag command\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Keep builds Test
|
||||
var keepBuildsYaml = `
|
||||
publish:
|
||||
docker:
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
keep_build: true
|
||||
username: username
|
||||
password: password
|
||||
email: email@example.com
|
||||
image_name: image
|
||||
`
|
||||
func TestKeepBuilds(t *testing.T) {
|
||||
response, err := setUpWithDrone(keepBuildsYaml)
|
||||
t.Log(keepBuildsYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
if strings.Contains(response, "docker -H server:1000 rmi") {
|
||||
t.Fatalf("Response: " + response + " incorrectly instructs the docker server to remove the builds when it shouldn't\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Tag test
|
||||
var customTagYaml = `
|
||||
publish:
|
||||
docker:
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
custom_tag: release-0.1
|
||||
username: username
|
||||
password: password
|
||||
email: email@example.com
|
||||
image_name: username/image
|
||||
`
|
||||
func TestCustomTag(t *testing.T) {
|
||||
response, err := setUpWithDrone(customTagYaml)
|
||||
t.Log(customTagYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n", err.Error())
|
||||
}
|
||||
if strings.Contains(response, "$(git rev-parse --short HEAD)") {
|
||||
t.Fatalf("Response: " + response + " is tagging images from git-refs when it should use a custom tag\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t username/image:release-0.1") {
|
||||
t.Fatalf("Response: " + response + " isn't tagging images using our custom tag\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 push username/image"){
|
||||
t.Fatalf("Response: " + response + " doesn't push the custom tagged image\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
var missingFieldsYaml = `
|
||||
publish:
|
||||
docker:
|
||||
dockerfile: file
|
||||
`
|
||||
|
||||
func TestMissingFields(t *testing.T) {
|
||||
response, err := setUpWithDrone(missingFieldsYaml)
|
||||
t.Log(missingFieldsYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
if !strings.Contains(response, "Missing argument(s)") {
|
||||
t.Fatalf("Response: " + response + " didn't contain missing arguments warning\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
var validYaml = `
|
||||
publish:
|
||||
docker:
|
||||
docker_file: file_path
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
username: user
|
||||
password: password
|
||||
email: email
|
||||
image_name: user/image
|
||||
push_latest: true
|
||||
registry_login: true
|
||||
`
|
||||
|
||||
func TestValidYaml(t *testing.T) {
|
||||
response, err := setUpWithDrone(validYaml)
|
||||
t.Log(validYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
|
||||
if !strings.Contains(response, "docker -H server:1000 tag user/image:$(git rev-parse --short HEAD) user/image:latest") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain tag command for latest\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t user/image:$(git rev-parse --short HEAD) - <") {
|
||||
t.Fatalf("Response: " + response + "doesn't contain build command for commit hash\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 login -u user -p password -e email") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain login command\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 push user/image") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain push command\n\n")
|
||||
}
|
||||
if !strings.Contains(response, "docker -H server:1000 rmi user/image:" +
|
||||
"$(git rev-parse --short HEAD)") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain remove image command\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
var withoutDockerFileYaml = `
|
||||
publish:
|
||||
docker:
|
||||
docker_server: server
|
||||
docker_port: 1000
|
||||
docker_version: 1.0
|
||||
image_name: user/image
|
||||
username: user
|
||||
password: password
|
||||
email: email
|
||||
`
|
||||
|
||||
func TestWithoutDockerFile(t *testing.T) {
|
||||
response, err := setUpWithDrone(withoutDockerFileYaml)
|
||||
t.Log(withoutDockerFileYaml)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't unmarshal script: %s\n\n", err.Error())
|
||||
}
|
||||
|
||||
if !strings.Contains(response, "docker -H server:1000 build -t user/image:$(git rev-parse --short HEAD) .") {
|
||||
t.Fatalf("Response: " + response + " doesn't contain build command\n\n")
|
||||
}
|
||||
}
|
|
@ -9,10 +9,11 @@ import (
|
|||
// for publishing build artifacts when
|
||||
// a Build has succeeded
|
||||
type Publish struct {
|
||||
S3 *S3 `yaml:"s3,omitempty"`
|
||||
Swift *Swift `yaml:"swift,omitempty"`
|
||||
PyPI *PyPI `yaml:"pypi,omitempty"`
|
||||
NPM *NPM `yaml:"npm,omitempty"`
|
||||
S3 *S3 `yaml:"s3,omitempty"`
|
||||
Swift *Swift `yaml:"swift,omitempty"`
|
||||
PyPI *PyPI `yaml:"pypi,omitempty"`
|
||||
NPM *NPM `yaml:"npm,omitempty"`
|
||||
Docker *Docker `yaml:"docker,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
||||
|
@ -35,4 +36,9 @@ func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) {
|
|||
if p.NPM != nil && (len(p.NPM.Branch) == 0 || (len(p.NPM.Branch) > 0 && r.Branch == p.NPM.Branch)) {
|
||||
p.NPM.Write(f)
|
||||
}
|
||||
|
||||
// Docker
|
||||
if p.Docker != nil && (len(p.Docker.Branch) == 0 || (len(p.Docker.Branch) > 0 && r.Branch == p.Docker.Branch)) {
|
||||
p.Docker.Write(f, r)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ func (w *worker) runBuild(task *BuildTask, buildscript *script.Build, buf io.Wri
|
|||
Branch: task.Commit.Branch,
|
||||
Commit: task.Commit.Hash,
|
||||
PR: task.Commit.PullRequest,
|
||||
Dir: filepath.Join("/var/cache/drone/src", task.Repo.Slug),
|
||||
Dir: filepath.Join("/var/cache/drone/src", git.GitPath(buildscript.Git, task.Repo.Slug)),
|
||||
Depth: git.GitDepth(buildscript.Git),
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="alert">GitHub OAuth Consumer Key and Secret</div>
|
||||
<label>GitHub Client ID and Secret:</label>
|
||||
<label>GitHub Client ID and Secret: (<a href="https://github.com/settings/applications/new"> Register a new OAuth application</a>)</label>
|
||||
<div>
|
||||
<input class="form-control form-control-large" type="text" name="GitHubKey" value="{{.Settings.GitHubKey}}" />
|
||||
<input class="form-control form-control-large" type="password" name="GitHubSecret" value="{{.Settings.GitHubSecret}}" />
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
<div class="col-xs-9" role="main">
|
||||
<div class="alert">Manage your repository settings.</div>
|
||||
<form method="POST" action="/{{.Repo.Slug}}" role="form">
|
||||
<div class="form-group">
|
||||
<label>Repository URL</label>
|
||||
<input class="form-control form-control-xlarge" type="text" name="URL" value="{{.Repo.URL}}" />
|
||||
</div>
|
||||
<div class="checkbox form-group">
|
||||
<label>
|
||||
<input class="" type="checkbox" name="Disabled" {{ if not .Repo.Disabled }}checked="True" {{ end }}/>
|
||||
|
|
1
vendor/src/github.com/GeertJohan/rsrc
vendored
Submodule
1
vendor/src/github.com/GeertJohan/rsrc
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 370d163b6fcb4dc5016a8dc40cbd9861828b2e9e
|
Loading…
Reference in a new issue