mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 11:51:02 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
7bef94881b
97 changed files with 1963 additions and 805 deletions
|
@ -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/
|
27
Dockerfile
Normal file
27
Dockerfile
Normal file
|
@ -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"]
|
14
Makefile
14
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"
|
||||
bin/droned --port=":8080" --datasource="drone.sqlite"
|
||||
|
|
76
README.md
76
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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1074,3 +1074,6 @@ ul.account-radio-group li img {
|
|||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.url {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
|
|
@ -1261,3 +1261,7 @@ pre {
|
|||
margin-bottom:30px;
|
||||
}
|
||||
}
|
||||
|
||||
.url {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
2
deb/drone/DEBIAN/conffiles
Normal file
2
deb/drone/DEBIAN/conffiles
Normal file
|
@ -0,0 +1,2 @@
|
|||
/etc/init/drone.conf
|
||||
/etc/default/drone
|
24
deb/drone/DEBIAN/postinst
Executable file
24
deb/drone/DEBIAN/postinst
Executable file
|
@ -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
|
26
deb/drone/DEBIAN/prerm
Executable file
26
deb/drone/DEBIAN/prerm
Executable file
|
@ -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
|
10
deb/drone/etc/default/drone
Normal file
10
deb/drone/etc/default/drone
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Upstart configuration file for droned.
|
||||
|
||||
# Command line options:
|
||||
#
|
||||
# -datasource="drone.sqlite":
|
||||
# -driver="sqlite3":
|
||||
# -path="":
|
||||
# -port=":8080":
|
||||
#
|
||||
#DRONED_OPTS="--port=:80"
|
|
@ -4,5 +4,9 @@ chdir /var/lib/drone
|
|||
console log
|
||||
|
||||
script
|
||||
droned --port=":80"
|
||||
DRONED_OPTS="--port=:80"
|
||||
if [ -f /etc/default/$UPSTART_JOB ]; then
|
||||
. /etc/default/$UPSTART_JOB
|
||||
fi
|
||||
droned $DRONED_OPTS
|
||||
end script
|
|
@ -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
|
||||
|
|
67
pkg/build/docker/client_test.go
Normal file
67
pkg/build/docker/client_test.go
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
31
pkg/build/git/git.go
Normal file
31
pkg/build/git/git.go
Normal file
|
@ -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
|
||||
}
|
40
pkg/build/git/git_test.go
Normal file
40
pkg/build/git/git_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type AppFog struct {
|
||||
}
|
||||
|
||||
func (a *AppFog) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type CloudControl struct {
|
||||
}
|
||||
|
||||
func (c *CloudControl) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type CloudFoundry struct {
|
||||
}
|
||||
|
||||
func (c *CloudFoundry) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type EngineYard struct {
|
||||
}
|
||||
|
||||
func (e *EngineYard) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package deployment
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type Nodejitsu struct {
|
||||
}
|
||||
|
||||
func (n *Nodejitsu) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type Openshift struct {
|
||||
}
|
||||
|
||||
func (o *Openshift) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package deployment
|
|
@ -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`
|
|
@ -1,64 +0,0 @@
|
|||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/andybons/hipchat"
|
||||
)
|
||||
|
||||
const (
|
||||
startedMessage = "Building %s, commit %s, author %s"
|
||||
successMessage = "<b>Success</b> %s, commit %s, author %s"
|
||||
failureMessage = "<b>Failed</b> %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)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package notification
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package notification
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -1 +0,0 @@
|
|||
package publish
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
package publish
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
cobertura.go
|
||||
coveralls.go
|
||||
gocov.go
|
||||
junit.go
|
||||
phpunit.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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
12
pkg/database/migrate/all.go
Normal file
12
pkg/database/migrate/all.go
Normal file
|
@ -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
|
||||
}
|
215
pkg/database/migrate/migrate.go
Normal file
215
pkg/database/migrate/migrate.go
Normal file
|
@ -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()
|
||||
}
|
30
pkg/database/migrate/migration
Executable file
30
pkg/database/migrate/migration
Executable file
|
@ -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
|
283
pkg/database/migrate/sqlite.go
Normal file
283
pkg/database/migrate/sqlite.go
Normal file
|
@ -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: <nil> -> *string") {
|
||||
continue
|
||||
}
|
||||
return sqls, err
|
||||
}
|
||||
sqls = append(sqls, sql)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return sqls, err
|
||||
}
|
||||
|
||||
return sqls, nil
|
||||
}
|
520
pkg/database/migrate/sqlite_test.go
Normal file
520
pkg/database/migrate/sqlite_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
32
pkg/database/migrate/util.go
Normal file
32
pkg/database/migrate/util.go
Normal file
|
@ -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, ", ")
|
||||
}
|
|
@ -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 = ?
|
||||
`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
`
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{ define "title" }}FAILURE{{end}}
|
||||
{{ define "title" }}SUCCESS{{end}}
|
||||
|
||||
{{ define "content" }}
|
||||
<!-- Callout Panel -->
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
{{ end }}
|
||||
</select>
|
||||
<input class="form-control form-control-xlarge" type="text" name="Domain" value="{{.Settings.Domain}}" />
|
||||
<label class="checkbox">
|
||||
Open invitations <input type="checkbox" name="OpenInvitations" {{ if .Settings.OpenInvitations }} checked {{ end }} />
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="alert">GitHub OAuth Consumer Key and Secret</div>
|
||||
|
@ -41,6 +44,11 @@
|
|||
<input class="form-control form-control-large" type="text" name="GitHubKey" value="{{.Settings.GitHubKey}}" />
|
||||
<input class="form-control form-control-large" type="password" name="GitHubSecret" value="{{.Settings.GitHubSecret}}" />
|
||||
</div>
|
||||
<label>GitHub Settings:</label>
|
||||
<div>
|
||||
<input class="form-control form-control-large" type="text" name="GitHubDomain" value="{{.Settings.GitHubDomain}}" />
|
||||
<input class="form-control form-control-large" type="text" name="GitHubApiUrl" value="{{.Settings.GitHubApiUrl}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hide">
|
||||
<div class="alert">Bitbucket OAuth Consumer Key and Secret.</div>
|
||||
|
|
|
@ -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:<br><span class='url'>" + this.responseText + "</span>";
|
||||
}
|
||||
$("#successAlert").text(msg);
|
||||
$("#successAlert").html(msg);
|
||||
$("#successAlert").show().removeClass("hide");
|
||||
$('#submitButton').button('reset')
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<ul class="nav navbar-nav">
|
||||
<li><a href="/dashboard">Home</a></li>
|
||||
<li><a href="/account/user/profile">Settings</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
<li><a href="http://drone.readthedocs.org/en/latest/">Help</a></li>
|
||||
<li><a class="btn btn-config" href="/account/admin/settings"><i class="fa fa-cogs"></i></a></li>
|
||||
<li><a href="/new/github.com" class="btn">New Repository</a></li>
|
||||
<li><a href="/logout" class="btn">Logout</a></li>
|
||||
|
|
|
@ -14,16 +14,18 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="/github.com/drone/drone/settings">GitHub</a></li>
|
||||
<li><a href="/github.com/drone/drone/secure">Bitbucket <small>(coming soon)</small></a></li>
|
||||
</div><!-- ./col-xs-3 -->
|
||||
<li class="active"><a href="/new/github.com">GitHub</a></li>
|
||||
<li><a href="/new/bitbucket.org">Bitbucket <small>(coming soon)</small></a></li>
|
||||
</ul>
|
||||
</div><!-- ./col-xs-3 -->
|
||||
|
||||
<div class="col-xs-9" role="main">
|
||||
<div class="col-xs-9" role="main">
|
||||
<div class="alert">
|
||||
Enter your repository details
|
||||
<a class="btn btn-default pull-right" href="/auth/login/github" style="font-size: 18px;background:#f4f4f4;">Re-Link Account</a>
|
||||
</div>
|
||||
<form class="form-repo" method="POST" action="/new/github.com">
|
||||
<input type="hidden" name="domain" autocomplete="off" value="{{.Settings.GitHubDomain}}">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<label>GitHub Owner</label>
|
||||
|
@ -84,7 +86,8 @@
|
|||
if (this.status == 200) {
|
||||
var name = $("input[name=name]").val()
|
||||
var owner = $("input[name=owner]").val()
|
||||
window.location.pathname = "/github.com/"+owner+"/"+name
|
||||
var domain = $("input[name=domain]").val()
|
||||
window.location.pathname = "/" + domain + "/"+owner+"/"+name
|
||||
} else {
|
||||
$("#failureAlert").text("Unable to setup the Repository");
|
||||
$("#failureAlert").show().removeClass("hide");
|
||||
|
|
|
@ -14,11 +14,12 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="/github.com/drone/drone/settings">GitHub</a></li>
|
||||
<li><a href="/github.com/drone/drone/secure">Bitbucket <small>(coming soon)</small></a></li>
|
||||
</div><!-- ./col-xs-3 -->
|
||||
<li class="active"><a href="/new/github.com">GitHub</a></li>
|
||||
<li><a href="/new/bitbucket.org">Bitbucket <small>(coming soon)</small></a></li>
|
||||
</ul>
|
||||
</div><!-- ./col-xs-3 -->
|
||||
|
||||
<div class="col-xs-9" role="main">
|
||||
<div class="col-xs-9" role="main">
|
||||
<div class="alert">Link Your GitHub Account
|
||||
<a class="btn btn-primary pull-right" href="/auth/login/github" style="font-size: 18px;">Link Now</a>
|
||||
</div>
|
||||
|
|
|
@ -2,20 +2,23 @@
|
|||
|
||||
{{ define "content" }}
|
||||
<h1>Installation</h1>
|
||||
<div>
|
||||
<input class="form-control" type="text" name="name" placeholder="Full Name (e.g. John Smith)" autocomplete="off" spellcheck="off" />
|
||||
<input class="form-control" type="text" name="email" placeholder="Email Address" autocomplete="off" spellcheck="off" style="border-top:0px;border-radius:0px;" />
|
||||
<input class="form-control" type="password" name="password" placeholder="Password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="hidden" name="Scheme" />
|
||||
<input type="hidden" name="Domain" />
|
||||
<input type="submit" value="Create Admin" />
|
||||
</div>
|
||||
<form action="install" method="post">
|
||||
<div>
|
||||
<input class="form-control" type="text" name="name" placeholder="Full Name (e.g. John Smith)" autocomplete="off" spellcheck="off" />
|
||||
<input class="form-control" type="text" name="email" placeholder="Email Address" autocomplete="off" spellcheck="off" style="border-top:0px;border-radius:0px;" />
|
||||
<input class="form-control" type="password" name="password" placeholder="Password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="hidden" name="Scheme" />
|
||||
<input type="hidden" name="Domain" />
|
||||
<input type="submit" value="Create Admin" />
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ define "script" }}
|
||||
<script>
|
||||
$("input[name=Domain]").val(window.location.host)
|
||||
$("input[name=Scheme]").val(window.location.protocol == "https:"?"https":"http")
|
||||
</script>
|
||||
{{ end }}
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
<input type="submit" value="Sign in" />
|
||||
</div>
|
||||
<div>
|
||||
{{ if .Settings.OpenInvitations }}
|
||||
<a href="/signup">request invitation</a> | <a href="/forgot">forgot password</a>
|
||||
{{ else }}
|
||||
<a href="/forgot">forgot password</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -69,9 +69,9 @@
|
|||
if (this.status == 200) {
|
||||
var msg = "An invitation has been sent (via email) to join the Team.";
|
||||
if (this.responseText != "OK") {
|
||||
msg = "Email is not currently enabled. In order to invite this team member user, you'll need to provide them the following link: " + this.responseText;
|
||||
msg = "Email is not currently enabled. In order to invite this team member user, you'll need to provide them the following link:<br><span class='url'>" + this.responseText + "</span>";
|
||||
}
|
||||
$("#successAlert").text(msg);
|
||||
$("#successAlert").html(msg);
|
||||
$("#successAlert").show().removeClass("hide");
|
||||
$('#submitButton').button('reset')
|
||||
} else {
|
||||
|
|
|
@ -34,11 +34,11 @@
|
|||
<div class="form-group">
|
||||
<img class="pull-right" src="{{.Host}}/{{.Repo.Slug}}/status.png?branch=master">
|
||||
<label>Badge, Markdown format</label>
|
||||
<textarea class="form-control" rows="3">[![Build Status]({{.Host}}/{{.Repo.Name}}/status.png?branch=master)]({{.Host}}/{{.Repo.Name}}/tree/master)</textarea>
|
||||
<textarea class="form-control" rows="3">[![Build Status]({{.Host}}/{{.Repo.Slug}}/status.png?branch=master)]({{.Host}}/{{.Repo.Slug}})</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Badge, HTML format</label>
|
||||
<textarea class="form-control" rows="3"><a href="{{.Host}}/{{.Repo.Slug}}/tree/master"><img src="{{.Host}}/{{.Repo.Slug}}/status.png?branch=master" /></a></textarea>
|
||||
<textarea class="form-control" rows="3"><a href="{{.Host}}/{{.Repo.Slug}}"><img src="{{.Host}}/{{.Repo.Slug}}/status.png?branch=master" /></a></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- ./col-xs-9 -->
|
||||
|
|
|
@ -55,6 +55,66 @@
|
|||
$(".timeago").timeago();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var re = /\u001B\[([0-9]+;?)*[Km]/g;
|
||||
|
||||
var styles = new Array();
|
||||
var formatLine = function(s) {
|
||||
// Check for newline and early exit?
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/>/g, ">");
|
||||
|
||||
var final = "";
|
||||
var current = 0;
|
||||
while (m = re.exec(s)) {
|
||||
var part = s.substring(current, m.index+1);
|
||||
current = re.lastIndex;
|
||||
|
||||
var token = s.substr(m.index, re.lastIndex - m.index);
|
||||
var code = token.substr(2, token.length-2);
|
||||
|
||||
var pre = "";
|
||||
var post = "";
|
||||
|
||||
switch (code) {
|
||||
case 'm':
|
||||
case '0m':
|
||||
var len = styles.length;
|
||||
for (var i=0; i < len; i++) {
|
||||
styles.pop();
|
||||
post += "</span>"
|
||||
}
|
||||
break;
|
||||
case '30;42m': pre = '<span style="color:black;background:lime">'; break;
|
||||
case '36m':
|
||||
case '36;1m': pre = '<span style="color:cyan;">'; break;
|
||||
case '31m':
|
||||
case '31;31m': pre = '<span style="color:red;">'; break;
|
||||
case '33m':
|
||||
case '33;33m': pre = '<span style="color:yellow;">'; break;
|
||||
case '32m':
|
||||
case '0;32m': pre = '<span style="color:lime;">'; break;
|
||||
case '90m': pre = '<span style="color:gray;">'; break;
|
||||
case 'K':
|
||||
case '0K':
|
||||
case '1K':
|
||||
case '2K': break;
|
||||
}
|
||||
|
||||
if (pre !== "") {
|
||||
styles.push(pre);
|
||||
}
|
||||
|
||||
final += part + pre + post;
|
||||
}
|
||||
|
||||
var part = s.substring(current, s.length);
|
||||
final += part;
|
||||
return final;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
{{ if .Build.IsRunning }}
|
||||
var outputBox = document.getElementById('stdout');
|
||||
|
@ -64,16 +124,11 @@
|
|||
outputWS.onclose = function (e) { window.location.reload(); };
|
||||
outputWS.onmessage = function (e) {
|
||||
outputBox.innerHTML += formatLine(e.data);
|
||||
//window.scrollTo(0, document.body.scrollHeight)
|
||||
};
|
||||
var re = /\u001B\[([0-9]+;?)*[Km]/g;
|
||||
var styles = new Array();
|
||||
var formatLine = function(s) {
|
||||
return s;
|
||||
window.scrollTo(0, document.body.scrollHeight)
|
||||
};
|
||||
{{ else }}
|
||||
$.get("/{{ .Repo.Slug }}/commit/{{ .Commit.Hash }}/build/{{ .Build.Slug }}/out.txt", function( data ) {
|
||||
$( "#stdout" ).html( data );
|
||||
$( "#stdout" ).html(formatLine(data));
|
||||
});
|
||||
{{ end }}
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-8" role="main">
|
||||
<a class="btn btn-refresh hide" href="/{{.Repo.Slug}}/tree/{{.Branch}}">
|
||||
<a class="btn btn-refresh hide" href="/{{.Repo.Slug}}/tree?branch={{.Branch}}">
|
||||
<i class="fa fa-rotate-right"></i>
|
||||
<span>0 new</span>
|
||||
</a>
|
||||
|
@ -33,7 +33,7 @@
|
|||
<a href="/{{$repo.Slug}}/commit/{{.Hash}}">{{.HashShort}}</a>
|
||||
<small class="timeago" title="{{.CreatedString}}"></small>
|
||||
{{ if .PullRequest }}
|
||||
<p>opened pull request <a href="/{{.Slug}}/commit/{{.Hash}}"># {{.PullRequest}}</a></p>
|
||||
<p>opened pull request <a href="/{{$repo.Slug}}/commit/{{.Hash}}"># {{.PullRequest}}</a></p>
|
||||
{{ else }}
|
||||
<p>{{.Message}} </p>
|
||||
{{ end }}
|
||||
|
@ -49,7 +49,7 @@
|
|||
<ul class="nav nav-pills nav-stacked nav-branches">
|
||||
{{ range .Branches }}
|
||||
<li{{ if eq $branch .Branch }} class="active"{{end}}>
|
||||
<a href="/{{ $repo.Slug }}/tree/{{.Branch}}">
|
||||
<a href="/{{ $repo.Slug }}/tree?branch={{.Branch}}">
|
||||
<span class="btn btn-mini btn-{{.Status}} "></span>
|
||||
<span>{{.Branch}}</span>
|
||||
</a>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
<div>
|
||||
<input class="form-control" type="password" name="password" value="" />
|
||||
</div>
|
||||
<div class="alert alert-error hide" id="failureAlert"></div>
|
||||
<div class="form-actions">
|
||||
<input class="btn btn-danger" id="submitButton" type="submit" value="Delete Repository" />
|
||||
<a class="btn btn-default" href="/{{.Repo.Slug}}/settings">Cancel</a>
|
||||
|
@ -51,4 +52,27 @@
|
|||
{{ end }}
|
||||
|
||||
{{ define "script" }}
|
||||
<script>
|
||||
document.forms[0].onsubmit = function(event) {
|
||||
|
||||
$("#failureAlert").hide();
|
||||
$('#submitButton').button('loading');
|
||||
|
||||
var form = event.target
|
||||
var formData = new FormData(form);
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', form.action);
|
||||
xhr.onload = function() {
|
||||
if (this.status == 400) {
|
||||
$("#failureAlert").text("The password you entered was incorrect.");
|
||||
$("#failureAlert").show().removeClass("hide");
|
||||
$('#submitButton').button('reset')
|
||||
} else {
|
||||
window.location.href = "/dashboard";
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
{{ end }}
|
45
pkg/template/pages/signup.html
Normal file
45
pkg/template/pages/signup.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
{{ define "title" }}Sign up · drone.io{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<h1>Sign up</h1>
|
||||
<form action="/signup" method="POST" role="form">
|
||||
<div class="alert alert-success hide" id="successAlert"></div>
|
||||
<div class="alert alert-error hide" id="failureAlert"></div>
|
||||
<div>
|
||||
<input type="text" name="email" placeholder="Email address" autocomplete="off" spellcheck="false" class="form-control only-child" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" id="submitButton" value="Request invite" data-loading-text="Sending Invite .." />
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ define "script" }}
|
||||
<script>
|
||||
document.forms[0].onsubmit = function(event) {
|
||||
|
||||
$("#successAlert").hide();
|
||||
$("#failureAlert").hide();
|
||||
$('#submitButton').button('loading');
|
||||
|
||||
var form = event.target
|
||||
var formData = new FormData(form);
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', form.action);
|
||||
xhr.onload = function() {
|
||||
if (this.status == 200) {
|
||||
$("#successAlert").text("User Invitation was sent successfully");
|
||||
$("#successAlert").show().removeClass("hide");
|
||||
$('#submitButton').button('reset')
|
||||
|
||||
} else {
|
||||
$("#failureAlert").text("Failed to send Invitation Email. " + this.response);
|
||||
$("#failureAlert").show().removeClass("hide");
|
||||
$('#submitButton').button('reset')
|
||||
};
|
||||
};
|
||||
xhr.send(formData);
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
{{ end }}
|
|
@ -52,7 +52,7 @@
|
|||
{{ if $commit.PullRequest }}
|
||||
<p>opened pull request <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}"># {{$commit.PullRequest}}</a></p>
|
||||
{{ else }}
|
||||
<p>commit <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}">{{$commit.HashShort}}</a> to <a href="/{{$commit.Slug}}/tree/{{$commit.Branch}}">{{$commit.Branch}}</a> branch</p>
|
||||
<p>commit <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}">{{$commit.HashShort}}</a> to <a href="/{{$commit.Slug}}?branch={{$commit.Branch}}">{{$commit.Branch}}</a> branch</p>
|
||||
{{ end }}
|
||||
</h3>
|
||||
</li>
|
||||
|
|
|
@ -43,14 +43,14 @@
|
|||
<ul class="commit-list">
|
||||
{{ range $commit := .Commits }}
|
||||
<li>
|
||||
<a href="/{{$commit.Name}}/commit/{{$commit.Hash}}" class="btn btn-{{$commit.Status}}"></a>
|
||||
<a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}" class="btn btn-{{$commit.Status}}"></a>
|
||||
<h3>
|
||||
<a href="/{{$commit.Slug}}">{{$commit.Owner}} / {{$commit.Name}}</a>
|
||||
<small class="timeago" title="{{$commit.CreatedString}}"></small>
|
||||
{{ if $commit.PullRequest }}
|
||||
<p>opened pull request <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}"># {{$commit.PullRequest}}</a></p>
|
||||
{{ else }}
|
||||
<p>commit <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}">{{$commit.HashShort}}</a> to <a href="/{{$commit.Slug}}/tree/{{$commit.Branch}}">{{$commit.Branch}}</a> branch</p>
|
||||
<p>commit <a href="/{{$commit.Slug}}/commit/{{$commit.Hash}}">{{$commit.HashShort}}</a> to <a href="/{{$commit.Slug}}?branch={{$commit.Branch}}">{{$commit.Branch}}</a> branch</p>
|
||||
{{ end }}
|
||||
</h3>
|
||||
</li>
|
||||
|
|
|
@ -45,6 +45,7 @@ func init() {
|
|||
"forgot.html",
|
||||
"forgot_sent.html",
|
||||
"reset.html",
|
||||
"signup.html",
|
||||
"register.html",
|
||||
"install.html",
|
||||
|
||||
|
@ -103,7 +104,7 @@ func init() {
|
|||
// HACK: choose which base template to use FOR THE RECORD I
|
||||
// don't really like this, but it works for now.
|
||||
var baseTemplate = base
|
||||
if i < 7 {
|
||||
if i < 8 {
|
||||
baseTemplate = form
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue