experimental branch. playing around with boltdb
This commit is contained in:
parent
1f8d65bb80
commit
d9fd23a6df
284 changed files with 1076 additions and 22907 deletions
34
.drone.yml
34
.drone.yml
|
@ -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
9
.gitignore
vendored
|
@ -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
|
17
Dockerfile
17
Dockerfile
|
@ -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"]
|
89
Makefile
89
Makefile
|
@ -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
|
||||
|
|
1
VERSION
1
VERSION
|
@ -1 +0,0 @@
|
|||
0.3.0-alpha
|
226
cli/build.go
226
cli/build.go
|
@ -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 ""
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
48
cli/keys.go
48
cli/keys.go
|
@ -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))
|
||||
}
|
48
cli/main.go
48
cli/main.go
|
@ -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)
|
||||
}
|
43
cli/repos.go
43
cli/repos.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
112
cli/util.go
112
cli/util.go
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
149
client/client.go
149
client/client.go
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
67
common/build.go
Normal 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"`
|
||||
}
|
|
@ -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}
|
16
common/gravatar/gravatar.go
Normal file
16
common/gravatar/gravatar.go
Normal 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
7
common/hook.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package common
|
||||
|
||||
type Hook struct {
|
||||
Repo *Repo
|
||||
Commit *Commit
|
||||
PullRequest *PullRequest
|
||||
}
|
|
@ -34,7 +34,7 @@
|
|||
// // btw, r.FormValue("state") == "foo"
|
||||
// }
|
||||
//
|
||||
package oauth
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
59
common/repo.go
Normal file
59
common/repo.go
Normal 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
14
common/task.go
Normal 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
9
common/token.go
Normal 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
13
common/user.go
Normal 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
66
datastore/bolt/bolt.go
Normal 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
91
datastore/bolt/build.go
Normal 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)
|
||||
}
|
1
datastore/bolt/build_test.go
Normal file
1
datastore/bolt/build_test.go
Normal file
|
@ -0,0 +1 @@
|
|||
package bolt
|
85
datastore/bolt/repo.go
Normal file
85
datastore/bolt/repo.go
Normal 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, ¶ms)
|
||||
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()
|
||||
}
|
24
datastore/bolt/repo_test.go
Normal file
24
datastore/bolt/repo_test.go
Normal 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
82
datastore/bolt/task.go
Normal 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()
|
||||
}
|
1
datastore/bolt/task_test.go
Normal file
1
datastore/bolt/task_test.go
Normal file
|
@ -0,0 +1 @@
|
|||
package bolt
|
27
datastore/bolt/token.go
Normal file
27
datastore/bolt/token.go
Normal 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)
|
||||
}
|
19
datastore/bolt/token_test.go
Normal file
19
datastore/bolt/token_test.go
Normal 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
136
datastore/bolt/user.go
Normal 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)
|
||||
}
|
86
datastore/bolt/user_test.go
Normal file
86
datastore/bolt/user_test.go
Normal 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
92
datastore/bolt/util.go
Normal 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
131
datastore/datastore.go
Normal 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
20
drone.go
Normal 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)
|
||||
}
|
|
@ -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"
|
||||
# ]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
`)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
`)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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>
|
||||
`))
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package notify
|
|
@ -1 +0,0 @@
|
|||
package pipeline
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue