diff --git a/.drone.yml b/.drone.yml
index c59e2b165..29db9066a 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -1,10 +1,10 @@
-image: mischief/docker-golang
+image: go1.2
env:
- GOROOT=/usr/local/go
- GOPATH=/var/cache/drone
- PATH=$GOPATH/bin:$GOPATH/bin:$PATH
script:
- - apt-get -y install libsqlite3-dev sqlite3 mercurial bzr 1> /dev/null 2> /dev/null
+ - sudo apt-get -y install libsqlite3-dev sqlite3 mercurial bzr 1> /dev/null 2> /dev/null
- make deps
- make
- make test
@@ -21,5 +21,5 @@ publish:
bucket: downloads.drone.io
access_key: $AWS_KEY
secret_key: $AWS_SECRET
- source: /tmp/drone.deb
+ source: deb/drone.deb
target: latest/
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..c03944e86
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM ubuntu:13.10
+
+MAINTAINER Drone.io Team
+
+RUN apt-get update
+RUN apt-get install -y wget gcc make g++ build-essential ca-certificates mercurial git bzr libsqlite3-dev sqlite3
+
+RUN wget https://go.googlecode.com/files/go1.2.src.tar.gz && tar zxvf go1.2.src.tar.gz && cd go/src && ./make.bash
+
+ENV PATH $PATH:/go/bin:/gocode/bin
+ENV GOPATH /gocode
+
+RUN mkdir -p /gocode/src/github.com/drone
+
+ADD . /gocode/src/github.com/drone/drone
+
+WORKDIR /gocode/src/github.com/drone/drone
+
+RUN make deps
+RUN make
+RUN make install
+
+EXPOSE 80
+
+ENTRYPOINT ["/usr/local/bin/droned"]
+
+CMD ["--port=:80", "--path=/var/lib/drone/drone.sqlite"]
diff --git a/Makefile b/Makefile
index a8fe1a6a1..4be0fb074 100644
--- a/Makefile
+++ b/Makefile
@@ -2,19 +2,22 @@
all: embed build
deps:
+ [ -d $$GOPATH/src/code.google.com/p/go ] || hg clone -u default https://code.google.com/p/go $$GOPATH/src/code.google.com/p/go
+ [ -d $$GOPATH/src/github.com/dotcloud/docker ] || git clone git://github.com/dotcloud/docker $$GOPATH/src/github.com/dotcloud/docker
go get code.google.com/p/go.crypto/bcrypt
go get code.google.com/p/go.crypto/ssh
go get code.google.com/p/go.net/websocket
go get code.google.com/p/go.text/unicode/norm
+ #go get code.google.com/p/go/src/pkg/archive/tar
go get launchpad.net/goyaml
go get github.com/andybons/hipchat
go get github.com/bmizerany/pat
go get github.com/dchest/authcookie
go get github.com/dchest/passwordreset
go get github.com/dchest/uniuri
- go get github.com/dotcloud/docker/archive
- go get github.com/dotcloud/docker/pkg/term
- go get github.com/dotcloud/docker/utils
+ #go get github.com/dotcloud/docker/archive
+ #go get github.com/dotcloud/docker/utils
+ #go get github.com/dotcloud/docker/pkg/term
go get github.com/drone/go-github/github
go get github.com/drone/go-bitbucket/bitbucket
go get github.com/GeertJohan/go.rice
@@ -41,6 +44,7 @@ test:
go test -v github.com/drone/drone/pkg/channel
go test -v github.com/drone/drone/pkg/database
go test -v github.com/drone/drone/pkg/database/encrypt
+ go test -v github.com/drone/drone/pkg/database/migrate
go test -v github.com/drone/drone/pkg/database/testing
go test -v github.com/drone/drone/pkg/mail
go test -v github.com/drone/drone/pkg/model
@@ -48,6 +52,7 @@ test:
install:
cp deb/drone/etc/init/drone.conf /etc/init/drone.conf
+ test -f /etc/default/drone || cp deb/drone/etc/default/drone /etc/default/drone
cd bin && install -t /usr/local/bin drone
cd bin && install -t /usr/local/bin droned
mkdir -p /var/lib/drone
@@ -64,6 +69,7 @@ clean:
rm -rf usr/local/bin/drone
rm -rf usr/local/bin/droned
rm -rf drone.sqlite
+ rm -rf /tmp/drone.sqlite
# creates a debian package for drone
# to install `sudo dpkg -i drone.deb`
@@ -75,4 +81,4 @@ dpkg:
dpkg-deb --build deb/drone
run:
- bin/droned --port=":8080" --datasource="/tmp/drone.sqlite"
\ No newline at end of file
+ bin/droned --port=":8080" --datasource="drone.sqlite"
diff --git a/README.md b/README.md
index 8e477bf5e..a3bcef814 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
-Drone is a Continuous Integration platform built on Docker
+Drone is a [Continuous Integration](http://en.wikipedia.org/wiki/Continuous_integration) platform built on [Docker](https://www.docker.io/)
+
+[![Build Status](http://beta.drone.io/github.com/drone/drone/status.png?branch=master)](http://beta.drone.io/github.com/drone/drone)
+[![GoDoc](https://godoc.org/github.com/drone/drone?status.png)](https://godoc.org/github.com/drone/drone)
### System
@@ -16,13 +19,25 @@ using the following commands:
```sh
$ wget http://downloads.drone.io/latest/drone.deb
-$ dpkg -i drone.deb
+$ sudo dpkg -i drone.deb
$ sudo start drone
```
Once Drone is running (by default on :80) navigate to **http://localhost:80/install**
and follow the steps in the setup wizard.
+**IMPORTANT** You will also need a GitHub Client ID and Secret:
+
+* Register a new application https://github.com/settings/applications
+* Set the homepage URL to http://$YOUR_IP_ADDRESS/
+* Set the callback URL to http://$YOUR_IP_ADDRESS/auth/login/github
+* Copy the Client ID and Secret into the Drone admin console http://localhost:80/account/admin/settings
+
+I'm working on a getting started video. Having issues with volume, but hopefully
+you can still get a feel for the steps:
+
+https://docs.google.com/file/d/0By8deR1ROz8memUxV0lTSGZPQUk
+
### Builds
Drone use a **.drone.yml** configuration file in the root of your
@@ -35,7 +50,7 @@ env:
script:
- go build
- go test -v
-service:
+services:
- redis
notify:
email:
@@ -60,7 +75,7 @@ image: go1.2 # same as bradrydzewski/go:1.2
Here is a list of our official images:
```sh
-# these are the base images for all Drone containers.
+# these two are base images. all Drone images are built on top of these
# these are BIG (~3GB) so make sure you have a FAST internet connection
docker pull bradrydzewski/ubuntu
docker pull bradrydzewski/base
@@ -135,20 +150,33 @@ if you are using a custom Docker image.
Drone can launch database containers for your build:
```
-service:
+services:
- cassandra
- couchdb
+ - couchdb:1.0
+ - couchdb:1.4
+ - couchdb:1.5
- elasticsearch
+ - elasticsearch:0.20
+ - elasticsearch:0.90
- neo4j
+ - neo4j:1.9
- mongodb
+ - mongodb:2.2
+ - mongodb:2.4
- mysql
+ - mysql:5.5
- postgres
+ - postgres:9.1
- rabbitmq
+ - rabbitmq:3.2
- redis
- riak
- zookeeper
```
+If you omit the version, Drone will launch the latest version of the database. (For example, if you set `mongodb`, Drone will launch MongoDB 2.4.)
+
**NOTE:** database and service containers are exposed over TCP connections and
have their own local IP address. If the **socat** utility is installed inside your
Docker image, Drone will automatically proxy localhost connections to the correct
@@ -177,8 +205,8 @@ publish:
### Notifications
-Drone can trigger email, hipchat and web hook notification at the completion
-of your build:
+Drone can trigger email, hipchat and web hook notification at the beginning and
+completion of your build:
```
notify:
@@ -192,11 +220,39 @@ notify:
hipchat:
room: support
- token: 3028700e5466d375
+ token: 3028700e5466d375
+ on_started: true
+ on_success: true
+ on_failure: true
```
+### Git Command Options
+
+You can specify the `--depth` option of the `git clone` command (default value is `50`):
+
+```
+git:
+ depth: 1
+```
+
+### Params Injection
+
+You can inject params into .drone.yml.
+
+```
+notify:
+ hipchat:
+ room: {{hipchatRoom}}
+ token: {{hipchatToken}}
+ on_started: true
+ on_success: true
+ on_failure: true
+```
+
+![params-injection](https://f.cloud.github.com/assets/1583973/2161187/2905077e-94c3-11e3-8499-a3844682c8af.png)
+
### Docs
-Coming Soon to [drone.readthedocs.org](http://drone.readthedocs.org/)
-
+* [drone.readthedocs.org](http://drone.readthedocs.org/) (Coming Soon)
+* [GoDoc](http://godoc.org/github.com/drone/drone)
diff --git a/cmd/drone/drone.go b/cmd/drone/drone.go
index e3f97ee3b..9fdc6b3c7 100644
--- a/cmd/drone/drone.go
+++ b/cmd/drone/drone.go
@@ -164,7 +164,7 @@ func run(path string) {
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)
+ fmt.Printf("[Error] Could not find or read identity file %s\n", *identity)
os.Exit(1)
return
}
diff --git a/cmd/droned/assets/css/drone.css b/cmd/droned/assets/css/drone.css
index 0d8684d6d..3b84a14bd 100644
--- a/cmd/droned/assets/css/drone.css
+++ b/cmd/droned/assets/css/drone.css
@@ -1074,3 +1074,6 @@ ul.account-radio-group li img {
padding: 20px;
margin-bottom: 30px;
}
+.url {
+ word-break: break-all;
+}
diff --git a/cmd/droned/assets/css/drone.less b/cmd/droned/assets/css/drone.less
index 7820bb869..a88f913f4 100644
--- a/cmd/droned/assets/css/drone.less
+++ b/cmd/droned/assets/css/drone.less
@@ -1260,4 +1260,8 @@ pre {
padding: 20px;
margin-bottom:30px;
}
-}
\ No newline at end of file
+}
+
+.url {
+ word-break: break-all;
+}
diff --git a/cmd/droned/drone.go b/cmd/droned/drone.go
index 6397d134e..177732221 100644
--- a/cmd/droned/drone.go
+++ b/cmd/droned/drone.go
@@ -15,6 +15,7 @@ import (
"github.com/drone/drone/pkg/channel"
"github.com/drone/drone/pkg/database"
+ "github.com/drone/drone/pkg/database/migrate"
"github.com/drone/drone/pkg/handler"
)
@@ -55,8 +56,9 @@ func main() {
// setup the database connection and register with the
// global database package.
func setupDatabase() {
- // inform meddler we're using sqlite
+ // inform meddler and migration we're using sqlite
meddler.Default = meddler.SQLite
+ migrate.Driver = migrate.SQLite
// connect to the SQLite database
db, err := sql.Open(driver, datasource)
@@ -65,6 +67,9 @@ func setupDatabase() {
}
database.Set(db)
+
+ migration := migrate.New(db)
+ migration.All().Migrate()
}
// setup routes for static assets. These assets may
@@ -73,7 +78,18 @@ func setupDatabase() {
func setupStatic() {
box := rice.MustFindBox("assets")
http.Handle("/css/", http.FileServer(box.HTTPBox()))
- http.Handle("/img/", http.FileServer(box.HTTPBox()))
+
+ // we need to intercept all attempts to serve images
+ // so that we can add a cache-control settings
+ var images = http.FileServer(box.HTTPBox())
+ http.HandleFunc("/img/", func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasPrefix(r.URL.Path, "/img/build_") {
+ w.Header().Add("Cache-Control", "no-cache")
+ }
+
+ // serce images
+ images.ServeHTTP(w, r)
+ })
}
// setup routes for serving dynamic content.
@@ -86,6 +102,8 @@ func setupHandlers() {
m.Post("/forgot", handler.ErrorHandler(handler.ForgotPost))
m.Get("/reset", handler.ErrorHandler(handler.Reset))
m.Post("/reset", handler.ErrorHandler(handler.ResetPost))
+ m.Get("/signup", handler.ErrorHandler(handler.SignUp))
+ m.Post("/signup", handler.ErrorHandler(handler.SignUpPost))
m.Get("/register", handler.ErrorHandler(handler.Register))
m.Post("/register", handler.ErrorHandler(handler.RegisterPost))
m.Get("/accept", handler.UserHandler(handler.TeamMemberAccept))
@@ -145,8 +163,7 @@ func setupHandlers() {
m.Get("/:host/:owner/:name/commit/:commit/build/:label/out.txt", handler.RepoHandler(handler.BuildOut))
m.Get("/:host/:owner/:name/commit/:commit/build/:label", handler.RepoHandler(handler.CommitShow))
m.Get("/:host/:owner/:name/commit/:commit", handler.RepoHandler(handler.CommitShow))
- m.Get("/:host/:owner/:name/tree/:branch/status.png", handler.ErrorHandler(handler.Badge))
- m.Get("/:host/:owner/:name/tree/:branch", handler.RepoHandler(handler.RepoDashboard))
+ m.Get("/:host/:owner/:name/tree", handler.RepoHandler(handler.RepoDashboard))
m.Get("/:host/:owner/:name/status.png", handler.ErrorHandler(handler.Badge))
m.Get("/:host/:owner/:name/settings", handler.RepoAdminHandler(handler.RepoSettingsForm))
m.Get("/:host/:owner/:name/params", handler.RepoAdminHandler(handler.RepoParamsForm))
@@ -165,8 +182,6 @@ func setupHandlers() {
// the first time a page is requested we should record
// the scheme and hostname.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- // get the hostname and scheme
-
// our multiplexer is a bit finnicky and therefore requires
// us to strip any trailing slashes in order to correctly
// find and match a route.
diff --git a/deb/drone/DEBIAN/conffiles b/deb/drone/DEBIAN/conffiles
new file mode 100644
index 000000000..fb1baab83
--- /dev/null
+++ b/deb/drone/DEBIAN/conffiles
@@ -0,0 +1,2 @@
+/etc/init/drone.conf
+/etc/default/drone
diff --git a/deb/drone/DEBIAN/postinst b/deb/drone/DEBIAN/postinst
new file mode 100755
index 000000000..776f572b1
--- /dev/null
+++ b/deb/drone/DEBIAN/postinst
@@ -0,0 +1,24 @@
+#!/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
+
+echo "Starting drone ..."
+if [ -f /etc/init/drone.conf ]; then
+ if pidof /usr/local/bin/droned >/dev/null; then
+ service drone stop || exit $?
+ fi
+ service drone start && echo "Drone started."
+fi
+
+#DEBHELPER#
+
+exit 0
diff --git a/deb/drone/DEBIAN/prerm b/deb/drone/DEBIAN/prerm
new file mode 100755
index 000000000..a0f7be8cb
--- /dev/null
+++ b/deb/drone/DEBIAN/prerm
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -e
+set -u
+
+case "$1" in
+ remove|remove-in-favour|deconfigure|deconfigure-in-favour)
+ if [ -f /etc/init/drone.conf ]; then
+ echo "Stopping drone ..."
+ service drone stop || exit $?
+ echo "Drone Stopped."
+ fi
+ ;;
+
+ upgrade|failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/deb/drone/etc/default/drone b/deb/drone/etc/default/drone
new file mode 100644
index 000000000..1eaa2679b
--- /dev/null
+++ b/deb/drone/etc/default/drone
@@ -0,0 +1,10 @@
+# Upstart configuration file for droned.
+
+# Command line options:
+#
+# -datasource="drone.sqlite":
+# -driver="sqlite3":
+# -path="":
+# -port=":8080":
+#
+#DRONED_OPTS="--port=:80"
diff --git a/deb/drone/etc/init/drone.conf b/deb/drone/etc/init/drone.conf
index 102b2bc34..6cae6ac34 100644
--- a/deb/drone/etc/init/drone.conf
+++ b/deb/drone/etc/init/drone.conf
@@ -4,5 +4,9 @@ chdir /var/lib/drone
console log
script
- droned --port=":80"
-end script
\ No newline at end of file
+ DRONED_OPTS="--port=:80"
+ if [ -f /etc/default/$UPSTART_JOB ]; then
+ . /etc/default/$UPSTART_JOB
+ fi
+ droned $DRONED_OPTS
+end script
diff --git a/pkg/build/docker/client.go b/pkg/build/docker/client.go
index 1bfcbd660..bb33a28fb 100644
--- a/pkg/build/docker/client.go
+++ b/pkg/build/docker/client.go
@@ -32,15 +32,8 @@ var Logging = true
// New creates an instance of the Docker Client
func New() *Client {
c := &Client{}
- c.proto = DEFAULTPROTOCOL
- c.addr = DEFAULTUNIXSOCKET
- // if the default socket doesn't exist then
- // we'll try to connect to the default tcp address
- if _, err := os.Stat(DEFAULTUNIXSOCKET); err != nil {
- c.proto = "tcp"
- c.addr = "0.0.0.0:4243"
- }
+ c.setHost(DEFAULTUNIXSOCKET)
c.Images = &ImageService{c}
c.Containers = &ContainerService{c}
@@ -76,6 +69,26 @@ var (
ErrBadRequest = errors.New("Bad Request")
)
+func (c *Client) setHost(defaultUnixSocket string) {
+ c.proto = DEFAULTPROTOCOL
+ c.addr = defaultUnixSocket
+
+ if os.Getenv("DOCKER_HOST") != "" {
+ pieces := strings.Split(os.Getenv("DOCKER_HOST"), "://")
+ if len(pieces) == 2 {
+ c.proto = pieces[0]
+ c.addr = pieces[1]
+ }
+ } else {
+ // if the default socket doesn't exist then
+ // we'll try to connect to the default tcp address
+ if _, err := os.Stat(defaultUnixSocket); err != nil {
+ c.proto = "tcp"
+ c.addr = "0.0.0.0:4243"
+ }
+ }
+}
+
// helper function used to make HTTP requests to the Docker daemon.
func (c *Client) do(method, path string, in, out interface{}) error {
// if data input is provided, serialize to JSON
diff --git a/pkg/build/docker/client_test.go b/pkg/build/docker/client_test.go
new file mode 100644
index 000000000..6869fddcc
--- /dev/null
+++ b/pkg/build/docker/client_test.go
@@ -0,0 +1,67 @@
+package docker
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestHostFromEnv(t *testing.T) {
+ os.Setenv("DOCKER_HOST", "tcp://1.1.1.1:4243")
+ defer os.Setenv("DOCKER_HOST", "")
+
+ client := New()
+
+ if client.proto != "tcp" {
+ t.Fail()
+ }
+
+ if client.addr != "1.1.1.1:4243" {
+ t.Fail()
+ }
+}
+
+func TestInvalidHostFromEnv(t *testing.T) {
+ os.Setenv("DOCKER_HOST", "tcp:1.1.1.1:4243") // missing tcp:// prefix
+ defer os.Setenv("DOCKER_HOST", "")
+
+ client := New()
+
+ if client.addr == "1.1.1.1:4243" {
+ t.Fail()
+ }
+}
+
+func TestSocketHost(t *testing.T) {
+ // create temporary file to represent the docker socket
+ file, err := ioutil.TempFile("", "TestDefaultUnixHost")
+ if err != nil {
+ t.Fail()
+ }
+ file.Close()
+ defer os.Remove(file.Name())
+
+ client := &Client{}
+ client.setHost(file.Name())
+
+ if client.proto != "unix" {
+ t.Fail()
+ }
+
+ if client.addr != file.Name() {
+ t.Fail()
+ }
+}
+
+func TestDefaultTcpHost(t *testing.T) {
+ client := &Client{}
+ client.setHost("/tmp/missing_socket")
+
+ if client.proto != "tcp" {
+ t.Fail()
+ }
+
+ if client.addr != "0.0.0.0:4243" {
+ t.Fail()
+ }
+}
diff --git a/pkg/build/docker/image.go b/pkg/build/docker/image.go
index 259128f86..ca5eb0528 100644
--- a/pkg/build/docker/image.go
+++ b/pkg/build/docker/image.go
@@ -59,7 +59,7 @@ func (c *ImageService) List() ([]*Images, error) {
// Create an image, either by pull it from the registry or by importing it.
func (c *ImageService) Create(image string) error {
- return c.do("POST", fmt.Sprintf("/images/create?fromImage=%s"), nil, nil)
+ return c.do("POST", fmt.Sprintf("/images/create?fromImage=%s", image), nil, nil)
}
func (c *ImageService) Pull(image string) error {
@@ -110,7 +110,7 @@ func (c *ImageService) Build(tag, dir string) error {
v := url.Values{}
v.Set("t", tag)
v.Set("q", "1")
- //v.Set("rm", "1")
+ v.Set("rm", "1")
// url path
path := fmt.Sprintf("/build?%s", v.Encode())
@@ -120,5 +120,5 @@ func (c *ImageService) Build(tag, dir string) error {
headers.Set("Content-Type", "application/tar")
// make the request
- return c.stream("POST", path, body, nil, headers)
+ return c.stream("POST", path, body, os.Stdout, headers)
}
diff --git a/pkg/build/git/git.go b/pkg/build/git/git.go
new file mode 100644
index 000000000..36e67f13e
--- /dev/null
+++ b/pkg/build/git/git.go
@@ -0,0 +1,31 @@
+package git
+
+const (
+ DefaultGitDepth = 50
+)
+
+// Git stores the configuration details for
+// executing Git commands.
+type Git struct {
+ // Depth options instructs git to create a shallow
+ // clone with a history truncated to the specified
+ // number of revisions.
+ Depth *int `yaml:"depth,omitempty"`
+
+ // The name of a directory to clone into.
+ // TODO this still needs to be implemented. this field is
+ // critical for forked Go projects, that need to clone
+ // to a specific repository.
+ Path string `yaml:"path,omitempty"`
+}
+
+// GitDepth returns GitDefaultDepth
+// when Git.Depth is empty.
+// GitDepth returns Git.Depth
+// when it is not empty.
+func GitDepth(g *Git) int {
+ if g == nil || g.Depth == nil {
+ return DefaultGitDepth
+ }
+ return *g.Depth
+}
diff --git a/pkg/build/git/git_test.go b/pkg/build/git/git_test.go
new file mode 100644
index 000000000..23eb395ba
--- /dev/null
+++ b/pkg/build/git/git_test.go
@@ -0,0 +1,40 @@
+package git
+
+import (
+ "testing"
+)
+
+func TestGitDepth(t *testing.T) {
+ var g *Git
+ var expected int
+
+ expected = DefaultGitDepth
+ g = nil
+ if actual := GitDepth(g); actual != expected {
+ t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
+ }
+
+ expected = DefaultGitDepth
+ g = &Git{}
+ if actual := GitDepth(g); actual != expected {
+ t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
+ }
+
+ expected = DefaultGitDepth
+ g = &Git{Depth: nil}
+ if actual := GitDepth(g); actual != expected {
+ t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
+ }
+
+ expected = 0
+ g = &Git{Depth: &expected}
+ if actual := GitDepth(g); actual != expected {
+ t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
+ }
+
+ expected = 1
+ g = &Git{Depth: &expected}
+ if actual := GitDepth(g); actual != expected {
+ t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
+ }
+}
diff --git a/pkg/build/images.go b/pkg/build/images.go
index d887ee367..5c09820b1 100644
--- a/pkg/build/images.go
+++ b/pkg/build/images.go
@@ -116,7 +116,7 @@ var services = map[string]*image{
// couchdb
"couchdb": {
Ports: []string{"5984"},
- Tag: "bradrydzewski/couchdb:1.0",
+ Tag: "bradrydzewski/couchdb:1.5",
Name: "couchdb",
},
"couchdb:1.0": {
diff --git a/pkg/build/repo/repo.go b/pkg/build/repo/repo.go
index 581be9688..d1af38fac 100644
--- a/pkg/build/repo/repo.go
+++ b/pkg/build/repo/repo.go
@@ -33,6 +33,9 @@ type Repo struct {
// will be cloned into (or copied to) inside the
// host system (Docker Container).
Dir string
+
+ // (optional) The depth of the `git clone` command.
+ Depth int
}
// IsRemote returns true if the Repository is located
@@ -70,9 +73,9 @@ func (r *Repo) IsGit() bool {
return true
case strings.HasPrefix(r.Path, "ssh://git@"):
return true
- case strings.HasPrefix(r.Path, "https://github.com/"):
+ case strings.HasPrefix(r.Path, "https://github"):
return true
- case strings.HasPrefix(r.Path, "http://github.com"):
+ case strings.HasPrefix(r.Path, "http://github"):
return true
case strings.HasSuffix(r.Path, ".git"):
return true
@@ -88,7 +91,6 @@ func (r *Repo) IsGit() bool {
//
// TODO we should also enable Mercurial projects and SVN projects
func (r *Repo) Commands() []string {
-
// get the branch. default to master
// if no branch exists.
branch := r.Branch
@@ -97,7 +99,7 @@ func (r *Repo) Commands() []string {
}
cmds := []string{}
- cmds = append(cmds, fmt.Sprintf("git clone --branch=%s %s %s", branch, r.Path, r.Dir))
+ cmds = append(cmds, fmt.Sprintf("git clone --depth=%d --recursive --branch=%s %s %s", r.Depth, branch, r.Path, r.Dir))
switch {
// if a specific commit is provided then we'll
diff --git a/pkg/build/script/deployment/appfog.go b/pkg/build/script/deployment/appfog.go
deleted file mode 100644
index 367fd2709..000000000
--- a/pkg/build/script/deployment/appfog.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type AppFog struct {
-}
-
-func (a *AppFog) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/cloudcontrol.go b/pkg/build/script/deployment/cloudcontrol.go
deleted file mode 100644
index 881410617..000000000
--- a/pkg/build/script/deployment/cloudcontrol.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type CloudControl struct {
-}
-
-func (c *CloudControl) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/cloudfoundry.go b/pkg/build/script/deployment/cloudfoundry.go
deleted file mode 100644
index 1f4620818..000000000
--- a/pkg/build/script/deployment/cloudfoundry.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type CloudFoundry struct {
-}
-
-func (c *CloudFoundry) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/deployment.go b/pkg/build/script/deployment/deployment.go
deleted file mode 100644
index eb26dbc2b..000000000
--- a/pkg/build/script/deployment/deployment.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-// Deploy stores the configuration details
-// for deploying build artifacts when
-// a Build has succeeded
-type Deploy struct {
- AppFog *AppFog `yaml:"appfog,omitempty"`
- CloudControl *CloudControl `yaml:"cloudcontrol,omitempty"`
- CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"`
- EngineYard *EngineYard `yaml:"engineyard,omitempty"`
- Heroku *Heroku `yaml:"heroku,omitempty"`
- Nodejitsu *Nodejitsu `yaml:"nodejitsu,omitempty"`
- Openshift *Openshift `yaml:"openshift,omitempty"`
-}
-
-func (d *Deploy) Write(f *buildfile.Buildfile) {
- if d.AppFog != nil {
- d.AppFog.Write(f)
- }
- if d.CloudControl != nil {
- d.CloudControl.Write(f)
- }
- if d.CloudFoundry != nil {
- d.CloudFoundry.Write(f)
- }
- if d.EngineYard != nil {
- d.EngineYard.Write(f)
- }
- if d.Heroku != nil {
- d.Heroku.Write(f)
- }
- if d.Nodejitsu != nil {
- d.Nodejitsu.Write(f)
- }
- if d.Openshift != nil {
- d.Openshift.Write(f)
- }
-}
diff --git a/pkg/build/script/deployment/engineyard.go b/pkg/build/script/deployment/engineyard.go
deleted file mode 100644
index 8aefa93d1..000000000
--- a/pkg/build/script/deployment/engineyard.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type EngineYard struct {
-}
-
-func (e *EngineYard) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/git.go b/pkg/build/script/deployment/git.go
deleted file mode 100644
index 3c65985a3..000000000
--- a/pkg/build/script/deployment/git.go
+++ /dev/null
@@ -1 +0,0 @@
-package deployment
diff --git a/pkg/build/script/deployment/heroku.go b/pkg/build/script/deployment/heroku.go
deleted file mode 100644
index 60dd2e371..000000000
--- a/pkg/build/script/deployment/heroku.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package deployment
-
-import (
- "fmt"
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type Heroku struct {
- App string `yaml:"app,omitempty"`
- Force bool `yaml:"force,omitempty"`
- Branch string `yaml:"branch,omitempty"`
-}
-
-func (h *Heroku) Write(f *buildfile.Buildfile) {
- // get the current commit hash
- f.WriteCmdSilent("COMMIT=$(git rev-parse HEAD)")
-
- // set the git user and email based on the individual
- // that made the commit.
- f.WriteCmdSilent("git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')")
- f.WriteCmdSilent("git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')")
-
- // add heroku as a git remote
- f.WriteCmd(fmt.Sprintf("git remote add heroku 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 $COMMIT:master --force"))
- case false:
- // otherwise we just do a standard git push
- f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master"))
- }
-}
diff --git a/pkg/build/script/deployment/nodejitsu.go b/pkg/build/script/deployment/nodejitsu.go
deleted file mode 100644
index 6a0af8a3a..000000000
--- a/pkg/build/script/deployment/nodejitsu.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type Nodejitsu struct {
-}
-
-func (n *Nodejitsu) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/openshift.go b/pkg/build/script/deployment/openshift.go
deleted file mode 100644
index dc325c742..000000000
--- a/pkg/build/script/deployment/openshift.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package deployment
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type Openshift struct {
-}
-
-func (o *Openshift) Write(f *buildfile.Buildfile) {
-
-}
diff --git a/pkg/build/script/deployment/ssh.go b/pkg/build/script/deployment/ssh.go
deleted file mode 100644
index 3c65985a3..000000000
--- a/pkg/build/script/deployment/ssh.go
+++ /dev/null
@@ -1 +0,0 @@
-package deployment
diff --git a/pkg/build/script/notification/email.go b/pkg/build/script/notification/email.go
deleted file mode 100644
index cdb39932b..000000000
--- a/pkg/build/script/notification/email.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package notification
-
-import (
- "fmt"
- "net/smtp"
-)
-
-type Email struct {
- Recipients []string `yaml:"recipients,omitempty"`
- Success string `yaml:"on_success"`
- Failure string `yaml:"on_failure"`
-
- host string // smtp host address
- port string // smtp host port
- user string // smtp username for authentication
- pass string // smtp password for authentication
- from string // smtp email address. send from this address
-}
-
-// SetServer is a function that will set the SMTP
-// server location and credentials
-func (e *Email) SetServer(host, port, user, pass, from string) {
- e.host = host
- e.port = port
- e.user = user
- e.pass = pass
- e.from = from
-}
-
-// Send will send an email, either success or failure,
-// based on the Commit Status.
-func (e *Email) Send(context *Context) error {
- switch {
- case context.Commit.Status == "Success" && e.Success != "never":
- return e.sendSuccess(context)
- case context.Commit.Status == "Failure" && e.Failure != "never":
- return e.sendFailure(context)
- }
-
- return nil
-}
-
-// sendFailure sends email notifications to the list of
-// recipients indicating the build failed.
-func (e *Email) sendFailure(context *Context) error {
- // loop through and email recipients
- /*for _, email := range e.Recipients {
- if err := mail.SendFailure(context.Repo.Slug, email, context); err != nil {
- return err
- }
- }*/
- return nil
-}
-
-// sendSuccess sends email notifications to the list of
-// recipients indicating the build was a success.
-func (e *Email) sendSuccess(context *Context) error {
- // loop through and email recipients
- /*for _, email := range e.Recipients {
- if err := mail.SendSuccess(context.Repo.Slug, email, context); err != nil {
- return err
- }
- }*/
- return nil
-}
-
-// send is a simple helper function to format and
-// send an email message.
-func (e *Email) send(to, subject, body string) error {
- // Format the raw email message body
- raw := fmt.Sprintf(emailTemplate, e.from, to, subject, body)
- auth := smtp.PlainAuth("", e.user, e.pass, e.host)
- addr := fmt.Sprintf("%s:%s", e.host, e.port)
-
- return smtp.SendMail(addr, auth, e.from, []string{to}, []byte(raw))
-}
-
-// text-template used to generate a raw Email message
-var emailTemplate = `From: %s
-To: %s
-Subject: %s
-MIME-version: 1.0
-Content-Type: text/html; charset="UTF-8"
-
-%s`
diff --git a/pkg/build/script/notification/hipchat.go b/pkg/build/script/notification/hipchat.go
deleted file mode 100644
index 5e8def651..000000000
--- a/pkg/build/script/notification/hipchat.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package notification
-
-import (
- "fmt"
-
- "github.com/andybons/hipchat"
-)
-
-const (
- startedMessage = "Building %s, commit %s, author %s"
- successMessage = "Success %s, commit %s, author %s"
- failureMessage = "Failed %s, commit %s, author %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"`
-}
-
-func (h *Hipchat) Send(context *Context) error {
- switch {
- case context.Commit.Status == "Started" && h.Started:
- return h.sendStarted(context)
- case context.Commit.Status == "Success" && h.Success:
- return h.sendSuccess(context)
- case context.Commit.Status == "Failure" && h.Failure:
- return h.sendFailure(context)
- }
-
- return nil
-}
-
-func (h *Hipchat) sendStarted(context *Context) error {
- msg := fmt.Sprintf(startedMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
- return h.send(hipchat.ColorYellow, hipchat.FormatHTML, msg)
-}
-
-func (h *Hipchat) sendFailure(context *Context) error {
- msg := fmt.Sprintf(failureMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
- return h.send(hipchat.ColorRed, hipchat.FormatHTML, msg)
-}
-
-func (h *Hipchat) sendSuccess(context *Context) error {
- msg := fmt.Sprintf(successMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
- return h.send(hipchat.ColorGreen, hipchat.FormatHTML, msg)
-}
-
-// helper function to send Hipchat requests
-func (h *Hipchat) send(color, format, message string) error {
- c := hipchat.Client{AuthToken: h.Token}
- req := hipchat.MessageRequest{
- RoomId: h.Room,
- From: "Drone",
- Message: message,
- Color: color,
- MessageFormat: format,
- Notify: true,
- }
-
- return c.PostMessage(req)
-}
diff --git a/pkg/build/script/notification/irc.go b/pkg/build/script/notification/irc.go
deleted file mode 100644
index 4306c87f1..000000000
--- a/pkg/build/script/notification/irc.go
+++ /dev/null
@@ -1 +0,0 @@
-package notification
diff --git a/pkg/build/script/notification/notification.go b/pkg/build/script/notification/notification.go
deleted file mode 100644
index bea1b6e71..000000000
--- a/pkg/build/script/notification/notification.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package notification
-
-import (
- "github.com/drone/drone/pkg/model"
-)
-
-// Context represents the context of an
-// in-progress build request.
-type Context struct {
- // Global settings
- Host string
-
- // User that owns the repository
- User *model.User
-
- // Repository being built.
- Repo *model.Repo
-
- // Commit being built
- Commit *model.Commit
-}
-
-type Sender interface {
- Send(context *Context) error
-}
-
-// Notification stores the configuration details
-// for notifying a user, or group of users,
-// when their Build has completed.
-type Notification struct {
- Email *Email `yaml:"email,omitempty"`
- Webhook *Webhook `yaml:"webhook,omitempty"`
- Hipchat *Hipchat `yaml:"hipchat,omitempty"`
-}
-
-func (n *Notification) Send(context *Context) error {
- // send email notifications
- //if n.Email != nil && n.Email.Enabled {
- // n.Email.Send(context)
- //}
-
- // send email notifications
- if n.Webhook != nil {
- n.Webhook.Send(context)
- }
-
- // send email notifications
- if n.Hipchat != nil {
- n.Hipchat.Send(context)
- }
-
- return nil
-}
diff --git a/pkg/build/script/notification/webhook.go b/pkg/build/script/notification/webhook.go
deleted file mode 100644
index 649fbbe2c..000000000
--- a/pkg/build/script/notification/webhook.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package notification
-
-import (
- "bytes"
- "encoding/json"
- "net/http"
-
- "github.com/drone/drone/pkg/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 *Context) error {
- switch {
- case context.Commit.Status == "Success" && w.Success:
- return w.send(context)
- case context.Commit.Status == "Failure" && w.Failure:
- return w.send(context)
- }
-
- return nil
-}
-
-// helper function to send HTTP requests
-func (w *Webhook) send(context *Context) error {
- // data will get posted in this format
- data := struct {
- Owner *model.User `json:"owner"`
- Repo *model.Repo `json:"repository"`
- Commit *model.Commit `json:"commit"`
- }{context.User, context.Repo, context.Commit}
-
- // data json encoded
- payload, err := json.Marshal(data)
- if err != nil {
- return err
- }
-
- // loop through and email recipients
- 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()
-}
diff --git a/pkg/build/script/notification/zapier.go b/pkg/build/script/notification/zapier.go
deleted file mode 100644
index 4306c87f1..000000000
--- a/pkg/build/script/notification/zapier.go
+++ /dev/null
@@ -1 +0,0 @@
-package notification
diff --git a/pkg/build/script/publish/bintray.go b/pkg/build/script/publish/bintray.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/bintray.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/dropbox.go b/pkg/build/script/publish/dropbox.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/dropbox.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/gems.go b/pkg/build/script/publish/gems.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/gems.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/maven.go b/pkg/build/script/publish/maven.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/maven.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/npm.go b/pkg/build/script/publish/npm.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/npm.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/pub.go b/pkg/build/script/publish/pub.go
deleted file mode 100644
index 30b1a5b2a..000000000
--- a/pkg/build/script/publish/pub.go
+++ /dev/null
@@ -1 +0,0 @@
-package publish
diff --git a/pkg/build/script/publish/publish.go b/pkg/build/script/publish/publish.go
deleted file mode 100644
index d31088f29..000000000
--- a/pkg/build/script/publish/publish.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package publish
-
-import (
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-// Publish stores the configuration details
-// for publishing build artifacts when
-// a Build has succeeded
-type Publish struct {
- S3 *S3 `yaml:"s3,omitempty"`
-}
-
-func (p *Publish) Write(f *buildfile.Buildfile) {
- if p.S3 != nil {
- p.S3.Write(f)
- }
-}
diff --git a/pkg/build/script/publish/pypi.go b/pkg/build/script/publish/pypi.go
deleted file mode 100644
index d46cd60ca..000000000
--- a/pkg/build/script/publish/pypi.go
+++ /dev/null
@@ -1,2 +0,0 @@
-package publish
-
diff --git a/pkg/build/script/publish/s3.go b/pkg/build/script/publish/s3.go
deleted file mode 100644
index cfa75e7d1..000000000
--- a/pkg/build/script/publish/s3.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package publish
-
-import (
- "fmt"
- "strings"
-
- "github.com/drone/drone/pkg/build/buildfile"
-)
-
-type S3 struct {
- Key string `yaml:"access_key,omitempty"`
- Secret string `yaml:"secret_key,omitempty"`
- Bucket string `yaml:"bucket,omitempty"`
-
- // us-east-1
- // us-west-1
- // us-west-2
- // eu-west-1
- // ap-southeast-1
- // ap-southeast-2
- // ap-northeast-1
- // sa-east-1
- Region string `yaml:"region,omitempty"`
-
- // Indicates the files ACL, which should be one
- // of the following:
- // private
- // public-read
- // public-read-write
- // authenticated-read
- // bucket-owner-read
- // bucket-owner-full-control
- Access string `yaml:"acl,omitempty"`
-
- // Copies the files from the specified directory.
- // Regexp matching will apply to match multiple
- // files
- //
- // Examples:
- // /path/to/file
- // /path/to/*.txt
- // /path/to/*/*.txt
- // /path/to/**
- Source string `yaml:"source,omitempty"`
- Target string `yaml:"target,omitempty"`
-
- // Recursive uploads
- Recursive bool `yaml:"recursive"`
-
- Branch string `yaml:"branch,omitempty"`
-}
-
-func (s *S3) Write(f *buildfile.Buildfile) {
- // install the AWS cli using PIP
- f.WriteCmdSilent("[ -f /usr/bin/sudo ] || pip install awscli 1> /dev/null 2> /dev/null")
- f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo pip install awscli 1> /dev/null 2> /dev/null")
-
- f.WriteEnv("AWS_ACCESS_KEY_ID", s.Key)
- f.WriteEnv("AWS_SECRET_ACCESS_KEY", s.Secret)
-
- // make sure a default region is set
- if len(s.Region) == 0 {
- s.Region = "us-east-1"
- }
-
- // make sure a default access is set
- // let's be conservative and assume private
- if len(s.Region) == 0 {
- s.Region = "private"
- }
-
- // if the target starts with a "/" we need
- // to remove it, otherwise we might adding
- // a 3rd slash to s3://
- if strings.HasPrefix(s.Target, "/") {
- s.Target = s.Target[1:]
- }
-
- switch s.Recursive {
- case true:
- f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --recursive --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region))
- case false:
- f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region))
- }
-}
diff --git a/pkg/build/script/report/README.md b/pkg/build/script/report/README.md
deleted file mode 100644
index 03260a5b1..000000000
--- a/pkg/build/script/report/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-cobertura.go
-coveralls.go
-gocov.go
-junit.go
-phpunit.go
\ No newline at end of file
diff --git a/pkg/build/script/script.go b/pkg/build/script/script.go
index 72af02a0f..032df3844 100644
--- a/pkg/build/script/script.go
+++ b/pkg/build/script/script.go
@@ -1,22 +1,25 @@
package script
import (
+ "bytes"
+ "fmt"
"io/ioutil"
"strings"
"launchpad.net/goyaml"
"github.com/drone/drone/pkg/build/buildfile"
- "github.com/drone/drone/pkg/build/script/deployment"
- "github.com/drone/drone/pkg/build/script/notification"
- "github.com/drone/drone/pkg/build/script/publish"
+ "github.com/drone/drone/pkg/build/git"
+ "github.com/drone/drone/pkg/plugin/deploy"
+ "github.com/drone/drone/pkg/plugin/notify"
+ "github.com/drone/drone/pkg/plugin/publish"
)
-func ParseBuild(data []byte) (*Build, error) {
+func ParseBuild(data []byte, params map[string]string) (*Build, error) {
build := Build{}
// parse the build configuration file
- err := goyaml.Unmarshal(data, &build)
+ err := goyaml.Unmarshal(injectParams(data, params), &build)
return &build, err
}
@@ -26,7 +29,15 @@ func ParseBuildFile(filename string) (*Build, error) {
return nil, err
}
- return ParseBuild(data)
+ return ParseBuild(data, nil)
+}
+
+// injectParams injects params into data.
+func injectParams(data []byte, params map[string]string) []byte {
+ for k, v := range params {
+ data = bytes.Replace(data, []byte(fmt.Sprintf("{{%s}}", k)), []byte(v), -1)
+ }
+ return data
}
// Build stores the configuration details for
@@ -51,9 +62,10 @@ type Build struct {
// linked to the build environment.
Services []string
- Deploy *deployment.Deploy `yaml:"deploy,omitempty"`
- Publish *publish.Publish `yaml:"publish,omitempty"`
- Notifications *notification.Notification `yaml:"notify,omitempty"`
+ Deploy *deploy.Deploy `yaml:"deploy,omitempty"`
+ Publish *publish.Publish `yaml:"publish,omitempty"`
+ Notifications *notify.Notification `yaml:"notify,omitempty"`
+ Git *git.Git `yaml:"git,omitempty"`
}
// Write adds all the steps to the build script, including
diff --git a/pkg/database/migrate/201402200603_rename_privelege_to_privilege.go b/pkg/database/migrate/201402200603_rename_privelege_to_privilege.go
new file mode 100644
index 000000000..379a6649e
--- /dev/null
+++ b/pkg/database/migrate/201402200603_rename_privelege_to_privilege.go
@@ -0,0 +1,23 @@
+package migrate
+
+type Rev1 struct{}
+
+var RenamePrivelegedToPrivileged = &Rev1{}
+
+func (r *Rev1) Revision() int64 {
+ return 201402200603
+}
+
+func (r *Rev1) Up(op Operation) error {
+ _, err := op.RenameColumns("repos", map[string]string{
+ "priveleged": "privileged",
+ })
+ return err
+}
+
+func (r *Rev1) Down(op Operation) error {
+ _, err := op.RenameColumns("repos", map[string]string{
+ "privileged": "priveleged",
+ })
+ return err
+}
diff --git a/pkg/database/migrate/201402211147_github_enterprise_support.go b/pkg/database/migrate/201402211147_github_enterprise_support.go
new file mode 100644
index 000000000..dcdd5e8f1
--- /dev/null
+++ b/pkg/database/migrate/201402211147_github_enterprise_support.go
@@ -0,0 +1,26 @@
+package migrate
+
+type Rev3 struct{}
+
+var GitHubEnterpriseSupport = &Rev3{}
+
+func (r *Rev3) Revision() int64 {
+ return 201402211147
+}
+
+func (r *Rev3) Up(op Operation) error {
+ _, err := op.AddColumn("settings", "github_domain VARCHAR(255)")
+ if err != nil {
+ return err
+ }
+ _, err = op.AddColumn("settings", "github_apiurl VARCHAR(255)")
+
+ op.Exec("update settings set github_domain=?", "github.com")
+ op.Exec("update settings set github_apiurl=?", "https://api.github.com")
+ return err
+}
+
+func (r *Rev3) Down(op Operation) error {
+ _, err := op.DropColumns("settings", []string{"github_domain", "github_apiurl"})
+ return err
+}
diff --git a/pkg/database/migrate/all.go b/pkg/database/migrate/all.go
new file mode 100644
index 000000000..ec5facdca
--- /dev/null
+++ b/pkg/database/migrate/all.go
@@ -0,0 +1,12 @@
+package migrate
+
+func (m *Migration) All() *Migration {
+
+ // List all migrations here
+ m.Add(RenamePrivelegedToPrivileged)
+ m.Add(GitHubEnterpriseSupport)
+
+ // m.Add(...)
+ // ...
+ return m
+}
diff --git a/pkg/database/migrate/migrate.go b/pkg/database/migrate/migrate.go
new file mode 100644
index 000000000..63cef3914
--- /dev/null
+++ b/pkg/database/migrate/migrate.go
@@ -0,0 +1,215 @@
+// Usage
+// migrate.To(2)
+// .Add(Version_1)
+// .Add(Version_2)
+// .Add(Version_3)
+// .Exec(db)
+//
+// migrate.ToLatest()
+// .Add(Version_1)
+// .Add(Version_2)
+// .Add(Version_3)
+// .SetDialect(migrate.MySQL)
+// .Exec(db)
+//
+// migrate.ToLatest()
+// .Add(Version_1)
+// .Add(Version_2)
+// .Add(Version_3)
+// .Backup(path)
+// .Exec()
+
+package migrate
+
+import (
+ "database/sql"
+ "log"
+)
+
+const migrationTableStmt = `
+CREATE TABLE IF NOT EXISTS migration (
+ revision NUMBER PRIMARY KEY
+)
+`
+
+const migrationSelectStmt = `
+SELECT revision FROM migration
+WHERE revision = ?
+`
+
+const migrationSelectMaxStmt = `
+SELECT max(revision) FROM migration
+`
+
+const insertRevisionStmt = `
+INSERT INTO migration (revision) VALUES (?)
+`
+
+const deleteRevisionStmt = `
+DELETE FROM migration where revision = ?
+`
+
+// Operation interface covers basic migration operations.
+// Implementation details is specific for each database,
+// see migrate/sqlite.go for implementation reference.
+type Operation interface {
+ CreateTable(tableName string, args []string) (sql.Result, error)
+
+ RenameTable(tableName, newName string) (sql.Result, error)
+
+ DropTable(tableName string) (sql.Result, error)
+
+ AddColumn(tableName, columnSpec string) (sql.Result, error)
+
+ DropColumns(tableName string, columnsToDrop []string) (sql.Result, error)
+
+ RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error)
+
+ Exec(query string, args ...interface{}) (sql.Result, error)
+
+ Query(query string, args ...interface{}) (*sql.Rows, error)
+
+ QueryRow(query string, args ...interface{}) *sql.Row
+}
+
+type Revision interface {
+ Up(op Operation) error
+ Down(op Operation) error
+ Revision() int64
+}
+
+type MigrationDriver struct {
+ Tx *sql.Tx
+}
+
+type Migration struct {
+ db *sql.DB
+ revs []Revision
+}
+
+var Driver func(tx *sql.Tx) Operation
+
+func New(db *sql.DB) *Migration {
+ return &Migration{db: db}
+}
+
+// Add the Revision to the list of migrations.
+func (m *Migration) Add(rev ...Revision) *Migration {
+ m.revs = append(m.revs, rev...)
+ return m
+}
+
+// Execute the full list of migrations.
+func (m *Migration) Migrate() error {
+ var target int64
+ if len(m.revs) > 0 {
+ // get the last revision number in
+ // the list. This is what we'll
+ // migrate toward.
+ target = m.revs[len(m.revs)-1].Revision()
+ }
+ return m.MigrateTo(target)
+}
+
+// Execute all database migration until
+// you are at the specified revision number.
+// If the revision number is less than the
+// current revision, then we will downgrade.
+func (m *Migration) MigrateTo(target int64) error {
+
+ // make sure the migration table is created.
+ if _, err := m.db.Exec(migrationTableStmt); err != nil {
+ return err
+ }
+
+ // get the current revision
+ var current int64
+ m.db.QueryRow(migrationSelectMaxStmt).Scan(¤t)
+
+ // already up to date
+ if current == target {
+ log.Println("Database already up-to-date.")
+ return nil
+ }
+
+ // should we downgrade?
+ if target < current {
+ return m.down(target, current)
+ }
+
+ // else upgrade
+ return m.up(target, current)
+}
+
+func (m *Migration) up(target, current int64) error {
+ // create the database transaction
+ tx, err := m.db.Begin()
+ if err != nil {
+ return err
+ }
+
+ op := Driver(tx)
+
+ // loop through and execute revisions
+ for _, rev := range m.revs {
+ if rev.Revision() > current && rev.Revision() <= target {
+ current = rev.Revision()
+ // execute the revision Upgrade.
+ if err := rev.Up(op); err != nil {
+ log.Printf("Failed to upgrade to Revision Number %v\n", current)
+ log.Println(err)
+ return tx.Rollback()
+ }
+ // update the revision number in the database
+ if _, err := tx.Exec(insertRevisionStmt, current); err != nil {
+ log.Printf("Failed to register Revision Number %v\n", current)
+ log.Println(err)
+ return tx.Rollback()
+ }
+
+ log.Printf("Successfully upgraded to Revision %v\n", current)
+ }
+ }
+
+ return tx.Commit()
+}
+
+func (m *Migration) down(target, current int64) error {
+ // create the database transaction
+ tx, err := m.db.Begin()
+ if err != nil {
+ return err
+ }
+
+ op := Driver(tx)
+
+ // reverse the list of revisions
+ revs := []Revision{}
+ for _, rev := range m.revs {
+ revs = append([]Revision{rev}, revs...)
+ }
+
+ // loop through the (reversed) list of
+ // revisions and execute.
+ for _, rev := range revs {
+ if rev.Revision() > target {
+ current = rev.Revision()
+ // execute the revision Upgrade.
+ if err := rev.Down(op); err != nil {
+ log.Printf("Failed to downgrade from Revision Number %v\n", current)
+ log.Println(err)
+ return tx.Rollback()
+ }
+ // update the revision number in the database
+ if _, err := tx.Exec(deleteRevisionStmt, current); err != nil {
+ log.Printf("Failed to unregistser Revision Number %v\n", current)
+ log.Println(err)
+ return tx.Rollback()
+ }
+
+ log.Printf("Successfully downgraded from Revision %v\n", current)
+ }
+ }
+
+ return tx.Commit()
+}
diff --git a/pkg/database/migrate/migration b/pkg/database/migrate/migration
new file mode 100755
index 000000000..3e3a2ff63
--- /dev/null
+++ b/pkg/database/migrate/migration
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+REV=$(date -u +%Y%m%d%H%M%S)
+filename=$1
+
+TAB="$(printf '\t')"
+
+titleize() {
+ echo "$1" | sed -r -e "s/-|_/ /g" -e 's/\b(.)/\U\1/g' -e 's/ //g'
+}
+
+cat > ${REV}_$filename.go << EOF
+package migrate
+
+type rev${REV} struct{}
+
+var $(titleize $filename) = &rev${REV}{}
+
+func (r *rev$REV) Revision() int64 {
+${TAB}return $REV
+}
+
+func (r *rev$REV) Up(op Operation) error {
+${TAB}// Migration steps here
+}
+
+func (r *rev$REV) Down(op Operation) error {
+${TAB}// Revert migration steps here
+}
+EOF
diff --git a/pkg/database/migrate/sqlite.go b/pkg/database/migrate/sqlite.go
new file mode 100644
index 000000000..2cec5a026
--- /dev/null
+++ b/pkg/database/migrate/sqlite.go
@@ -0,0 +1,283 @@
+package migrate
+
+import (
+ "database/sql"
+ "fmt"
+ "strings"
+
+ "github.com/dchest/uniuri"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+type SQLiteDriver MigrationDriver
+
+func SQLite(tx *sql.Tx) Operation {
+ return &SQLiteDriver{Tx: tx}
+}
+
+func (s *SQLiteDriver) Exec(query string, args ...interface{}) (sql.Result, error) {
+ return s.Tx.Exec(query, args...)
+}
+
+func (s *SQLiteDriver) Query(query string, args ...interface{}) (*sql.Rows, error) {
+ return s.Tx.Query(query, args...)
+}
+
+func (s *SQLiteDriver) QueryRow(query string, args ...interface{}) *sql.Row {
+ return s.Tx.QueryRow(query, args...)
+}
+
+func (s *SQLiteDriver) CreateTable(tableName string, args []string) (sql.Result, error) {
+ return s.Tx.Exec(fmt.Sprintf("CREATE TABLE %s (%s);", tableName, strings.Join(args, ", ")))
+}
+
+func (s *SQLiteDriver) RenameTable(tableName, newName string) (sql.Result, error) {
+ return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s;", tableName, newName))
+}
+
+func (s *SQLiteDriver) DropTable(tableName string) (sql.Result, error) {
+ return s.Tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s;", tableName))
+}
+
+func (s *SQLiteDriver) AddColumn(tableName, columnSpec string) (sql.Result, error) {
+ return s.Tx.Exec(fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s;", tableName, columnSpec))
+}
+
+func (s *SQLiteDriver) DropColumns(tableName string, columnsToDrop []string) (sql.Result, error) {
+ var err error
+ var result sql.Result
+
+ if len(columnsToDrop) == 0 {
+ return nil, fmt.Errorf("No columns to drop.")
+ }
+
+ tableSQL, err := s.getDDLFromTable(tableName)
+ if err != nil {
+ return nil, err
+ }
+
+ columns, err := fetchColumns(tableSQL)
+ if err != nil {
+ return nil, err
+ }
+
+ columnNames := selectName(columns)
+
+ var preparedColumns []string
+ for k, column := range columnNames {
+ listed := false
+ for _, dropped := range columnsToDrop {
+ if column == dropped {
+ listed = true
+ break
+ }
+ }
+ if !listed {
+ preparedColumns = append(preparedColumns, columns[k])
+ }
+ }
+
+ if len(preparedColumns) == 0 {
+ return nil, fmt.Errorf("No columns match, drops nothing.")
+ }
+
+ // fetch indices for this table
+ oldSQLIndices, err := s.getDDLFromIndex(tableName)
+ if err != nil {
+ return nil, err
+ }
+
+ var oldIdxColumns [][]string
+ for _, idx := range oldSQLIndices {
+ idxCols, err := fetchColumns(idx)
+ if err != nil {
+ return nil, err
+ }
+ oldIdxColumns = append(oldIdxColumns, idxCols)
+ }
+
+ var indices []string
+ for k, idx := range oldSQLIndices {
+ listed := false
+ OIdxLoop:
+ for _, oidx := range oldIdxColumns[k] {
+ for _, cols := range columnsToDrop {
+ if oidx == cols {
+ listed = true
+ break OIdxLoop
+ }
+ }
+ }
+ if !listed {
+ indices = append(indices, idx)
+ }
+ }
+
+ // Rename old table, here's our proxy
+ proxyName := fmt.Sprintf("%s_%s", tableName, uniuri.NewLen(16))
+ if result, err := s.RenameTable(tableName, proxyName); err != nil {
+ return result, err
+ }
+
+ // Recreate table with dropped columns omitted
+ if result, err = s.CreateTable(tableName, preparedColumns); err != nil {
+ return result, err
+ }
+
+ // Move data from old table
+ if result, err = s.Tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT %s FROM %s;", tableName,
+ strings.Join(selectName(preparedColumns), ", "), proxyName)); err != nil {
+ return result, err
+ }
+
+ // Clean up proxy table
+ if result, err = s.DropTable(proxyName); err != nil {
+ return result, err
+ }
+
+ // Recreate Indices
+ for _, idx := range indices {
+ if result, err = s.Tx.Exec(idx); err != nil {
+ return result, err
+ }
+ }
+ return result, err
+}
+
+func (s *SQLiteDriver) RenameColumns(tableName string, columnChanges map[string]string) (sql.Result, error) {
+ var err error
+ var result sql.Result
+
+ tableSQL, err := s.getDDLFromTable(tableName)
+ if err != nil {
+ return nil, err
+ }
+
+ columns, err := fetchColumns(tableSQL)
+ if err != nil {
+ return nil, err
+ }
+
+ // We need a list of columns name to migrate data to the new table
+ var oldColumnsName = selectName(columns)
+
+ // newColumns will be used to create the new table
+ var newColumns []string
+
+ for k, column := range oldColumnsName {
+ added := false
+ for Old, New := range columnChanges {
+ if column == Old {
+ columnToAdd := strings.Replace(columns[k], Old, New, 1)
+ newColumns = append(newColumns, columnToAdd)
+ added = true
+ break
+ }
+ }
+ if !added {
+ newColumns = append(newColumns, columns[k])
+ }
+ }
+
+ // fetch indices for this table
+ oldSQLIndices, err := s.getDDLFromIndex(tableName)
+ if err != nil {
+ return nil, err
+ }
+
+ var idxColumns [][]string
+ for _, idx := range oldSQLIndices {
+ idxCols, err := fetchColumns(idx)
+ if err != nil {
+ return nil, err
+ }
+ idxColumns = append(idxColumns, idxCols)
+ }
+
+ var indices []string
+ for k, idx := range oldSQLIndices {
+ added := false
+ IdcLoop:
+ for _, oldIdx := range idxColumns[k] {
+ for Old, New := range columnChanges {
+ if oldIdx == Old {
+ indx := strings.Replace(idx, Old, New, 2)
+ indices = append(indices, indx)
+ added = true
+ break IdcLoop
+ }
+ }
+ }
+ if !added {
+ indices = append(indices, idx)
+ }
+ }
+
+ // Rename current table
+ proxyName := fmt.Sprintf("%s_%s", tableName, uniuri.NewLen(16))
+ if result, err := s.RenameTable(tableName, proxyName); err != nil {
+ return result, err
+ }
+
+ // Create new table with the new columns
+ if result, err = s.CreateTable(tableName, newColumns); err != nil {
+ return result, err
+ }
+
+ // Migrate data
+ if result, err = s.Tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT %s FROM %s", tableName,
+ strings.Join(oldColumnsName, ", "), proxyName)); err != nil {
+ return result, err
+ }
+
+ // Clean up proxy table
+ if result, err = s.DropTable(proxyName); err != nil {
+ return result, err
+ }
+
+ for _, idx := range indices {
+ if result, err = s.Tx.Exec(idx); err != nil {
+ return result, err
+ }
+ }
+ return result, err
+}
+
+func (s *SQLiteDriver) getDDLFromTable(tableName string) (string, error) {
+ var sql string
+ query := `SELECT sql FROM sqlite_master WHERE type='table' and name=?;`
+ err := s.Tx.QueryRow(query, tableName).Scan(&sql)
+ if err != nil {
+ return "", err
+ }
+ return sql, nil
+}
+
+func (s *SQLiteDriver) getDDLFromIndex(tableName string) ([]string, error) {
+ var sqls []string
+
+ query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name=?;`
+ rows, err := s.Tx.Query(query, tableName)
+ if err != nil {
+ return sqls, err
+ }
+
+ for rows.Next() {
+ var sql string
+ if err := rows.Scan(&sql); err != nil {
+ // This error came from autoindex, since its sql value is null,
+ // we want to continue.
+ if strings.Contains(err.Error(), "Scan pair: -> *string") {
+ continue
+ }
+ return sqls, err
+ }
+ sqls = append(sqls, sql)
+ }
+
+ if err := rows.Err(); err != nil {
+ return sqls, err
+ }
+
+ return sqls, nil
+}
diff --git a/pkg/database/migrate/sqlite_test.go b/pkg/database/migrate/sqlite_test.go
new file mode 100644
index 000000000..c26d5b9bf
--- /dev/null
+++ b/pkg/database/migrate/sqlite_test.go
@@ -0,0 +1,520 @@
+package migrate
+
+import (
+ "database/sql"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/russross/meddler"
+)
+
+type Sample struct {
+ ID int64 `meddler:"id,pk"`
+ Imel string `meddler:"imel"`
+ Name string `meddler:"name"`
+}
+
+type RenameSample struct {
+ ID int64 `meddler:"id,pk"`
+ Email string `meddler:"email"`
+ Name string `meddler:"name"`
+}
+
+type AddColumnSample struct {
+ ID int64 `meddler:"id,pk"`
+ Imel string `meddler:"imel"`
+ Name string `meddler:"name"`
+ Url string `meddler:"url"`
+ Num int64 `meddler:"num"`
+}
+
+// ---------- revision 1
+
+type revision1 struct{}
+
+func (r *revision1) Up(op Operation) error {
+ _, err := op.CreateTable("samples", []string{
+ "id INTEGER PRIMARY KEY AUTOINCREMENT",
+ "imel VARCHAR(255) UNIQUE",
+ "name VARCHAR(255)",
+ })
+ return err
+}
+
+func (r *revision1) Down(op Operation) error {
+ _, err := op.DropTable("samples")
+ return err
+}
+
+func (r *revision1) Revision() int64 {
+ return 1
+}
+
+// ---------- end of revision 1
+
+// ---------- revision 2
+
+type revision2 struct{}
+
+func (r *revision2) Up(op Operation) error {
+ _, err := op.RenameTable("samples", "examples")
+ return err
+}
+
+func (r *revision2) Down(op Operation) error {
+ _, err := op.RenameTable("examples", "samples")
+ return err
+}
+
+func (r *revision2) Revision() int64 {
+ return 2
+}
+
+// ---------- end of revision 2
+
+// ---------- revision 3
+
+type revision3 struct{}
+
+func (r *revision3) Up(op Operation) error {
+ if _, err := op.AddColumn("samples", "url VARCHAR(255)"); err != nil {
+ return err
+ }
+ _, err := op.AddColumn("samples", "num INTEGER")
+ return err
+}
+
+func (r *revision3) Down(op Operation) error {
+ _, err := op.DropColumns("samples", []string{"num", "url"})
+ return err
+}
+
+func (r *revision3) Revision() int64 {
+ return 3
+}
+
+// ---------- end of revision 3
+
+// ---------- revision 4
+
+type revision4 struct{}
+
+func (r *revision4) Up(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "imel": "email",
+ })
+ return err
+}
+
+func (r *revision4) Down(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "email": "imel",
+ })
+ return err
+}
+
+func (r *revision4) Revision() int64 {
+ return 4
+}
+
+// ---------- end of revision 4
+
+// ---------- revision 5
+
+type revision5 struct{}
+
+func (r *revision5) Up(op Operation) error {
+ _, err := op.Exec(`CREATE INDEX samples_url_name_ix ON samples (url, name)`)
+ return err
+}
+
+func (r *revision5) Down(op Operation) error {
+ _, err := op.Exec(`DROP INDEX samples_url_name_ix`)
+ return err
+}
+
+func (r *revision5) Revision() int64 {
+ return 5
+}
+
+// ---------- end of revision 5
+
+// ---------- revision 6
+type revision6 struct{}
+
+func (r *revision6) Up(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "url": "host",
+ })
+ return err
+}
+
+func (r *revision6) Down(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "host": "url",
+ })
+ return err
+}
+
+func (r *revision6) Revision() int64 {
+ return 6
+}
+
+// ---------- end of revision 6
+
+// ---------- revision 7
+type revision7 struct{}
+
+func (r *revision7) Up(op Operation) error {
+ _, err := op.DropColumns("samples", []string{"host", "num"})
+ return err
+}
+
+func (r *revision7) Down(op Operation) error {
+ if _, err := op.AddColumn("samples", "host VARCHAR(255)"); err != nil {
+ return err
+ }
+ _, err := op.AddColumn("samples", "num INSTEGER")
+ return err
+}
+
+func (r *revision7) Revision() int64 {
+ return 7
+}
+
+// ---------- end of revision 7
+
+// ---------- revision 8
+type revision8 struct{}
+
+func (r *revision8) Up(op Operation) error {
+ if _, err := op.AddColumn("samples", "repo_id INTEGER"); err != nil {
+ return err
+ }
+ _, err := op.AddColumn("samples", "repo VARCHAR(255)")
+ return err
+}
+
+func (r *revision8) Down(op Operation) error {
+ _, err := op.DropColumns("samples", []string{"repo", "repo_id"})
+ return err
+}
+
+func (r *revision8) Revision() int64 {
+ return 8
+}
+
+// ---------- end of revision 8
+
+// ---------- revision 9
+type revision9 struct{}
+
+func (r *revision9) Up(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "repo": "repository",
+ })
+ return err
+}
+
+func (r *revision9) Down(op Operation) error {
+ _, err := op.RenameColumns("samples", map[string]string{
+ "repository": "repo",
+ })
+ return err
+}
+
+func (r *revision9) Revision() int64 {
+ return 9
+}
+
+// ---------- end of revision 9
+
+var db *sql.DB
+
+var testSchema = `
+CREATE TABLE samples (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ imel VARCHAR(255) UNIQUE,
+ name VARCHAR(255)
+);
+`
+
+var dataDump = []string{
+ `INSERT INTO samples (imel, name) VALUES ('test@example.com', 'Test Tester');`,
+ `INSERT INTO samples (imel, name) VALUES ('foo@bar.com', 'Foo Bar');`,
+ `INSERT INTO samples (imel, name) VALUES ('crash@bandicoot.io', 'Crash Bandicoot');`,
+}
+
+func TestMigrateCreateTable(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ mgr := New(db)
+ if err := mgr.Add(&revision1{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ sample := Sample{
+ ID: 1,
+ Imel: "test@example.com",
+ Name: "Test Tester",
+ }
+ if err := meddler.Save(db, "samples", &sample); err != nil {
+ t.Errorf("Can not save data: %q", err)
+ }
+}
+
+func TestMigrateRenameTable(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ mgr := New(db)
+ if err := mgr.Add(&revision1{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ loadFixture(t)
+
+ if err := mgr.Add(&revision2{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ sample := Sample{}
+ if err := meddler.QueryRow(db, &sample, `SELECT * FROM examples WHERE id = ?`, 2); err != nil {
+ t.Errorf("Can not fetch data: %q", err)
+ }
+
+ if sample.Imel != "foo@bar.com" {
+ t.Errorf("Column doesn't match. Expect: %s, got: %s", "foo@bar.com", sample.Imel)
+ }
+}
+
+type TableInfo struct {
+ CID int64 `meddler:"cid,pk"`
+ Name string `meddler:"name"`
+ Type string `meddler:"type"`
+ Notnull bool `meddler:"notnull"`
+ DfltValue interface{} `meddler:"dflt_value"`
+ PK bool `meddler:"pk"`
+}
+
+func TestMigrateAddRemoveColumns(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ mgr := New(db)
+ if err := mgr.Add(&revision1{}, &revision3{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var columns []*TableInfo
+ if err := meddler.QueryAll(db, &columns, `PRAGMA table_info(samples);`); err != nil {
+ t.Errorf("Can not access table info: %q", err)
+ }
+
+ if len(columns) < 5 {
+ t.Errorf("Expect length columns: %d\nGot: %d", 5, len(columns))
+ }
+
+ var row = AddColumnSample{
+ ID: 33,
+ Name: "Foo",
+ Imel: "foo@bar.com",
+ Url: "http://example.com",
+ Num: 42,
+ }
+ if err := meddler.Save(db, "samples", &row); err != nil {
+ t.Errorf("Can not save into database: %q", err)
+ }
+
+ if err := mgr.MigrateTo(1); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var another_columns []*TableInfo
+ if err := meddler.QueryAll(db, &another_columns, `PRAGMA table_info(samples);`); err != nil {
+ t.Errorf("Can not access table info: %q", err)
+ }
+
+ if len(another_columns) != 3 {
+ t.Errorf("Expect length columns = %d, got: %d", 3, len(columns))
+ }
+}
+
+func TestRenameColumn(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ mgr := New(db)
+ if err := mgr.Add(&revision1{}, &revision4{}).MigrateTo(1); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ loadFixture(t)
+
+ if err := mgr.MigrateTo(4); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ row := RenameSample{}
+ if err := meddler.QueryRow(db, &row, `SELECT * FROM samples WHERE id = 3;`); err != nil {
+ t.Errorf("Can not query database: %q", err)
+ }
+
+ if row.Email != "crash@bandicoot.io" {
+ t.Errorf("Expect %s, got %s", "crash@bandicoot.io", row.Email)
+ }
+}
+
+func TestMigrateExistingTable(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ if _, err := db.Exec(testSchema); err != nil {
+ t.Errorf("Can not create database: %q", err)
+ }
+
+ loadFixture(t)
+
+ mgr := New(db)
+ if err := mgr.Add(&revision4{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var rows []*RenameSample
+ if err := meddler.QueryAll(db, &rows, `SELECT * from samples;`); err != nil {
+ t.Errorf("Can not query database: %q", err)
+ }
+
+ if len(rows) != 3 {
+ t.Errorf("Expect rows length = %d, got %d", 3, len(rows))
+ }
+
+ if rows[1].Email != "foo@bar.com" {
+ t.Errorf("Expect email = %s, got %s", "foo@bar.com", rows[1].Email)
+ }
+}
+
+type sqliteMaster struct {
+ Sql interface{} `meddler:"sql"`
+}
+
+func TestIndexOperations(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ mgr := New(db)
+
+ // Migrate, create index
+ if err := mgr.Add(&revision1{}, &revision3{}, &revision5{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var esquel []*sqliteMaster
+ // Query sqlite_master, check if index is exists.
+ query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name='samples'`
+ if err := meddler.QueryAll(db, &esquel, query); err != nil {
+ t.Errorf("Can not find index: %q", err)
+ }
+
+ indexStatement := `CREATE INDEX samples_url_name_ix ON samples (url, name)`
+ if string(esquel[1].Sql.([]byte)) != indexStatement {
+ t.Errorf("Can not find index")
+ }
+
+ // Migrate, rename indexed columns
+ if err := mgr.Add(&revision6{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var esquel1 []*sqliteMaster
+ if err := meddler.QueryAll(db, &esquel1, query); err != nil {
+ t.Errorf("Can not find index: %q", err)
+ }
+
+ indexStatement = `CREATE INDEX samples_host_name_ix ON samples (host, name)`
+ if string(esquel1[1].Sql.([]byte)) != indexStatement {
+ t.Errorf("Can not find index, got: %s", esquel[0])
+ }
+
+ if err := mgr.Add(&revision7{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var esquel2 []*sqliteMaster
+ if err := meddler.QueryAll(db, &esquel2, query); err != nil {
+ t.Errorf("Can not find index: %q", err)
+ }
+
+ if len(esquel2) != 1 {
+ t.Errorf("Expect row length equal to %d, got %d", 1, len(esquel2))
+ }
+}
+
+func TestColumnRedundancy(t *testing.T) {
+ defer tearDown()
+ if err := setUp(); err != nil {
+ t.Fatalf("Error preparing database: %q", err)
+ }
+
+ Driver = SQLite
+
+ migr := New(db)
+ if err := migr.Add(&revision1{}, &revision8{}, &revision9{}).Migrate(); err != nil {
+ t.Errorf("Can not migrate: %q", err)
+ }
+
+ var tableSql string
+ query := `SELECT sql FROM sqlite_master where type='table' and name='samples'`
+ if err := db.QueryRow(query).Scan(&tableSql); err != nil {
+ t.Errorf("Can not query sqlite_master: %q", err)
+ }
+
+ if !strings.Contains(tableSql, "repository ") {
+ t.Errorf("Expect column with name repository")
+ }
+}
+
+func setUp() error {
+ var err error
+ db, err = sql.Open("sqlite3", "migration_tests.sqlite")
+ return err
+}
+
+func tearDown() {
+ db.Close()
+ os.Remove("migration_tests.sqlite")
+}
+
+func loadFixture(t *testing.T) {
+ for _, sql := range dataDump {
+ if _, err := db.Exec(sql); err != nil {
+ t.Errorf("Can not insert into database: %q", err)
+ }
+ }
+}
diff --git a/pkg/database/migrate/util.go b/pkg/database/migrate/util.go
new file mode 100644
index 000000000..d2001a071
--- /dev/null
+++ b/pkg/database/migrate/util.go
@@ -0,0 +1,32 @@
+package migrate
+
+import (
+ "fmt"
+ "strings"
+)
+
+func fetchColumns(sql string) ([]string, error) {
+ if !strings.HasPrefix(sql, "CREATE ") {
+ return []string{}, fmt.Errorf("Sql input is not a DDL statement.")
+ }
+
+ parenIdx := strings.Index(sql, "(")
+ return strings.Split(sql[parenIdx+1:strings.LastIndex(sql, ")")], ","), nil
+}
+
+func selectName(columns []string) []string {
+ var results []string
+ for _, column := range columns {
+ col := strings.SplitN(strings.Trim(column, " \n\t"), " ", 2)
+ results = append(results, col[0])
+ }
+ return results
+}
+
+func setForUpdate(left []string, right []string) string {
+ var results []string
+ for k, str := range left {
+ results = append(results, fmt.Sprintf("%s = %s", str, right[k]))
+ }
+ return strings.Join(results, ", ")
+}
diff --git a/pkg/database/repos.go b/pkg/database/repos.go
index e4407c028..0d9e2f15e 100644
--- a/pkg/database/repos.go
+++ b/pkg/database/repos.go
@@ -13,7 +13,7 @@ const repoTable = "repos"
// SQL Queries to retrieve a list of all repos belonging to a User.
const repoStmt = `
SELECT id, slug, host, owner, name, private, disabled, disabled_pr, scm, url, username, password,
-public_key, private_key, params, timeout, priveleged, created, updated, user_id, team_id
+public_key, private_key, params, timeout, privileged, created, updated, user_id, team_id
FROM repos
WHERE user_id = ? AND team_id = 0
ORDER BY slug ASC
@@ -22,7 +22,7 @@ ORDER BY slug ASC
// SQL Queries to retrieve a list of all repos belonging to a Team.
const repoTeamStmt = `
SELECT id, slug, host, owner, name, private, disabled, disabled_pr, scm, url, username, password,
-public_key, private_key, params, timeout, priveleged, created, updated, user_id, team_id
+public_key, private_key, params, timeout, privileged, created, updated, user_id, team_id
FROM repos
WHERE team_id = ?
ORDER BY slug ASC
@@ -31,7 +31,7 @@ ORDER BY slug ASC
// SQL Queries to retrieve a repo by id.
const repoFindStmt = `
SELECT id, slug, host, owner, name, private, disabled, disabled_pr, scm, url, username, password,
-public_key, private_key, params, timeout, priveleged, created, updated, user_id, team_id
+public_key, private_key, params, timeout, privileged, created, updated, user_id, team_id
FROM repos
WHERE id = ?
`
@@ -39,7 +39,7 @@ WHERE id = ?
// SQL Queries to retrieve a repo by name.
const repoFindSlugStmt = `
SELECT id, slug, host, owner, name, private, disabled, disabled_pr, scm, url, username, password,
-public_key, private_key, params, timeout, priveleged, created, updated, user_id, team_id
+public_key, private_key, params, timeout, privileged, created, updated, user_id, team_id
FROM repos
WHERE slug = ?
`
diff --git a/pkg/database/schema/sample.sql b/pkg/database/schema/sample.sql
index 6b3f6f297..c19dbe4b9 100644
--- a/pkg/database/schema/sample.sql
+++ b/pkg/database/schema/sample.sql
@@ -49,7 +49,7 @@ insert into builds values (9, 3, 'node_0.80', 'Success', '2013-09-16 00:00:00','
-- insert default, dummy settings
-insert into settings values (1,'','','','','','','','','','localhost:8080','http');
+insert into settings values (1,'','','github.com','https://api.github.com','','','','','','','','localhost:8080','http',0);
-- add public & private keys to all repositories
@@ -123,4 +123,4 @@ Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.849 sec
Results :
-Tests run: 7, Failures: 0, Errors: 0, Skipped: 0';
\ No newline at end of file
+Tests run: 7, Failures: 0, Errors: 0, Skipped: 0';
diff --git a/pkg/database/schema/schema.go b/pkg/database/schema/schema.go
index 993db07e2..ec93774a8 100644
--- a/pkg/database/schema/schema.go
+++ b/pkg/database/schema/schema.go
@@ -127,6 +127,7 @@ CREATE TABLE settings (
,smtp_password VARCHAR(1024)
,hostname VARCHAR(1024)
,scheme VARCHAR(5)
+ ,open_invitations BOOLEAN
);
`
@@ -194,5 +195,9 @@ func Load(db *sql.DB) error {
db.Exec(buildCommitIndex)
db.Exec(buildSlugIndex)
+ // migrations for backward compatibility
+ db.Exec("ALTER TABLE settings ADD COLUMN open_invitations BOOLEAN")
+ db.Exec("UPDATE settings SET open_invitations=0 WHERE open_invitations IS NULL")
+
return nil
}
diff --git a/pkg/database/schema/schema.sql b/pkg/database/schema/schema.sql
index 950458508..cae432d07 100644
--- a/pkg/database/schema/schema.sql
+++ b/pkg/database/schema/schema.sql
@@ -51,7 +51,7 @@ CREATE TABLE repos (
,private BOOLEAN
,disabled BOOLEAN
,disabled_pr BOOLEAN
- ,priveleged BOOLEAN
+ ,privileged BOOLEAN
,timeout INTEGER
,scm VARCHAR(25)
@@ -103,6 +103,8 @@ CREATE TABLE settings (
id INTEGER PRIMARY KEY
,github_key VARCHAR(255)
,github_secret VARCHAR(255)
+ ,github_domain VARCHAR(255)
+ ,github_apiurl VARCHAR(255)
,bitbucket_key VARCHAR(255)
,bitbucket_secret VARCHAR(255)
,smtp_server VARCHAR(1024)
@@ -112,6 +114,7 @@ CREATE TABLE settings (
,smtp_password VARCHAR(1024)
,hostname VARCHAR(1024)
,scheme VARCHAR(5)
+ ,open_invitations BOOLEAN
);
CREATE UNIQUE INDEX member_uix ON members (team_id, user_id);
diff --git a/pkg/database/settings.go b/pkg/database/settings.go
index 0e210c991..883b87d3d 100644
--- a/pkg/database/settings.go
+++ b/pkg/database/settings.go
@@ -10,8 +10,8 @@ const settingsTable = "settings"
// SQL Queries to retrieve the system settings
const settingsStmt = `
-SELECT id, github_key, github_secret, bitbucket_key, bitbucket_secret,
-smtp_server, smtp_port, smtp_address, smtp_username, smtp_password, hostname, scheme
+SELECT id, github_key, github_secret, github_domain, github_apiurl, bitbucket_key, bitbucket_secret,
+smtp_server, smtp_port, smtp_address, smtp_username, smtp_password, hostname, scheme, open_invitations
FROM settings WHERE id = 1
`
diff --git a/pkg/database/testing/testing.go b/pkg/database/testing/testing.go
index 11106e2d3..69fde3787 100644
--- a/pkg/database/testing/testing.go
+++ b/pkg/database/testing/testing.go
@@ -7,6 +7,7 @@ import (
"github.com/drone/drone/pkg/database"
"github.com/drone/drone/pkg/database/encrypt"
+ "github.com/drone/drone/pkg/database/migrate"
. "github.com/drone/drone/pkg/model"
_ "github.com/mattn/go-sqlite3"
@@ -31,6 +32,7 @@ func init() {
// notify meddler that we are working with sqlite
meddler.Default = meddler.SQLite
+ migrate.Driver = migrate.SQLite
}
func Setup() {
@@ -40,6 +42,9 @@ func Setup() {
// make sure all the tables and indexes are created
database.Set(db)
+ migration := migrate.New(db)
+ migration.All().Migrate()
+
// create dummy user data
user1 := User{
Password: "$2a$10$b8d63QsTL38vx7lj0HEHfOdbu1PCAg6Gfca74UavkXooIBx9YxopS",
diff --git a/pkg/database/testing/users_test.go b/pkg/database/testing/users_test.go
index 9742c42bb..988e4aeb5 100644
--- a/pkg/database/testing/users_test.go
+++ b/pkg/database/testing/users_test.go
@@ -28,6 +28,10 @@ func TestGetUser(t *testing.T) {
t.Errorf("Exepected Password %s, got %s", "$2a$10$b8d63QsTL38vx7lj0HEHfOdbu1PCAg6Gfca74UavkXooIBx9YxopS", u.Password)
}
+ if u.Token != "123" {
+ t.Errorf("Exepected Token %s, got %s", "123", u.Token)
+ }
+
if u.Name != "Brad Rydzewski" {
t.Errorf("Exepected Name %s, got %s", "Brad Rydzewski", u.Name)
}
@@ -60,6 +64,10 @@ func TestGetUserEmail(t *testing.T) {
t.Errorf("Exepected Password %s, got %s", "$2a$10$b8d63QsTL38vx7lj0HEHfOdbu1PCAg6Gfca74UavkXooIBx9YxopS", u.Password)
}
+ if u.Token != "123" {
+ t.Errorf("Exepected Token %s, got %s", "123", u.Token)
+ }
+
if u.Name != "Brad Rydzewski" {
t.Errorf("Exepected Name %s, got %s", "Brad Rydzewski", u.Name)
}
@@ -155,6 +163,10 @@ func TestListUsers(t *testing.T) {
t.Errorf("Exepected Password %s, got %s", "$2a$10$b8d63QsTL38vx7lj0HEHfOdbu1PCAg6Gfca74UavkXooIBx9YxopS", u.Password)
}
+ if u.Token != "123" {
+ t.Errorf("Exepected Token %s, got %s", "123", u.Token)
+ }
+
if u.Name != "Brad Rydzewski" {
t.Errorf("Exepected Name %s, got %s", "Brad Rydzewski", u.Name)
}
diff --git a/pkg/database/users.go b/pkg/database/users.go
index 602f71d84..3bea97452 100644
--- a/pkg/database/users.go
+++ b/pkg/database/users.go
@@ -12,21 +12,21 @@ const userTable = "users"
// SQL Queries to retrieve a user by their unique database key
const userFindIdStmt = `
-SELECT id, email, password, name, gravatar, created, updated, admin,
+SELECT id, email, password, token, name, gravatar, created, updated, admin,
github_login, github_token, bitbucket_login, bitbucket_token, bitbucket_secret
FROM users WHERE id = ?
`
// SQL Queries to retrieve a user by their email address
const userFindEmailStmt = `
-SELECT id, email, password, name, gravatar, created, updated, admin,
+SELECT id, email, password, token, name, gravatar, created, updated, admin,
github_login, github_token, bitbucket_login, bitbucket_token, bitbucket_secret
FROM users WHERE email = ?
`
// SQL Queries to retrieve a list of all users
const userStmt = `
-SELECT id, email, password, name, gravatar, created, updated, admin,
+SELECT id, email, password, token, name, gravatar, created, updated, admin,
github_login, github_token, bitbucket_login, bitbucket_token, bitbucket_secret
FROM users
ORDER BY name ASC
diff --git a/pkg/handler/admin.go b/pkg/handler/admin.go
index be3b67a75..d7840c7fb 100644
--- a/pkg/handler/admin.go
+++ b/pkg/handler/admin.go
@@ -2,7 +2,6 @@ package handler
import (
"fmt"
- "log"
"net/http"
"strconv"
"time"
@@ -33,8 +32,7 @@ func AdminUserAdd(w http.ResponseWriter, r *http.Request, u *User) error {
return RenderTemplate(w, "admin_users_add.html", &struct{ User *User }{u})
}
-// Invite a user to join the system
-func AdminUserInvite(w http.ResponseWriter, r *http.Request, u *User) error {
+func UserInvite(w http.ResponseWriter, r *http.Request) error {
// generate the password reset token
email := r.FormValue("email")
token := authcookie.New(email, time.Now().Add(12*time.Hour), secret)
@@ -57,15 +55,16 @@ func AdminUserInvite(w http.ResponseWriter, r *http.Request, u *User) error {
}{hostname, email, token}
// send the email message async
- go func() {
- if err := mail.SendActivation(email, data); err != nil {
- log.Printf("error sending account activation email to %s. %s", email, err)
- }
- }()
+ go mail.SendActivation(email, data)
return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
+// Invite a user to join the system
+func AdminUserInvite(w http.ResponseWriter, r *http.Request, u *User) error {
+ return UserInvite(w, r)
+}
+
// Form to edit a user
func AdminUserEdit(w http.ResponseWriter, r *http.Request, u *User) error {
idstr := r.FormValue("id")
@@ -140,7 +139,7 @@ func AdminUserDelete(w http.ResponseWriter, r *http.Request, u *User) error {
return nil
}
-// Display a list of ALL users in the system
+// Return an HTML form for the User to update the site settings.
func AdminSettings(w http.ResponseWriter, r *http.Request, u *User) error {
// get settings from database
settings := database.SettingsMust()
@@ -153,7 +152,6 @@ func AdminSettings(w http.ResponseWriter, r *http.Request, u *User) error {
return RenderTemplate(w, "admin_settings.html", &data)
}
-// Display a list of ALL users in the system
func AdminSettingsUpdate(w http.ResponseWriter, r *http.Request, u *User) error {
// get settings from database
settings := database.SettingsMust()
@@ -169,6 +167,8 @@ func AdminSettingsUpdate(w http.ResponseWriter, r *http.Request, u *User) error
// update github settings
settings.GitHubKey = r.FormValue("GitHubKey")
settings.GitHubSecret = r.FormValue("GitHubSecret")
+ settings.GitHubDomain = r.FormValue("GitHubDomain")
+ settings.GitHubApiUrl = r.FormValue("GitHubApiUrl")
// update smtp settings
settings.SmtpServer = r.FormValue("SmtpServer")
@@ -177,6 +177,8 @@ func AdminSettingsUpdate(w http.ResponseWriter, r *http.Request, u *User) error
settings.SmtpUsername = r.FormValue("SmtpUsername")
settings.SmtpPassword = r.FormValue("SmtpPassword")
+ settings.OpenInvitations = (r.FormValue("OpenInvitations") == "on")
+
// persist changes
if err := database.SaveSettings(settings); err != nil {
return RenderError(w, err, http.StatusBadRequest)
@@ -243,6 +245,8 @@ func InstallPost(w http.ResponseWriter, r *http.Request) error {
settings := Settings{}
settings.Domain = r.FormValue("Domain")
settings.Scheme = r.FormValue("Scheme")
+ settings.GitHubApiUrl = "https://api.github.com";
+ settings.GitHubDomain = "github.com";
database.SaveSettings(&settings)
// add the user to the session object
diff --git a/pkg/handler/app.go b/pkg/handler/app.go
index 86cd1121d..9d720113c 100644
--- a/pkg/handler/app.go
+++ b/pkg/handler/app.go
@@ -47,7 +47,13 @@ func Index(w http.ResponseWriter, r *http.Request) error {
// Return an HTML form for the User to login.
func Login(w http.ResponseWriter, r *http.Request) error {
- return RenderTemplate(w, "login.html", nil)
+ var settings, _ = database.GetSettings()
+
+ data := struct {
+ Settings *Settings
+ }{settings}
+
+ return RenderTemplate(w, "login.html", &data)
}
// Terminate the User session.
@@ -70,6 +76,18 @@ func Reset(w http.ResponseWriter, r *http.Request) error {
return RenderTemplate(w, "reset.html", &struct{ Error string }{""})
}
+// Return an HTML form for the User to signup.
+func SignUp(w http.ResponseWriter, r *http.Request) error {
+ var settings, _ = database.GetSettings()
+
+ if settings == nil || !settings.OpenInvitations {
+ http.Redirect(w, r, "/login", http.StatusSeeOther)
+ return nil
+ }
+
+ return RenderTemplate(w, "signup.html", nil)
+}
+
// Return an HTML form to register for a new account. This
// page must be visited from a Signup email that contains
// a hash to verify the Email address is correct.
@@ -138,15 +156,40 @@ func ResetPost(w http.ResponseWriter, r *http.Request) error {
}
// add the user to the session object
- //session, _ := store.Get(r, "_sess")
- //session.Values["username"] = user.Email
- //session.Save(r, w)
SetCookie(w, r, "_sess", user.Email)
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
return nil
}
+func SignUpPost(w http.ResponseWriter, r *http.Request) error {
+ // if self-registration is disabled we should display an
+ // error message to the user.
+ if !database.SettingsMust().OpenInvitations {
+ http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+ return nil
+ }
+
+ // generate the password reset token
+ email := r.FormValue("email")
+ token := authcookie.New(email, time.Now().Add(12*time.Hour), secret)
+
+ // get the hostname from the database for use in the email
+ hostname := database.SettingsMust().URL().String()
+
+ // data used to generate the email template
+ data := struct {
+ Host string
+ Email string
+ Token string
+ }{hostname, email, token}
+
+ // send the email message async
+ go mail.SendActivation(email, data)
+
+ return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
+}
+
func RegisterPost(w http.ResponseWriter, r *http.Request) error {
// verify the token and extract the username
token := r.FormValue("token")
@@ -156,9 +199,7 @@ func RegisterPost(w http.ResponseWriter, r *http.Request) error {
}
// set the email and name
- user := User{}
- user.SetEmail(email)
- user.Name = r.FormValue("name")
+ user := NewUser(r.FormValue("name"), email)
// set the new password
password := r.FormValue("password")
@@ -172,7 +213,7 @@ func RegisterPost(w http.ResponseWriter, r *http.Request) error {
}
// save to the database
- if err := database.SaveUser(&user); err != nil {
+ if err := database.SaveUser(user); err != nil {
return err
}
diff --git a/pkg/handler/auth.go b/pkg/handler/auth.go
index 091bb494d..7e91787ee 100644
--- a/pkg/handler/auth.go
+++ b/pkg/handler/auth.go
@@ -48,8 +48,8 @@ func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error {
// github OAuth2 Data
var oauth = oauth2.Client{
RedirectURL: settings.URL().String() + "/auth/login/github",
- AccessTokenURL: "https://github.com/login/oauth/access_token",
- AuthorizationURL: "https://github.com/login/oauth/authorize",
+ AccessTokenURL: "https://" + settings.GitHubDomain + "/login/oauth/access_token",
+ AuthorizationURL: "https://" + settings.GitHubDomain + "/login/oauth/authorize",
ClientId: settings.GitHubKey,
ClientSecret: settings.GitHubSecret,
}
@@ -72,6 +72,7 @@ func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error {
// create the client
client := github.New(token.AccessToken)
+ client.ApiUrl = settings.GitHubApiUrl
// get the user information
githubUser, err := client.Users.Current()
diff --git a/pkg/handler/badges.go b/pkg/handler/badges.go
index bfe13e612..20415115d 100644
--- a/pkg/handler/badges.go
+++ b/pkg/handler/badges.go
@@ -11,7 +11,7 @@ import (
// repository and an optional branch.
// TODO this needs to implement basic caching
func Badge(w http.ResponseWriter, r *http.Request) error {
- branchParam := r.FormValue(":branch")
+ branchParam := r.FormValue("branch")
hostParam := r.FormValue(":host")
ownerParam := r.FormValue(":owner")
nameParam := r.FormValue(":name")
diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go
index 2b3d28069..bf9914e4f 100644
--- a/pkg/handler/handler.go
+++ b/pkg/handler/handler.go
@@ -41,7 +41,7 @@ func (h UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// AdminHandler wraps the default http.HandlerFunc to include
// the currently authenticated User in the method signature,
// in addition to handling an error as the return value. It also
-// verifies the user has Administrative priveleges.
+// verifies the user has Administrative privileges.
type AdminHandler func(w http.ResponseWriter, r *http.Request, user *User) error
func (h AdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -51,7 +51,7 @@ func (h AdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- // User MUST have administrative priveleges in order
+ // User MUST have administrative privileges in order
// to execute the handler.
if user.Admin == false {
RenderNotFound(w)
diff --git a/pkg/handler/hooks.go b/pkg/handler/hooks.go
index dbec12705..2cb719681 100644
--- a/pkg/handler/hooks.go
+++ b/pkg/handler/hooks.go
@@ -100,8 +100,13 @@ func Hook(w http.ResponseWriter, r *http.Request) error {
commit.SetAuthor(hook.Commits[0].Author.Email)
}
+ // get the github settings from the database
+ settings := database.SettingsMust()
+
// get the drone.yml file from GitHub
client := github.New(user.GithubToken)
+ client.ApiUrl = settings.GitHubApiUrl
+
content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash)
if err != nil {
msg := "No .drone.yml was found in this repository. You need to add one.\n"
@@ -122,7 +127,7 @@ func Hook(w http.ResponseWriter, r *http.Request) error {
}
// parse the build script
- buildscript, err := script.ParseBuild(raw)
+ buildscript, err := script.ParseBuild(raw, repo.Params)
if err != nil {
msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
if err := saveFailedBuild(commit, msg); err != nil {
@@ -216,8 +221,13 @@ func PullRequestHook(w http.ResponseWriter, r *http.Request) {
commit.Message = hook.PullRequest.Title
// label := p.PullRequest.Head.Labe
+ // get the github settings from the database
+ settings := database.SettingsMust()
+
// get the drone.yml file from GitHub
client := github.New(user.GithubToken)
+ client.ApiUrl = settings.GitHubApiUrl
+
content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash??
if err != nil {
println(err.Error())
@@ -233,7 +243,7 @@ func PullRequestHook(w http.ResponseWriter, r *http.Request) {
}
// parse the build script
- buildscript, err := script.ParseBuild(raw)
+ buildscript, err := script.ParseBuild(raw, repo.Params)
if err != nil {
// TODO if the YAML is invalid we should create a commit record
// with an ERROR status so that the user knows why a build wasn't
diff --git a/pkg/handler/members.go b/pkg/handler/members.go
index ff0dea55d..27e4b0af9 100644
--- a/pkg/handler/members.go
+++ b/pkg/handler/members.go
@@ -173,7 +173,7 @@ func TeamMemberInvite(w http.ResponseWriter, r *http.Request, u *User) error {
}
// generate a token that is valid for 3 days to join the team
- token := authcookie.New(team.Name, time.Now().Add(72*time.Hour), secret)
+ token := authcookie.New(strconv.Itoa(int(team.ID)), time.Now().Add(72*time.Hour), secret)
// hostname from settings
hostname := database.SettingsMust().URL().String()
@@ -202,14 +202,14 @@ func TeamMemberInvite(w http.ResponseWriter, r *http.Request, u *User) error {
func TeamMemberAccept(w http.ResponseWriter, r *http.Request, u *User) error {
// get the team name from the token
token := r.FormValue("token")
- teamName := authcookie.Login(token, secret)
- if len(teamName) == 0 {
+ teamToken := authcookie.Login(token, secret)
+ teamId, err := strconv.Atoi(teamToken)
+ if err != nil || teamId == 0 {
return ErrInvalidTeamName
}
// get the team from the database
- // TODO it might make more sense to use the ID in case the Slug changes
- team, err := database.GetTeamSlug(teamName)
+ team, err := database.GetTeam(int64(teamId))
if err != nil {
return RenderError(w, err, http.StatusNotFound)
}
@@ -222,6 +222,6 @@ func TeamMemberAccept(w http.ResponseWriter, r *http.Request, u *User) error {
}
// send the user to the dashboard
- http.Redirect(w, r, "/dashboard/team/"+team.Name, http.StatusSeeOther)
+ http.Redirect(w, r, "/dashboard/team/"+team.Slug, http.StatusSeeOther)
return nil
}
diff --git a/pkg/handler/repos.go b/pkg/handler/repos.go
index 99195e104..fe3336c01 100644
--- a/pkg/handler/repos.go
+++ b/pkg/handler/repos.go
@@ -15,7 +15,7 @@ import (
// Display a Repository dashboard.
func RepoDashboard(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error {
- branch := r.FormValue(":branch")
+ branch := r.FormValue("branch")
// get a list of all branches
branches, err := database.ListBranches(repo.ID)
@@ -54,6 +54,7 @@ func RepoDashboard(w http.ResponseWriter, r *http.Request, u *User, repo *Repo)
}
func RepoAdd(w http.ResponseWriter, r *http.Request, u *User) error {
+ settings := database.SettingsMust()
teams, err := database.ListTeams(u.ID)
if err != nil {
return err
@@ -61,7 +62,8 @@ func RepoAdd(w http.ResponseWriter, r *http.Request, u *User) error {
data := struct {
User *User
Teams []*Team
- }{u, teams}
+ Settings *Settings
+ }{u, teams, settings}
// if the user hasn't linked their GitHub account
// render a different template
if len(u.GithubToken) == 0 {
@@ -82,12 +84,13 @@ func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error {
// create the GitHub client
client := github.New(u.GithubToken)
+ client.ApiUrl = settings.GitHubApiUrl
githubRepo, err := client.Repos.Find(owner, name)
if err != nil {
return err
}
- repo, err := NewGitHubRepo(owner, name, githubRepo.Private)
+ repo, err := NewGitHubRepo(settings.GitHubDomain, owner, name, githubRepo.Private)
if err != nil {
return err
}
@@ -274,7 +277,7 @@ func RepoDelete(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) err
// the user must confirm their password before deleting
password := r.FormValue("password")
if err := u.ComparePassword(password); err != nil {
- return err
+ return RenderError(w, err, http.StatusBadRequest)
}
// delete the repo
diff --git a/pkg/mail/mail.go b/pkg/mail/mail.go
index aa63d444f..2bc83938b 100644
--- a/pkg/mail/mail.go
+++ b/pkg/mail/mail.go
@@ -122,7 +122,11 @@ func Send(msg *Message) error {
// format the raw email message body
body := fmt.Sprintf(emailTemplate, msg.Sender, msg.To, msg.Subject, msg.Body)
- auth := smtp.PlainAuth("", s.SmtpUsername, s.SmtpPassword, s.SmtpServer)
+
+ var auth smtp.Auth
+ if len(s.SmtpUsername) > 0 {
+ auth = smtp.PlainAuth("", s.SmtpUsername, s.SmtpPassword, s.SmtpServer)
+ }
addr := fmt.Sprintf("%s:%s", s.SmtpServer, s.SmtpPort)
err = smtp.SendMail(addr, auth, msg.Sender, []string{msg.To}, []byte(body))
diff --git a/pkg/model/repo.go b/pkg/model/repo.go
index 5b89ad71c..c420b7976 100644
--- a/pkg/model/repo.go
+++ b/pkg/model/repo.go
@@ -12,7 +12,6 @@ const (
)
const (
- HostGithub = "github.com"
HostBitbucket = "bitbucket.org"
HostGoogle = "code.google.com"
HostCustom = "custom"
@@ -25,8 +24,8 @@ const (
)
const (
- githubRepoPattern = "git://github.com/%s/%s.git"
- githubRepoPatternPrivate = "git@github.com:%s/%s.git"
+ githubRepoPattern = "git://%s/%s/%s.git"
+ githubRepoPatternPrivate = "git@%s:%s/%s.git"
bitbucketRepoPattern = "https://bitbucket.org/%s/%s.git"
bitbucketRepoPatternPrivate = "git@bitbucket.org:%s/%s.git"
)
@@ -88,9 +87,9 @@ type Repo struct {
// before exceeding its timelimit and being killed.
Timeout int64 `meddler:"timeout" json:"timeout"`
- // Indicates the build should be executed in priveleged
+ // Indicates the build should be executed in privileged
// mode. This could, for example, be used to run Docker in Docker.
- Priveleged bool `meddler:"priveleged" json:"priveleged"`
+ Privileged bool `meddler:"privileged" json:"privileged"`
// Foreign keys signify the User that created
// the repository and team account linked to
@@ -122,15 +121,15 @@ func NewRepo(host, owner, name, scm, url string) (*Repo, error) {
}
// Creates a new GitHub repository
-func NewGitHubRepo(owner, name string, private bool) (*Repo, error) {
+func NewGitHubRepo(domain, owner, name string, private bool) (*Repo, error) {
var url string
switch private {
case false:
- url = fmt.Sprintf(githubRepoPattern, owner, name)
+ url = fmt.Sprintf(githubRepoPattern, domain, owner, name)
case true:
- url = fmt.Sprintf(githubRepoPatternPrivate, owner, name)
+ url = fmt.Sprintf(githubRepoPatternPrivate, domain, owner, name)
}
- return NewRepo(HostGithub, owner, name, ScmGit, url)
+ return NewRepo(domain, owner, name, ScmGit, url)
}
// Creates a new Bitbucket repository
@@ -142,7 +141,7 @@ func NewBitbucketRepo(owner, name string, private bool) (*Repo, error) {
case true:
url = fmt.Sprintf(bitbucketRepoPatternPrivate, owner, name)
}
- return NewRepo(HostGithub, owner, name, ScmGit, url)
+ return NewRepo(HostBitbucket, owner, name, ScmGit, url)
}
func (r *Repo) DefaultBranch() string {
diff --git a/pkg/model/settings.go b/pkg/model/settings.go
index ec7f69158..3112d1d4b 100644
--- a/pkg/model/settings.go
+++ b/pkg/model/settings.go
@@ -17,6 +17,8 @@ type Settings struct {
// GitHub Consumer key and secret.
GitHubKey string `meddler:"github_key"`
GitHubSecret string `meddler:"github_secret"`
+ GitHubDomain string `meddler:"github_domain"`
+ GitHubApiUrl string `meddler:"github_apiurl"`
// Bitbucket Consumer Key and secret.
BitbucketKey string `meddler:"bitbucket_key"`
@@ -27,6 +29,8 @@ type Settings struct {
// Scheme of the server, eg https
Scheme string `meddler:"scheme"`
+
+ OpenInvitations bool `meddler:"open_invitations"`
}
func (s *Settings) URL() *url.URL {
diff --git a/pkg/plugin/deploy/deployment.go b/pkg/plugin/deploy/deployment.go
index 71e79c718..0181586c4 100644
--- a/pkg/plugin/deploy/deployment.go
+++ b/pkg/plugin/deploy/deployment.go
@@ -12,6 +12,7 @@ type Deploy struct {
CloudControl *CloudControl `yaml:"cloudcontrol,omitempty"`
CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"`
EngineYard *EngineYard `yaml:"engineyard,omitempty"`
+ Git *Git `yaml:"git,omitempty"`
Heroku *Heroku `yaml:"heroku,omitempty"`
Nodejitsu *Nodejitsu `yaml:"nodejitsu,omitempty"`
Openshift *Openshift `yaml:"openshift,omitempty"`
@@ -30,6 +31,9 @@ func (d *Deploy) Write(f *buildfile.Buildfile) {
if d.EngineYard != nil {
d.EngineYard.Write(f)
}
+ if d.Git != nil {
+ d.Git.Write(f)
+ }
if d.Heroku != nil {
d.Heroku.Write(f)
}
diff --git a/pkg/plugin/deploy/git.go b/pkg/plugin/deploy/git.go
index b2b65d9ca..cc8a3e837 100644
--- a/pkg/plugin/deploy/git.go
+++ b/pkg/plugin/deploy/git.go
@@ -1 +1,38 @@
package deploy
+
+import (
+ "fmt"
+ "github.com/drone/drone/pkg/build/buildfile"
+)
+
+type Git struct {
+ Target string `yaml:"target,omitempty"`
+ Force bool `yaml:"force,omitempty"`
+ Branch string `yaml:"branch,omitempty"`
+}
+
+func (g *Git) Write(f *buildfile.Buildfile) {
+ // get the current commit hash
+ f.WriteCmdSilent("COMMIT=$(git rev-parse HEAD)")
+
+ // set the git user and email based on the individual
+ // that made the commit.
+ f.WriteCmdSilent("git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')")
+ f.WriteCmdSilent("git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')")
+
+ // add target as a git remote
+ f.WriteCmd(fmt.Sprintf("git remote add deploy %s", g.Target))
+
+ 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 $COMMIT:master --force"))
+ case false:
+ // otherwise we just do a standard git push
+ f.WriteCmd(fmt.Sprintf("git push deploy $COMMIT:master"))
+ }
+}
diff --git a/pkg/plugin/notify/email.go b/pkg/plugin/notify/email.go
index ed7b84759..3699a7c2a 100644
--- a/pkg/plugin/notify/email.go
+++ b/pkg/plugin/notify/email.go
@@ -1,30 +1,11 @@
package notify
-import (
- "fmt"
- "net/smtp"
-)
+import "github.com/drone/drone/pkg/mail"
type Email struct {
Recipients []string `yaml:"recipients,omitempty"`
Success string `yaml:"on_success"`
Failure string `yaml:"on_failure"`
-
- host string // smtp host address
- port string // smtp host port
- user string // smtp username for authentication
- pass string // smtp password for authentication
- from string // smtp email address. send from this address
-}
-
-// SetServer is a function that will set the SMTP
-// server location and credentials
-func (e *Email) SetServer(host, port, user, pass, from string) {
- e.host = host
- e.port = port
- e.user = user
- e.pass = pass
- e.from = from
}
// Send will send an email, either success or failure,
@@ -44,11 +25,11 @@ func (e *Email) Send(context *Context) error {
// recipients indicating the build failed.
func (e *Email) sendFailure(context *Context) error {
// loop through and email recipients
- /*for _, email := range e.Recipients {
- if err := mail.SendFailure(context.Repo.Slug, email, context); err != nil {
+ for _, email := range e.Recipients {
+ if err := mail.SendFailure(context.Repo.Name, email, context); err != nil {
return err
}
- }*/
+ }
return nil
}
@@ -56,30 +37,10 @@ func (e *Email) sendFailure(context *Context) error {
// recipients indicating the build was a success.
func (e *Email) sendSuccess(context *Context) error {
// loop through and email recipients
- /*for _, email := range e.Recipients {
- if err := mail.SendSuccess(context.Repo.Slug, email, context); err != nil {
+ for _, email := range e.Recipients {
+ if err := mail.SendSuccess(context.Repo.Name, email, context); err != nil {
return err
}
- }*/
+ }
return nil
}
-
-// send is a simple helper function to format and
-// send an email message.
-func (e *Email) send(to, subject, body string) error {
- // Format the raw email message body
- raw := fmt.Sprintf(emailTemplate, e.from, to, subject, body)
- auth := smtp.PlainAuth("", e.user, e.pass, e.host)
- addr := fmt.Sprintf("%s:%s", e.host, e.port)
-
- return smtp.SendMail(addr, auth, e.from, []string{to}, []byte(raw))
-}
-
-// text-template used to generate a raw Email message
-var emailTemplate = `From: %s
-To: %s
-Subject: %s
-MIME-version: 1.0
-Content-Type: text/html; charset="UTF-8"
-
-%s`
diff --git a/pkg/plugin/notify/notification.go b/pkg/plugin/notify/notification.go
index 0b80ae4d8..a4c6feabf 100644
--- a/pkg/plugin/notify/notification.go
+++ b/pkg/plugin/notify/notification.go
@@ -8,7 +8,7 @@ import (
// in-progress build request.
type Context struct {
// Global settings
- Settings *model.Settings
+ Host string
// User that owns the repository
User *model.User
@@ -35,9 +35,9 @@ type Notification struct {
func (n *Notification) Send(context *Context) error {
// send email notifications
- //if n.Email != nil && n.Email.Enabled {
- // n.Email.Send(context)
- //}
+ if n.Email != nil {
+ n.Email.Send(context)
+ }
// send email notifications
if n.Webhook != nil {
diff --git a/pkg/plugin/publish/s3.go b/pkg/plugin/publish/s3.go
index cfa75e7d1..079be15c0 100644
--- a/pkg/plugin/publish/s3.go
+++ b/pkg/plugin/publish/s3.go
@@ -51,6 +51,14 @@ type S3 struct {
}
func (s *S3) Write(f *buildfile.Buildfile) {
+
+ // skip if AWS key or SECRET are empty. A good example for this would
+ // be forks building a project. S3 might be configured in the source
+ // repo, but not in the fork
+ if len(s.Key) == 0 || len(s.Secret) == 0 {
+ return
+ }
+
// install the AWS cli using PIP
f.WriteCmdSilent("[ -f /usr/bin/sudo ] || pip install awscli 1> /dev/null 2> /dev/null")
f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo pip install awscli 1> /dev/null 2> /dev/null")
@@ -65,8 +73,8 @@ func (s *S3) Write(f *buildfile.Buildfile) {
// make sure a default access is set
// let's be conservative and assume private
- if len(s.Region) == 0 {
- s.Region = "private"
+ if len(s.Access) == 0 {
+ s.Access = "private"
}
// if the target starts with a "/" we need
diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go
index 643df98de..e931c46f0 100644
--- a/pkg/queue/queue.go
+++ b/pkg/queue/queue.go
@@ -4,14 +4,15 @@ import (
"bytes"
"fmt"
bldr "github.com/drone/drone/pkg/build"
+ "github.com/drone/drone/pkg/build/git"
r "github.com/drone/drone/pkg/build/repo"
"github.com/drone/drone/pkg/build/script"
- "github.com/drone/drone/pkg/build/script/notification"
"github.com/drone/drone/pkg/channel"
"github.com/drone/drone/pkg/database"
- "github.com/drone/drone/pkg/mail"
. "github.com/drone/drone/pkg/model"
+ "github.com/drone/drone/pkg/plugin/notify"
"github.com/drone/go-github/github"
+ "log"
"path/filepath"
"time"
)
@@ -93,7 +94,7 @@ func (b *BuildTask) execute() error {
settings, _ := database.GetSettings()
// notification context
- context := ¬ification.Context{
+ context := ¬ify.Context{
Repo: b.Repo,
Commit: b.Commit,
Host: settings.URL().String(),
@@ -104,6 +105,11 @@ func (b *BuildTask) execute() error {
b.Script.Notifications.Send(context)
}
+ // Send "started" notification to Github
+ if err := updateGitHubStatus(b.Repo, b.Commit); err != nil {
+ log.Printf("error updating github status: %s\n", err.Error())
+ }
+
// make sure a channel exists for the repository,
// the commit, and the commit output (TODO)
reposlug := fmt.Sprintf("%s/%s/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name)
@@ -130,10 +136,19 @@ func (b *BuildTask) execute() error {
// execute the build
builder := bldr.Builder{}
builder.Build = b.Script
- builder.Repo = &r.Repo{Path: b.Repo.URL, Branch: b.Commit.Branch, Commit: b.Commit.Hash, PR: b.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", b.Repo.Slug)}
+ builder.Repo = &r.Repo{Path: b.Repo.URL, Branch: b.Commit.Branch, Commit: b.Commit.Hash, PR: b.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", b.Repo.Slug), Depth: git.GitDepth(b.Script.Git)}
builder.Key = []byte(b.Repo.PrivateKey)
builder.Stdout = buf
builder.Timeout = 300 * time.Minute
+
+ defer func() {
+ // update the status of the commit using the
+ // GitHub status API.
+ if err := updateGitHubStatus(b.Repo, b.Commit); err != nil {
+ log.Printf("error updating github status: %s\n", err.Error())
+ }
+ }()
+
buildErr := builder.Run()
b.Build.Finished = time.Now().UTC()
@@ -169,24 +184,11 @@ func (b *BuildTask) execute() error {
channel.SendJSON(commitslug, b.Build)
channel.Close(consoleslug)
- // add the smtp address to the notificaitons
- //if b.Script.Notifications != nil && b.Script.Notifications.Email != nil {
- // b.Script.Notifications.Email.SetServer(settings.SmtpServer, settings.SmtpPort,
- // settings.SmtpUsername, settings.SmtpPassword, settings.SmtpAddress)
- //}
-
// send all "finished" notifications
if b.Script.Notifications != nil {
- b.sendEmail(context) // send email from queue, not from inside /build/script package
b.Script.Notifications.Send(context)
}
- // update the status of the commit using the
- // GitHub status API.
- if err := updateGitHubStatus(b.Repo, b.Commit); err != nil {
- return err
- }
-
return nil
}
@@ -197,14 +199,14 @@ func updateGitHubStatus(repo *Repo, commit *Commit) error {
// convert from drone status to github status
var message, status string
- switch status {
+ switch commit.Status {
case "Success":
status = "success"
message = "The build succeeded on drone.io"
case "Failure":
status = "failure"
message = "The build failed on drone.io"
- case "Pending":
+ case "Started":
status = "pending"
message = "The build is pending on drone.io"
default:
@@ -218,56 +220,17 @@ func updateGitHubStatus(repo *Repo, commit *Commit) error {
// get the user from the database
// since we need his / her GitHub token
user, err := database.GetUser(repo.UserID)
- if err == nil {
+ if err != nil {
return err
}
client := github.New(user.GithubToken)
- return client.Repos.CreateStatus(repo.Owner, repo.Name, status, settings.URL().String(), message, commit.Hash)
-}
+ client.ApiUrl = settings.GitHubApiUrl;
-func (t *BuildTask) sendEmail(c *notification.Context) error {
- // make sure a notifications object exists
- if t.Script.Notifications == nil && t.Script.Notifications.Email != nil {
- return nil
- }
+ var url string
+ url = settings.URL().String() + "/" + repo.Slug + "/commit/" + commit.Hash
- switch {
- case t.Commit.Status == "Success" && t.Script.Notifications.Email.Success != "never":
- return t.sendSuccessEmail(c)
- case t.Commit.Status == "Failure" && t.Script.Notifications.Email.Failure != "never":
- return t.sendFailureEmail(c)
- default:
- println("sending nothing")
- }
-
- return nil
-}
-
-// sendFailure sends email notifications to the list of
-// recipients indicating the build failed.
-func (t *BuildTask) sendFailureEmail(c *notification.Context) error {
-
- // loop through and email recipients
- for _, email := range t.Script.Notifications.Email.Recipients {
- if err := mail.SendFailure(t.Repo.Name, email, c); err != nil {
- return err
- }
- }
- return nil
-}
-
-// sendSuccess sends email notifications to the list of
-// recipients indicating the build was a success.
-func (t *BuildTask) sendSuccessEmail(c *notification.Context) error {
-
- // loop through and email recipients
- for _, email := range t.Script.Notifications.Email.Recipients {
- if err := mail.SendSuccess(t.Repo.Name, email, c); err != nil {
- return err
- }
- }
- return nil
+ return client.Repos.CreateStatus(repo.Owner, repo.Name, status, url, message, commit.Hash)
}
type bufferWrapper struct {
diff --git a/pkg/template/emails/success.html b/pkg/template/emails/success.html
index a5d9a4b9e..31facdfe4 100644
--- a/pkg/template/emails/success.html
+++ b/pkg/template/emails/success.html
@@ -1,4 +1,4 @@
-{{ define "title" }}FAILURE{{end}}
+{{ define "title" }}SUCCESS{{end}}
{{ define "content" }}
@@ -25,4 +25,4 @@
{{ .Commit.Message }}
-{{ end }}
\ No newline at end of file
+{{ end }}
diff --git a/pkg/template/pages/admin_settings.html b/pkg/template/pages/admin_settings.html
index 76097d610..d3f1b66ee 100644
--- a/pkg/template/pages/admin_settings.html
+++ b/pkg/template/pages/admin_settings.html
@@ -33,6 +33,9 @@
{{ end }}
+
GitHub OAuth Consumer Key and Secret
@@ -41,6 +44,11 @@
+
+
+
+
+
Bitbucket OAuth Consumer Key and Secret.
diff --git a/pkg/template/pages/admin_users_add.html b/pkg/template/pages/admin_users_add.html
index 08d379fd1..f0d2ee750 100644
--- a/pkg/template/pages/admin_users_add.html
+++ b/pkg/template/pages/admin_users_add.html
@@ -59,9 +59,9 @@
if (this.status == 200) {
var msg = "User Invitation was sent successfully";
if (this.responseText != "OK") {
- msg = "Email is not currently enabled. In order to invite the user, you'll need to provide them the following link: " + this.responseText;
+ msg = "Email is not currently enabled. In order to invite the user, you'll need to provide them the following link: " + this.responseText + "";
}
- $("#successAlert").text(msg);
+ $("#successAlert").html(msg);
$("#successAlert").show().removeClass("hide");
$('#submitButton').button('reset')
@@ -75,4 +75,4 @@
return false;
}
-{{ end }}
\ No newline at end of file
+{{ end }}
diff --git a/pkg/template/pages/base.html b/pkg/template/pages/base.html
index 5af15d959..840f8c750 100644
--- a/pkg/template/pages/base.html
+++ b/pkg/template/pages/base.html
@@ -38,7 +38,7 @@