experimental branch. playing around with boltdb

This commit is contained in:
Brad Rydzewski 2015-04-07 01:20:55 -07:00
parent 1f8d65bb80
commit d9fd23a6df
284 changed files with 1076 additions and 22907 deletions

View file

@ -5,41 +5,11 @@ env:
- GOROOT=/usr/local/go
- PATH=$PATH:$GOROOT/bin:$GOPATH/bin
script:
- sudo add-apt-repository ppa:git-core/ppa 1> /dev/null 2> /dev/null
- sudo apt-get update 1> /dev/null 2> /dev/null
- sudo apt-get update 1> /dev/null 2> /dev/null
- sudo apt-get -y install git zip libsqlite3-dev sqlite3 rpm 1> /dev/null 2> /dev/null
- gem install fpm
- rbenv rehash
- make docker
- make deps
- make
- make test
- make test_postgres
- make test_mysql
- make packages
services:
- postgres
- mysql
notify:
email:
recipients:
- brad@drone.io
webhook:
urls:
- https://webhooks.gitter.im/e/$$GITTER_KEY
on_started: false
on_success: true
on_failure: true
publish:
s3:
acl: public-read
region: us-east-1
bucket: downloads.drone.io
access_key: $$AWS_KEY
secret_key: $$AWS_SECRET
source: packaging/output/
target: $DRONE_BRANCH/
recursive: true
when:
owner: drone

9
.gitignore vendored
View file

@ -1,4 +1,3 @@
NOTES.txt
drone.sublime-project
drone.sublime-workspace
.vagrant
@ -11,8 +10,8 @@ drone.sublime-workspace
*.rpm
*.out
*.rice-box.go
*.db
*.txt
*.toml
cli/cli
client/client
server/server
packaging/root/usr/local
drone

View file

@ -1,17 +0,0 @@
# This is a Docker image for the Drone CI system.
# Use the following command to start the container:
# docker run -p 127.0.0.1:80:80 -t drone/drone
FROM google/golang
ENV DRONE_SERVER_PORT :80
ADD . /gopath/src/github.com/drone/drone/
WORKDIR /gopath/src/github.com/drone/drone
RUN apt-get update
RUN apt-get -y install zip libsqlite3-dev sqlite3 1> /dev/null 2> /dev/null
RUN make docker deps build embed install
EXPOSE 80
VOLUME ["/var/lib/drone"]
ENTRYPOINT ["/usr/local/bin/droned"]

View file

@ -1,101 +1,18 @@
SHA := $(shell git rev-parse --short HEAD)
VERSION := $(shell cat VERSION)
ITTERATION := $(shell date +%s)
VERSION := 0.4.0-alpha
all: build
deps:
go get github.com/GeertJohan/go.rice/rice
go get -t -v ./...
docker:
mkdir -p $$GOPATH/src/github.com/docker/docker
git clone --depth=1 --branch=v1.5.0 git://github.com/docker/docker.git $$GOPATH/src/github.com/docker/docker
test:
@test -z "$(shell find . -name '*.go' | xargs gofmt -l)" || (echo "Need to run 'go fmt ./...'"; exit 1)
go vet ./...
go test -cover -short ./...
test_mysql:
mysql -P 3306 --protocol=tcp -u root -e 'create database if not exists test;'
TEST_DRIVER="mysql" TEST_DATASOURCE="root@tcp(127.0.0.1:3306)/test" go test -short github.com/drone/drone/server/datastore/database
mysql -P 3306 --protocol=tcp -u root -e 'drop database test;'
test_postgres:
TEST_DRIVER="postgres" TEST_DATASOURCE="host=127.0.0.1 user=postgres dbname=postgres sslmode=disable" go test -short github.com/drone/drone/server/datastore/database
build:
mkdir -p packaging/output
mkdir -p packaging/root/usr/local/bin
go build -o packaging/root/usr/local/bin/drone -ldflags "-X main.revision $(SHA) -X main.version $(VERSION)" github.com/drone/drone/cli
go build -o packaging/root/usr/local/bin/droned -ldflags "-X main.revision $(SHA) -X main.version $(VERSION)" github.com/drone/drone/server
install:
install -t /usr/local/bin packaging/root/usr/local/bin/drone
install -t /usr/local/bin packaging/root/usr/local/bin/droned
run:
@go run server/main.go --config=$$HOME/.drone/config.toml
go build -ldflags "-X main.revision $(SHA) -X main.version $(VERSION).$(SHA)"
clean:
find . -name "*.out" -delete
rm -rf packaging/output
rm -f packaging/root/usr/local/bin/drone
rm -f packaging/root/usr/local/bin/droned
lessc:
lessc --clean-css server/app/styles/drone.less | autoprefixer > server/app/styles/drone.css
packages: clean build embed deb rpm
# embeds content in go source code so that it is compiled
# and packaged inside the go binary file.
embed:
rice --import-path="github.com/drone/drone/server" append --exec="packaging/root/usr/local/bin/droned"
# creates a debian package for drone to install
# `sudo dpkg -i drone.deb`
deb:
fpm -s dir -t deb -n drone -v $(VERSION) -p packaging/output/drone.deb \
--deb-priority optional --category admin \
--force \
--iteration $(ITTERATION) \
--deb-compression bzip2 \
--after-install packaging/scripts/postinst.deb \
--before-remove packaging/scripts/prerm.deb \
--after-remove packaging/scripts/postrm.deb \
--url https://github.com/drone/drone \
--description "Drone continuous integration server" \
-m "Brad Rydzewski <brad@drone.io>" \
--license "Apache License 2.0" \
--vendor "drone.io" -a amd64 \
--config-files /etc/drone/drone.toml \
packaging/root/=/
cp packaging/output/drone.deb packaging/output/drone.deb.$(SHA)
rpm:
fpm -s dir -t rpm -n drone -v $(VERSION) -p packaging/output/drone.rpm \
--rpm-compression bzip2 --rpm-os linux \
--force \
--iteration $(ITTERATION) \
--after-install packaging/scripts/postinst.rpm \
--before-remove packaging/scripts/prerm.rpm \
--after-remove packaging/scripts/postrm.rpm \
--url https://github.com/drone/drone \
--description "Drone continuous integration server" \
-m "Brad Rydzewski <brad@drone.io>" \
--license "Apache License 2.0" \
--vendor "drone.io" -a amd64 \
--config-files /etc/drone/drone.toml \
packaging/root/=/
# deploys drone to a staging server. this requires the following
# environment variables are set:
#
# DRONE_STAGING_HOST -- the hostname or ip
# DRONE_STAGING_USER -- the username used to login
# DRONE_STAGING_KEY -- the identity file path (~/.ssh/id_rsa)
deploy:
scp -i $$DRONE_STAGING_KEY packaging/output/drone.deb $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST:/tmp
ssh -i $$DRONE_STAGING_KEY $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST -- dpkg -i /tmp/drone.deb
rm -f drone

View file

@ -1 +0,0 @@
0.3.0-alpha

View file

@ -1,226 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/drone/drone/shared/build"
"github.com/drone/drone/shared/build/docker"
"github.com/drone/drone/shared/build/log"
"github.com/drone/drone/shared/build/repo"
"github.com/drone/drone/shared/build/script"
"github.com/codegangsta/cli"
)
const EXIT_STATUS = 1
// NewBuildCommand returns the CLI command for "build".
func NewBuildCommand() cli.Command {
return cli.Command{
Name: "build",
Usage: "run a local build",
Flags: []cli.Flag{
cli.StringFlag{
Name: "i",
Value: "",
Usage: "identify file injected in the container",
},
cli.BoolFlag{
Name: "p",
Usage: "runs drone build in a privileged container",
},
cli.BoolFlag{
Name: "deploy",
Usage: "runs drone build with deployments enabled",
},
cli.BoolFlag{
Name: "publish",
Usage: "runs drone build with publishing enabled",
},
cli.StringFlag{
Name: "docker-host",
Value: getHost(),
Usage: "docker daemon address",
},
cli.StringFlag{
Name: "docker-cert",
Value: getCert(),
Usage: "docker daemon tls certificate",
},
cli.StringFlag{
Name: "docker-key",
Value: getKey(),
Usage: "docker daemon tls key",
},
},
Action: func(c *cli.Context) {
buildCommandFunc(c)
},
}
}
// buildCommandFunc executes the "build" command.
func buildCommandFunc(c *cli.Context) {
var privileged = c.Bool("p")
var identity = c.String("i")
var deploy = c.Bool("deploy")
var publish = c.Bool("publish")
var path string
var dockerhost = c.String("docker-host")
var dockercert = c.String("docker-cert")
var dockerkey = c.String("docker-key")
// the path is provided as an optional argument that
// will otherwise default to $PWD/.drone.yml
if len(c.Args()) > 0 {
path = c.Args()[0]
}
switch len(path) {
case 0:
path, _ = os.Getwd()
path = filepath.Join(path, ".drone.yml")
default:
path = filepath.Clean(path)
path, _ = filepath.Abs(path)
path = filepath.Join(path, ".drone.yml")
}
// this configures the default Docker logging levels,
// and suffix and prefix values.
log.SetPrefix("\033[2m[DRONE] ")
log.SetSuffix("\033[0m\n")
log.SetOutput(os.Stdout)
log.SetPriority(log.LOG_DEBUG) //LOG_NOTICE
docker.Logging = false
var exit, _ = run(path, identity, dockerhost, dockercert, dockerkey, publish, deploy, privileged)
os.Exit(exit)
}
// TODO this has gotten a bit out of hand. refactor input params
func run(path, identity, dockerhost, dockercert, dockerkey string, publish, deploy, privileged bool) (int, error) {
dockerClient, err := docker.NewHostCertFile(dockerhost, dockercert, dockerkey)
if err != nil {
log.Err(err.Error())
return EXIT_STATUS, err
}
// parse the private environment variables
envs := getParamMap("DRONE_ENV_")
// parse the Drone yml file
s, err := script.ParseBuildFile(path, envs)
if err != nil {
log.Err(err.Error())
return EXIT_STATUS, err
}
// inject private environment variables into build script
for key, val := range envs {
s.Env = append(s.Env, key+"="+val)
}
if deploy == false {
s.Deploy = nil
}
if publish == false {
s.Publish = nil
}
// get the repository root directory
dir := filepath.Dir(path)
code := repo.Repo{
Name: filepath.Base(dir),
Branch: "HEAD", // should we do this?
Path: dir,
}
// does the local repository match the
// $GOPATH/src/{package} pattern? This is
// important so we know the target location
// where the code should be copied inside
// the container.
if gopath, ok := getRepoPath(dir); ok {
code.Dir = gopath
} else if gopath, ok := getGoPath(dir); ok {
// in this case we found a GOPATH and
// reverse engineered the package path
code.Dir = gopath
} else {
// otherwise just use directory name
code.Dir = filepath.Base(dir)
}
// this is where the code gets uploaded to the container
// TODO move this code to the build package
code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir))
// ssh key to import into container
var key []byte
if len(identity) != 0 {
key, err = ioutil.ReadFile(identity)
if err != nil {
fmt.Printf("[Error] Could not find or read identity file %s\n", identity)
return EXIT_STATUS, err
}
}
// loop through and create builders
builder := build.New(dockerClient)
builder.Build = s
builder.Repo = &code
builder.Key = key
builder.Stdout = os.Stdout
builder.Timeout = 300 * time.Minute
builder.Privileged = privileged
// execute the build
if err := builder.Run(); err != nil {
log.Errf("Error executing build: %s", err.Error())
return EXIT_STATUS, err
}
fmt.Printf("\nDrone Build Results \033[90m(%s)\033[0m\n", dir)
// loop through and print results
build := builder.Build
res := builder.BuildState
duration := time.Duration(res.Finished - res.Started)
switch {
case builder.BuildState.ExitCode == 0:
fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second))
case builder.BuildState.ExitCode != 0:
fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second))
}
return builder.BuildState.ExitCode, nil
}
func getHost() string {
return os.Getenv("DOCKER_HOST")
}
func getCert() string {
if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" {
return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "cert.pem")
} else {
return ""
}
}
func getKey() string {
if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" {
return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "key.pem")
} else {
return ""
}
}

View file

@ -1,30 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewDeleteCommand returns the CLI command for "delete".
func NewDeleteCommand() cli.Command {
return cli.Command{
Name: "delete",
Usage: "delete a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, deleteCommandFunc)
},
}
}
// deleteCommandFunc executes the "delete" command.
func deleteCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Delete(host, owner, name)
}

View file

@ -1,30 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewDisableCommand returns the CLI command for "disable".
func NewDisableCommand() cli.Command {
return cli.Command{
Name: "disable",
Usage: "disable a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, disableCommandFunc)
},
}
}
// disableCommandFunc executes the "disable" command.
func disableCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Disable(host, owner, name)
}

View file

@ -1,30 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewEnableCommand returns the CLI command for "enable".
func NewEnableCommand() cli.Command {
return cli.Command{
Name: "enable",
Usage: "enable a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, enableCommandFunc)
},
}
}
// enableCommandFunc executes the "enable" command.
func enableCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Enable(host, owner, name)
}

View file

@ -1,32 +0,0 @@
package main
import (
"os"
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
type handlerFunc func(*cli.Context, *client.Client) error
// handle wraps the command function handlers and
// sets up the environment.
func handle(c *cli.Context, fn handlerFunc) {
var token = c.GlobalString("token")
var server = c.GlobalString("server")
// if no server url is provided we can default
// to the hosted Drone service.
if len(server) == 0 {
server = "http://test.drone.io"
}
// create the drone client
client := client.New(token, server)
// handle the function
if err := fn(c, client); err != nil {
println(err.Error())
os.Exit(1)
}
}

View file

@ -1,48 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewSetKeyCommand returns the CLI command for "set-key".
func NewSetKeyCommand() cli.Command {
return cli.Command{
Name: "set-key",
Usage: "sets the SSH private key used to clone",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, setKeyCommandFunc)
},
}
}
// setKeyCommandFunc executes the "set-key" command.
func setKeyCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, name, path string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
if len(args) == 2 {
path = args[1]
}
pub, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("Could not find private RSA key %s. %s", path, err)
}
path_pub := path + ".pub"
priv, err := ioutil.ReadFile(path_pub)
if err != nil {
return fmt.Errorf("Could not find public RSA key %s. %s", path_pub, err)
}
return client.Repos.SetKey(host, owner, name, string(pub), string(priv))
}

View file

@ -1,48 +0,0 @@
package main
import (
"os"
"github.com/codegangsta/cli"
)
var (
// commit sha for the current build.
version string
revision string
)
func main() {
app := cli.NewApp()
app.Name = "drone"
app.Version = version
app.Usage = "command line utility"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "t, token",
Value: "",
Usage: "server auth token",
EnvVar: "DRONE_TOKEN",
},
cli.StringFlag{
Name: "s, server",
Value: "",
Usage: "server location",
EnvVar: "DRONE_SERVER",
},
}
app.Commands = []cli.Command{
NewBuildCommand(),
NewReposCommand(),
NewStatusCommand(),
NewEnableCommand(),
NewDisableCommand(),
NewRestartCommand(),
NewWhoamiCommand(),
NewSetKeyCommand(),
NewDeleteCommand(),
}
app.Run(os.Args)
}

View file

@ -1,43 +0,0 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewReposCommand returns the CLI command for "repos".
func NewReposCommand() cli.Command {
return cli.Command{
Name: "repos",
Usage: "lists active remote repositories",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "a, all",
Usage: "list all repositories",
},
},
Action: func(c *cli.Context) {
handle(c, reposCommandFunc)
},
}
}
// reposCommandFunc executes the "repos" command.
func reposCommandFunc(c *cli.Context, client *client.Client) error {
repos, err := client.Repos.List()
if err != nil {
return err
}
var all = c.Bool("a")
for _, repo := range repos {
if !all && !repo.Active {
continue
}
fmt.Printf("%s/%s/%s\n", repo.Host, repo.Owner, repo.Name)
}
return nil
}

View file

@ -1,39 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewRestartCommand returns the CLI command for "restart".
func NewRestartCommand() cli.Command {
return cli.Command{
Name: "restart",
Usage: "restarts a build on the server",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, restartCommandFunc)
},
}
}
// restartCommandFunc executes the "restart" command.
func restartCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, repo, branch, sha string
var args = c.Args()
if len(args) != 0 {
host, owner, repo = parseRepo(args[0])
}
switch len(args) {
case 2:
branch = "master"
sha = args[1]
case 3, 4, 5:
branch = args[1]
sha = args[2]
}
return client.Commits.Rebuild(host, owner, repo, branch, sha)
}

View file

@ -1,52 +0,0 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewStatusCommand returns the CLI command for "status".
func NewStatusCommand() cli.Command {
return cli.Command{
Name: "status",
Usage: "display a repository build status",
Flags: []cli.Flag{
cli.StringFlag{
Name: "b, branch",
Usage: "branch to display",
},
},
Action: func(c *cli.Context) {
handle(c, statusCommandFunc)
},
}
}
// statusCommandFunc executes the "status" command.
func statusCommandFunc(c *cli.Context, client *client.Client) error {
var host, owner, repo, branch string
var args = c.Args()
if len(args) != 0 {
host, owner, repo = parseRepo(args[0])
}
if c.IsSet("branch") {
branch = c.String("branch")
} else {
branch = "master"
}
builds, err := client.Commits.ListBranch(host, owner, repo, branch)
if err != nil {
return err
} else if len(builds) == 0 {
return nil
}
var build = builds[len(builds)-1]
fmt.Printf("%s\t%s\t%s\t%s\t%v", build.Status, build.ShaShort(), build.Timestamp, build.Author, build.Message)
return nil
}

View file

@ -1,112 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
func parseRepo(str string) (host, owner, repo string) {
var parts = strings.Split(str, "/")
if len(parts) != 3 {
return
}
host = parts[0]
owner = parts[1]
repo = parts[2]
return
}
// getGoPath checks the source codes absolute path
// in reference to the host operating system's GOPATH
// to correctly determine the code's package path. This
// is Go-specific, since Go code must exist in
// $GOPATH/src/github.com/{owner}/{name}
func getGoPath(dir string) (string, bool) {
path := os.Getenv("GOPATH")
if len(path) == 0 {
return "", false
}
// append src to the GOPATH, since
// the code will be stored in the src dir
path = filepath.Join(path, "src")
if !filepath.HasPrefix(dir, path) {
return "", false
}
// remove the prefix from the directory
// this should leave us with the go package name
return dir[len(path):], true
}
var gopathExp = regexp.MustCompile("./src/(github.com/[^/]+/[^/]+|bitbucket.org/[^/]+/[^/]+|code.google.com/[^/]+/[^/]+)")
// getRepoPath checks the source codes absolute path
// on the host operating system in an attempt
// to correctly determine the code's package path. This
// is Go-specific, since Go code must exist in
// $GOPATH/src/github.com/{owner}/{name}
func getRepoPath(dir string) (path string, ok bool) {
// let's get the package directory based
// on the path in the host OS
indexes := gopathExp.FindStringIndex(dir)
if len(indexes) == 0 {
return
}
index := indexes[len(indexes)-1]
// if the dir is /home/ubuntu/go/src/github.com/foo/bar
// the index will start at /src/github.com/foo/bar.
// We'll need to strip "/src/" which is where the
// magic number 5 comes from.
index = strings.LastIndex(dir, "/src/")
return dir[index+5:], true
}
// GetRepoMap returns a map of enivronment variables that
// should be injected into the .drone.yml
func getParamMap(prefix string) map[string]string {
envs := map[string]string{}
for _, item := range os.Environ() {
env := strings.SplitN(item, "=", 2)
if len(env) != 2 {
continue
}
key := env[0]
val := env[1]
if strings.HasPrefix(key, prefix) {
envs[strings.TrimPrefix(key, prefix)] = val
}
}
return envs
}
// prints the time as a human readable string
func humanizeDuration(d time.Duration) string {
if seconds := int(d.Seconds()); seconds < 1 {
return "Less than a second"
} else if seconds < 60 {
return fmt.Sprintf("%d seconds", seconds)
} else if minutes := int(d.Minutes()); minutes == 1 {
return "About a minute"
} else if minutes < 60 {
return fmt.Sprintf("%d minutes", minutes)
} else if hours := int(d.Hours()); hours == 1 {
return "About an hour"
} else if hours < 48 {
return fmt.Sprintf("%d hours", hours)
} else if hours < 24*7*2 {
return fmt.Sprintf("%d days", hours/24)
} else if hours < 24*30*3 {
return fmt.Sprintf("%d weeks", hours/24/7)
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
return fmt.Sprintf("%f years", d.Hours()/24/365)
}

View file

@ -1,32 +0,0 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone/client"
)
// NewWhoamiCommand returns the CLI command for "whoami".
func NewWhoamiCommand() cli.Command {
return cli.Command{
Name: "whoami",
Usage: "outputs the current user",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, whoamiCommandFunc)
},
}
}
// whoamiCommandFunc communicates with the server and echoes
// the currently authenticated user.
func whoamiCommandFunc(c *cli.Context, client *client.Client) error {
user, err := client.Users.GetCurrent()
if err != nil {
return err
}
fmt.Println(user.Login)
return nil
}

View file

@ -1,149 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strconv"
)
type Client struct {
token string
url string
Commits *CommitService
Repos *RepoService
Users *UserService
}
func New(token, url string) *Client {
c := Client{
token: token,
url: url,
}
c.Commits = &CommitService{&c}
c.Repos = &RepoService{&c}
c.Users = &UserService{&c}
return &c
}
var (
ErrNotFound = errors.New("Not Found")
ErrForbidden = errors.New("Forbidden")
ErrBadRequest = errors.New("Bad Request")
ErrNotAuthorized = errors.New("Unauthorized")
ErrInternalServer = errors.New("Internal Server Error")
)
// runs an http.Request and parses the JSON-encoded http.Response,
// storing the result in the value pointed to by v.
func (c *Client) run(method, path string, in, out interface{}) error {
// create the URI
uri, err := url.Parse(c.url + path)
if err != nil {
return err
}
if len(uri.Scheme) == 0 {
uri.Scheme = "http"
}
if len(c.token) > 0 {
params := uri.Query()
params.Add("access_token", c.token)
uri.RawQuery = params.Encode()
}
// create the request
req := &http.Request{
URL: uri,
Method: method,
ProtoMajor: 1,
ProtoMinor: 1,
Close: true,
ContentLength: 0,
}
// if data input is provided, serialize to JSON
if in != nil {
inJson, err := json.Marshal(in)
if err != nil {
return err
}
buf := bytes.NewBuffer(inJson)
req.Body = ioutil.NopCloser(buf)
req.ContentLength = int64(len(inJson))
req.Header.Set("Content-Length", strconv.Itoa(len(inJson)))
req.Header.Set("Content-Type", "application/json")
}
// make the request using the default http client
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
// make sure we defer close the body
defer resp.Body.Close()
// Check for an http error status (ie not 200 StatusOK)
switch resp.StatusCode {
case 404:
return ErrNotFound
case 403:
return ErrForbidden
case 401:
return ErrNotAuthorized
case 400:
return ErrBadRequest
case 500:
return ErrInternalServer
}
// Decode the JSON response
if out != nil {
return json.NewDecoder(resp.Body).Decode(out)
}
return nil
}
// do makes an http.Request and returns the response
func (c *Client) do(method, path string) (*http.Response, error) {
// create the URI
uri, err := url.Parse(c.url + path)
if err != nil {
return nil, err
}
if len(uri.Scheme) == 0 {
uri.Scheme = "http"
}
if len(c.token) > 0 {
params := uri.Query()
params.Add("access_token", c.token)
uri.RawQuery = params.Encode()
}
// create the request
req := &http.Request{
URL: uri,
Method: method,
ProtoMajor: 1,
ProtoMinor: 1,
Close: true,
ContentLength: 0,
}
// make the request using the default http client
return http.DefaultClient.Do(req)
}

View file

@ -1,52 +0,0 @@
package client
import (
"fmt"
"io"
"github.com/drone/drone/shared/model"
)
type CommitService struct {
*Client
}
// GET /api/repos/{host}/{owner}/{name}/branch/{branch}/commit/{commit}
func (s *CommitService) Get(host, owner, name, branch, sha string) (*model.Commit, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s", host, owner, name, branch, sha)
var commit = model.Commit{}
var err = s.run("GET", path, nil, &commit)
return &commit, err
}
// GET /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console
func (s *CommitService) GetOutput(host, owner, name, branch, sha string) (io.ReadCloser, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s/console", host, owner, name, branch, sha)
resp, err := s.do("GET", path)
if err != nil {
return nil, nil
}
return resp.Body, nil
}
// POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}?action=rebuild
func (s *CommitService) Rebuild(host, owner, name, branch, sha string) error {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s?action=rebuild", host, owner, name, branch, sha)
return s.run("POST", path, nil, nil)
}
// GET /api/repos/{host}/{owner}/{name}/feed
func (s *CommitService) List(host, owner, name string) ([]*model.Commit, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/feed", host, owner, name)
var list []*model.Commit
var err = s.run("GET", path, nil, &list)
return list, err
}
// GET /api/repos/{host}/{owner}/{name}/branch/{branch}
func (s *CommitService) ListBranch(host, owner, name, branch string) ([]*model.Commit, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name)
var list []*model.Commit
var err = s.run("GET", path, nil, &list)
return list, err
}

View file

@ -1,62 +0,0 @@
package client
import (
"fmt"
"github.com/drone/drone/shared/model"
)
type RepoService struct {
*Client
}
// GET /api/repos/{host}/{owner}/{name}
func (s *RepoService) Get(host, owner, name string) (*model.Repo, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name)
var repo = model.Repo{}
var err = s.run("GET", path, nil, &repo)
return &repo, err
}
// PUT /api/repos/{host}/{owner}/{name}
func (s *RepoService) Update(repo *model.Repo) (*model.Repo, error) {
var path = fmt.Sprintf("/api/repos/%s/%s/%s", repo.Host, repo.Owner, repo.Name)
var result = model.Repo{}
var err = s.run("PUT", path, &repo, &result)
return &result, err
}
// POST /api/repos/{host}/{owner}/{name}
func (s *RepoService) Enable(host, owner, name string) error {
var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name)
return s.run("POST", path, nil, nil)
}
// POST /api/repos/{host}/{owner}/{name}/deactivate
func (s *RepoService) Disable(host, owner, name string) error {
var path = fmt.Sprintf("/api/repos/%s/%s/%s/deactivate", host, owner, name)
return s.run("POST", path, nil, nil)
}
// DELETE /api/repos/{host}/{owner}/{name}?remove=true
func (s *RepoService) Delete(host, owner, name string) error {
var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name)
return s.run("DELETE", path, nil, nil)
}
// PUT /api/repos/{host}/{owner}/{name}
func (s *RepoService) SetKey(host, owner, name, pub, priv string) error {
var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name)
var in = struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
}{pub, priv}
return s.run("PUT", path, &in, nil)
}
// GET /api/user/repos
func (s *RepoService) List() ([]*model.Repo, error) {
var repos []*model.Repo
var err = s.run("GET", "/api/user/repos", nil, &repos)
return repos, err
}

View file

@ -1,47 +0,0 @@
package client
import (
"fmt"
"github.com/drone/drone/shared/model"
)
type UserService struct {
*Client
}
// GET /api/users/{host}/{login}
func (s *UserService) Get(remote, login string) (*model.User, error) {
var path = fmt.Sprintf("/api/users/%s/%s", remote, login)
var user = model.User{}
var err = s.run("GET", path, nil, &user)
return &user, err
}
// GET /api/user
func (s *UserService) GetCurrent() (*model.User, error) {
var user = model.User{}
var err = s.run("GET", "/api/user", nil, &user)
return &user, err
}
// POST /api/users/{host}/{login}
func (s *UserService) Create(remote, login string) (*model.User, error) {
var path = fmt.Sprintf("/api/users/%s/%s", remote, login)
var user = model.User{}
var err = s.run("POST", path, nil, &user)
return &user, err
}
// DELETE /api/users/{host}/{login}
func (s *UserService) Delete(remote, login string) error {
var path = fmt.Sprintf("/api/users/%s/%s", remote, login)
return s.run("DELETE", path, nil, nil)
}
// GET /api/users
func (s *UserService) List() ([]*model.User, error) {
var users []*model.User
var err = s.run("GET", "/api/users", nil, &users)
return users, err
}

67
common/build.go Normal file
View file

@ -0,0 +1,67 @@
package common
const (
StatePending = "pending"
StateRunning = "running"
StateSuccess = "success"
StateFailure = "failure"
StateKilled = "killed"
StateError = "error"
)
type Build struct {
Number int `json:"number"`
State string `json:"state"`
Tasks int `json:"task_count"`
Duration int64 `json:"duration"`
Started int64 `json:"started_at"`
Finished int64 `json:"finished_at"`
Created int64 `json:"created_at"`
Updated int64 `json:"updated_at"`
// Commit represents the commit data send in the
// post-commit hook. This will not be populated when
// a pull requests.
Commit *Commit `json:"head_commit,omitempty"`
// PullRequest represents the pull request data sent
// in the post-commit hook. This will only be populated
// when a pull request.
PullRequest *PullRequest `json:"pull_request,omitempty"`
}
type Status struct {
State string `json:"state"`
Link string `json:"target_url"`
Desc string `json:"description"`
Context string `json:"context"`
}
type Commit struct {
Sha string `json:"sha,omitempty"`
Ref string `json:"ref,omitempty"`
Message string `json:"message,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
Author *Author `json:"author,omitempty"`
Remote *Remote `json:"repo,omitempty"`
}
type PullRequest struct {
Number int `json:"number,omitempty"`
Title string `json:"title,omitempty"`
Source *Commit `json:"source,omitempty"`
Target *Commit `json:"target,omitempty"`
}
type Author struct {
Name string `json:"name,omitempty"`
Login string `json:"login,omitempty"`
Email string `json:"email,omitempty"`
Gravatar string `json:"gravatar_id,omitempty"`
}
type Remote struct {
Name string `json:"name,omitempty"`
FullName string `json:"full_name,omitempty"`
Clone string `json:"clone_url,omitempty"`
}

View file

@ -1,8 +1,11 @@
package model
package ccmenu
import (
"encoding/xml"
"strconv"
"time"
"github.com/drone/drone/common"
)
type CCProjects struct {
@ -20,7 +23,7 @@ type CCProject struct {
WebURL string `xml:"webUrl,attr"`
}
func NewCC(r *Repo, c *Commit, url string) *CCProjects {
func NewCC(r *common.Repo, b *common.Build, url string) *CCProjects {
proj := &CCProject{
Name: r.Owner + "/" + r.Name,
WebURL: url,
@ -31,21 +34,24 @@ func NewCC(r *Repo, c *Commit, url string) *CCProjects {
// if the build is not currently running then
// we can return the latest build status.
if c.Status != StatusStarted &&
c.Status != StatusEnqueue {
if b.State != common.StatePending &&
b.State != common.StateRunning {
proj.Activity = "Sleeping"
proj.LastBuildStatus = c.Status
proj.LastBuildTime = time.Unix(c.Started, 0).Format(time.RFC3339)
proj.LastBuildLabel = c.ShaShort()
proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339)
proj.LastBuildLabel = strconv.Itoa(b.Number)
}
// If the build is not running, and not successful,
// then set to Failure. Not sure CCTray will support
// our custom failure types (ie Killed)
if c.Status != StatusStarted &&
c.Status != StatusEnqueue &&
c.Status != StatusSuccess {
proj.LastBuildStatus = StatusFailure
// ensure the last build state accepts a valid
// ccmenu enumeration
switch b.State {
case common.StateError, common.StateKilled:
proj.LastBuildStatus = "Exception"
case common.StateSuccess:
proj.LastBuildStatus = "Success"
case common.StateFailure:
proj.LastBuildStatus = "Failure"
default:
proj.LastBuildStatus = "Unknown"
}
return &CCProjects{Project: proj}

View file

@ -0,0 +1,16 @@
package gravatar
import (
"crypto/md5"
"fmt"
"strings"
)
// helper function to create a Gravatar Hash
// for the given Email address.
func Generate(email string) string {
email = strings.ToLower(strings.TrimSpace(email))
hash := md5.New()
hash.Write([]byte(email))
return fmt.Sprintf("%x", hash.Sum(nil))
}

7
common/hook.go Normal file
View file

@ -0,0 +1,7 @@
package common
type Hook struct {
Repo *Repo
Commit *Commit
PullRequest *PullRequest
}

View file

@ -34,7 +34,7 @@
// // btw, r.FormValue("state") == "foo"
// }
//
package oauth
package oauth2
import (
"encoding/json"

59
common/repo.go Normal file
View file

@ -0,0 +1,59 @@
package common
type Repo struct {
ID int64 `json:"id"`
Owner string `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Language string `json:"language"`
Private bool `json:"private"`
Link string `json:"link_url"`
Clone string `json:"clone_url"`
Branch string `json:"default_branch"`
Timeout int64 `json:"timeout"`
Trusted bool `json:"trusted"`
Disabled bool `json:"disabled"`
DisablePR bool `json:"disable_prs"`
DisableTag bool `json:"disable_tags"`
Created int64 `json:"created_at"`
Updated int64 `json:"updated_at"`
User *User `json:"user,omitempty"`
Last *Build `json:"last_build,omitempty"`
}
// Keypair represents an RSA public and private key
// assigned to a repository. It may be used to clone
// private repositories, or as a deployment key.
type Keypair struct {
Public string `json:"public"`
Private string `json:"-"`
}
// Subscriber represents a user's subscription
// to a repository. This determines if the repository
// is displayed on the user dashboard and in the user
// event feed.
type Subscriber struct {
Login string `json:"login,omitempty"`
// Determines if notifications should be
// received from this repository.
Subscribed bool `json:"subscribed"`
// Determines if all notifications should be
// blocked from this repository.
Ignored bool `json:"ignored"`
}
// Perm represents a user's permissiont to access
// a repository. Pull indicates read-only access. Push
// indiates write access. Admin indicates god access.
type Perm struct {
Login string `json:"login,omitempty"`
Pull bool `json:"pull"`
Push bool `json:"push"`
Admin bool `json:"admin"`
}

14
common/task.go Normal file
View file

@ -0,0 +1,14 @@
package common
type Task struct {
Number int `json:"number"`
State string `json:"state"`
ExitCode int `json:"exit_code"`
Duration int64 `json:"duration"`
Started int64 `json:"started_at"`
Finished int64 `json:"finished_at"`
// Environment represents the build environment
// combination from the matrix.
Environment map[string]string `json:"environment,omitempty"`
}

9
common/token.go Normal file
View file

@ -0,0 +1,9 @@
package common
type Token struct {
Sha string `json:"-"`
Login string `json:"-"`
Repos []string `json:"repos,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Expiry int64 `json:"expiry,omitempty"`
}

13
common/user.go Normal file
View file

@ -0,0 +1,13 @@
package common
type User struct {
Login string `json:"login,omitempty"`
Token string `json:"-"`
Secret string `json:"-"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Gravatar string `json:"gravatar_id,omitempty"`
Admin bool `json:"admin,omitempty"`
Created int64 `json:"created_at,omitempty"`
Updated int64 `json:"updated_at,omitempty"`
}

66
datastore/bolt/bolt.go Normal file
View file

@ -0,0 +1,66 @@
package bolt
import (
"errors"
"github.com/boltdb/bolt"
)
var (
ErrKeyNotFound = errors.New("Key not found")
ErrKeyExists = errors.New("Key exists")
)
var (
bucketUser = []byte("user")
bucketUserRepos = []byte("user_repos")
bucketUserTokens = []byte("user_tokens")
bucketTokens = []byte("token")
bucketRepo = []byte("repo")
bucketRepoKeys = []byte("repo_keys")
bucketRepoParams = []byte("repo_params")
bucketRepoUsers = []byte("repo_users")
bucketBuild = []byte("build")
bucketBuildStatus = []byte("build_status")
bucketBuildTasks = []byte("build_tasks")
bucketBuildLogs = []byte("build_logs")
bucketBuildSeq = []byte("build_seq")
)
type DB struct {
*bolt.DB
}
func New(path string) (*DB, error) {
db, err := bolt.Open(path, 0600, nil)
if err != nil {
return nil, err
}
// Initialize all the required buckets.
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucketIfNotExists(bucketUser)
tx.CreateBucketIfNotExists(bucketUserRepos)
tx.CreateBucketIfNotExists(bucketUserTokens)
tx.CreateBucketIfNotExists(bucketTokens)
tx.CreateBucketIfNotExists(bucketRepo)
tx.CreateBucketIfNotExists(bucketRepoKeys)
tx.CreateBucketIfNotExists(bucketRepoParams)
tx.CreateBucketIfNotExists(bucketRepoUsers)
tx.CreateBucketIfNotExists(bucketBuild)
tx.CreateBucketIfNotExists(bucketBuildStatus)
tx.CreateBucketIfNotExists(bucketBuildTasks)
tx.CreateBucketIfNotExists(bucketBuildLogs)
tx.CreateBucketIfNotExists(bucketBuildSeq)
return nil
})
return &DB{db}, nil
}
func Must(path string) *DB {
db, err := New(path)
if err != nil {
panic(err)
}
return db
}

91
datastore/bolt/build.go Normal file
View file

@ -0,0 +1,91 @@
package bolt
import (
"bytes"
"strconv"
"time"
"github.com/boltdb/bolt"
"github.com/drone/drone/common"
)
// GetBuild gets the specified build number for the
// named repository and build number
func (db *DB) GetBuild(repo string, build int) (*common.Build, error) {
build_ := &common.Build{}
key := []byte(repo + "/" + strconv.Itoa(build))
err := get(db, bucketBuild, key, build_)
return build_, err
}
// GetBuildList gets a list of recent builds for the
// named repository.
func (db *DB) GetBuildList(repo string) ([]*common.Build, error) {
// get the last build sequence number (stored in key in `bucketBuildSeq`)
// get all builds where build number > sequent-20
// github.com/foo/bar/{number}
return nil, nil
}
// GetBuildLast gets the last executed build for the
// named repository.
func (db *DB) GetBuildLast(repo string) (*common.Build, error) {
// get the last build sequence number (stored in key in `bucketBuildSeq`)
// return that build
return nil, nil
}
// GetBuildStatus gets the named build status for the
// named repository and build number.
func (db *DB) GetBuildStatus(repo string, build int, status string) (*common.Status, error) {
status_ := &common.Status{}
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + status)
err := update(db, bucketBuildStatus, key, status)
return status_, err
}
// GetBuildStatusList gets a list of all build statues for
// the named repository and build number.
func (db *DB) GetBuildStatusList(repo string, build int) ([]*common.Status, error) {
// TODO (bradrydzewski) explore efficiency of cursor vs index
statuses := []*common.Status{}
err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket(bucketBuildStatus).Cursor()
prefix := []byte(repo + "/" + strconv.Itoa(build) + "/")
for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() {
status := &common.Status{}
if err := decode(v, status); err != nil {
return err
}
statuses = append(statuses, status)
}
return nil
})
return statuses, err
}
// InsertBuild inserts a new build for the named repository
func (db *DB) InsertBuild(repo string, build *common.Build) error {
// TODO(bradrydzewski) use the `bucketBuildSeq` to increment the
// sequence for the build and set the build number.
key := []byte(repo + "/" + strconv.Itoa(build.Number))
return update(db, bucketBuild, key, build)
}
// InsertBuildStatus inserts a new build status for the
// named repository and build number. If the status already
// exists an error is returned.
func (db *DB) InsertBuildStatus(repo string, build int, status *common.Status) error {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + status.Context)
return update(db, bucketBuildStatus, key, status)
}
// UpdateBuild updates an existing build for the named
// repository. If the build already exists and error is
// returned.
func (db *DB) UpdateBuild(repo string, build *common.Build) error {
key := []byte(repo + "/" + strconv.Itoa(build.Number))
build.Updated = time.Now().UTC().Unix()
return update(db, bucketBuild, key, build)
}

View file

@ -0,0 +1 @@
package bolt

85
datastore/bolt/repo.go Normal file
View file

@ -0,0 +1,85 @@
package bolt
import (
"time"
"github.com/drone/drone/common"
)
// GetRepo gets the repository by name.
func (db *DB) GetRepo(repo string) (*common.Repo, error) {
repo_ := &common.Repo{}
key := []byte(repo)
err := get(db, bucketRepo, key, repo_)
return repo_, err
}
// GetRepoParams gets the private environment parameters
// for the given repository.
func (db *DB) GetRepoParams(repo string) (map[string]string, error) {
params := map[string]string{}
key := []byte(repo)
err := get(db, bucketRepoParams, key, &params)
return params, err
}
// GetRepoParams gets the private and public rsa keys
// for the given repository.
func (db *DB) GetRepoKeys(repo string) (*common.Keypair, error) {
keypair := &common.Keypair{}
key := []byte(repo)
err := get(db, bucketRepoKeys, key, keypair)
return keypair, err
}
// UpdateRepos updates a repository. If the repository
// does not exist an error is returned.
func (db *DB) UpdateRepo(repo *common.Repo) error {
key := []byte(repo.FullName)
repo.Updated = time.Now().UTC().Unix()
return update(db, bucketRepo, key, repo)
}
// InsertRepo inserts a repository in the datastore and
// subscribes the user to that repository.
func (db *DB) InsertRepo(user *common.User, repo *common.Repo) error {
key := []byte(repo.FullName)
repo.Created = time.Now().UTC().Unix()
repo.Updated = time.Now().UTC().Unix()
// TODO(bradrydzewski) add repo to user index
// TODO(bradrydzewski) add user to repo index
return insert(db, bucketRepo, key, repo)
}
// UpsertRepoParams inserts or updates the private
// environment parameters for the named repository.
func (db *DB) UpsertRepoParams(repo string, params map[string]string) error {
key := []byte(repo)
return update(db, bucketRepoParams, key, params)
}
// UpsertRepoKeys inserts or updates the private and
// public keypair for the named repository.
func (db *DB) UpsertRepoKeys(repo string, keypair *common.Keypair) error {
key := []byte(repo)
return update(db, bucketRepoKeys, key, keypair)
}
// DeleteRepo deletes the repository.
func (db *DB) DeleteRepo(repo *common.Repo) error {
t, err := db.Begin(true)
if err != nil {
return err
}
key := []byte(repo.FullName)
err = t.Bucket(bucketRepo).Delete(key)
if err != nil {
t.Rollback()
return err
}
t.Bucket(bucketRepoKeys).Delete(key)
t.Bucket(bucketRepoParams).Delete(key)
// TODO(bradrydzewski) delete all builds
// TODO(bradrydzewski) delete all tasks
return t.Commit()
}

View file

@ -0,0 +1,24 @@
package bolt
import (
"testing"
. "github.com/franela/goblin"
)
func TestRepo(t *testing.T) {
g := Goblin(t)
g.Describe("Repos", func() {
g.It("Should find by name")
g.It("Should find params")
g.It("Should find keys")
g.It("Should delete")
g.It("Should insert")
g.It("Should not insert if exists")
g.It("Should insert params")
g.It("Should update params")
g.It("Should insert keys")
g.It("Should update keys")
})
}

82
datastore/bolt/task.go Normal file
View file

@ -0,0 +1,82 @@
package bolt
import (
"strconv"
"github.com/drone/drone/common"
)
// GetTask gets the task at index N for the named
// repository and build number.
func (db *DB) GetTask(repo string, build int, task int) (*common.Task, error) {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task))
task_ := &common.Task{}
err := get(db, bucketBuildTasks, key, task_)
return task_, err
}
// GetTaskLogs gets the task logs at index N for
// the named repository and build number.
func (db *DB) GetTaskLogs(repo string, build int, task int) ([]byte, error) {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task))
log, err := raw(db, bucketBuildLogs, key)
return log, err
}
// GetTaskList gets all tasks for the named repository
// and build number.
func (db *DB) GetTaskList(repo string, build int) ([]*common.Task, error) {
// fetch the build so that we can get the
// number of child tasks.
build_, err := db.GetBuild(repo, build)
if err != nil {
return nil, err
}
t, err := db.Begin(false)
if err != nil {
return nil, err
}
defer t.Rollback()
// based on the number of child tasks, incrment
// and loop to get each task from the bucket.
tasks := []*common.Task{}
for i := 1; i <= build_.Number; i++ {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(i))
raw := t.Bucket(bucketBuildTasks).Get(key)
if raw == nil {
return nil, ErrKeyNotFound
}
task := &common.Task{}
err := decode(raw, task)
if err != nil {
return nil, err
}
tasks = append(tasks, task)
}
return tasks, nil
}
// UpsertTask inserts or updates a task for the named
// repository and build number.
func (db *DB) UpsertTask(repo string, build int, task *common.Task) error {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task.Number))
return update(db, bucketBuildTasks, key, task)
}
// UpsertTaskLogs inserts or updates a task logs for the
// named repository and build number.
func (db *DB) UpsertTaskLogs(repo string, build int, task int, log []byte) error {
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task))
t, err := db.Begin(true)
if err != nil {
return err
}
err = t.Bucket(bucketBuildLogs).Put(key, log)
if err != nil {
t.Rollback()
return err
}
return t.Commit()
}

View file

@ -0,0 +1 @@
package bolt

27
datastore/bolt/token.go Normal file
View file

@ -0,0 +1,27 @@
package bolt
import (
"github.com/drone/drone/common"
)
// GetToken gets a token by sha value.
func (db *DB) GetToken(sha string) (*common.Token, error) {
token := &common.Token{}
key := []byte(sha)
err := get(db, bucketTokens, key, token)
return token, err
}
// InsertToken inserts a new user token in the datastore.
// If the token already exists and error is returned.
func (db *DB) InsertToken(token *common.Token) error {
key := []byte(token.Sha)
return insert(db, bucketTokens, key, token)
// TODO(bradrydzewski) add token to users_token index
}
// DeleteUser deletes the token.
func (db *DB) DeleteToken(token *common.Token) error {
key := []byte(token.Sha)
return delete(db, bucketUser, key)
}

View file

@ -0,0 +1,19 @@
package bolt
import (
"testing"
. "github.com/franela/goblin"
)
func TestToken(t *testing.T) {
g := Goblin(t)
g.Describe("Tokens", func() {
g.It("Should find by sha")
g.It("Should list for user")
g.It("Should delete")
g.It("Should insert")
g.It("Should not insert if exists")
})
}

136
datastore/bolt/user.go Normal file
View file

@ -0,0 +1,136 @@
package bolt
import (
"time"
"github.com/drone/drone/common"
)
// GetUser gets a user by user login.
func (db *DB) GetUser(login string) (*common.User, error) {
user := &common.User{}
key := []byte(login)
err := get(db, bucketUser, key, user)
return user, err
}
// GetUserTokens gets a list of all tokens for
// the given user login.
func (db *DB) GetUserTokens(login string) ([]*common.Token, error) {
t, err := db.Begin(false)
if err != nil {
return nil, err
}
defer t.Rollback()
tokens := []*common.Token{}
// get the index of user tokens and unmarshal
// to a string array.
key := []byte(login)
raw := t.Bucket(bucketUserTokens).Get(key)
keys := [][]byte{}
err = decode(raw, &keys)
if err != nil {
return tokens, err
}
// for each item in the index, get the repository
// and append to the array
for _, key := range keys {
token := &common.Token{}
raw = t.Bucket(bucketTokens).Get(key)
err = decode(raw, token)
if err != nil {
break
}
tokens = append(tokens, token)
}
return tokens, err
}
// GetUserRepos gets a list of repositories for the
// given user account.
func (db *DB) GetUserRepos(login string) ([]*common.Repo, error) {
t, err := db.Begin(false)
if err != nil {
return nil, err
}
defer t.Rollback()
repos := []*common.Repo{}
// get the index of user repos and unmarshal
// to a string array.
key := []byte(login)
raw := t.Bucket(bucketUserRepos).Get(key)
keys := [][]byte{}
err = decode(raw, &keys)
if err != nil {
return repos, err
}
// for each item in the index, get the repository
// and append to the array
for _, key := range keys {
repo := &common.Repo{}
raw = t.Bucket(bucketRepo).Get(key)
err = decode(raw, repo)
if err != nil {
break
}
repos = append(repos, repo)
}
return repos, err
}
// GetUserCount gets a count of all registered users
// in the system.
func (db *DB) GetUserCount() (int, error) {
t, err := db.Begin(false)
if err != nil {
return 0, err
}
defer t.Rollback()
return t.Bucket(bucketUser).Stats().KeyN, nil
}
// GetUserList gets a list of all registered users.
func (db *DB) GetUserList() ([]*common.User, error) {
t, err := db.Begin(false)
if err != nil {
return nil, err
}
defer t.Rollback()
users := []*common.User{}
err = t.Bucket(bucketUser).ForEach(func(key, raw []byte) error {
user := &common.User{}
err := decode(raw, user)
users = append(users, user)
return err
})
return users, err
}
// UpdateUser updates an existing user. If the user
// does not exists an error is returned.
func (db *DB) UpdateUser(user *common.User) error {
key := []byte(user.Login)
user.Updated = time.Now().UTC().Unix()
return update(db, bucketUser, key, user)
}
// InsertUser inserts a new user into the datastore. If
// the user login already exists an error is returned.
func (db *DB) InsertUser(user *common.User) error {
key := []byte(user.Login)
user.Created = time.Now().UTC().Unix()
user.Updated = time.Now().UTC().Unix()
return insert(db, bucketUser, key, user)
}
// DeleteUser deletes the user.
func (db *DB) DeleteUser(user *common.User) error {
key := []byte(user.Login)
// TODO(bradrydzewski) delete user subscriptions
// TODO(bradrydzewski) delete user tokens
return delete(db, bucketUser, key)
}

View file

@ -0,0 +1,86 @@
package bolt
import (
"os"
"testing"
"github.com/drone/drone/common"
. "github.com/franela/goblin"
)
func TestUser(t *testing.T) {
g := Goblin(t)
g.Describe("Users", func() {
var db *DB // temporary database
// create a new database before each unit
// test and destroy afterwards.
g.BeforeEach(func() {
db = Must("/tmp/drone.test.db")
})
g.AfterEach(func() {
os.Remove(db.Path())
})
g.It("Should find", func() {
db.InsertUser(&common.User{Login: "octocat"})
user, err := db.GetUser("octocat")
g.Assert(err).Equal(nil)
g.Assert(user.Login).Equal("octocat")
})
g.It("Should insert", func() {
err := db.InsertUser(&common.User{Login: "octocat"})
g.Assert(err).Equal(nil)
user, err := db.GetUser("octocat")
g.Assert(err).Equal(nil)
g.Assert(user.Login).Equal("octocat")
g.Assert(user.Created != 0).IsTrue()
g.Assert(user.Updated != 0).IsTrue()
})
g.It("Should not insert if exists", func() {
db.InsertUser(&common.User{Login: "octocat"})
err := db.InsertUser(&common.User{Login: "octocat"})
g.Assert(err).Equal(ErrKeyExists)
})
g.It("Should update", func() {
db.InsertUser(&common.User{Login: "octocat"})
user, err := db.GetUser("octocat")
g.Assert(err).Equal(nil)
user.Email = "octocat@github.com"
err = db.UpdateUser(user)
g.Assert(err).Equal(nil)
user_, err := db.GetUser("octocat")
g.Assert(err).Equal(nil)
g.Assert(user_.Login).Equal(user.Login)
g.Assert(user_.Email).Equal(user.Email)
})
g.It("Should delete", func() {
db.InsertUser(&common.User{Login: "octocat"})
user, err := db.GetUser("octocat")
g.Assert(err).Equal(nil)
err = db.DeleteUser(user)
g.Assert(err).Equal(nil)
_, err = db.GetUser("octocat")
g.Assert(err).Equal(ErrKeyNotFound)
})
g.It("Should list")
g.It("Should count", func() {
db.InsertUser(&common.User{Login: "bert"})
db.InsertUser(&common.User{Login: "ernie"})
count, err := db.GetUserCount()
g.Assert(err).Equal(nil)
g.Assert(count).Equal(2)
})
})
}

92
datastore/bolt/util.go Normal file
View file

@ -0,0 +1,92 @@
package bolt
import "github.com/youtube/vitess/go/bson"
func encode(v interface{}) ([]byte, error) {
return bson.Marshal(v)
}
func decode(raw []byte, v interface{}) error {
return bson.Unmarshal(raw, v)
}
func get(db *DB, bucket, key []byte, v interface{}) error {
t, err := db.Begin(false)
if err != nil {
return err
}
defer t.Rollback()
raw := t.Bucket(bucket).Get(key)
if raw == nil {
return ErrKeyNotFound
}
return bson.Unmarshal(raw, v)
}
func raw(db *DB, bucket, key []byte) ([]byte, error) {
t, err := db.Begin(false)
if err != nil {
return nil, err
}
defer t.Rollback()
raw := t.Bucket(bucket).Get(key)
if raw == nil {
return nil, ErrKeyNotFound
}
return raw, nil
}
func update(db *DB, bucket, key []byte, v interface{}) error {
t, err := db.Begin(true)
if err != nil {
return err
}
raw, err := encode(v)
if err != nil {
t.Rollback()
return err
}
err = t.Bucket(bucket).Put(key, raw)
if err != nil {
t.Rollback()
return err
}
return t.Commit()
}
func insert(db *DB, bucket, key []byte, v interface{}) error {
t, err := db.Begin(true)
if err != nil {
return err
}
raw, err := encode(v)
if err != nil {
t.Rollback()
return err
}
// verify the key does not already exists
// in the bucket. If exists, fail
if t.Bucket(bucket).Get(key) != nil {
t.Rollback()
return ErrKeyExists
}
err = t.Bucket(bucket).Put(key, raw)
if err != nil {
t.Rollback()
return err
}
return t.Commit()
}
func delete(db *DB, bucket, key []byte) error {
t, err := db.Begin(true)
if err != nil {
return err
}
err = t.Bucket(bucket).Delete(key)
if err != nil {
t.Rollback()
return err
}
return t.Commit()
}

131
datastore/datastore.go Normal file
View file

@ -0,0 +1,131 @@
package datastore
import "github.com/drone/drone/common"
type Datastore interface {
// GetUser gets a user by user login.
GetUser(string) (*common.User, error)
// GetUserTokens gets a list of all tokens for
// the given user login.
GetUserTokens(string) ([]*common.Token, error)
// GetUserRepos gets a list of repositories for the
// given user account.
GetUserRepos(string) ([]*common.Repo, error)
// GetUserCount gets a count of all registered users
// in the system.
GetUserCount() (int, error)
// GetUserList gets a list of all registered users.
GetUserList() ([]*common.User, error)
// UpdateUser updates an existing user. If the user
// does not exists an error is returned.
UpdateUser(*common.User) error
// InsertUser inserts a new user into the datastore. If
// the user login already exists an error is returned.
InsertUser(*common.User) error
// DeleteUser deletes the user.
DeleteUser(*common.User) error
// GetToken gets a token by sha value.
GetToken(string) (*common.Token, error)
// InsertToken inserts a new user token in the datastore.
// If the token already exists and error is returned.
InsertToken(*common.Token) error
// DeleteUser deletes the token.
DeleteToken(*common.Token) error
// GetRepo gets the repository by name.
GetRepo(string) (*common.Repo, error)
// GetRepoParams gets the private environment parameters
// for the given repository.
GetRepoParams(string) (map[string]string, error)
// GetRepoParams gets the private and public rsa keys
// for the given repository.
GetRepoKeys(string) (*common.Keypair, error)
// UpdateRepos updates a repository. If the repository
// does not exist an error is returned.
UpdateRepo(*common.Repo) error
// InsertRepo inserts a repository in the datastore and
// subscribes the user to that repository.
InsertRepo(*common.User, *common.Repo) error
// UpsertRepoParams inserts or updates the private
// environment parameters for the named repository.
UpsertRepoParams(string, map[string]string) error
// UpsertRepoKeys inserts or updates the private and
// public keypair for the named repository.
UpsertRepoKeys(string, *common.Keypair) error
// DeleteRepo deletes the repository.
DeleteRepo(*common.Repo) error
// GetBuild gets the specified build number for the
// named repository and build number
GetBuild(string, int) (*common.Build, error)
// GetBuildList gets a list of recent builds for the
// named repository.
GetBuildList(string) ([]*common.Build, error)
// GetBuildLast gets the last executed build for the
// named repository.
GetBuildLast(string) (*common.Build, error)
// GetBuildStatus gets the named build status for the
// named repository and build number.
GetBuildStatus(string, int, string) (*common.Status, error)
// GetBuildStatusList gets a list of all build statues for
// the named repository and build number.
GetBuildStatusList(string, int) ([]*common.Status, error)
// InsertBuild inserts a new build for the named repository
InsertBuild(string, *common.Build) error
// InsertBuildStatus inserts a new build status for the
// named repository and build number. If the status already
// exists an error is returned.
InsertBuildStatus(string, int, *common.Status) error
// UpdateBuild updates an existing build for the named
// repository. If the build already exists and error is
// returned.
UpdateBuild(string, *common.Build) error
// GetTask gets the task at index N for the named
// repository and build number.
GetTask(string, int, int) (*common.Task, error)
// GetTaskLogs gets the task logs at index N for
// the named repository and build number.
GetTaskLogs(string, int, int) ([]byte, error)
// GetTaskList gets all tasks for the named repository
// and build number.
GetTaskList(string, int) ([]*common.Task, error)
// UpsertTask inserts or updates a task for the named
// repository and build number.
UpsertTask(string, int, *common.Task) error
// UpsertTaskLogs inserts or updates a task logs for the
// named repository and build number.
UpsertTaskLogs(string, int, int, []byte) error
}
// GetSubscriber(string, string) (*common.Subscriber, error)
// InsertSubscriber(string, *common.Subscriber) error
// DeleteSubscriber(string, string) error

20
drone.go Normal file
View file

@ -0,0 +1,20 @@
package main
import (
_ "github.com/drone/drone/common"
"github.com/drone/drone/datastore"
"github.com/drone/drone/datastore/bolt"
)
var (
revision string
version string
)
var ds datastore.Datastore
func main() {
ds, _ = bolt.New("drone.toml")
println(revision)
println(version)
}

View file

@ -1,82 +0,0 @@
[server]
port=":80"
#####################################################################
# SSL configuration
#
# [server.ssl]
# key=""
# cert=""
#####################################################################
# Assets configuration
#
# [server.assets]
# folder=""
# [session]
# secret=""
# expires=""
#####################################################################
# Database configuration, by default using SQLite3.
# You can also use postgres and mysql. See the documentation
# for more details.
[database]
driver="sqlite3"
datasource="/var/lib/drone/drone.sqlite"
# [github]
# client=""
# secret=""
# orgs=[]
# open=false
# [github_enterprise]
# client=""
# secret=""
# api=""
# url=""
# orgs=[]
# private_mode=false
# open=false
# [bitbucket]
# client=""
# secret=""
# open=false
# [gitlab]
# url=""
# client=""
# secret=""
# skip_verify=false
# open=false
# [gogs]
# url=""
# secret=""
# open=false
#####################################################################
# SMTP configuration for Drone. This is required if you plan
# to send email notifications for build statuses.
#
# [smtp]
# host=""
# port=""
# from=""
# user=""
# pass=""
# [docker]
# cert=""
# key=""
# [worker]
# nodes=[
# "unix:///var/run/docker.sock",
# "unix:///var/run/docker.sock"
# ]

View file

@ -1,75 +0,0 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: drone
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Drone continuous integration server
### END INIT INFO
DAEMON_OPTS="--config=/etc/drone/drone.toml"
pid() {
if [ -f /usr/local/bin/droned ]; then
pidof /usr/local/bin/droned
fi
}
stop() {
if pidof /usr/local/bin/droned >/dev/null; then
kill -9 "$(pid)"
else
echo "Drone not runned"
exit 1
fi
}
start() {
if pidof /usr/local/bin/droned >/dev/null; then
echo "Drone already runned"
exit 1
else
nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 &
fi
}
restart() {
if pidof /usr/local/bin/droned >/dev/null; then
kill -9 "$(pid)"
nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 &
exit 0
else
nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 &
exit 0
fi
}
status() {
if pidof /usr/local/bin/droned >/dev/null; then
echo "Drone with pid $(pid) is running"
else
echo "Drone is not running"
fi
exit 0
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo "Usage: service drone {start|stop|restart|status}"
exit 1
;;
esac

View file

@ -1,8 +0,0 @@
start on (filesystem and net-device-up)
chdir /var/lib/drone
console log
script
/usr/local/bin/droned --config=/etc/drone/drone.toml
end script

View file

@ -1,26 +0,0 @@
#
# systemd unit file for CentOS 7, Ubuntu bleeding edge
#
[Unit]
Description=Drone
# start us only once the network and logging subsystems are available
After=syslog.target network.target
# See these pages for lots of options:
# http://0pointer.de/public/systemd-man/systemd.service.html
# http://0pointer.de/public/systemd-man/systemd.exec.html
[Service]
Type=simple
ExecStart=/usr/local/bin/droned --config=/etc/drone/drone.toml
# if we crash, restart
RestartSec=1
Restart=on-failure
# use syslog for logging
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=droned
[Install]
WantedBy=multi-user.target

View file

@ -1,103 +0,0 @@
#!/bin/sh
set -e
case "$1" in
abort-upgrade|abort-remove|abort-deconfigure|configure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
if [ -f /etc/drone/drone.toml ]; then
chmod 600 /etc/drone/drone.toml
fi
dist() {
lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//'
}
version() {
lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }'
}
upstart() {
if [ -d /etc/init ]; then
echo "Your system $(dist) $(version): using upstart to control Drone"
if [ -f /usr/local/bin/droned ]; then
if pidof /usr/local/bin/droned >/dev/null; then
initctl stop drone || :
fi
fi
cp -r /usr/share/drone/init/drone.conf /etc/init/drone.conf
initctl start drone || :
else
echo "Couldn't find upstart to control Drone, cannot proceed."
echo "Open an issue and tell us about your system."
exit 1
fi
}
sysv() {
if [ -d /etc/init.d ]; then
echo "Your system $(dist) $(version): using SysV to control Drone"
if [ -f /usr/local/bin/droned ] && [ -f /etc/init.d/drone ]; then
if pidof /usr/local/bin/droned >/dev/null; then
/etc/init.d/drone stop
fi
fi
cp -r /usr/share/drone/init.d/drone /etc/init.d/drone
chmod 0755 /etc/init.d/drone
update-rc.d drone defaults
exec /etc/init.d/drone start || :
else
echo "Couldn't find SysV to control Drone, cannot proceed."
echo "Open an issue and tell us about your system."
exit 1
fi
}
systemd() {
if which systemctl > /dev/null; then
cp /usr/share/drone/systemd/drone.service /lib/systemd/system/drone.service
systemctl daemon-reload || :
if [ "$1" = "configure" ] ; then
echo "Your system $(dist) $(version): using systemd to control Drone"
systemctl enable drone || :
systemctl restart drone || :
fi
else
echo "Couldn't find systemd to control Drone, cannot proceed."
echo "Open an issue and tell us about your system."
exit 1
fi
}
case "$(dist)" in
debian)
if [ "$(version)" -lt "8" ]; then
sysv
else
systemd $1
fi
;;
ubuntu)
if [ "$(version)" -lt "15" ]; then
upstart
else
systemd $1
fi
;;
*)
echo "\033[33m Your system $(dist) $(version) \033[0m"
echo "\033[33m This system is not supported, you can install service manually \033[0m"
;;
esac
exit 0

View file

@ -1,27 +0,0 @@
#!/bin/sh
set -e
if [ -f /etc/drone/drone.toml ]; then
chmod 600 /etc/drone/drone.toml
fi
if which systemctl > /dev/null; then
echo "Using systemd to control Drone"
cp /usr/share/drone/systemd/drone.service /lib/systemd/system/drone.service
systemctl daemon-reload || :
if [ "$1" = 1 ] ; then
# first time install
systemctl enable drone || :
systemctl start drone || :
else
echo "Upgrading drone"
fi
else
echo "Couldn't find systemd to control Drone, cannot proceed."
echo "Open an issue and tell us about your system."
exit 1
fi
exit 0

View file

@ -1,55 +0,0 @@
#!/bin/sh
set -e
dist() {
lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//'
}
version() {
lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }'
}
upstart() {
rm -f /etc/init/drone.conf
}
systemd() {
rm -f /lib/systemd/system/drone.service
}
sysv() {
update-rc.d -f drone remove
rm -f /etc/init.d/drone
}
validate_ver() {
echo "$(version) < $1" | bc
}
case "$(dist)" in
debian)
if [ "$(version)" -lt "8" ]; then
sysv
else
systemd
fi
;;
ubuntu)
if [ "$(version)" -lt "15" ]; then
upstart
else
systemd
fi
;;
*)
echo "\033[33m Please remove service manually \033[0m"
;;
esac
# https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html
if [ "$1" = "purge" ] ; then
echo "Purging drone configuration"
rm -rf /etc/drone
fi

View file

@ -1,11 +0,0 @@
#!/bin/sh
set -e
# https://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch09s04s05.html
systemctl daemon-reload || :
if [ "$1" -ge 1 ] ; then
# Package upgrade, not uninstall
systemctl try-restart drone || :
fi

View file

@ -1,59 +0,0 @@
#!/bin/sh
set -e
dist() {
lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//'
}
version() {
lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }'
}
echo Stopping drone
upstart() {
initctl stop drone || :
}
systemd() {
if [ $1 = "remove" ] ; then
systemctl --no-reload disable drone || :
systemctl stop drone || :
fi
}
sysv() {
if [ -f /etc/init.d/drone ] ; then
if pidof /usr/local/bin/droned >/dev/null; then
exec /etc/init.d/drone stop || :
fi
fi
}
validate_ver() {
echo "$(version) < $1" | bc
}
case "$(dist)" in
debian)
if [ "$(version)" -lt "8" ]; then
sysv
else
systemd $1
fi
;;
ubuntu)
if [ "$(version)" -lt "15" ]; then
upstart
else
systemd $1
fi
;;
*)
if [ -f /usr/local/bin/droned ]; then
if pidof /usr/local/bin/droned >/dev/null; then
kill -9 `pidof /usr/local/bin/droned`
fi
fi
;;
esac

View file

@ -1,8 +0,0 @@
#!/bin/sh
set -e
if [ "$1" -eq 0 ] ; then
echo Stopping Drone
systemctl --no-reload disable drone || :
systemctl stop drone || :
fi

View file

@ -1,67 +0,0 @@
package condition
import (
"path/filepath"
"strings"
)
type Condition struct {
Owner string // Indicates the step should run only for this repo (useful for forks)
Branch string // Indicates the step should run only for this branch
Condition string // Indicates the step should run if bash condition evals to true
PullRequest *bool `yaml:"pull_requests"` // Indicates the step should run for all pull requests
AllBranches *bool `yaml:"all_branches"` // Indicates the step should run for all branches
// Indicates the step should only run when the following
// matrix values are present for the sub-build.
Matrix map[string]string
}
// MatchPullRequest is a helper function that returns false
// if Pull Requests are disbled, but the pull request string
// is not empty.
func (c *Condition) MatchPullRequest(pr string) bool {
if len(pr) == 0 {
return true
}
if c.PullRequest == nil {
return false
}
return *c.PullRequest
}
// MatchBranch is a helper function that returns true
// if all_branches is true. Else it returns false if a
// branch condition is specified, and the branch does
// not match.
func (c *Condition) MatchBranch(branch string) bool {
if len(c.Branch) == 0 {
return true
}
if c.AllBranches != nil && *c.AllBranches == true {
return true
}
match, _ := filepath.Match(c.Branch, branch)
return match
}
// MatchOwner is a helper function that returns false
// if an owner condition is specified and the repository
// owner does not match.
//
// This is useful when you want to prevent forks from
// executing deployment, publish or notification steps.
func (c *Condition) MatchOwner(owner string) bool {
if len(c.Owner) == 0 {
return true
}
parts := strings.Split(owner, "/")
switch len(parts) {
case 2:
return c.Owner == parts[0]
case 3:
return c.Owner == parts[1]
default:
return c.Owner == owner
}
}

View file

@ -1,119 +0,0 @@
package condition
import (
"testing"
)
type Bool bool
func Test_MatchPullRequest(t *testing.T) {
var c = Condition{}
var got, want = c.MatchPullRequest(""), true
if got != want {
t.Errorf("Non-pull requests are always enabled, expected %v, got %v", want, got)
}
got, want = c.MatchPullRequest("65"), false
if got != want {
t.Errorf("Pull requests should be disabled by default, expected %v, got %v", want, got)
}
c.PullRequest = new(bool)
*c.PullRequest = false
got, want = c.MatchPullRequest("65"), false
if got != want {
t.Errorf("Pull requests can be explicity disabled, expected %v, got %v", want, got)
}
c.PullRequest = new(bool)
*c.PullRequest = true
got, want = c.MatchPullRequest("65"), true
if got != want {
t.Errorf("Pull requests can be explicitly enabled, expected %v, got %v", want, got)
}
}
func Test_MatchBranch(t *testing.T) {
var c = Condition{}
var got, want = c.MatchBranch("master"), true
if got != want {
t.Errorf("All branches should be enabled by default, expected %v, got %v", want, got)
}
c.Branch = ""
got, want = c.MatchBranch("master"), true
if got != want {
t.Errorf("Empty branch should match, expected %v, got %v", want, got)
}
c.Branch = "master"
got, want = c.MatchBranch("master"), true
if got != want {
t.Errorf("Branch should match, expected %v, got %v", want, got)
}
c.Branch = "master"
got, want = c.MatchBranch("dev"), false
if got != want {
t.Errorf("Branch should not match, expected %v, got %v", want, got)
}
c.Branch = "release/*"
got, want = c.MatchBranch("release/1.0.0"), true
if got != want {
t.Errorf("Branch should match wildcard, expected %v, got %v", want, got)
}
}
func Test_MatchOwner(t *testing.T) {
var c = Condition{}
var got, want = c.MatchOwner("drone"), true
if got != want {
t.Errorf("All owners should be enabled by default, expected %v, got %v", want, got)
}
c.Owner = ""
got, want = c.MatchOwner("drone"), true
if got != want {
t.Errorf("Empty owner should match, expected %v, got %v", want, got)
}
c.Owner = "drone"
got, want = c.MatchOwner("drone"), true
if got != want {
t.Errorf("Owner should match, expected %v, got %v", want, got)
}
c.Owner = "drone"
got, want = c.MatchOwner("drone/config"), true
if got != want {
t.Errorf("Owner/Repo should match, expected %v, got %v", want, got)
}
c.Owner = "drone"
got, want = c.MatchOwner("github.com/drone/config"), true
if got != want {
t.Errorf("Host/Owner/Repo should match, expected %v, got %v", want, got)
}
c.Owner = "bradrydzewski"
got, want = c.MatchOwner("drone"), false
if got != want {
t.Errorf("Owner should not match, expected %v, got %v", want, got)
}
c.Owner = "drone"
got, want = c.MatchOwner("bradrydzewski/drone"), false
if got != want {
t.Errorf("Owner/Repo should not match, expected %v, got %v", want, got)
}
c.Owner = "drone"
got, want = c.MatchOwner("github.com/bradrydzewski/drone"), false
if got != want {
t.Errorf("Host/Owner/Repo should not match, expected %v, got %v", want, got)
}
}

View file

@ -1,25 +0,0 @@
package deploy
import (
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type Bash struct {
Script []string `yaml:"script,omitempty"`
Command string `yaml:"command,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (g *Bash) Write(f *buildfile.Buildfile) {
g.Script = append(g.Script, g.Command)
for _, cmd := range g.Script {
f.WriteCmd(cmd)
}
}
func (g *Bash) GetCondition() *condition.Condition {
return g.Condition
}

View file

@ -1,94 +0,0 @@
package deploy
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"gopkg.in/yaml.v1"
)
// emulate Build struct
type buildWithBash struct {
Deploy *Deploy `yaml:"deploy,omitempty"`
}
var sampleYmlWithBash = `
deploy:
bash:
command: 'echo bash_deployed'
`
var sampleYmlWithScript = `
deploy:
bash:
script:
- ./bin/deploy.sh
- ./bin/check.sh
`
var sampleYmlWithBashAndScript = `
deploy:
bash:
command: ./bin/some_cmd.sh
script:
- ./bin/deploy.sh
- ./bin/check.sh
`
func setUpWithBash(input string) (string, error) {
var buildStruct buildWithBash
err := yaml.Unmarshal([]byte(input), &buildStruct)
if err != nil {
return "", err
}
bf := buildfile.New()
buildStruct.Deploy.Write(bf, nil)
return bf.String(), err
}
func TestBashDeployment(t *testing.T) {
bscr, err := setUpWithBash(sampleYmlWithBash)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "echo bash_deployed") {
t.Error("Expect script to contains bash command")
}
}
func TestBashDeploymentWithScript(t *testing.T) {
bscr, err := setUpWithBash(sampleYmlWithScript)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "./bin/deploy.sh") {
t.Error("Expect script to contains bash script")
}
if !strings.Contains(bscr, "./bin/check.sh") {
t.Error("Expect script to contains bash script")
}
}
func TestBashDeploymentWithBashAndScript(t *testing.T) {
bscr, err := setUpWithBash(sampleYmlWithBashAndScript)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "./bin/deploy.sh") {
t.Error("Expect script to contains bash script")
}
if !strings.Contains(bscr, "./bin/check.sh") {
t.Error("Expect script to contains bash script")
}
if !strings.Contains(bscr, "./bin/some_cmd.sh") {
t.Error("Expect script to contains bash script")
}
}

View file

@ -1,51 +0,0 @@
package deploy
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type CloudFoundry struct {
Target string `yaml:"target,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Org string `yaml:"org,omitempty"`
Space string `yaml:"space,omitempty"`
App string `yaml:"app,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (cf *CloudFoundry) Write(f *buildfile.Buildfile) {
downloadCmd := "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb"
installCmd := "dpkg -i cf-cli_amd64.deb 1> /dev/null 2> /dev/null"
// download and install the cf tool
f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", downloadCmd, downloadCmd))
f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", installCmd, installCmd))
// login
loginCmd := "cf login -a %s -u %s -p %s"
organization := cf.Org
if organization != "" {
loginCmd += fmt.Sprintf(" -o %s", organization)
}
space := cf.Space
if space != "" {
loginCmd += fmt.Sprintf(" -s %s", space)
}
f.WriteCmdSilent(fmt.Sprintf(loginCmd, cf.Target, cf.Username, cf.Password))
// push app
pushCmd := "cf push %s"
f.WriteCmd(fmt.Sprintf(pushCmd, cf.App))
}
func (cf *CloudFoundry) GetCondition() *condition.Condition {
return cf.Condition
}

View file

@ -1,51 +0,0 @@
package cloudfoundry
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type CloudFoundry struct {
Target string `yaml:"target,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Org string `yaml:"org,omitempty"`
Space string `yaml:"space,omitempty"`
App string `yaml:"app,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (cf *CloudFoundry) Write(f *buildfile.Buildfile) {
downloadCmd := "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb"
installCmd := "dpkg -i cf-cli_amd64.deb 1> /dev/null 2> /dev/null"
// download and install the cf tool
f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", downloadCmd, downloadCmd))
f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", installCmd, installCmd))
// login
loginCmd := "cf login -a %s -u %s -p %s"
organization := cf.Org
if organization != "" {
loginCmd += fmt.Sprintf(" -o %s", organization)
}
space := cf.Space
if space != "" {
loginCmd += fmt.Sprintf(" -s %s", space)
}
f.WriteCmdSilent(fmt.Sprintf(loginCmd, cf.Target, cf.Username, cf.Password))
// push app
pushCmd := "cf push %s"
f.WriteCmd(fmt.Sprintf(pushCmd, cf.App))
}
func (cf *CloudFoundry) GetCondition() *condition.Condition {
return cf.Condition
}

View file

@ -1,125 +0,0 @@
package deploy
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"gopkg.in/yaml.v1"
)
// emulate Build struct
type DeployToCF struct {
Deploy *Deploy `yaml:"deploy,omitempty"`
}
var sampleYmlBasic = `
deploy:
cloudfoundry:
target: https://api.example.com
username: foo
password: bar
`
var sampleYmlWithOrg = `
deploy:
cloudfoundry:
target: https://api.example.com
username: foo
password: bar
org: custom-org
`
var sampleYmlWithSpace = `
deploy:
cloudfoundry:
target: https://api.example.com
username: foo
password: bar
org: custom-org
space: dev
`
var sampleYmlWithAppName = `
deploy:
cloudfoundry:
target: https://api.example.com
username: foo
password: bar
app: test-app
`
func setUpWithCF(input string) (string, error) {
var buildStruct DeployToCF
err := yaml.Unmarshal([]byte(input), &buildStruct)
if err != nil {
return "", err
}
bf := buildfile.New()
buildStruct.Deploy.Write(bf, nil)
return bf.String(), err
}
func TestCloudFoundryToolInstall(t *testing.T) {
bscr, err := setUpWithCF(sampleYmlBasic)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb") {
t.Error("Expect script to contain download command")
}
if !strings.Contains(bscr, "dpkg -i cf-cli_amd64.deb") {
t.Error("Expect script to contain install command")
}
}
func TestCloudFoundryDeployment(t *testing.T) {
bscr, err := setUpWithCF(sampleYmlBasic)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar") {
t.Error("Expect login script to contain username and password")
}
if !strings.Contains(bscr, "cf push") {
t.Error("Expect script to contain push")
}
}
func TestCloudFoundryDeploymentWithOrg(t *testing.T) {
bscr, err := setUpWithCF(sampleYmlWithOrg)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar -o custom-org") {
t.Error("Expect login script to contain organization")
}
}
func TestCloudFoundryDeploymentWithSpace(t *testing.T) {
bscr, err := setUpWithCF(sampleYmlWithSpace)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar -o custom-org -s dev") {
t.Error("Expect login script to contain space")
}
}
func TestCloudFoundryDeploymentWithApp(t *testing.T) {
bscr, err := setUpWithCF(sampleYmlWithAppName)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, "cf push test-app") {
t.Error("Expect login script to contain app name")
}
}

View file

@ -1,56 +0,0 @@
package deis
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
const (
// Gommand to the current commit hash
CmdRevParse = "COMMIT=$(git rev-parse HEAD)"
// Command to set the git user and email based on the
// individual that made the commit.
CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')"
CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')"
)
// deploy:
// deis:
// app: safe-island-6261
// deisurl: deis.myurl.tdl:2222/
type Deis struct {
App string `yaml:"app,omitempty"`
Force bool `yaml:"force,omitempty"`
Deisurl string `yaml:"deisurl,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (h *Deis) Write(f *buildfile.Buildfile) {
f.WriteCmdSilent(CmdRevParse)
f.WriteCmdSilent(CmdGlobalUser)
f.WriteCmdSilent(CmdGlobalEmail)
// git@deis.yourdomain.com:2222/drone.git
f.WriteCmd(fmt.Sprintf("git remote add deis ssh://git@%s%s.git", h.Deisurl, h.App))
switch h.Force {
case true:
// this is useful when the there are artifacts generated
// by the build script, such as less files converted to css,
// that need to be deployed to Deis.
f.WriteCmd(fmt.Sprintf("git add -A"))
f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'"))
f.WriteCmd(fmt.Sprintf("git push deis HEAD:master --force"))
case false:
// otherwise we just do a standard git push
f.WriteCmd(fmt.Sprintf("git push deis $COMMIT:master"))
}
}
func (h *Deis) GetCondition() *condition.Condition {
return h.Condition
}

View file

@ -1,68 +0,0 @@
package deis
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Deis(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Deis Deploy", func() {
g.It("Should set git.config", func() {
b := new(buildfile.Buildfile)
h := Deis{
App: "drone",
Deisurl: "deis.yourdomain.com:2222",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, CmdRevParse)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true)
})
g.It("Should add remote", func() {
b := new(buildfile.Buildfile)
h := Deis{
App: "drone",
Deisurl: "deis.yourdomain.com:2222/",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit remote add deis ssh://git@deis.yourdomain.com:2222/drone.git\n")).Equal(true)
})
g.It("Should push to remote", func() {
b := new(buildfile.Buildfile)
d := Deis{
App: "drone",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit push deis $COMMIT:master\n")).Equal(true)
})
g.It("Should force push to remote", func() {
b := new(buildfile.Buildfile)
h := Deis{
Force: true,
App: "drone",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit push deis HEAD:master --force\n")).Equal(true)
})
})
}

View file

@ -1,74 +0,0 @@
package deploy
import (
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
"github.com/drone/drone/shared/build/repo"
"github.com/drone/drone/plugin/deploy/deis"
"github.com/drone/drone/plugin/deploy/git"
"github.com/drone/drone/plugin/deploy/heroku"
"github.com/drone/drone/plugin/deploy/modulus"
"github.com/drone/drone/plugin/deploy/nodejitsu"
"github.com/drone/drone/plugin/deploy/tsuru"
)
// Deploy stores the configuration details
// for deploying build artifacts when
// a Build has succeeded
type Deploy struct {
CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"`
Git *git.Git `yaml:"git,omitempty"`
Heroku *heroku.Heroku `yaml:"heroku,omitempty"`
Deis *deis.Deis `yaml:"deis,omitempty"`
Modulus *modulus.Modulus `yaml:"modulus,omitempty"`
Nodejitsu *nodejitsu.Nodejitsu `yaml:"nodejitsu,omitempty"`
SSH *SSH `yaml:"ssh,omitempty"`
Tsuru *tsuru.Tsuru `yaml:"tsuru,omitempty"`
Bash *Bash `yaml:"bash,omitempty"`
}
func (d *Deploy) Write(f *buildfile.Buildfile, r *repo.Repo) {
if d.CloudFoundry != nil && match(d.CloudFoundry.GetCondition(), r) {
d.CloudFoundry.Write(f)
}
if d.Git != nil && match(d.Git.GetCondition(), r) {
d.Git.Write(f)
}
if d.Heroku != nil && match(d.Heroku.GetCondition(), r) {
d.Heroku.Write(f)
}
if d.Deis != nil && match(d.Deis.GetCondition(), r) {
d.Deis.Write(f)
}
if d.Modulus != nil && match(d.Modulus.GetCondition(), r) {
d.Modulus.Write(f)
}
if d.Nodejitsu != nil && match(d.Nodejitsu.GetCondition(), r) {
d.Nodejitsu.Write(f)
}
if d.SSH != nil && match(d.SSH.GetCondition(), r) {
d.SSH.Write(f)
}
if d.Tsuru != nil && match(d.Tsuru.GetCondition(), r) {
d.Tsuru.Write(f)
}
if d.Bash != nil && match(d.Bash.GetCondition(), r) {
d.Bash.Write(f)
}
}
func match(c *condition.Condition, r *repo.Repo) bool {
switch {
case c == nil:
return true
case !c.MatchBranch(r.Branch):
return false
case !c.MatchOwner(r.Name):
return false
case !c.MatchPullRequest(r.PR):
return false
}
return true
}

View file

@ -1,56 +0,0 @@
package git
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
const (
// Gommand to the current commit hash
CmdRevParse = "COMMIT=$(git rev-parse HEAD)"
// Command to set the git user and email based on the
// individual that made the commit.
CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')"
CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')"
)
type Git struct {
Target string `yaml:"target,omitempty"`
Force bool `yaml:"force,omitempty"`
Branch string `yaml:"branch,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (g *Git) Write(f *buildfile.Buildfile) {
f.WriteCmdSilent(CmdRevParse)
f.WriteCmdSilent(CmdGlobalUser)
f.WriteCmdSilent(CmdGlobalEmail)
// add target as a git remote
f.WriteCmd(fmt.Sprintf("git remote add deploy %s", g.Target))
dest := g.Branch
if len(dest) == 0 {
dest = "master"
}
switch g.Force {
case true:
// this is useful when the there are artifacts generated
// by the build script, such as less files converted to css,
// 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 HEAD:%s --force", dest))
case false:
// otherwise we just do a standard git push
f.WriteCmd(fmt.Sprintf("git push deploy $COMMIT:%s", dest))
}
}
func (g *Git) GetCondition() *condition.Condition {
return g.Condition
}

View file

@ -1,78 +0,0 @@
package git
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Git(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Git Deploy", func() {
g.It("Should set git.config", func() {
b := new(buildfile.Buildfile)
d := Git{
Target: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, CmdRevParse)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true)
})
g.It("Should add remote", func() {
b := new(buildfile.Buildfile)
d := Git{
Target: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit remote add deploy git://foo.com/bar/baz.git\n")).Equal(true)
})
g.It("Should push to remote", func() {
b := new(buildfile.Buildfile)
d := Git{
Target: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit push deploy $COMMIT:master\n")).Equal(true)
})
g.It("Should push to alternate branch", func() {
b := new(buildfile.Buildfile)
d := Git{
Branch: "foo",
Target: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit push deploy $COMMIT:foo\n")).Equal(true)
})
g.It("Should force push to remote", func() {
b := new(buildfile.Buildfile)
d := Git{
Force: true,
Target: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit commit -m 'add build artifacts'\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit push deploy HEAD:master --force\n")).Equal(true)
})
})
}

View file

@ -1,56 +0,0 @@
package heroku
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
const (
// Gommand to the current commit hash
CmdRevParse = "COMMIT=$(git rev-parse HEAD)"
// Command to set the git user and email based on the
// individual that made the commit.
CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')"
CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')"
// Command to write the API token to ~/.netrc
// use "_" since heroku git authentication ignores username
CmdLogin = "echo 'machine git.heroku.com login _ password %s' >> ~/.netrc"
)
type Heroku struct {
App string `yaml:"app,omitempty"`
Force bool `yaml:"force,omitempty"`
Token string `yaml:"token,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (h *Heroku) Write(f *buildfile.Buildfile) {
f.WriteCmdSilent(CmdRevParse)
f.WriteCmdSilent(CmdGlobalUser)
f.WriteCmdSilent(CmdGlobalEmail)
f.WriteCmdSilent(fmt.Sprintf(CmdLogin, h.Token))
// add heroku as a git remote
f.WriteCmd(fmt.Sprintf("git remote add heroku https://git.heroku.com/%s.git", h.App))
switch h.Force {
case true:
// this is useful when the there are artifacts generated
// by the build script, such as less files converted to css,
// 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 HEAD:refs/heads/master --force"))
case false:
// otherwise we just do a standard git push
f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:refs/heads/master"))
}
}
func (h *Heroku) GetCondition() *condition.Condition {
return h.Condition
}

View file

@ -1,78 +0,0 @@
package heroku
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Heroku(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Heroku Deploy", func() {
g.It("Should set git.config", func() {
b := new(buildfile.Buildfile)
h := Heroku{
App: "drone",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, CmdRevParse)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true)
})
g.It("Should write token", func() {
b := new(buildfile.Buildfile)
h := Heroku{
App: "drone",
Token: "mock-token",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\necho 'machine git.heroku.com login _ password mock-token' >> ~/.netrc\n")).Equal(true)
})
g.It("Should add remote", func() {
b := new(buildfile.Buildfile)
h := Heroku{
App: "drone",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit remote add heroku https://git.heroku.com/drone.git\n")).Equal(true)
})
g.It("Should push to remote", func() {
b := new(buildfile.Buildfile)
d := Heroku{
App: "drone",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit push heroku $COMMIT:refs/heads/master\n")).Equal(true)
})
g.It("Should force push to remote", func() {
b := new(buildfile.Buildfile)
h := Heroku{
Force: true,
App: "drone",
}
h.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit push heroku HEAD:refs/heads/master --force\n")).Equal(true)
})
})
}

View file

@ -1,36 +0,0 @@
package modulus
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type Modulus struct {
Project string `yaml:"project,omitempty"`
Token string `yaml:"token,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (m *Modulus) Write(f *buildfile.Buildfile) {
if len(m.Token) == 0 || len(m.Project) == 0 {
return
}
f.WriteEnv("MODULUS_TOKEN", m.Token)
// Verify npm exists, otherwise we cannot install the
// modulus command line utility.
f.WriteCmdSilent("[ -f /usr/bin/npm ] || echo ERROR: npm is required for modulus.io deployments")
f.WriteCmdSilent("[ -f /usr/bin/npm ] || exit 1")
// Install the Modulus command line interface then deploy the configured
// project.
f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g modulus")
f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g modulus")
f.WriteCmd(fmt.Sprintf("modulus deploy -p %q", m.Project))
}
func (m *Modulus) GetCondition() *condition.Condition {
return m.Condition
}

View file

@ -1,53 +0,0 @@
package modulus
import (
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Modulus(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Modulus Deploy", func() {
g.It("Requires a Project name", func() {
b := new(buildfile.Buildfile)
m := Modulus{
Project: "foo",
}
m.Write(b)
g.Assert(b.String()).Equal("")
})
g.It("Requires a Token", func() {
b := new(buildfile.Buildfile)
m := Modulus{
Token: "bar",
}
m.Write(b)
g.Assert(b.String()).Equal("")
})
g.It("Should execute deploy commands", func() {
b := new(buildfile.Buildfile)
m := Modulus{
Project: "foo",
Token: "bar",
}
m.Write(b)
g.Assert(b.String()).Equal(`export MODULUS_TOKEN="bar"
[ -f /usr/bin/npm ] || echo ERROR: npm is required for modulus.io deployments
[ -f /usr/bin/npm ] || exit 1
[ -f /usr/bin/sudo ] || npm install -g modulus
[ -f /usr/bin/sudo ] && sudo npm install -g modulus
echo '#DRONE:6d6f64756c7573206465706c6f79202d702022666f6f22'
modulus deploy -p "foo"
`)
})
})
}

View file

@ -1,32 +0,0 @@
package nodejitsu
import (
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type Nodejitsu struct {
User string `yaml:"user,omitempty"`
Token string `yaml:"token,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (n *Nodejitsu) Write(f *buildfile.Buildfile) {
if len(n.Token) == 0 || len(n.User) == 0 {
return
}
f.WriteEnv("username", n.User)
f.WriteEnv("apiToken", n.Token)
// Install the jitsu command line interface then
// deploy the configured app.
f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g jitsu")
f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g jitsu")
f.WriteCmd("jitsu deploy")
}
func (n *Nodejitsu) GetCondition() *condition.Condition {
return n.Condition
}

View file

@ -1,52 +0,0 @@
package nodejitsu
import (
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Modulus(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Nodejitsu Deploy", func() {
g.It("Requires a User", func() {
b := new(buildfile.Buildfile)
n := Nodejitsu{
User: "foo",
}
n.Write(b)
g.Assert(b.String()).Equal("")
})
g.It("Requires a Token", func() {
b := new(buildfile.Buildfile)
n := Nodejitsu{
Token: "bar",
}
n.Write(b)
g.Assert(b.String()).Equal("")
})
g.It("Should execute deploy commands", func() {
b := new(buildfile.Buildfile)
n := Nodejitsu{
User: "foo",
Token: "bar",
}
n.Write(b)
g.Assert(b.String()).Equal(`export username="foo"
export apiToken="bar"
[ -f /usr/bin/sudo ] || npm install -g jitsu
[ -f /usr/bin/sudo ] && sudo npm install -g jitsu
echo '#DRONE:6a69747375206465706c6f79'
jitsu deploy
`)
})
})
}

View file

@ -1,105 +0,0 @@
package deploy
import (
"fmt"
"strconv"
"strings"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
// SSH struct holds configuration data for deployment
// via ssh, deployment done by scp-ing file(s) listed
// in artifacts to the target host, and then run cmd
// remotely.
// It is assumed that the target host already
// add this repo public key in the host's `authorized_hosts`
// file. And the private key is already copied to `.ssh/id_rsa`
// inside the build container. No further check will be done.
type SSH struct {
// Target is the deployment host in this format
// user@hostname:/full/path <PORT>
//
// PORT may be omitted if its default to port 22.
Target string `yaml:"target,omitempty"`
// Artifacts is a list of files/dirs to be deployed
// to the target host. If artifacts list more than one file
// it will be compressed into a single tar.gz file.
// if artifacts contain:
// - GITARCHIVE
//
// other file listed in artifacts will be ignored, instead, we will
// create git archive from the current revision and deploy that file
// alone.
// If you need to deploy the git archive along with some other files,
// please use build script to create the git archive, and then list
// the archive name here with the other files.
Artifacts []string `yaml:"artifacts,omitempty"`
// Cmd is a single command executed at target host after the artifacts
// is deployed.
Cmd string `yaml:"cmd,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
// Write down the buildfile
func (s *SSH) Write(f *buildfile.Buildfile) {
host := strings.SplitN(s.Target, " ", 2)
if len(host) == 1 {
host = append(host, "22")
}
if _, err := strconv.Atoi(host[1]); err != nil {
host[1] = "22"
}
// Is artifact created?
artifact := false
for _, a := range s.Artifacts {
if a == "GITARCHIVE" {
artifact = createGitArchive(f)
break
}
}
if !artifact {
if len(s.Artifacts) > 1 {
artifact = compress(f, s.Artifacts)
} else if len(s.Artifacts) == 1 {
f.WriteEnv("ARTIFACT", s.Artifacts[0])
artifact = true
}
}
if artifact {
scpCmd := "scp -o StrictHostKeyChecking=no -P %s -r ${ARTIFACT} %s"
f.WriteCmd(fmt.Sprintf(scpCmd, host[1], host[0]))
}
if len(s.Cmd) > 0 {
sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s %q"
f.WriteCmd(fmt.Sprintf(sshCmd, host[1], strings.SplitN(host[0], ":", 2)[0], s.Cmd))
}
}
func createGitArchive(f *buildfile.Buildfile) bool {
f.WriteEnv("COMMIT", "$(git rev-parse HEAD)")
f.WriteEnv("ARTIFACT", "${PWD##*/}-${COMMIT}.tar.gz")
f.WriteCmdSilent("git archive --format=tar.gz --prefix=${PWD##*/}/ ${COMMIT} > ${ARTIFACT}")
return true
}
func compress(f *buildfile.Buildfile, files []string) bool {
cmd := "tar -cf ${ARTIFACT} %s"
f.WriteEnv("ARTIFACT", "${PWD##*/}.tar.gz")
f.WriteCmdSilent(fmt.Sprintf(cmd, strings.Join(files, " ")))
return true
}
func (s *SSH) GetCondition() *condition.Condition {
return s.Condition
}

View file

@ -1,129 +0,0 @@
package deploy
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"gopkg.in/yaml.v1"
)
// emulate Build struct
type build struct {
Deploy *Deploy `yaml:"deploy,omitempty"`
}
var sampleYml = `
deploy:
ssh:
target: user@test.example.com
cmd: /opt/bin/redeploy.sh
`
var sampleYml1 = `
deploy:
ssh:
target: user@test.example.com:/srv/app/location 2212
artifacts:
- build.result
cmd: /opt/bin/redeploy.sh
`
var sampleYml2 = `
deploy:
ssh:
target: user@test.example.com:/srv/app/location 2212
artifacts:
- build.result
- config/file
cmd: /opt/bin/redeploy.sh
`
var sampleYml3 = `
deploy:
ssh:
target: user@test.example.com:/srv/app/location 2212
artifacts:
- GITARCHIVE
cmd: /opt/bin/redeploy.sh
`
func setUp(input string) (string, error) {
var buildStruct build
err := yaml.Unmarshal([]byte(input), &buildStruct)
if err != nil {
return "", err
}
bf := buildfile.New()
buildStruct.Deploy.Write(bf, nil)
return bf.String(), err
}
func TestSSHNoArtifact(t *testing.T) {
bscr, err := setUp(sampleYml)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if strings.Contains(bscr, `scp`) {
t.Error("Expect script not to contains scp command")
}
if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com \"/opt/bin/redeploy.sh\"") {
t.Error("Expect script to contains ssh command")
}
}
func TestSSHOneArtifact(t *testing.T) {
bscr, err := setUp(sampleYml1)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, `ARTIFACT="build.result"`) {
t.Error("Expect script to contains artifact")
}
if !strings.Contains(bscr, "scp -o StrictHostKeyChecking=no -P 2212 -r ${ARTIFACT} user@test.example.com:/srv/app/location") {
t.Errorf("Expect script to contains scp command, got:\n%s", bscr)
}
}
func TestSSHMultiArtifact(t *testing.T) {
bscr, err := setUp(sampleYml2)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, `ARTIFACT="${PWD##*/}.tar.gz"`) {
t.Errorf("Expect script to contains artifact")
}
if !strings.Contains(bscr, "tar -cf ${ARTIFACT} build.result config/file") {
t.Errorf("Expect script to contains tar command. got: %s\n", bscr)
}
}
func TestSSHGitArchive(t *testing.T) {
bscr, err := setUp(sampleYml3)
if err != nil {
t.Fatalf("Can't unmarshal deploy script: %s", err)
}
if !strings.Contains(bscr, `COMMIT="$(git rev-parse HEAD)"`) {
t.Errorf("Expect script to contains commit ref")
}
if !strings.Contains(bscr, `ARTIFACT="${PWD##*/}-${COMMIT}.tar.gz"`) {
t.Errorf("Expect script to contains artifact")
}
if strings.Contains(bscr, "=GITARCHIVE") {
t.Errorf("Doesn't expect script to contains GITARCHIVE literals")
}
if !strings.Contains(bscr, "git archive --format=tar.gz --prefix=${PWD##*/}/ ${COMMIT} > ${ARTIFACT}") {
t.Errorf("Expect script to run git archive")
}
}

View file

@ -1,50 +0,0 @@
package tsuru
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
const (
// Gommand to the current commit hash
CmdRevParse = "COMMIT=$(git rev-parse HEAD)"
// Command to set the git user and email based on the
// individual that made the commit.
CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')"
CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')"
)
type Tsuru struct {
Force bool `yaml:"force,omitempty"`
Remote string `yaml:"remote,omitempty"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (t *Tsuru) Write(f *buildfile.Buildfile) {
f.WriteCmdSilent(CmdRevParse)
f.WriteCmdSilent(CmdGlobalUser)
f.WriteCmdSilent(CmdGlobalEmail)
// add tsuru as a git remote
f.WriteCmd(fmt.Sprintf("git remote add tsuru %s", t.Remote))
switch t.Force {
case true:
// this is useful when the there are artifacts generated
// by the build script, such as less files converted to css,
// 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 HEAD:master --force"))
case false:
// otherwise we just do a standard git push
f.WriteCmd(fmt.Sprintf("git push tsuru $COMMIT:master"))
}
}
func (t *Tsuru) GetCondition() *condition.Condition {
return t.Condition
}

View file

@ -1,66 +0,0 @@
package tsuru
import (
"strings"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/franela/goblin"
)
func Test_Tsuru(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Tsuru Deploy", func() {
g.It("Should set git.config", func() {
b := new(buildfile.Buildfile)
d := Tsuru{
Remote: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, CmdRevParse)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true)
g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true)
})
g.It("Should add remote", func() {
b := new(buildfile.Buildfile)
d := Tsuru{
Remote: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit remote add tsuru git://foo.com/bar/baz.git\n")).Equal(true)
})
g.It("Should push to remote", func() {
b := new(buildfile.Buildfile)
d := Tsuru{
Remote: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit push tsuru $COMMIT:master\n")).Equal(true)
})
g.It("Should force push to remote", func() {
b := new(buildfile.Buildfile)
d := Tsuru{
Force: true,
Remote: "git://foo.com/bar/baz.git",
}
d.Write(b)
out := b.String()
g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true)
g.Assert(strings.Contains(out, "\ngit push tsuru HEAD:master --force\n")).Equal(true)
})
})
}

View file

@ -1,175 +0,0 @@
package email
import (
"bytes"
"fmt"
"net"
"net/smtp"
"strings"
"github.com/drone/config"
"github.com/drone/drone/shared/model"
)
const (
NotifyAlways = "always" // always send email notification
NotifyNever = "never" // never send email notifications
NotifyAuthor = "author" // only send email notifications to the author
NotifyAfterChange = "change" // only if the previous commit has a different status
NotifyTrue = "true" // alias for NotifyTrue
NotifyFalse = "false" // alias for NotifyFalse
NotifyOn = "on" // alias for NotifyTrue
NotifyOff = "off" // alias for NotifyFalse
NotifyBlame = "blame" // alias for NotifyAuthor
)
const (
Subject = "[%s] %s/%s (%s - %s)"
)
var (
DefaultHost = config.String("smtp-host", "")
DefaultPort = config.String("smtp-port", "")
DefaultFrom = config.String("smtp-from", "")
DefaultUser = config.String("smtp-user", "")
DefaultPass = config.String("smtp-pass", "")
)
type Email struct {
Recipients []string `yaml:"recipients"`
Success string `yaml:"on_success"`
Failure string `yaml:"on_failure"`
Host string `yaml:"host"`
Port string `yaml:"port"`
From string `yaml:"from"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
// Send will send an email, either success or failure,
// based on the Commit Status.
func (e *Email) Send(context *model.Request) error {
var status = context.Commit.Status
switch status {
// no builds are triggered for pending builds
case model.StatusEnqueue, model.StatusStarted:
return nil
case model.StatusSuccess:
return e.sendSuccess(context)
default:
return e.sendFailure(context)
}
}
// sendFailure sends email notifications to the list of
// recipients indicating the build failed.
func (e *Email) sendFailure(context *model.Request) error {
switch e.Failure {
case NotifyFalse, NotifyNever, NotifyOff:
return nil
// if the last commit in this branch was a different status, notify
case NotifyAfterChange:
if context.Prior.Status == context.Commit.Status {
return nil
}
// if configured to email the author, replace
// the recipiends with the commit author email.
case NotifyBlame, NotifyAuthor:
e.Recipients = []string{context.Commit.Author}
}
// generate the email failure template
var buf bytes.Buffer
err := failureTemplate.ExecuteTemplate(&buf, "_", context)
if err != nil {
return err
}
// generate the email subject
var subject = fmt.Sprintf(
Subject,
context.Commit.Status,
context.Repo.Owner,
context.Repo.Name,
context.Commit.Branch,
context.Commit.ShaShort(),
)
return e.send(subject, buf.String(), e.Recipients)
}
// sendSuccess sends email notifications to the list of
// recipients indicating the build was a success.
func (e *Email) sendSuccess(context *model.Request) error {
switch e.Success {
case NotifyFalse, NotifyNever, NotifyOff:
return nil
// if the last commit in this branch was a different status, notify
case NotifyAfterChange:
if context.Prior.Status == context.Commit.Status {
return nil
}
// if configured to email the author, replace
// the recipiends with the commit author email.
case NotifyBlame, NotifyAuthor:
e.Recipients = []string{context.Commit.Author}
}
// generate the email success template
var buf bytes.Buffer
err := successTemplate.ExecuteTemplate(&buf, "_", context)
if err != nil {
return err
}
// generate the email subject
var subject = fmt.Sprintf(
Subject,
context.Commit.Status,
context.Repo.Owner,
context.Repo.Name,
context.Commit.Branch,
context.Commit.ShaShort(),
)
return e.send(subject, buf.String(), e.Recipients)
}
func (e *Email) send(subject, body string, recipients []string) error {
if len(recipients) == 0 {
return nil
}
// the user can provide their own smtp server
// configuration. If None provided, attempt to
// use the global configuration set in the environet
// variables.
if len(*DefaultHost) != 0 {
e.Host = *DefaultHost
e.Port = *DefaultPort
e.From = *DefaultFrom
e.Username = *DefaultUser
e.Password = *DefaultPass
}
var auth smtp.Auth
var addr = net.JoinHostPort(e.Host, e.Port)
// setup the authentication to the smtp server
// if the username and password are provided.
if len(e.Username) > 0 {
auth = smtp.PlainAuth("", e.Username, e.Password, e.Host)
}
// genereate the raw email message
var to = strings.Join(e.Recipients, ",")
var raw = fmt.Sprintf(rawMessage, e.From, to, subject, body)
return smtp.SendMail(addr, auth, e.From, e.Recipients, []byte(raw))
}

View file

@ -1,42 +0,0 @@
package email
import (
"html/template"
)
// raw email message template
var rawMessage = `From: %s
To: %s
Subject: %s
MIME-version: 1.0
Content-Type: text/html; charset="UTF-8"
%s`
// default success email template
var successTemplate = template.Must(template.New("_").Parse(`
<p>
<b>Build was Successful</b>
(<a href="{{.Host}}/{{.Repo.Host}}/{{.Repo.Owner}}/{{.Repo.Name}}/{{.Commit.Branch}}/{{.Commit.Sha}}">see results</a>)
</p>
<p>Repository : {{.Repo.Owner}}/{{.Repo.Name}}</p>
<p>Commit : {{.Commit.ShaShort}}</p>
<p>Author : {{.Commit.Author}}</p>
<p>Branch : {{.Commit.Branch}}</p>
<p>Message:</p>
<p>{{ .Commit.Message }}</p>
`))
// default failure email template
var failureTemplate = template.Must(template.New("_").Parse(`
<p>
<b>Build Failed</b>
(<a href="{{.Host}}/{{.Repo.Host}}/{{.Repo.Owner}}/{{.Repo.Name}}/{{.Commit.Branch}}/{{.Commit.Sha}}">see results</a>)
</p>
<p>Repository : {{.Repo.Owner}}/{{.Repo.Name}}</p>
<p>Commit : {{.Commit.ShaShort}}</p>
<p>Author : {{.Commit.Author}}</p>
<p>Branch : {{.Commit.Branch}}</p>
<p>Message:</p>
<p>{{ .Commit.Message }}</p>
`))

View file

@ -1,105 +0,0 @@
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))
}
}

View file

@ -1,83 +0,0 @@
package flowdock
import (
"fmt"
"strings"
"github.com/drone/drone/shared/model"
)
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 := Client{Token: f.Token, Source: f.Source, FromName: "drone.io", FromAddress: fromAddress, Tags: tags}
go c.Inbox(subject, message)
return nil
}

View file

@ -1,158 +0,0 @@
package github
import (
"fmt"
"net/url"
"code.google.com/p/goauth2/oauth"
"github.com/drone/drone/shared/model"
"github.com/google/go-github/github"
)
const (
NotifyDisabled = "disabled"
NotifyFalse = "false"
NotifyOff = "off"
)
const (
StatusPending = "pending"
StatusSuccess = "success"
StatusFailure = "failure"
StatusError = "error"
)
const (
DescPending = "this build is pending"
DescSuccess = "the build was successful"
DescFailure = "the build failed"
DescError = "oops, something went wrong"
)
const (
BaseURL = "https://api.github.com/"
)
type GitHub string
// Send uses the github status API to update the build
// status in github or github enterprise.
func (g GitHub) Send(context *model.Request) error {
// a user can toggle the status api on / off
// in the .drone.yml
switch g {
case NotifyDisabled, NotifyOff, NotifyFalse:
return nil
}
// this should only be executed for GitHub and
// GitHub enterprise requests.
switch context.Repo.Remote {
case model.RemoteGithub, model.RemoteGithubEnterprise:
break
default:
return nil
}
var target = getTarget(
context.Host,
context.Repo.Host,
context.Repo.Owner,
context.Repo.Name,
context.Commit.Branch,
context.Commit.Sha,
)
return send(
context.Repo.URL,
context.Repo.Host,
context.Repo.Owner,
context.Repo.Name,
getStatus(context.Commit.Status),
getDesc(context.Commit.Status),
target,
context.Commit.Sha,
context.User.Access,
)
}
func send(rawurl, host, owner, repo, status, desc, target, ref, token string) error {
transport := &oauth.Transport{
Token: &oauth.Token{AccessToken: token},
}
data := github.RepoStatus{
Context: github.String("Drone"),
State: github.String(status),
Description: github.String(desc),
TargetURL: github.String(target),
}
client := github.NewClient(transport.Client())
// if this is for github enterprise we need to set
// the base url. Per the documentation, we need to
// ensure there is a trailing slash.
if host != model.RemoteGithub {
client.BaseURL, _ = getEndpoint(rawurl)
}
_, _, err := client.Repositories.CreateStatus(owner, repo, ref, &data)
return err
}
// getStatus is a helper functin that converts a Drone
// status to a GitHub status.
func getStatus(status string) string {
switch status {
case model.StatusEnqueue, model.StatusStarted:
return StatusPending
case model.StatusSuccess:
return StatusSuccess
case model.StatusFailure:
return StatusFailure
case model.StatusError, model.StatusKilled:
return StatusError
default:
return StatusError
}
}
// getDesc is a helper function that generates a description
// message for the build based on the status.
func getDesc(status string) string {
switch status {
case model.StatusEnqueue, model.StatusStarted:
return DescPending
case model.StatusSuccess:
return DescSuccess
case model.StatusFailure:
return DescFailure
case model.StatusError, model.StatusKilled:
return DescError
default:
return DescError
}
}
// getTarget is a helper function that generates a URL
// for the user to click and jump to the build results.
//
// for example:
// https://drone.io/github.com/drone/drone-test-go/master/c22aec9c53
func getTarget(url, host, owner, repo, branch, commit string) string {
return fmt.Sprintf("%s/%s/%s/%s/%s/%s", url, host, owner, repo, branch, commit)
}
// getEndpoint is a helper funcation that parsed the
// repository HTML URL to determine the API URL. It is
// intended for use with GitHub enterprise.
func getEndpoint(rawurl string) (*url.URL, error) {
uri, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
uri.Path = "/api/v3/"
return uri, nil
}

View file

@ -1,50 +0,0 @@
package github
import (
"testing"
"github.com/drone/drone/shared/model"
"github.com/franela/goblin"
)
func Test_Client(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Github Status", func() {
g.It("Should get a status", func() {
g.Assert(getStatus(model.StatusEnqueue)).Equal(StatusPending)
g.Assert(getStatus(model.StatusStarted)).Equal(StatusPending)
g.Assert(getStatus(model.StatusSuccess)).Equal(StatusSuccess)
g.Assert(getStatus(model.StatusFailure)).Equal(StatusFailure)
g.Assert(getStatus(model.StatusError)).Equal(StatusError)
g.Assert(getStatus(model.StatusKilled)).Equal(StatusError)
g.Assert(getStatus(model.StatusNone)).Equal(StatusError)
})
g.It("Should get a description", func() {
g.Assert(getDesc(model.StatusEnqueue)).Equal(DescPending)
g.Assert(getDesc(model.StatusStarted)).Equal(DescPending)
g.Assert(getDesc(model.StatusSuccess)).Equal(DescSuccess)
g.Assert(getDesc(model.StatusFailure)).Equal(DescFailure)
g.Assert(getDesc(model.StatusError)).Equal(DescError)
g.Assert(getDesc(model.StatusKilled)).Equal(DescError)
g.Assert(getDesc(model.StatusNone)).Equal(DescError)
})
g.It("Should get a target url", func() {
var (
url = "https://drone.io"
host = "github.com"
owner = "drone"
repo = "go-bitbucket"
branch = "master"
commit = "0c0cf4ece975efdfcf6daa78b03d4e84dd257da7"
)
var got = getTarget(url, host, owner, repo, branch, commit)
var want = "https://drone.io/github.com/drone/go-bitbucket/master/0c0cf4ece975efdfcf6daa78b03d4e84dd257da7"
g.Assert(got).Equal(want)
})
})
}

View file

@ -1,77 +0,0 @@
package notify
import (
"encoding/json"
"fmt"
"github.com/drone/drone/shared/model"
)
const (
gitterEndpoint = "https://api.gitter.im/v1/rooms/%s/chatMessages"
gitterStartedMessage = "*Building* %s, commit [%s](%s), author %s"
gitterSuccessMessage = "*Success* %s, commit [%s](%s), author %s"
gitterFailureMessage = "*Failed* %s, commit [%s](%s), author %s"
)
type Gitter struct {
RoomID string `yaml:"room_id,omitempty"`
Token string `yaml:"token,omitempty"`
Started bool `yaml:"on_started,omitempty"`
Success bool `yaml:"on_success,omitempty"`
Failure bool `yaml:"on_failure,omitempty"`
}
func (g *Gitter) Send(context *model.Request) error {
switch {
case context.Commit.Status == model.StatusStarted && g.Started:
return g.sendStarted(context)
case context.Commit.Status == model.StatusSuccess && g.Success:
return g.sendSuccess(context)
case context.Commit.Status == model.StatusFailure && g.Failure:
return g.sendFailure(context)
}
return nil
}
func (g *Gitter) getMessage(context *model.Request, message string) string {
url := getBuildUrl(context)
return fmt.Sprintf(message, context.Repo.Name, context.Commit.ShaShort(), url, context.Commit.Author)
}
func (g *Gitter) sendStarted(context *model.Request) error {
return g.send(g.getMessage(context, gitterStartedMessage))
}
func (g *Gitter) sendSuccess(context *model.Request) error {
return g.send(g.getMessage(context, gitterSuccessMessage))
}
func (g *Gitter) sendFailure(context *model.Request) error {
return g.send(g.getMessage(context, gitterFailureMessage))
}
// helper function to send HTTP requests
func (g *Gitter) send(msg string) error {
// data will get posted in this format
data := struct {
Text string `json:"text"`
}{msg}
// data json encoded
payload, err := json.Marshal(data)
if err != nil {
return err
}
// send payload
url := fmt.Sprintf(gitterEndpoint, g.RoomID)
// create headers
headers := make(map[string]string)
headers["Accept"] = "application/json"
headers["Authorization"] = fmt.Sprintf("Bearer %s", g.Token)
return sendJson(url, payload, headers)
}

View file

@ -1,79 +0,0 @@
package notify
import (
"fmt"
"github.com/andybons/hipchat"
"github.com/drone/drone/shared/model"
)
const (
startedMessage = "Building %s (%s) by %s <br> - %s"
successMessage = "Success %s (%s) by %s"
failureMessage = "Failed %s (%s) by %s"
)
type Hipchat struct {
Room string `yaml:"room,omitempty"`
Token string `yaml:"token,omitempty"`
Started bool `yaml:"on_started,omitempty"`
Success bool `yaml:"on_success,omitempty"`
Failure bool `yaml:"on_failure,omitempty"`
}
type HipchatClient interface {
PostMessage(req hipchat.MessageRequest) error
}
func (h *Hipchat) Send(context *model.Request) error {
client := &hipchat.Client{AuthToken: h.Token}
return h.SendWithClient(client, context)
}
func (h *Hipchat) SendWithClient(client HipchatClient, context *model.Request) error {
switch {
case context.Commit.Status == "Started" && h.Started:
return h.sendStarted(client, context)
case context.Commit.Status == "Success" && h.Success:
return h.sendSuccess(client, context)
case context.Commit.Status == "Failure" && h.Failure:
return h.sendFailure(client, context)
}
return nil
}
func (h *Hipchat) buildLink(context *model.Request) string {
repoName := context.Repo.Owner + "/" + context.Repo.Name
url := context.Host + "/" + context.Repo.Host + "/" + repoName + "/" + context.Commit.Branch + "/" + context.Commit.Sha
return fmt.Sprintf("<a href=\"%s\">%s#%s</a>", url, repoName, context.Commit.ShaShort())
}
func (h *Hipchat) sendStarted(client HipchatClient, context *model.Request) error {
msg := fmt.Sprintf(startedMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author, context.Commit.Message)
return h.send(client, hipchat.ColorYellow, hipchat.FormatHTML, msg, false)
}
func (h *Hipchat) sendFailure(client HipchatClient, context *model.Request) error {
msg := fmt.Sprintf(failureMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author)
return h.send(client, hipchat.ColorRed, hipchat.FormatHTML, msg, true)
}
func (h *Hipchat) sendSuccess(client HipchatClient, context *model.Request) error {
msg := fmt.Sprintf(successMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author)
return h.send(client, hipchat.ColorGreen, hipchat.FormatHTML, msg, false)
}
// helper function to send Hipchat requests
func (h *Hipchat) send(client HipchatClient, color, format, message string, notify bool) error {
req := hipchat.MessageRequest{
RoomId: h.Room,
From: "Drone",
Message: message,
Color: color,
MessageFormat: format,
Notify: notify,
}
return client.PostMessage(req)
}

View file

@ -1,100 +0,0 @@
package notify
import (
"testing"
"github.com/andybons/hipchat"
"github.com/drone/drone/shared/model"
)
type MockHipchatClient struct {
Request hipchat.MessageRequest
}
func (c *MockHipchatClient) PostMessage(req hipchat.MessageRequest) error {
c.Request = req
return nil
}
var client = &MockHipchatClient{}
var subject = &Hipchat{
Room: "SampleRoom",
Token: "foo",
Started: true,
Success: true,
Failure: true,
}
var request = &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
Status: "Started",
Message: "Test Commit",
Author: "Test User",
},
User: &model.User{
Login: "TestUser",
},
}
func Test_SendStarted(t *testing.T) {
request.Commit.Status = "Started"
subject.SendWithClient(client, request)
expected := hipchat.MessageRequest{
RoomId: "SampleRoom",
From: "Drone",
Message: "Building <a href=\"http://examplehost.com/examplegit.com/owner/repo/example/abc\">owner/repo#abc</a> (example) by Test User <br> - Test Commit",
Color: hipchat.ColorYellow,
MessageFormat: hipchat.FormatHTML,
Notify: false,
}
if client.Request != expected {
t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request)
}
}
func Test_SendSuccess(t *testing.T) {
request.Commit.Status = "Success"
subject.SendWithClient(client, request)
expected := hipchat.MessageRequest{
RoomId: "SampleRoom",
From: "Drone",
Message: "Success <a href=\"http://examplehost.com/examplegit.com/owner/repo/example/abc\">owner/repo#abc</a> (example) by Test User",
Color: hipchat.ColorGreen,
MessageFormat: hipchat.FormatHTML,
Notify: false,
}
if client.Request != expected {
t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request)
}
}
func Test_SendFailure(t *testing.T) {
request.Commit.Status = "Failure"
subject.SendWithClient(client, request)
expected := hipchat.MessageRequest{
RoomId: "SampleRoom",
From: "Drone",
Message: "Failed <a href=\"http://examplehost.com/examplegit.com/owner/repo/example/abc\">owner/repo#abc</a> (example) by Test User",
Color: hipchat.ColorRed,
MessageFormat: hipchat.FormatHTML,
Notify: true,
}
if client.Request != expected {
t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request)
}
}

View file

@ -1,75 +0,0 @@
package irc
import (
"fmt"
"github.com/drone/drone/shared/model"
"github.com/thoj/go-ircevent"
)
const (
MessageStarted = "Building: %s, commit %s, author %s"
MessageSuccess = "Success: %s, commit %s, author %s"
MessageFailure = "Failed: %s, commit %s, author %s"
)
type IRC struct {
Channel string
Nick string
Server string
Started *bool `yaml:"on_started,omitempty"`
Success *bool `yaml:"on_success,omitempty"`
Failure *bool `yaml:"on_failure,omitempty"`
}
func (i *IRC) Send(req *model.Request) error {
switch {
case req.Commit.Status == model.StatusStarted && i.Started != nil && *i.Started == true:
return i.sendStarted(req)
case req.Commit.Status == model.StatusSuccess && i.Success != nil && *i.Success == true:
return i.sendSuccess(req)
case req.Commit.Status == model.StatusFailure && i.Failure != nil && *i.Failure == true:
return i.sendFailure(req)
}
return nil
}
func (i *IRC) sendStarted(req *model.Request) error {
msg := fmt.Sprintf(MessageStarted, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author)
return i.send(i.Channel, msg)
}
func (i *IRC) sendFailure(req *model.Request) error {
msg := fmt.Sprintf(MessageFailure, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author)
return i.send(i.Channel, msg)
}
func (i *IRC) sendSuccess(req *model.Request) error {
msg := fmt.Sprintf(MessageSuccess, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author)
return i.send(i.Channel, msg)
}
// send is a helper function that will send notice messages
// to the connected IRC client
func (i *IRC) send(channel string, message string) error {
client := irc.IRC(i.Nick, i.Nick)
if client == nil {
return fmt.Errorf("Error creating IRC client")
}
err := client.Connect(i.Server)
if err != nil {
return fmt.Errorf("Error connecting to IRC server: %v", err)
}
client.AddCallback("001", func(_ *irc.Event) {
client.Notice(channel, message)
client.Quit()
})
go client.Loop()
return nil
}

View file

@ -1,139 +0,0 @@
package katoim
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/drone/drone/shared/model"
)
const (
katoimEndpoint = "https://api.kato.im/rooms/%s/simple"
katoimStartedMessage = "*Building* %s, commit [%s](%s), author %s"
katoimSuccessMessage = "*Success* %s, commit [%s](%s), author %s"
katoimFailureMessage = "*Failed* %s, commit [%s](%s), author %s"
NotifyTrue = "true"
NotifyFalse = "false"
NotifyOn = "on"
NotifyOff = "off"
NotifyNever = "never"
NotifyAlways = "always"
)
type KatoIM struct {
RoomID string `yaml:"room_id,omitempty"`
Started string `yaml:"on_started,omitempty"`
Success string `yaml:"on_success,omitempty"`
Failure string `yaml:"on_failure,omitempty"`
}
func (k *KatoIM) Send(context *model.Request) error {
switch {
case context.Commit.Status == model.StatusStarted:
return k.sendStarted(context)
case context.Commit.Status == model.StatusSuccess:
return k.sendSuccess(context)
case context.Commit.Status == model.StatusFailure:
return k.sendFailure(context)
}
return nil
}
func (k *KatoIM) getMessage(context *model.Request, message string) string {
url := getBuildUrl(context)
return fmt.Sprintf(message, context.Repo.Name, context.Commit.ShaShort(), url, context.Commit.Author)
}
// sendStarted disabled by default
func (k *KatoIM) sendStarted(context *model.Request) error {
switch k.Started {
case NotifyTrue, NotifyAlways, NotifyOn:
return k.send(k.getMessage(context, katoimStartedMessage), "yellow")
default:
return nil
}
}
// sendSuccess enabled by default
func (k *KatoIM) sendSuccess(context *model.Request) error {
switch k.Success {
case NotifyFalse, NotifyNever, NotifyOff:
return nil
case NotifyTrue, NotifyAlways, NotifyOn, "":
return k.send(k.getMessage(context, katoimSuccessMessage), "green")
default:
return nil
}
}
// sendFailure enabled by default
func (k *KatoIM) sendFailure(context *model.Request) error {
switch k.Failure {
case NotifyFalse, NotifyNever, NotifyOff:
return nil
case NotifyTrue, NotifyAlways, NotifyOn, "":
return k.send(k.getMessage(context, katoimFailureMessage), "red")
default:
return nil
}
}
// helper function to send HTTP requests
func (k *KatoIM) send(msg, color string) error {
// data will get posted in this format
data := struct {
Text string `json:"text"`
Color string `json:"color"`
Renderer string `json:"renderer"`
From string `json:"from"`
}{msg, color, "markdown", "Drone"}
// data json encoded
payload, err := json.Marshal(data)
if err != nil {
return err
}
// send payload
url := fmt.Sprintf(katoimEndpoint, k.RoomID)
// create headers
headers := make(map[string]string)
headers["Accept"] = "application/json"
return sendJson(url, payload, headers)
}
func 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)
}
// helper fuction to sent HTTP Post requests
// with JSON data as the payload.
func sendJson(url string, payload []byte, headers map[string]string) error {
client := &http.Client{}
buf := bytes.NewBuffer(payload)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if headers != nil {
for k, v := range headers {
req.Header.Add(k, v)
}
}
resp, err := client.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
}

View file

@ -1,141 +0,0 @@
package notify
import (
"bytes"
"fmt"
"log"
"net/http"
"github.com/drone/drone/plugin/notify/email"
"github.com/drone/drone/plugin/notify/flowdock"
"github.com/drone/drone/plugin/notify/github"
"github.com/drone/drone/plugin/notify/irc"
"github.com/drone/drone/plugin/notify/katoim"
"github.com/drone/drone/plugin/notify/webhook"
"github.com/drone/drone/shared/model"
)
type Sender interface {
Send(context *model.Request) error
}
// Notification stores the configuration details
// 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"`
Flowdock *flowdock.Flowdock `yaml:"flowdock,omitempty"`
KatoIM *katoim.KatoIM `yaml:"katoim,omitempty"`
GitHub github.GitHub `yaml:"--"`
}
func (n *Notification) Send(context *model.Request) error {
// send email notifications
if n.Email != nil {
err := n.Email.Send(context)
if err != nil {
log.Println(err)
}
}
// send webhook notifications
if n.Webhook != nil {
err := n.Webhook.Send(context)
if err != nil {
log.Println(err)
}
}
// send hipchat notifications
if n.Hipchat != nil {
err := n.Hipchat.Send(context)
if err != nil {
log.Println(err)
}
}
// send irc notifications
if n.Irc != nil {
err := n.Irc.Send(context)
if err != nil {
log.Println(err)
}
}
// send slack notifications
if n.Slack != nil {
err := n.Slack.Send(context)
if err != nil {
log.Println(err)
}
}
// send gitter notifications
if n.Gitter != nil {
err := n.Gitter.Send(context)
if err != nil {
log.Println(err)
}
}
// send gitter notifications
if n.Flowdock != nil {
err := n.Flowdock.Send(context)
if err != nil {
log.Println(err)
}
}
// send kato-im notifications
if n.KatoIM != nil {
err := n.KatoIM.Send(context)
if err != nil {
log.Println(err)
}
}
// send email notifications
// TODO (bradrydzewski) need to improve this code
githubStatus := new(github.GitHub)
if err := githubStatus.Send(context); err != nil {
log.Println(err)
}
return nil
}
func 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)
}
// helper fuction to sent HTTP Post requests
// with JSON data as the payload.
func sendJson(url string, payload []byte, headers map[string]string) error {
client := &http.Client{}
buf := bytes.NewBuffer(payload)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if headers != nil {
for k, v := range headers {
req.Header.Add(k, v)
}
}
resp, err := client.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
}

View file

@ -1,28 +0,0 @@
package notify
import (
"testing"
"github.com/drone/drone/shared/model"
)
func Test_getBuildUrl(t *testing.T) {
c := &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
},
}
expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc"
output := getBuildUrl(c)
if output != expected {
t.Errorf("Failed to build url. Expected: %s, got %s", expected, output)
}
}

View file

@ -1,104 +0,0 @@
package notify
import (
"encoding/json"
"fmt"
"github.com/drone/drone/shared/model"
)
const (
slackStartedMessage = "*Building* <%s|%s> (%s) by %s"
slackStartedFallbackMessage = "Building %s (%s) by %s"
slackSuccessMessage = "*Success* <%s|%s> (%s) by %s"
slackSuccessFallbackMessage = "Success %s (%s) by %s"
slackFailureMessage = "*Failed* <%s|%s> (%s) by %s"
slackFailureFallbackMessage = "Failed %s (%s) by %s"
drone_icon = "https://avatars.githubusercontent.com/drone"
)
type Slack struct {
WebhookUrl string `yaml:"webhook_url,omitempty"`
Channel string `yaml:"channel,omitempty"`
Username string `yaml:"username,omitempty"`
Started bool `yaml:"on_started,omitempty"`
Success bool `yaml:"on_success,omitempty"`
Failure bool `yaml:"on_failure,omitempty"`
}
func (s *Slack) Send(context *model.Request) error {
switch {
case context.Commit.Status == "Started" && s.Started:
return s.sendStarted(context)
case context.Commit.Status == "Success" && s.Success:
return s.sendSuccess(context)
case context.Commit.Status == "Failure" && s.Failure:
return s.sendFailure(context)
}
return nil
}
func (s *Slack) getMessage(context *model.Request, message string) string {
url := getBuildUrl(context)
// drone/drone#3333333
linktext := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort()
return fmt.Sprintf(message, url, linktext, context.Commit.Branch, context.Commit.Author)
}
func (s *Slack) getFallbackMessage(context *model.Request, message string) string {
// drone/drone#3333333
text := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort()
return fmt.Sprintf(message, text, context.Commit.Branch, context.Commit.Author)
}
func (s *Slack) sendStarted(context *model.Request) error {
return s.send(s.getMessage(context, slackStartedMessage)+"\n - "+context.Commit.Message,
s.getFallbackMessage(context, slackStartedFallbackMessage), "warning")
}
func (s *Slack) sendSuccess(context *model.Request) error {
return s.send(s.getMessage(context, slackSuccessMessage),
s.getFallbackMessage(context, slackSuccessFallbackMessage), "good")
}
func (s *Slack) sendFailure(context *model.Request) error {
return s.send(s.getMessage(context, slackFailureMessage),
s.getFallbackMessage(context, slackFailureFallbackMessage), "danger")
}
// helper function to send HTTP requests
func (s *Slack) send(msg string, fallback string, color string) error {
type Attachment struct {
Fallback string `json:"fallback"`
Text string `json:"text"`
Color string `json:"color"`
MrkdwnIn []string `json:"mrkdwn_in"`
}
attachments := []Attachment{
Attachment{
fallback,
msg,
color,
[]string{"fallback", "text"},
},
}
// data will get posted in this format
data := struct {
Channel string `json:"channel"`
Username string `json:"username"`
Icon string `json:"icon_url"`
Attachments []Attachment `json:"attachments"`
}{s.Channel, s.Username, drone_icon, attachments}
// data json encoded
payload, err := json.Marshal(data)
if err != nil {
return err
}
return sendJson(s.WebhookUrl, payload, nil)
}

View file

@ -1,90 +0,0 @@
package notify
import "testing"
/*
var request = &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
Status: "Started",
Message: "Test Commit",
Author: "Test User",
},
User: &model.User{
Login: "TestUser",
},
}
*/
var (
slackExpectedLink = "<http://examplehost.com/examplegit.com/owner/repo/example/abc|owner/repo#abc>"
slackExpectedFallbackText = "owner/repo#abc (example) by Test User"
slackExpectedBase = slackExpectedLink + " (example) by Test User"
)
func Test_slackStartedMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackStartedMessage)
expected := "*Building* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackStartedFallbackMessage(t *testing.T) {
actual := (&Slack{}).getFallbackMessage(request, slackStartedFallbackMessage)
expected := "Building " + slackExpectedFallbackText
if actual != expected {
t.Errorf("Invalid fallback started message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackSuccessMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackSuccessMessage)
expected := "*Success* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackSuccessFallbackMessage(t *testing.T) {
actual := (&Slack{}).getFallbackMessage(request, slackSuccessFallbackMessage)
expected := "Success " + slackExpectedFallbackText
if actual != expected {
t.Errorf("Invalid success fallback message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackFailureMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackFailureMessage)
expected := "*Failed* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackFailureFallbackMessage(t *testing.T) {
actual := (&Slack{}).getFallbackMessage(request, slackFailureFallbackMessage)
expected := "Failed " + slackExpectedFallbackText
if actual != expected {
t.Errorf("Invalid failure fallback message for Slack. Expected %v, got %v", expected, actual)
}
}

View file

@ -1,59 +0,0 @@
package webhook
import (
"bytes"
"encoding/json"
"net/http"
"github.com/drone/drone/shared/model"
)
type Webhook struct {
URL []string `yaml:"urls,omitempty"`
Success *bool `yaml:"on_success,omitempty"`
Failure *bool `yaml:"on_failure,omitempty"`
}
func (w *Webhook) Send(context *model.Request) error {
switch {
case context.Commit.Status == model.StatusSuccess && w.Success != nil && *w.Success == true:
return w.send(context)
case context.Commit.Status == model.StatusFailure && w.Failure != nil && *w.Failure == true:
return w.send(context)
}
return nil
}
// helper function to send HTTP requests
func (w *Webhook) send(context *model.Request) error {
// data will get posted in this format
data := struct {
From string `json:"from_url"`
Owner *model.User `json:"owner"`
Repo *model.Repo `json:"repository"`
Commit *model.Commit `json:"commit"`
}{context.Host, context.User, context.Repo, context.Commit}
// data json encoded
payload, err := json.Marshal(data)
if err != nil {
return err
}
for _, url := range w.URL {
go sendJson(url, payload)
}
return nil
}
// helper fuction to sent HTTP Post requests
// with JSON data as the payload.
func sendJson(url string, payload []byte) {
buf := bytes.NewBuffer(payload)
resp, err := http.Post(url, "application/json", buf)
if err != nil {
return
}
resp.Body.Close()
}

View file

@ -1 +0,0 @@
package notify

View file

@ -1 +0,0 @@
package pipeline

View file

@ -1,48 +0,0 @@
package publish
import (
"fmt"
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type Azure struct {
StorageAccount string `yaml:"storage_account,omitempty"`
StorageAccessKey string `yaml:"storage_access_key,omitempty"`
StorageContainer string `yaml:"storage_container,omitempty"`
// Uploads file indicated by Source to file
// indicated by Target. Only individual file names
// are supported by Source and Target
Source string `yaml:"source,omitempty"`
Target string `yaml:"target"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (a *Azure) Write(f *buildfile.Buildfile) {
if len(a.StorageAccount) == 0 || len(a.StorageAccessKey) == 0 || len(a.StorageContainer) == 0 || len(a.Source) == 0 {
return
}
f.WriteCmdSilent("echo 'publishing to Azure Storage ...'")
// install Azure xplat CLI
f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g azure-cli 1> /dev/null 2> /dev/null")
f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g azure-cli 1> /dev/null 2> /dev/null")
f.WriteEnv("AZURE_STORAGE_ACCOUNT", a.StorageAccount)
f.WriteEnv("AZURE_STORAGE_ACCESS_KEY", a.StorageAccessKey)
// if target isn't specified, set to source
if len(a.Target) == 0 {
a.Target = a.Source
}
f.WriteCmd(fmt.Sprintf(`azure storage blob upload --container %s %s %s`, a.StorageContainer, a.Source, a.Target))
}
func (a *Azure) GetCondition() *condition.Condition {
return a.Condition
}

View file

@ -1,50 +0,0 @@
package bintray
import (
"github.com/drone/drone/plugin/condition"
"github.com/drone/drone/shared/build/buildfile"
)
type Bintray struct {
Username string `yaml:"username"`
ApiKey string `yaml:"api_key"`
Packages []Package `yaml:"packages"`
Condition *condition.Condition `yaml:"when,omitempty"`
}
func (b *Bintray) Write(f *buildfile.Buildfile) {
var cmd string
// Validate Username, ApiKey, Packages
if len(b.Username) == 0 || len(b.ApiKey) == 0 || len(b.Packages) == 0 {
f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`)
if len(b.Username) == 0 {
f.WriteCmdSilent(`echo -e "\tusername not defined in yaml config"`)
}
if len(b.ApiKey) == 0 {
f.WriteCmdSilent(`echo -e "\tapi_key not defined in yaml config"`)
}
if len(b.Packages) == 0 {
f.WriteCmdSilent(`echo -e "\tpackages not defined in yaml config"`)
}
f.WriteCmdSilent("exit 1")
return
}
for _, pkg := range b.Packages {
pkg.Write(b.Username, b.ApiKey, f)
}
f.WriteCmd(cmd)
}
func (b *Bintray) GetCondition() *condition.Condition {
return b.Condition
}

View file

@ -1,116 +0,0 @@
package bintray
import (
"fmt"
"strings"
"github.com/drone/drone/shared/build/buildfile"
)
const bintray_endpoint = "https://api.bintray.com/content/%s/%s/%s/%s/%s"
type Package struct {
File string `yaml:"file"`
Type string `yaml:"type"`
Owner string `yaml:"owner"`
Repository string `yaml:"repository"`
Package string `yaml:"package"`
Version string `yaml:"version"`
Target string `yaml:"target"`
Distr string `yaml:"distr,omitempty"`
Component string `yaml:"component,omitempty"`
Arch []string `yaml:"arch,omitempty"`
Publish bool `yaml:"publish,omitempty"`
Override bool `yaml:"override,omitempty"`
}
func (p *Package) Write(username, api_key string, f *buildfile.Buildfile) {
if len(p.File) == 0 || len(p.Owner) == 0 || len(p.Repository) == 0 || len(p.Package) == 0 || len(p.Version) == 0 || len(p.Target) == 0 {
f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`)
if len(p.Package) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage not defined in yaml config"`))
return
}
if len(p.File) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: file not defined in yaml config"`, p.Package))
}
if len(p.Owner) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: owner not defined in yaml config"`, p.Package))
}
if len(p.Repository) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: repository not defined in yaml config"`, p.Package))
}
if len(p.Version) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: version not defined in yaml config"`, p.Package))
}
if len(p.Target) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: target not defined in yaml config"`, p.Package))
}
f.WriteCmdSilent("exit 1")
return
}
switch p.Type {
case "deb":
p.debUpload(username, api_key, f)
case "rpm":
p.upload(username, api_key, f)
case "maven":
p.upload(username, api_key, f)
default:
p.upload(username, api_key, f)
}
}
func (p *Package) debUpload(username, api_key string, f *buildfile.Buildfile) {
if len(p.Distr) == 0 || len(p.Component) == 0 || len(p.Arch) == 0 {
f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`)
if len(p.Distr) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: distr not defined in yaml config"`, p.Package))
}
if len(p.Component) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: component not defined in yaml config"`, p.Package))
}
if len(p.Arch) == 0 {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: arch not defined in yaml config"`, p.Package))
}
f.WriteCmdSilent("exit 1")
return
}
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\nUpload %s to %s/%s/%s"`, p.File, p.Owner, p.Repository, p.Package))
f.WriteCmdSilent(fmt.Sprintf("curl -s -T %s -u%s:%s %s\\;deb_distribution\\=%s\\;deb_component\\=%s\\;deb_architecture=\\%s\\;publish\\=%d\\;override\\=%d",
p.File, username, api_key, p.getEndpoint(), p.Distr, p.Component, strings.Join(p.Arch, ","), boolToInt(p.Publish), boolToInt(p.Override)))
}
func (p *Package) upload(username, api_key string, f *buildfile.Buildfile) {
f.WriteCmdSilent(fmt.Sprintf(`echo -e "\nUpload %s to %s/%s/%s"`, p.File, p.Owner, p.Repository, p.Package))
f.WriteCmdSilent(fmt.Sprintf("curl -s -T %s -u%s:%s %s\\;publish\\=%d\\;override\\=%d",
p.File, username, api_key, p.getEndpoint(), boolToInt(p.Publish), boolToInt(p.Override)))
}
func (p *Package) getEndpoint() string {
return fmt.Sprintf(bintray_endpoint, p.Owner, p.Repository, p.Package, p.Version, p.Target)
}
func boolToInt(val bool) int {
if val {
return 1
} else {
return 0
}
}

Some files were not shown because too many files have changed in this diff Show more