mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-05 07:08:41 +00:00
updated vendor files and paths
This commit is contained in:
parent
155576fb03
commit
dfea14c7e5
719 changed files with 128749 additions and 34774 deletions
|
@ -1,13 +0,0 @@
|
||||||
bin/
|
|
||||||
cmd/drone-server/drone_bindata.go
|
|
||||||
dist/
|
|
||||||
doc/
|
|
||||||
|
|
||||||
.git/
|
|
||||||
.dockerignore
|
|
||||||
.drone.yml
|
|
||||||
.gitignore
|
|
||||||
drone.sqlite
|
|
||||||
Dockerfile
|
|
||||||
LICENSE
|
|
||||||
README.md
|
|
46
.drone.yml
46
.drone.yml
|
@ -8,14 +8,12 @@ env:
|
||||||
- PATH=$PATH:$GOROOT/bin:$GOPATH/bin
|
- PATH=$PATH:$GOROOT/bin:$GOPATH/bin
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go run make.go deps
|
- apt-get -y -qq update
|
||||||
- go run make.go bindata
|
- apt-get -y -qq install libsqlite3-dev
|
||||||
- go run make.go vet
|
- make deps
|
||||||
- go run make.go fmt
|
- make
|
||||||
- go run make.go build
|
- make test
|
||||||
- go run make.go test
|
- make deb
|
||||||
|
|
||||||
- make dist
|
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
email:
|
email:
|
||||||
|
@ -29,34 +27,26 @@ publish:
|
||||||
bucket: downloads.drone.io
|
bucket: downloads.drone.io
|
||||||
access_key: $$AWS_KEY
|
access_key: $$AWS_KEY
|
||||||
secret_key: $$AWS_SECRET
|
secret_key: $$AWS_SECRET
|
||||||
source: dist/drone.deb
|
source: contrib/debian/drone.deb
|
||||||
target: $DRONE_BRANCH/
|
target: $DRONE_BRANCH/
|
||||||
when:
|
when:
|
||||||
owner: drone
|
owner: drone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
clone:
|
clone:
|
||||||
path: github.com/drone/drone
|
path: github.com/drone/drone
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: golang:1.5.0
|
image: golang:1.5
|
||||||
commands:
|
commands:
|
||||||
- export GOPATH=/drone
|
- apt-get -y -qq update
|
||||||
- export PATH=$PATH:$GOPATH/bin
|
- apt-get -y -qq install libsqlite3-dev
|
||||||
|
- make deps
|
||||||
- go run make.go deps
|
- make gen
|
||||||
- go run make.go bindata
|
- make test
|
||||||
- go run make.go vet
|
- make build
|
||||||
- go run make.go fmt
|
- make build_static
|
||||||
- go run make.go build
|
- make deb
|
||||||
- go run make.go test
|
|
||||||
|
|
||||||
- make dist
|
|
||||||
|
|
||||||
compose:
|
|
||||||
database:
|
|
||||||
image: mysql:5.5
|
|
||||||
environment:
|
|
||||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
|
||||||
- MYSQL_DATABASE=test
|
|
||||||
|
|
41
.gitignore
vendored
41
.gitignore
vendored
|
@ -1,34 +1,13 @@
|
||||||
drone.sublime-project
|
drone
|
||||||
drone.sublime-workspace
|
drone_*
|
||||||
.vagrant
|
|
||||||
|
|
||||||
*~
|
|
||||||
~*
|
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*_gen.go
|
||||||
*.deb
|
*.html
|
||||||
*.deb.*
|
*.css
|
||||||
*.rpm
|
|
||||||
*.out
|
|
||||||
*.prof
|
|
||||||
*.rice-box.go
|
|
||||||
*.db
|
|
||||||
*.txt
|
*.txt
|
||||||
*.min.css
|
*.zip
|
||||||
|
*.gz
|
||||||
|
*.out
|
||||||
*.min.js
|
*.min.js
|
||||||
*_bindata.go
|
*.deb
|
||||||
*.toml
|
temp/
|
||||||
|
|
||||||
# generate binaries
|
|
||||||
cmd/drone-agent/drone-agent
|
|
||||||
cmd/drone-build/drone-build
|
|
||||||
cmd/drone-agent/drone-server
|
|
||||||
|
|
||||||
# generated binaries in ./bin
|
|
||||||
bin/drone
|
|
||||||
bin/drone-agent
|
|
||||||
bin/drone-build
|
|
||||||
bin/drone-server
|
|
||||||
|
|
||||||
# generated binaries in dpkg
|
|
||||||
dist/drone/usr/local/bin/drone
|
|
||||||
|
|
36
Dockerfile
36
Dockerfile
|
@ -1,22 +1,20 @@
|
||||||
FROM golang:1.4.2
|
# Build the drone executable on a x64 Linux host:
|
||||||
|
#
|
||||||
|
# go build --ldflags '-extldflags "-static"' -o drone_static
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Alternate command for Go 1.4 and older:
|
||||||
|
#
|
||||||
|
# go build -a -tags netgo --ldflags '-extldflags "-static"' -o drone_static
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Build the docker image:
|
||||||
|
#
|
||||||
|
# docker build --rm=true -t drone/drone .
|
||||||
|
|
||||||
ENV DRONE_SERVER_PORT :80
|
FROM centurylink/ca-certs
|
||||||
WORKDIR $GOPATH/src/github.com/drone/drone
|
EXPOSE 8080
|
||||||
|
|
||||||
EXPOSE 80
|
ADD drone_static /drone_static
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/drone"]
|
ENTRYPOINT ["/drone_static"]
|
||||||
CMD ["-config", "/tmp/drone.toml"]
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y libsqlite3-dev \
|
|
||||||
&& git clone git://github.com/gin-gonic/gin.git $GOPATH/src/github.com/gin-gonic/gin \
|
|
||||||
&& go get -u github.com/jteeuwen/go-bindata/...
|
|
||||||
|
|
||||||
RUN touch /tmp/drone.toml
|
|
||||||
|
|
||||||
ADD . .
|
|
||||||
RUN make bindata deps \
|
|
||||||
&& make build \
|
|
||||||
&& mv bin/* /usr/local/bin/ \
|
|
||||||
&& rm -rf bin cmd/drone-server/drone_bindata.go
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Docker image for the Drone build runner
|
|
||||||
#
|
|
||||||
# docker build --file=Dockerfile.alpine --rm=true -t drone/drone-alpine .
|
|
||||||
|
|
||||||
FROM alpine:3.2
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENV GOROOT=/usr/lib/go \
|
|
||||||
GOPATH=/gopath \
|
|
||||||
GOBIN=/gopath/bin \
|
|
||||||
PATH=$PATH:$GOROOT/bin:$GOPATH/bin
|
|
||||||
|
|
||||||
WORKDIR /gopath/src/github.com/drone/drone
|
|
||||||
ADD . /gopath/src/github.com/drone/drone
|
|
||||||
|
|
||||||
RUN apk add -U go ca-certificates libc-dev gcc git sqlite-libs && \
|
|
||||||
go get github.com/jteeuwen/go-bindata/... && \
|
|
||||||
/gopath/bin/go-bindata -o="cmd/drone-server/drone_bindata.go" cmd/drone-server/static/... && \
|
|
||||||
go run make.go build && \
|
|
||||||
apk del git go gcc libc-dev && \
|
|
||||||
mv bin/drone /bin/drone && \
|
|
||||||
rm -rf /gopath && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/drone"]
|
|
58
Makefile
58
Makefile
|
@ -1,39 +1,35 @@
|
||||||
.PHONY: dist
|
.PHONY: vendor
|
||||||
|
|
||||||
SHA := $(shell git rev-parse --short HEAD)
|
PACKAGES = $(shell go list ./... | grep -v /vendor/)
|
||||||
VERSION := 0.4.0-alpha
|
|
||||||
|
|
||||||
all: build
|
all: gen build
|
||||||
|
|
||||||
|
deps:
|
||||||
|
go get golang.org/x/tools/cmd/cover
|
||||||
|
go get golang.org/x/tools/cmd/vet
|
||||||
|
go get -u github.com/kr/vexp
|
||||||
|
go get -u github.com/eknkc/amber/amberc
|
||||||
|
go get -u github.com/jteeuwen/go-bindata/...
|
||||||
|
go get -u github.com/elazarl/go-bindata-assetfs/...
|
||||||
|
|
||||||
|
gen:
|
||||||
|
go generate $(go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go run make.go bindata build
|
GO15VENDOREXPERIMENT=1 go build
|
||||||
|
|
||||||
|
build_static:
|
||||||
|
GO15VENDOREXPERIMENT=1 go build --ldflags '-extldflags "-static"' -o drone_static
|
||||||
|
|
||||||
# Execute the database test suite against mysql 5.5
|
test:
|
||||||
#
|
go test -cover $(PACKAGES)
|
||||||
# You can launch a mysql container locally for testing:
|
|
||||||
# docker run -rm -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -e MYSQL_DATABASE=test -p 3306:3306 mysql:5.5
|
|
||||||
test_mysql:
|
|
||||||
mysql -P 3306 --protocol=tcp -u root -e 'create database if not exists test;'
|
|
||||||
TEST_DRIVER="mysql" TEST_DATASOURCE="root@tcp(127.0.0.1:3306)/test" go test -short github.com/drone/drone/pkg/store/builtin
|
|
||||||
mysql -P 3306 --protocol=tcp -u root -e 'drop database test;'
|
|
||||||
|
|
||||||
run:
|
deb:
|
||||||
bin/drone --debug
|
mkdir -p contrib/debian/drone/usr/local/bin
|
||||||
|
mkdir -p contrib/debian/drone/var/lib/drone
|
||||||
|
mkdir -p contrib/debian/drone/var/cache/drone
|
||||||
|
cp drone contrib/debian/drone/usr/local/bin
|
||||||
|
-dpkg-deb --build contrib/debian/drone
|
||||||
|
|
||||||
# installs the drone binaries into bin
|
vendor:
|
||||||
install:
|
vexp
|
||||||
install -t /usr/local/bin bin/drone
|
|
||||||
install -t /usr/local/bin bin/drone-agent
|
|
||||||
|
|
||||||
docker:
|
|
||||||
docker build --file=cmd/drone-build/Dockerfile.alpine --rm=true -t drone/drone-build .
|
|
||||||
|
|
||||||
# creates a debian package for drone
|
|
||||||
# to install `sudo dpkg -i drone.deb`
|
|
||||||
dist:
|
|
||||||
mkdir -p dist/drone/usr/local/bin
|
|
||||||
mkdir -p dist/drone/var/lib/drone
|
|
||||||
mkdir -p dist/drone/var/cache/drone
|
|
||||||
cp bin/drone dist/drone/usr/local/bin
|
|
||||||
-dpkg-deb --build dist/drone
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
This is where Drone packages go after running "make dist" in the root directory.
|
|
6
contrib/generate-amber.go
Normal file
6
contrib/generate-amber.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// This program converts amber templates to standard
|
||||||
|
// Go template files.
|
||||||
|
|
||||||
|
package main
|
60
contrib/generate-js.go
Normal file
60
contrib/generate-js.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// This program minifies JavaScript files
|
||||||
|
// $ go run generate-js.go -dir scripts/ -out scripts/drone.min.js
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/dchest/jsmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dir = flag.String("dir", "scripts/", "")
|
||||||
|
out = flag.String("o", "scripts/drone.min.js", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// walk the directory tree and write all
|
||||||
|
// javascript files to the buffer.
|
||||||
|
filepath.Walk(*dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if filepath.Ext(path) != ".js" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// write the file name to the minified output
|
||||||
|
fmt.Fprintf(&buf, "// %s\n", path)
|
||||||
|
|
||||||
|
// copy the file to the buffer
|
||||||
|
_, err = io.Copy(&buf, f)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// minifies the javascript
|
||||||
|
data, err := jsmin.Minify(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the minified output
|
||||||
|
ioutil.WriteFile(*out, data, 0700)
|
||||||
|
}
|
11
contrib/setup-sqlite.sh
Normal file
11
contrib/setup-sqlite.sh
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
|
||||||
|
curl -O https://www.sqlite.org/2015/sqlite-autoconf-3081101.tar.gz
|
||||||
|
tar xzf sqlite-autoconf-3081101.tar.gz
|
||||||
|
cd sqlite-autoconf-3081101
|
||||||
|
cd sqlite-3.6.421
|
||||||
|
./configure -prefix=/scratch/usr/local
|
||||||
|
make
|
||||||
|
make install
|
|
@ -1,32 +1,29 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/ccmenu"
|
"github.com/drone/drone/model"
|
||||||
common "github.com/drone/drone/pkg/types"
|
"github.com/drone/drone/router/middleware/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
badgeSuccess = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="91" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="91" height="20" fill="#555"/><rect rx="3" x="37" width="54" height="20" fill="#4c1"/><path fill="#4c1" d="M37 0h4v20h-4z"/><rect rx="3" width="91" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="63" y="15" fill="#010101" fill-opacity=".3">success</text><text x="63" y="14">success</text></g></svg>`)
|
badgeSuccess = `<svg xmlns="http://www.w3.org/2000/svg" width="91" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="91" height="20" fill="#555"/><rect rx="3" x="37" width="54" height="20" fill="#4c1"/><path fill="#4c1" d="M37 0h4v20h-4z"/><rect rx="3" width="91" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="63" y="15" fill="#010101" fill-opacity=".3">success</text><text x="63" y="14">success</text></g></svg>`
|
||||||
badgeFailure = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="83" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="83" height="20" fill="#555"/><rect rx="3" x="37" width="46" height="20" fill="#e05d44"/><path fill="#e05d44" d="M37 0h4v20h-4z"/><rect rx="3" width="83" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="59" y="15" fill="#010101" fill-opacity=".3">failure</text><text x="59" y="14">failure</text></g></svg>`)
|
badgeFailure = `<svg xmlns="http://www.w3.org/2000/svg" width="83" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="83" height="20" fill="#555"/><rect rx="3" x="37" width="46" height="20" fill="#e05d44"/><path fill="#e05d44" d="M37 0h4v20h-4z"/><rect rx="3" width="83" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="59" y="15" fill="#010101" fill-opacity=".3">failure</text><text x="59" y="14">failure</text></g></svg>`
|
||||||
badgeStarted = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="87" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="87" height="20" fill="#555"/><rect rx="3" x="37" width="50" height="20" fill="#dfb317"/><path fill="#dfb317" d="M37 0h4v20h-4z"/><rect rx="3" width="87" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="61" y="15" fill="#010101" fill-opacity=".3">started</text><text x="61" y="14">started</text></g></svg>`)
|
badgeStarted = `<svg xmlns="http://www.w3.org/2000/svg" width="87" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="87" height="20" fill="#555"/><rect rx="3" x="37" width="50" height="20" fill="#dfb317"/><path fill="#dfb317" d="M37 0h4v20h-4z"/><rect rx="3" width="87" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="61" y="15" fill="#010101" fill-opacity=".3">started</text><text x="61" y="14">started</text></g></svg>`
|
||||||
badgeError = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="76" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="76" height="20" fill="#555"/><rect rx="3" x="37" width="39" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="76" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55.5" y="15" fill="#010101" fill-opacity=".3">error</text><text x="55.5" y="14">error</text></g></svg>`)
|
badgeError = `<svg xmlns="http://www.w3.org/2000/svg" width="76" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="76" height="20" fill="#555"/><rect rx="3" x="37" width="39" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="76" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55.5" y="15" fill="#010101" fill-opacity=".3">error</text><text x="55.5" y="14">error</text></g></svg>`
|
||||||
badgeNone = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="75" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="75" height="20" fill="#555"/><rect rx="3" x="37" width="38" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="75" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55" y="15" fill="#010101" fill-opacity=".3">none</text><text x="55" y="14">none</text></g></svg>`)
|
badgeNone = `<svg xmlns="http://www.w3.org/2000/svg" width="75" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="75" height="20" fill="#555"/><rect rx="3" x="37" width="38" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="75" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55" y="15" fill="#010101" fill-opacity=".3">none</text><text x="55" y="14">none</text></g></svg>`
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetBadge accepts a request to retrieve the named
|
|
||||||
// repo and branhes latest build details from the datastore
|
|
||||||
// and return an SVG badges representing the build results.
|
|
||||||
//
|
|
||||||
// GET /api/badge/:owner/:name/status.svg
|
|
||||||
//
|
|
||||||
func GetBadge(c *gin.Context) {
|
func GetBadge(c *gin.Context) {
|
||||||
var repo = ToRepo(c)
|
db := context.Database(c)
|
||||||
var store = ToDatastore(c)
|
repo, err := model.GetRepoName(db,
|
||||||
var branch = c.Request.FormValue("branch")
|
c.Param("owner"),
|
||||||
if len(branch) == 0 {
|
c.Param("name"),
|
||||||
branch = repo.Branch
|
)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(404)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// an SVG response is always served, even when error, so
|
// an SVG response is always served, even when error, so
|
||||||
|
@ -36,44 +33,48 @@ func GetBadge(c *gin.Context) {
|
||||||
// if no commit was found then display
|
// if no commit was found then display
|
||||||
// the 'none' badge, instead of throwing
|
// the 'none' badge, instead of throwing
|
||||||
// an error response
|
// an error response
|
||||||
build, err := store.BuildLast(repo, branch)
|
branch := c.Query("branch")
|
||||||
|
if len(branch) == 0 {
|
||||||
|
branch = repo.Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
build, err := model.GetBuildLast(db, repo, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Writer.Write(badgeNone)
|
c.String(404, badgeNone)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch build.Status {
|
switch build.Status {
|
||||||
case common.StateSuccess:
|
case model.StatusSuccess:
|
||||||
c.Writer.Write(badgeSuccess)
|
c.String(200, badgeSuccess)
|
||||||
case common.StateFailure:
|
case model.StatusFailure:
|
||||||
c.Writer.Write(badgeFailure)
|
c.String(200, badgeFailure)
|
||||||
case common.StateError, common.StateKilled:
|
case model.StatusError, model.StatusKilled:
|
||||||
c.Writer.Write(badgeError)
|
c.String(200, badgeError)
|
||||||
case common.StatePending, common.StateRunning:
|
case model.StatusPending, model.StatusRunning:
|
||||||
c.Writer.Write(badgeStarted)
|
c.String(200, badgeStarted)
|
||||||
default:
|
default:
|
||||||
c.Writer.Write(badgeNone)
|
c.String(404, badgeNone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCC accepts a request to retrieve the latest build
|
|
||||||
// status for the given repository from the datastore and
|
|
||||||
// in CCTray XML format.
|
|
||||||
//
|
|
||||||
// GET /api/badge/:host/:owner/:name/cc.xml
|
|
||||||
//
|
|
||||||
// TODO(bradrydzewski) this will not return in-progress builds, which it should
|
|
||||||
func GetCC(c *gin.Context) {
|
func GetCC(c *gin.Context) {
|
||||||
store := ToDatastore(c)
|
db := context.Database(c)
|
||||||
repo := ToRepo(c)
|
repo, err := model.GetRepoName(db,
|
||||||
list, err := store.BuildList(repo, 1, 0)
|
c.Param("owner"),
|
||||||
if err != nil || len(list) == 0 {
|
c.Param("name"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
c.AbortWithStatus(404)
|
c.AbortWithStatus(404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := ccmenu.NewCC(repo, list[0])
|
builds, err := model.GetBuildList(db, repo)
|
||||||
|
if err != nil || len(builds) == 0 {
|
||||||
|
c.AbortWithStatus(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Writer.Header().Set("Content-Type", "application/xml")
|
cc := model.NewCC(repo, builds[0], "")
|
||||||
c.XML(200, cc)
|
c.XML(200, cc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"encoding/xml"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/ccmenu"
|
|
||||||
"github.com/drone/drone/pkg/server/recorder"
|
|
||||||
"github.com/drone/drone/pkg/store/mock"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
|
|
||||||
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
var badgeTests = []struct {
|
|
||||||
branch string
|
|
||||||
badge []byte
|
|
||||||
state string
|
|
||||||
activity string
|
|
||||||
status string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{"", badgeSuccess, common.StateSuccess, "Sleeping", "Success", nil},
|
|
||||||
{"master", badgeSuccess, common.StateSuccess, "Sleeping", "Success", nil},
|
|
||||||
{"", badgeStarted, common.StateRunning, "Building", "Unknown", nil},
|
|
||||||
{"", badgeError, common.StateError, "Sleeping", "Exception", nil},
|
|
||||||
{"", badgeError, common.StateKilled, "Sleeping", "Exception", nil},
|
|
||||||
{"", badgeFailure, common.StateFailure, "Sleeping", "Failure", nil},
|
|
||||||
{"", badgeNone, "", "", "", sql.ErrNoRows},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadges(t *testing.T) {
|
|
||||||
store := new(mocks.Store)
|
|
||||||
url_, _ := url.Parse("http://localhost:8080")
|
|
||||||
|
|
||||||
g := Goblin(t)
|
|
||||||
g.Describe("Badges", func() {
|
|
||||||
|
|
||||||
g.It("should serve svg badges", func() {
|
|
||||||
for _, test := range badgeTests {
|
|
||||||
rw := recorder.New()
|
|
||||||
ctx := &gin.Context{Engine: gin.Default(), Writer: rw}
|
|
||||||
ctx.Request = &http.Request{
|
|
||||||
Form: url.Values{},
|
|
||||||
}
|
|
||||||
if len(test.branch) != 0 {
|
|
||||||
ctx.Request.Form.Set("branch", test.branch)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &common.Repo{FullName: "foo/bar"}
|
|
||||||
ctx.Set("datastore", store)
|
|
||||||
ctx.Set("repo", repo)
|
|
||||||
|
|
||||||
commit := &common.Build{Status: test.state}
|
|
||||||
store.On("BuildLast", repo, test.branch).Return(commit, test.err).Once()
|
|
||||||
GetBadge(ctx)
|
|
||||||
|
|
||||||
g.Assert(rw.Code).Equal(200)
|
|
||||||
g.Assert(rw.Body.Bytes()).Equal(test.badge)
|
|
||||||
g.Assert(rw.HeaderMap.Get("Content-Type")).Equal("image/svg+xml")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should serve ccmenu xml", func() {
|
|
||||||
|
|
||||||
for _, test := range badgeTests {
|
|
||||||
rw := recorder.New()
|
|
||||||
ctx := &gin.Context{Engine: gin.Default(), Writer: rw}
|
|
||||||
ctx.Request = &http.Request{URL: url_}
|
|
||||||
|
|
||||||
repo := &common.Repo{FullName: "foo/bar"}
|
|
||||||
ctx.Set("datastore", store)
|
|
||||||
ctx.Set("repo", repo)
|
|
||||||
|
|
||||||
commits := []*common.Build{
|
|
||||||
&common.Build{Status: test.state},
|
|
||||||
}
|
|
||||||
store.On("BuildList", repo, mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(commits, test.err).Once()
|
|
||||||
GetCC(ctx)
|
|
||||||
|
|
||||||
// in an error scenario (ie no build exists) we should
|
|
||||||
// return a 404 not found error.
|
|
||||||
if test.err != nil {
|
|
||||||
g.Assert(rw.Status()).Equal(404)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// else parse the CCMenu xml output and verify
|
|
||||||
// it matches the expected values.
|
|
||||||
cc := &ccmenu.CCProjects{}
|
|
||||||
xml.Unmarshal(rw.Body.Bytes(), cc)
|
|
||||||
g.Assert(rw.Code).Equal(200)
|
|
||||||
g.Assert(cc.Project.Activity).Equal(test.activity)
|
|
||||||
g.Assert(cc.Project.LastBuildStatus).Equal(test.status)
|
|
||||||
g.Assert(rw.HeaderMap.Get("Content-Type")).Equal("application/xml; charset=utf-8")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
199
controller/build.go
Normal file
199
controller/build.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/drone/drone/engine"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetBuilds(c *gin.Context) {
|
||||||
|
repo := session.Repo(c)
|
||||||
|
db := context.Database(c)
|
||||||
|
builds, err := model.GetBuildList(db, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.IndentedJSON(http.StatusOK, builds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuild(c *gin.Context) {
|
||||||
|
repo := session.Repo(c)
|
||||||
|
db := context.Database(c)
|
||||||
|
|
||||||
|
num, err := strconv.Atoi(c.Param("number"))
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
build, err := model.GetBuildNumber(db, repo, num)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jobs, _ := model.GetJobList(db, build)
|
||||||
|
|
||||||
|
out := struct {
|
||||||
|
*model.Build
|
||||||
|
Jobs []*model.Job `json:"jobs"`
|
||||||
|
}{build, jobs}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, &out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuildLogs(c *gin.Context) {
|
||||||
|
repo := session.Repo(c)
|
||||||
|
db := context.Database(c)
|
||||||
|
|
||||||
|
// the user may specify to stream the full logs,
|
||||||
|
// or partial logs, capped at 2MB.
|
||||||
|
full, _ := strconv.ParseBool(c.Params.ByName("full"))
|
||||||
|
|
||||||
|
// parse the build number and job sequence number from
|
||||||
|
// the repquest parameter.
|
||||||
|
num, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||||
|
seq, _ := strconv.Atoi(c.Params.ByName("job"))
|
||||||
|
|
||||||
|
build, err := model.GetBuildNumber(db, repo, num)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := model.GetJobNumber(db, build, seq)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := model.GetLog(db, job)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Close()
|
||||||
|
if full {
|
||||||
|
io.Copy(c.Writer, r)
|
||||||
|
} else {
|
||||||
|
io.Copy(c.Writer, io.LimitReader(r, 2000000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteBuild(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "DeleteBuild")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostBuild(c *gin.Context) {
|
||||||
|
|
||||||
|
remote := context.Remote(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
db := context.Database(c)
|
||||||
|
|
||||||
|
num, err := strconv.Atoi(c.Param("number"))
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := model.GetUser(db, repo.UserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
build, err := model.GetBuildNumber(db, repo, num)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to get build %d. %s", num, err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the .drone.yml file from the database
|
||||||
|
raw, sec, err := remote.Script(user, repo, build)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := model.GetKey(db, repo)
|
||||||
|
netrc, err := remote.Netrc(user, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs, err := model.GetJobList(db, build)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to get build %d jobs. %s", build.Number, err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// must not restart a running build
|
||||||
|
if build.Status == model.StatusPending || build.Status == model.StatusRunning {
|
||||||
|
c.AbortWithStatus(409)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
build.Status = model.StatusPending
|
||||||
|
build.Started = 0
|
||||||
|
build.Finished = 0
|
||||||
|
for _, job := range jobs {
|
||||||
|
job.Status = model.StatusPending
|
||||||
|
job.Started = 0
|
||||||
|
job.Finished = 0
|
||||||
|
job.ExitCode = 0
|
||||||
|
model.UpdateJob(db, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = model.UpdateBuild(db, build)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
c.JSON(202, build)
|
||||||
|
|
||||||
|
engine_ := context.Engine(c)
|
||||||
|
go engine_.Schedule(&engine.Task{
|
||||||
|
User: user,
|
||||||
|
Repo: repo,
|
||||||
|
Build: build,
|
||||||
|
Jobs: jobs,
|
||||||
|
Keys: key,
|
||||||
|
Netrc: netrc,
|
||||||
|
Config: string(raw),
|
||||||
|
Secret: string(sec),
|
||||||
|
System: &model.System{
|
||||||
|
Link: httputil.GetURL(c.Request),
|
||||||
|
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
|
||||||
|
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -1,285 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
"github.com/drone/drone/pkg/queue"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
"github.com/drone/drone/pkg/utils/httputil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetCommit accepts a request to retrieve a commit
|
|
||||||
// from the datastore for the given repository and
|
|
||||||
// commit sequence.
|
|
||||||
//
|
|
||||||
// GET /api/repos/:owner/:name/:number
|
|
||||||
//
|
|
||||||
func GetBuild(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build, err := store.BuildNumber(repo, num)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build.Jobs, err = store.JobList(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(200, build)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommits accepts a request to retrieve a list
|
|
||||||
// of commits from the datastore for the given repository.
|
|
||||||
//
|
|
||||||
// GET /api/repos/:owner/:name/builds
|
|
||||||
//
|
|
||||||
func GetBuilds(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
builds, err := store.BuildList(repo, 20, 0)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(200, builds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogs accepts a request to retrieve logs from the
|
|
||||||
// datastore for the given repository, build and task
|
|
||||||
// number.
|
|
||||||
//
|
|
||||||
// GET /api/repos/:owner/:name/logs/:number/:task
|
|
||||||
//
|
|
||||||
func GetLogs(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
full, _ := strconv.ParseBool(c.Params.ByName("full"))
|
|
||||||
build, _ := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
job, _ := strconv.Atoi(c.Params.ByName("task"))
|
|
||||||
|
|
||||||
path := fmt.Sprintf("/logs/%s/%v/%v", repo.FullName, build, job)
|
|
||||||
r, err := store.GetBlobReader(path)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer r.Close()
|
|
||||||
if full {
|
|
||||||
io.Copy(c.Writer, r)
|
|
||||||
} else {
|
|
||||||
io.Copy(c.Writer, io.LimitReader(r, 2000000))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // PostBuildStatus accepts a request to create a new build
|
|
||||||
// // status. The created user status is returned in JSON
|
|
||||||
// // format if successful.
|
|
||||||
// //
|
|
||||||
// // POST /api/repos/:owner/:name/status/:number
|
|
||||||
// //
|
|
||||||
// func PostBuildStatus(c *gin.Context) {
|
|
||||||
// store := ToDatastore(c)
|
|
||||||
// repo := ToRepo(c)
|
|
||||||
// num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
// if err != nil {
|
|
||||||
// c.Fail(400, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// in := &common.Status{}
|
|
||||||
// if !c.BindWith(in, binding.JSON) {
|
|
||||||
// c.AbortWithStatus(400)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if err := store.SetBuildStatus(repo.Name, num, in); err != nil {
|
|
||||||
// c.Fail(400, err)
|
|
||||||
// } else {
|
|
||||||
// c.JSON(201, in)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// RunBuild accepts a request to restart an existing build.
|
|
||||||
//
|
|
||||||
// POST /api/builds/:owner/:name/builds/:number
|
|
||||||
//
|
|
||||||
func RunBuild(c *gin.Context) {
|
|
||||||
remote := ToRemote(c)
|
|
||||||
store := ToDatastore(c)
|
|
||||||
queue_ := ToQueue(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
|
|
||||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build, err := store.BuildNumber(repo, num)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build.Jobs, err = store.JobList(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := store.User(repo.UserID)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// must not restart a running build
|
|
||||||
if build.Status == common.StatePending || build.Status == common.StateRunning {
|
|
||||||
c.AbortWithStatus(409)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Status = common.StatePending
|
|
||||||
build.Started = 0
|
|
||||||
build.Finished = 0
|
|
||||||
for _, job := range build.Jobs {
|
|
||||||
job.Status = common.StatePending
|
|
||||||
job.Started = 0
|
|
||||||
job.Finished = 0
|
|
||||||
job.ExitCode = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.SetBuild(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
netrc, err := remote.Netrc(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// featch the .drone.yml file from the database
|
|
||||||
raw, sec, err := remote.Script(user, repo, build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the previous build so taht we can send
|
|
||||||
// on status change notifications
|
|
||||||
last, _ := store.BuildLast(repo, build.Commit.Branch)
|
|
||||||
|
|
||||||
c.JSON(202, build)
|
|
||||||
|
|
||||||
queue_.Publish(&queue.Work{
|
|
||||||
User: user,
|
|
||||||
Repo: repo,
|
|
||||||
Build: build,
|
|
||||||
BuildPrev: last,
|
|
||||||
Keys: repo.Keys,
|
|
||||||
Netrc: netrc,
|
|
||||||
Config: raw,
|
|
||||||
Secret: sec,
|
|
||||||
System: &common.System{
|
|
||||||
Link: httputil.GetURL(c.Request),
|
|
||||||
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
|
|
||||||
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// KillBuild accepts a request to kill a running build.
|
|
||||||
//
|
|
||||||
// DELETE /api/builds/:owner/:name/builds/:number
|
|
||||||
//
|
|
||||||
func KillBuild(c *gin.Context) {
|
|
||||||
runner := ToRunner(c)
|
|
||||||
queue := ToQueue(c)
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build, err := store.BuildNumber(repo, num)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build.Jobs, err = store.JobList(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// must not restart a running build
|
|
||||||
if build.Status != common.StatePending && build.Status != common.StateRunning {
|
|
||||||
c.Fail(409, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove from the queue if exists
|
|
||||||
//
|
|
||||||
// TODO(bradrydzewski) this could yield a race condition
|
|
||||||
// because other threads may also be accessing these items.
|
|
||||||
for _, item := range queue.Items() {
|
|
||||||
if item.Repo.FullName == repo.FullName && item.Build.Number == build.Number {
|
|
||||||
queue.Remove(item)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Status = common.StateKilled
|
|
||||||
build.Finished = time.Now().Unix()
|
|
||||||
if build.Started == 0 {
|
|
||||||
build.Started = build.Finished
|
|
||||||
}
|
|
||||||
for _, job := range build.Jobs {
|
|
||||||
if job.Status != common.StatePending && job.Status != common.StateRunning {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
job.Status = common.StateKilled
|
|
||||||
job.Started = build.Started
|
|
||||||
job.Finished = build.Finished
|
|
||||||
}
|
|
||||||
err = store.SetBuild(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, job := range build.Jobs {
|
|
||||||
runner.Cancel(job)
|
|
||||||
}
|
|
||||||
// // get the agent from the repository so we can
|
|
||||||
// // notify the agent to kill the build.
|
|
||||||
// agent, err := store.BuildAgent(repo.FullName, build.Number)
|
|
||||||
// if err != nil {
|
|
||||||
// c.JSON(200, build)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// url_, _ := url.Parse("http://" + agent.Addr)
|
|
||||||
// url_.Path = fmt.Sprintf("/cancel/%s/%v", repo.FullName, build.Number)
|
|
||||||
// resp, err := http.Post(url_.String(), "application/json", nil)
|
|
||||||
// if err != nil {
|
|
||||||
// c.Fail(500, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// defer resp.Body.Close()
|
|
||||||
|
|
||||||
c.JSON(200, build)
|
|
||||||
}
|
|
|
@ -1,158 +1,105 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"net/http"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/token"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedirectSha accepts a request to retvie a redirect
|
|
||||||
// to job from the datastore for the given repository
|
|
||||||
// and commit sha
|
|
||||||
//
|
|
||||||
// GET /gitlab/:owner/:name/redirect/commits/:sha
|
|
||||||
//
|
|
||||||
// REASON: It required by GitLab, becuase we get only
|
|
||||||
// sha and ref name, but drone uses build numbers
|
|
||||||
func RedirectSha(c *gin.Context) {
|
|
||||||
var branch string
|
|
||||||
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
sha := c.Params.ByName("sha")
|
|
||||||
|
|
||||||
branch = c.Request.FormValue("branch")
|
|
||||||
if branch == "" {
|
|
||||||
branch = repo.Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
build, err := store.BuildSha(repo, sha, branch)
|
|
||||||
if err != nil {
|
|
||||||
c.Redirect(301, "/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedirectPullRequest accepts a request to retvie a redirect
|
|
||||||
// to job from the datastore for the given repository
|
|
||||||
// and pull request number
|
|
||||||
//
|
|
||||||
// GET /gitlab/:owner/:name/redirect/pulls/:number
|
|
||||||
//
|
|
||||||
// REASON: It required by GitLab, because we get only
|
|
||||||
// internal merge request id/ref/sha, but drone uses
|
|
||||||
// build numbers
|
|
||||||
func RedirectPullRequest(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.Redirect(301, "/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
build, err := store.BuildPullRequestNumber(repo, num)
|
|
||||||
if err != nil {
|
|
||||||
c.Redirect(301, "/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPullRequest accepts a requests to retvie a pull request
|
|
||||||
// from the datastore for the given repository and
|
|
||||||
// pull request number
|
|
||||||
//
|
|
||||||
// GET /gitlab/:owner/:name/pulls/:number
|
|
||||||
//
|
|
||||||
// REASON: It required by GitLab, becuase we get only
|
|
||||||
// sha and ref name, but drone uses build numbers
|
|
||||||
func GetPullRequest(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
|
|
||||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
|
||||||
return repo.Hash, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if parsed.Text != repo.FullName {
|
|
||||||
c.AbortWithStatus(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build, err := store.BuildPullRequestNumber(repo, num)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build.Jobs, err = store.JobList(build)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(200, build)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommit accepts a requests to retvie a sha and branch
|
|
||||||
// from the datastore for the given repository and
|
|
||||||
// pull request number
|
|
||||||
//
|
|
||||||
// GET /gitlab/:owner/:name/commits/:sha
|
|
||||||
//
|
|
||||||
// REASON: It required by GitLab, becuase we get only
|
|
||||||
// sha and ref name, but drone uses build numbers
|
|
||||||
func GetCommit(c *gin.Context) {
|
func GetCommit(c *gin.Context) {
|
||||||
var branch string
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
sha := c.Params.ByName("sha")
|
|
||||||
|
|
||||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||||
return repo.Hash, nil
|
return repo.Hash, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(400, err)
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if parsed.Text != repo.FullName {
|
if parsed.Text != repo.FullName {
|
||||||
c.AbortWithStatus(403)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
branch = c.Request.FormValue("branch")
|
commit := c.Param("sha")
|
||||||
if branch == "" {
|
branch := c.Query("branch")
|
||||||
|
if len(branch) == 0 {
|
||||||
branch = repo.Branch
|
branch = repo.Branch
|
||||||
}
|
}
|
||||||
|
|
||||||
build, err := store.BuildSha(repo, sha, branch)
|
build, err := model.GetBuildCommit(db, repo, commit, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(404, err)
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
build.Jobs, err = store.JobList(build)
|
c.JSON(http.StatusOK, build)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPullRequest(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
|
||||||
|
|
||||||
|
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||||
|
return repo.Hash, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(404, err)
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, build)
|
}
|
||||||
|
if parsed.Text != repo.FullName {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
build, err := model.GetBuildRef(db, repo, refs)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, build)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RedirectSha(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
|
||||||
|
commit := c.Param("sha")
|
||||||
|
branch := c.Query("branch")
|
||||||
|
if len(branch) == 0 {
|
||||||
|
branch = repo.Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
build, err := model.GetBuildCommit(db, repo, commit, branch)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
|
||||||
|
c.Redirect(http.StatusSeeOther, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RedirectPullRequest(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
|
||||||
|
|
||||||
|
build, err := model.GetBuildRef(db, repo, refs)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
|
||||||
|
c.Redirect(http.StatusSeeOther, path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,37 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"github.com/drone/drone/engine"
|
||||||
"github.com/drone/drone/pkg/queue"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/token"
|
"github.com/drone/drone/router/middleware/context"
|
||||||
common "github.com/drone/drone/pkg/types"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/pkg/utils/httputil"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/pkg/yaml"
|
"github.com/drone/drone/yaml"
|
||||||
"github.com/drone/drone/pkg/yaml/matrix"
|
"github.com/drone/drone/yaml/matrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PostHook accepts a post-commit hook and parses the payload
|
|
||||||
// in order to trigger a build.
|
|
||||||
//
|
|
||||||
// GET /api/hook
|
|
||||||
//
|
|
||||||
func PostHook(c *gin.Context) {
|
func PostHook(c *gin.Context) {
|
||||||
remote := ToRemote(c)
|
remote := context.Remote(c)
|
||||||
store := ToDatastore(c)
|
db := context.Database(c)
|
||||||
queue_ := ToQueue(c)
|
|
||||||
|
|
||||||
hook, err := remote.Hook(c.Request)
|
tmprepo, build, err := remote.Hook(c.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to parse hook. %s", err)
|
log.Errorf("failure to parse hook. %s", err)
|
||||||
c.Fail(400, err)
|
c.AbortWithError(400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hook == nil {
|
if build == nil {
|
||||||
c.Writer.WriteHeader(200)
|
c.Writer.WriteHeader(200)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hook.Repo == nil {
|
if tmprepo == nil {
|
||||||
log.Errorf("failure to ascertain repo from hook.")
|
log.Errorf("failure to ascertain repo from hook.")
|
||||||
c.Writer.WriteHeader(400)
|
c.Writer.WriteHeader(400)
|
||||||
return
|
return
|
||||||
|
@ -42,16 +39,16 @@ func PostHook(c *gin.Context) {
|
||||||
|
|
||||||
// a build may be skipped if the text [CI SKIP]
|
// a build may be skipped if the text [CI SKIP]
|
||||||
// is found inside the commit message
|
// is found inside the commit message
|
||||||
if hook.Commit != nil && strings.Contains(hook.Commit.Message, "[CI SKIP]") {
|
if strings.Contains(build.Message, "[CI SKIP]") {
|
||||||
log.Infof("ignoring hook. [ci skip] found for %s")
|
log.Infof("ignoring hook. [ci skip] found for %s")
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := store.RepoName(hook.Repo.Owner, hook.Repo.Name)
|
repo, err := model.GetRepoName(db, tmprepo.Owner, tmprepo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to find repo %s/%s from hook. %s", hook.Repo.Owner, hook.Repo.Name, err)
|
log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||||
c.Fail(404, err)
|
c.AbortWithError(404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +58,7 @@ func PostHook(c *gin.Context) {
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||||
c.Fail(400, err)
|
c.AbortWithError(400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if parsed.Text != repo.FullName {
|
if parsed.Text != repo.FullName {
|
||||||
|
@ -70,106 +67,134 @@ func PostHook(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
if repo.UserID == 0 {
|
||||||
case repo.UserID == 0:
|
|
||||||
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
case !repo.Hooks.Push && hook.Commit != nil:
|
}
|
||||||
log.Infof("ignoring hook. repo %s is disabled.", repo.FullName)
|
var skipped = true
|
||||||
c.Writer.WriteHeader(204)
|
if (build.Event == model.EventPush && repo.AllowPush) ||
|
||||||
return
|
(build.Event == model.EventPull && repo.AllowPull) ||
|
||||||
case !repo.Hooks.PullRequest && hook.PullRequest != nil:
|
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
||||||
log.Warnf("ignoring hook. repo %s is disabled for pull requests.", repo.FullName)
|
(build.Event == model.EventTag && repo.AllowTag) {
|
||||||
|
skipped = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipped {
|
||||||
|
log.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := store.User(repo.UserID)
|
user, err := model.GetUser(db, repo.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||||
c.Fail(500, err)
|
c.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
build := &common.Build{}
|
|
||||||
build.Commit = hook.Commit
|
|
||||||
build.PullRequest = hook.PullRequest
|
|
||||||
build.Status = common.StatePending
|
|
||||||
build.RepoID = repo.ID
|
|
||||||
|
|
||||||
// fetch the .drone.yml file from the database
|
// fetch the .drone.yml file from the database
|
||||||
raw, sec, err := remote.Script(user, repo, build)
|
raw, sec, err := remote.Script(user, repo, build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||||
c.Fail(404, err)
|
c.AbortWithError(404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
axes, err := matrix.Parse(string(raw))
|
axes, err := matrix.Parse(string(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err)
|
log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err)
|
||||||
c.Fail(400, err)
|
c.AbortWithError(400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(axes) == 0 {
|
if len(axes) == 0 {
|
||||||
axes = append(axes, matrix.Axis{})
|
axes = append(axes, matrix.Axis{})
|
||||||
}
|
}
|
||||||
for num, axis := range axes {
|
|
||||||
build.Jobs = append(build.Jobs, &common.Job{
|
|
||||||
BuildID: build.ID,
|
|
||||||
Number: num + 1,
|
|
||||||
Status: common.StatePending,
|
|
||||||
Environment: axis,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
netrc, err := remote.Netrc(user, repo)
|
netrc, err := remote.Netrc(user, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||||
c.Fail(500, err)
|
c.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key, _ := model.GetKey(db, repo)
|
||||||
|
|
||||||
// verify the branches can be built vs skipped
|
// verify the branches can be built vs skipped
|
||||||
when, _ := parser.ParseCondition(string(raw))
|
yconfig, _ := yaml.Parse(string(raw))
|
||||||
if build.PullRequest != nil && when != nil && !when.MatchBranch(build.Commit.Branch) {
|
var match = false
|
||||||
log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, build.Commit.Branch)
|
for _, branch := range yconfig.Branches {
|
||||||
|
if branch == build.Branch {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
match, _ = filepath.Match(branch, build.Branch)
|
||||||
|
if match {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match && len(yconfig.Branches) != 0 {
|
||||||
|
log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, build.Branch)
|
||||||
c.AbortWithStatus(200)
|
c.AbortWithStatus(200)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tx, err := db.Begin()
|
||||||
err = store.AddBuild(build)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
log.Errorf("failure to begin database transaction", err)
|
||||||
c.Fail(500, err)
|
c.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// update some build fields
|
||||||
|
build.Status = model.StatusPending
|
||||||
|
build.RepoID = repo.ID
|
||||||
|
|
||||||
|
var jobs []*model.Job
|
||||||
|
for num, axis := range axes {
|
||||||
|
jobs = append(jobs, &model.Job{
|
||||||
|
BuildID: build.ID,
|
||||||
|
Number: num + 1,
|
||||||
|
Status: model.StatusPending,
|
||||||
|
Environment: axis,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = model.CreateBuild(tx, build, jobs...)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
c.JSON(200, build)
|
c.JSON(200, build)
|
||||||
|
|
||||||
err = remote.Status(user, repo, build)
|
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||||
|
err = remote.Status(user, repo, build, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the previous build so taht we can send
|
// get the previous build so taht we can send
|
||||||
// on status change notifications
|
// on status change notifications
|
||||||
last, _ := store.BuildLast(repo, build.Commit.Branch)
|
last, _ := model.GetBuildLast(db, repo, build.Branch)
|
||||||
|
|
||||||
queue_.Publish(&queue.Work{
|
engine_ := context.Engine(c)
|
||||||
|
go engine_.Schedule(&engine.Task{
|
||||||
User: user,
|
User: user,
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Build: build,
|
Build: build,
|
||||||
BuildPrev: last,
|
BuildPrev: last,
|
||||||
Keys: repo.Keys,
|
Jobs: jobs,
|
||||||
|
Keys: key,
|
||||||
Netrc: netrc,
|
Netrc: netrc,
|
||||||
Config: raw,
|
Config: string(raw),
|
||||||
Secret: sec,
|
Secret: string(sec),
|
||||||
System: &common.System{
|
System: &model.System{
|
||||||
Link: httputil.GetURL(c.Request),
|
Link: httputil.GetURL(c.Request),
|
||||||
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
|
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
|
||||||
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
|
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
199
controller/index.go
Normal file
199
controller/index.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShowIndex(c *gin.Context) {
|
||||||
|
// remote := context.Remote(c)
|
||||||
|
user := session.User(c)
|
||||||
|
if user == nil {
|
||||||
|
c.HTML(200, "login.html", gin.H{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to get the repository list from the
|
||||||
|
// cache since the operation is expensive
|
||||||
|
// v, ok := cache.Get(user.Login)
|
||||||
|
// if ok {
|
||||||
|
// c.HTML(200, "repos.html", gin.H{
|
||||||
|
// "User": user,
|
||||||
|
// "Repos": v,
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fetch the repmote repos
|
||||||
|
// repos, err := remote.Repos(user)
|
||||||
|
// if err != nil {
|
||||||
|
// c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// cache.Add(user.Login, repos)
|
||||||
|
|
||||||
|
c.HTML(200, "repos.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
// "Repos": repos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowLogin(c *gin.Context) {
|
||||||
|
c.HTML(200, "login.html", gin.H{"Error": c.Query("error")})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowUser(c *gin.Context) {
|
||||||
|
user := session.User(c)
|
||||||
|
token, _ := token.New(
|
||||||
|
token.CsrfToken,
|
||||||
|
user.Login,
|
||||||
|
).Sign(user.Hash)
|
||||||
|
|
||||||
|
c.HTML(200, "user.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Csrf": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowUsers(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
user := session.User(c)
|
||||||
|
if !user.Admin {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users, _ := model.GetUserList(db)
|
||||||
|
|
||||||
|
token, _ := token.New(
|
||||||
|
token.CsrfToken,
|
||||||
|
user.Login,
|
||||||
|
).Sign(user.Hash)
|
||||||
|
|
||||||
|
c.HTML(200, "users.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Users": users,
|
||||||
|
"Csrf": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowRepo(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
user := session.User(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
if !user.Admin {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builds, _ := model.GetBuildList(db, repo)
|
||||||
|
groups := []*model.BuildGroup{}
|
||||||
|
|
||||||
|
var curr *model.BuildGroup
|
||||||
|
for _, build := range builds {
|
||||||
|
date := time.Unix(build.Created, 0).Format("Jan 2 2006")
|
||||||
|
if curr == nil || curr.Date != date {
|
||||||
|
curr = &model.BuildGroup{}
|
||||||
|
curr.Date = date
|
||||||
|
groups = append(groups, curr)
|
||||||
|
}
|
||||||
|
curr.Builds = append(curr.Builds, build)
|
||||||
|
}
|
||||||
|
|
||||||
|
httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName)
|
||||||
|
|
||||||
|
c.HTML(200, "repo.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
"Builds": builds,
|
||||||
|
"Groups": groups,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowRepoConf(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
user := session.User(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
key, _ := model.GetKey(db, repo)
|
||||||
|
if !user.Admin {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var view = "repo_config.html"
|
||||||
|
switch c.Param("action") {
|
||||||
|
case "delete":
|
||||||
|
view = "repo_delete.html"
|
||||||
|
case "encrypt":
|
||||||
|
view = "repo_secret.html"
|
||||||
|
case "badges":
|
||||||
|
view = "repo_badge.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _ := token.New(
|
||||||
|
token.CsrfToken,
|
||||||
|
user.Login,
|
||||||
|
).Sign(user.Hash)
|
||||||
|
|
||||||
|
c.HTML(200, view, gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
"Key": key,
|
||||||
|
"Csrf": token,
|
||||||
|
"Link": httputil.GetURL(c.Request),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowBuild(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
user := session.User(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
num, _ := strconv.Atoi(c.Param("number"))
|
||||||
|
seq, _ := strconv.Atoi(c.Param("job"))
|
||||||
|
if seq == 0 {
|
||||||
|
seq = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
build, err := model.GetBuildNumber(db, repo, num)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs, err := model.GetJobList(db, build)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var job *model.Job
|
||||||
|
for _, j := range jobs {
|
||||||
|
if j.Number == seq {
|
||||||
|
job = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName)
|
||||||
|
|
||||||
|
token, _ := token.New(
|
||||||
|
token.CsrfToken,
|
||||||
|
user.Login,
|
||||||
|
).Sign(user.Hash)
|
||||||
|
|
||||||
|
c.HTML(200, "build.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
"Build": build,
|
||||||
|
"Jobs": jobs,
|
||||||
|
"Job": job,
|
||||||
|
"Csrf": token,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,82 +1,72 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
|
|
||||||
|
|
||||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/pkg/token"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/shared/crypto"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLogin accepts a request to authorize the user and to
|
|
||||||
// return a valid OAuth2 access token. The access token is
|
|
||||||
// returned as url segment #access_token
|
|
||||||
//
|
|
||||||
// GET /authorize
|
|
||||||
//
|
|
||||||
func GetLogin(c *gin.Context) {
|
func GetLogin(c *gin.Context) {
|
||||||
remote := ToRemote(c)
|
db := context.Database(c)
|
||||||
store := ToDatastore(c)
|
remote := context.Remote(c)
|
||||||
|
|
||||||
// when dealing with redirects we may need
|
// when dealing with redirects we may need
|
||||||
// to adjust the content type. I cannot, however,
|
// to adjust the content type. I cannot, however,
|
||||||
// rememver why, so need to revisit this line.
|
// rememver why, so need to revisit this line.
|
||||||
c.Writer.Header().Del("Content-Type")
|
c.Writer.Header().Del("Content-Type")
|
||||||
|
|
||||||
// TODO: move this back to the remote section
|
tmpuser, open, err := remote.Login(c.Writer, c.Request)
|
||||||
getLoginOauth2(c)
|
if err != nil {
|
||||||
|
log.Errorf("cannot authenticate user. %s", err)
|
||||||
// exit if authorization fails
|
c.Redirect(303, "/login?error=oauth_error")
|
||||||
if c.Writer.Status() != 200 {
|
return
|
||||||
|
}
|
||||||
|
// this will happen when the user is redirected by
|
||||||
|
// the remote provide as part of the oauth dance.
|
||||||
|
if tmpuser == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
login := ToUser(c)
|
|
||||||
|
|
||||||
// check organization membership, if applicable
|
|
||||||
if len(remote.GetOrgs()) != 0 {
|
|
||||||
orgs, _ := remote.Orgs(login)
|
|
||||||
if !checkMembership(orgs, remote.GetOrgs()) {
|
|
||||||
c.Redirect(303, "/login#error=access_denied_org")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the user from the database
|
// get the user from the database
|
||||||
u, err := store.UserLogin(login.Login)
|
u, err := model.GetUserLogin(db, tmpuser.Login)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
count, err := store.UserCount()
|
count, err := model.GetUserCount(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("cannot register %s. %s", login.Login, err)
|
log.Errorf("cannot register %s. %s", tmpuser.Login, err)
|
||||||
c.Redirect(303, "/login#error=internal_error")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if self-registration is disabled we should
|
// if self-registration is disabled we should
|
||||||
// return a notAuthorized error. the only exception
|
// return a notAuthorized error. the only exception
|
||||||
// is if no users exist yet in the system we'll proceed.
|
// is if no users exist yet in the system we'll proceed.
|
||||||
if !remote.GetOpen() && count != 0 {
|
if !open && count != 0 {
|
||||||
log.Errorf("cannot register %s. registration closed", login.Login)
|
log.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
||||||
c.Redirect(303, "/login#error=access_denied")
|
c.Redirect(303, "/login?error=access_denied")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the user account
|
// create the user account
|
||||||
u = &types.User{}
|
u = &model.User{}
|
||||||
u.Login = login.Login
|
u.Login = tmpuser.Login
|
||||||
u.Token = login.Token
|
u.Token = tmpuser.Token
|
||||||
u.Secret = login.Secret
|
u.Secret = tmpuser.Secret
|
||||||
u.Email = login.Email
|
u.Email = tmpuser.Email
|
||||||
u.Avatar = login.Avatar
|
u.Avatar = tmpuser.Avatar
|
||||||
u.Hash = types.GenerateToken()
|
u.Hash = crypto.Rand()
|
||||||
|
|
||||||
// insert the user into the database
|
// insert the user into the database
|
||||||
if err := store.AddUser(u); err != nil {
|
if err := model.CreateUser(db, u); err != nil {
|
||||||
log.Errorf("cannot insert %s. %s", login.Login, err)
|
log.Errorf("cannot insert %s. %s", u.Login, err)
|
||||||
c.Redirect(303, "/login#error=internal_error")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,20 +79,14 @@ func GetLogin(c *gin.Context) {
|
||||||
|
|
||||||
// update the user meta data and authorization
|
// update the user meta data and authorization
|
||||||
// data and cache in the datastore.
|
// data and cache in the datastore.
|
||||||
u.Token = login.Token
|
u.Token = tmpuser.Token
|
||||||
u.Secret = login.Secret
|
u.Secret = tmpuser.Secret
|
||||||
u.Email = login.Email
|
u.Email = tmpuser.Email
|
||||||
u.Avatar = login.Avatar
|
u.Avatar = tmpuser.Avatar
|
||||||
|
|
||||||
// TODO: remove this once gitlab implements setting
|
if err := model.UpdateUser(db, u); err != nil {
|
||||||
// avatar in the remote package, similar to github
|
|
||||||
if len(u.Avatar) == 0 {
|
|
||||||
u.Avatar = gravatar.Hash(u.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := store.SetUser(u); err != nil {
|
|
||||||
log.Errorf("cannot update %s. %s", u.Login, err)
|
log.Errorf("cannot update %s. %s", u.Login, err)
|
||||||
c.Redirect(303, "/login#error=internal_error")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,93 +95,65 @@ func GetLogin(c *gin.Context) {
|
||||||
tokenstr, err := token.SignExpires(u.Hash, exp)
|
tokenstr, err := token.SignExpires(u.Hash, exp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("cannot create token for %s. %s", u.Login, err)
|
log.Errorf("cannot create token for %s. %s", u.Login, err)
|
||||||
c.Redirect(303, "/login#error=internal_error")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Redirect(303, "/#access_token="+tokenstr)
|
|
||||||
|
httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr)
|
||||||
|
redirect := httputil.GetCookie(c.Request, "user_last")
|
||||||
|
if len(redirect) == 0 {
|
||||||
|
redirect = "/"
|
||||||
|
}
|
||||||
|
c.Redirect(303, redirect)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLoginOauth2 is the default authorization implementation
|
func GetLogout(c *gin.Context) {
|
||||||
// using the oauth2 protocol.
|
|
||||||
func getLoginOauth2(c *gin.Context) {
|
|
||||||
var remote = ToRemote(c)
|
|
||||||
|
|
||||||
// Bugagazavr: I think this must be moved to remote config
|
httputil.DelCookie(c.Writer, c.Request, "user_sess")
|
||||||
//var scope = strings.Join(settings.Auth.Scope, ",")
|
httputil.DelCookie(c.Writer, c.Request, "user_last")
|
||||||
//if scope == "" {
|
c.Redirect(303, "/login")
|
||||||
// scope = remote.Scope()
|
}
|
||||||
//}
|
|
||||||
var transport = remote.Oauth2Transport(c.Request)
|
|
||||||
|
|
||||||
// get the OAuth code
|
func GetLoginToken(c *gin.Context) {
|
||||||
var code = c.Request.FormValue("code")
|
db := context.Database(c)
|
||||||
//var state = c.Request.FormValue("state")
|
remote := context.Remote(c)
|
||||||
if len(code) == 0 {
|
|
||||||
// TODO this should be a random number, verified by a cookie
|
|
||||||
c.Redirect(303, transport.AuthCodeURL("random"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// exhange for a token
|
in := &tokenPayload{}
|
||||||
var token, err = transport.Exchange(code)
|
err := c.Bind(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("cannot get access_token. %s", err)
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
c.Redirect(303, "/login#error=token_exchange")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get user account
|
login, err := remote.Auth(in.Access, in.Refresh)
|
||||||
user, err := remote.Login(token.AccessToken, token.RefreshToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("cannot get user with access_token. %s", err)
|
c.AbortWithError(http.StatusUnauthorized, err)
|
||||||
c.Redirect(303, "/login#error=user_not_found")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the user to the request
|
user, err := model.GetUserLogin(db, login)
|
||||||
c.Set("user", user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLoginOauth1 is able to authorize a user with the oauth1
|
|
||||||
// authentication protocol. This is used primarily with Bitbucket
|
|
||||||
// and Stash only, and one day I hope can be removed.
|
|
||||||
func getLoginOauth1(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLoginBasic is able to authorize a user with a username and
|
|
||||||
// password. This can be used for systems that do not support oauth.
|
|
||||||
func getLoginBasic(c *gin.Context) {
|
|
||||||
var (
|
|
||||||
remote = ToRemote(c)
|
|
||||||
username = c.Request.FormValue("username")
|
|
||||||
password = c.Request.FormValue("password")
|
|
||||||
)
|
|
||||||
|
|
||||||
// get user account
|
|
||||||
user, err := remote.Login(username, password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("invalid username or password for %s. %s", username, err)
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
c.Redirect(303, "/login#error=invalid_credentials")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the user to the request
|
exp := time.Now().Add(time.Hour * 72).Unix()
|
||||||
c.Set("user", user)
|
token := token.New(token.SessToken, user.Login)
|
||||||
|
tokenstr, err := token.SignExpires(user.Hash, exp)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, &tokenPayload{
|
||||||
|
Access: tokenstr,
|
||||||
|
Expires: exp - time.Now().Unix(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkMembership is a helper function that compares the user's
|
type tokenPayload struct {
|
||||||
// organization list to a whitelist of organizations that are
|
Access string `json:"access_token,omitempty"`
|
||||||
// approved to use the system.
|
Refresh string `json:"refresh_token,omitempty"`
|
||||||
func checkMembership(orgs, whitelist []string) bool {
|
Expires int64 `json:"expires_in,omitempty"`
|
||||||
orgs_ := make(map[string]struct{}, len(orgs))
|
|
||||||
for _, org := range orgs {
|
|
||||||
orgs_[org] = struct{}{}
|
|
||||||
}
|
|
||||||
for _, org := range whitelist {
|
|
||||||
if _, ok := orgs_[org]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
80
controller/node.go
Normal file
80
controller/node.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetNodes(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
nodes, err := model.GetNodeList(db)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
c.IndentedJSON(http.StatusOK, nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowNodes(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
user := session.User(c)
|
||||||
|
nodes, _ := model.GetNodeList(db)
|
||||||
|
token, _ := token.New(token.CsrfToken, user.Login).Sign(user.Hash)
|
||||||
|
c.HTML(http.StatusOK, "nodes.html", gin.H{"User": user, "Nodes": nodes, "Csrf": token})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNode(c *gin.Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostNode(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
engine := context.Engine(c)
|
||||||
|
|
||||||
|
node := &model.Node{}
|
||||||
|
err := c.Bind(node)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.Arch = "linux_amd64"
|
||||||
|
|
||||||
|
err = model.InsertNode(db, node)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := engine.Allocate(node)
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
c.IndentedJSON(http.StatusOK, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteNode(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
engine := context.Engine(c)
|
||||||
|
|
||||||
|
id, _ := strconv.Atoi(c.Param("node"))
|
||||||
|
node, err := model.GetNode(db, int64(id))
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = model.DeleteNode(db, node)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
engine.Deallocate(node)
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetQueue(c *gin.Context) {
|
|
||||||
queue := ToQueue(c)
|
|
||||||
items := queue.Items()
|
|
||||||
c.JSON(200, items)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package recorder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResponseRecorder struct {
|
|
||||||
*httptest.ResponseRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *ResponseRecorder {
|
|
||||||
return &ResponseRecorder{httptest.NewRecorder()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *ResponseRecorder) reset() {
|
|
||||||
rr.ResponseRecorder = httptest.NewRecorder()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *ResponseRecorder) CloseNotify() <-chan bool {
|
|
||||||
return http.ResponseWriter(rr).(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
return http.ResponseWriter(rr).(http.Hijacker).Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *ResponseRecorder) Size() int { return rr.Body.Len() }
|
|
||||||
func (rr *ResponseRecorder) Status() int { return rr.Code }
|
|
||||||
func (rr *ResponseRecorder) WriteHeaderNow() {}
|
|
||||||
func (rr *ResponseRecorder) Written() bool { return rr.Code != 0 }
|
|
274
controller/repo.go
Normal file
274
controller/repo.go
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/crypto"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostRepo(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
remote := context.Remote(c)
|
||||||
|
user := session.User(c)
|
||||||
|
owner := c.Param("owner")
|
||||||
|
name := c.Param("name")
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.AbortWithStatus(403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := remote.Repo(user, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
c.String(404, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m, err := remote.Perm(user, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
c.String(404, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !m.Admin {
|
||||||
|
c.String(403, "Administrative access is required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// error if the repository already exists
|
||||||
|
_, err = model.GetRepoName(db, owner, name)
|
||||||
|
if err == nil {
|
||||||
|
c.String(409, "Repository already exists.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the repository owner to the
|
||||||
|
// currently authenticated user.
|
||||||
|
r.UserID = user.ID
|
||||||
|
r.AllowPush = true
|
||||||
|
r.AllowPull = true
|
||||||
|
r.Timeout = 60 // 1 hour default build time
|
||||||
|
r.Hash = crypto.Rand()
|
||||||
|
|
||||||
|
// crates the jwt token used to verify the repository
|
||||||
|
t := token.New(token.HookToken, r.FullName)
|
||||||
|
sig, err := t.Sign(r.Hash)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf(
|
||||||
|
"%s/hook?access_token=%s",
|
||||||
|
httputil.GetURL(c.Request),
|
||||||
|
sig,
|
||||||
|
)
|
||||||
|
|
||||||
|
// generate an RSA key and add to the repo
|
||||||
|
key, err := crypto.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys := new(model.Key)
|
||||||
|
keys.Public = string(crypto.MarshalPublicKey(&key.PublicKey))
|
||||||
|
keys.Private = string(crypto.MarshalPrivateKey(key))
|
||||||
|
|
||||||
|
// activate the repository before we make any
|
||||||
|
// local changes to the database.
|
||||||
|
err = remote.Activate(user, r, keys, link)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// persist the repository
|
||||||
|
err = model.CreateRepo(tx, r)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys.RepoID = r.ID
|
||||||
|
err = model.CreateKey(tx, keys)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = model.CreateStar(tx, user, r)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
c.JSON(200, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PatchRepo(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
user := session.User(c)
|
||||||
|
|
||||||
|
in := &struct {
|
||||||
|
IsTrusted *bool `json:"trusted,omitempty"`
|
||||||
|
Timeout *int64 `json:"timeout,omitempty"`
|
||||||
|
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||||
|
AllowPush *bool `json:"allow_push,omitempty"`
|
||||||
|
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
||||||
|
AllowTag *bool `json:"allow_tag,omitempty"`
|
||||||
|
}{}
|
||||||
|
if err := c.Bind(in); err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.AllowPush != nil {
|
||||||
|
repo.AllowPush = *in.AllowPush
|
||||||
|
}
|
||||||
|
if in.AllowPull != nil {
|
||||||
|
repo.AllowPull = *in.AllowPull
|
||||||
|
}
|
||||||
|
if in.AllowDeploy != nil {
|
||||||
|
repo.AllowDeploy = *in.AllowDeploy
|
||||||
|
}
|
||||||
|
if in.AllowTag != nil {
|
||||||
|
repo.AllowTag = *in.AllowTag
|
||||||
|
}
|
||||||
|
if in.IsTrusted != nil && user.Admin {
|
||||||
|
repo.IsTrusted = *in.IsTrusted
|
||||||
|
}
|
||||||
|
if in.Timeout != nil && user.Admin {
|
||||||
|
repo.Timeout = *in.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
err := model.UpdateRepo(db, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user is authenticated we should
|
||||||
|
// check to see if they've starred the repository
|
||||||
|
repo.IsStarred, _ = model.GetStar(db, user, repo)
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepo(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
user := session.User(c)
|
||||||
|
if user == nil {
|
||||||
|
c.IndentedJSON(http.StatusOK, repo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user is authenticated we should
|
||||||
|
// check to see if they've starred the repository
|
||||||
|
repo.IsStarred, _ = model.GetStar(db, user, repo)
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepoKey(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
keys, err := model.GetKey(db, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusNotFound, err)
|
||||||
|
} else {
|
||||||
|
c.String(http.StatusOK, keys.Public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRepo(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
|
||||||
|
err := model.DeleteRepo(db, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
} else {
|
||||||
|
c.Writer.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostSecure(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
|
||||||
|
in, err := ioutil.ReadAll(c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we found some strange characters included in
|
||||||
|
// the yaml file when entered into a browser textarea.
|
||||||
|
// these need to be removed
|
||||||
|
in = bytes.Replace(in, []byte{'\xA0'}, []byte{' '}, -1)
|
||||||
|
|
||||||
|
// make sure the Yaml is valid format to prevent
|
||||||
|
// a malformed value from being used in the build
|
||||||
|
err = yaml.Unmarshal(in, &yaml.MapSlice{})
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := model.GetKey(db, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypts using go-jose
|
||||||
|
out, err := crypto.Encrypt(string(in), key.Private)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.String(http.StatusOK, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostStar(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
user := session.User(c)
|
||||||
|
|
||||||
|
err := model.CreateStar(db, user, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
} else {
|
||||||
|
c.Writer.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteStar(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
user := session.User(c)
|
||||||
|
|
||||||
|
err := model.DeleteStar(db, user, repo)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
} else {
|
||||||
|
c.Writer.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,349 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/remote"
|
|
||||||
"github.com/drone/drone/pkg/token"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
"github.com/drone/drone/pkg/utils/httputil"
|
|
||||||
"github.com/drone/drone/pkg/utils/sshutil"
|
|
||||||
"github.com/drone/drone/pkg/yaml/secure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// repoResp is a data structure used for sending
|
|
||||||
// repository data to the client, augmented with
|
|
||||||
// additional repository meta-data.
|
|
||||||
type repoResp struct {
|
|
||||||
*common.Repo
|
|
||||||
Perms *common.Perm `json:"permissions,omitempty"`
|
|
||||||
Keypair *common.Keypair `json:"keypair,omitempty"`
|
|
||||||
Params map[string]string `json:"params,omitempty"`
|
|
||||||
Starred bool `json:"starred,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// repoReq is a data structure used for receiving
|
|
||||||
// repository data from the client to modify the
|
|
||||||
// attributes of an existing repository.
|
|
||||||
//
|
|
||||||
// note that attributes are pointers so that we can
|
|
||||||
// accept null values, effectively patching an existing
|
|
||||||
// repository object with only the supplied fields.
|
|
||||||
type repoReq struct {
|
|
||||||
Trusted *bool `json:"trusted"`
|
|
||||||
Timeout *int64 `json:"timeout"`
|
|
||||||
|
|
||||||
Hooks struct {
|
|
||||||
PullReqeust *bool `json:"pull_request"`
|
|
||||||
Push *bool `json:"push"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// optional private parameters can only be
|
|
||||||
// supplied by the repository admin.
|
|
||||||
Params *map[string]string `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepo accepts a request to retrieve a commit
|
|
||||||
// from the datastore for the given repository, branch and
|
|
||||||
// commit hash.
|
|
||||||
//
|
|
||||||
// GET /api/repos/:owner/:name
|
|
||||||
//
|
|
||||||
func GetRepo(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
perm := ToPerm(c)
|
|
||||||
data := repoResp{repo, perm, nil, nil, false}
|
|
||||||
|
|
||||||
// if the user is authenticated, we should display
|
|
||||||
// if she is watching the current repository.
|
|
||||||
if user == nil {
|
|
||||||
c.JSON(200, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user is an administrator of the project
|
|
||||||
// we should display the private parameter data
|
|
||||||
// and keypair data.
|
|
||||||
if perm.Push {
|
|
||||||
data.Params = repo.Params
|
|
||||||
data.Keypair = repo.Keys
|
|
||||||
}
|
|
||||||
// check to see if the user is subscribing to the repo
|
|
||||||
data.Starred, _ = store.Starred(user, repo)
|
|
||||||
|
|
||||||
c.JSON(200, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutRepo accepts a request to update the named repository
|
|
||||||
// in the datastore. It expects a JSON input and returns the
|
|
||||||
// updated repository in JSON format if successful.
|
|
||||||
//
|
|
||||||
// PUT /api/repos/:owner/:name
|
|
||||||
//
|
|
||||||
func PutRepo(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
perm := ToPerm(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
|
|
||||||
in := &repoReq{}
|
|
||||||
if !c.BindWith(in, binding.JSON) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Params != nil {
|
|
||||||
repo.Params = *in.Params
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.Hooks.Push != nil {
|
|
||||||
repo.Hooks.Push = *in.Hooks.Push
|
|
||||||
}
|
|
||||||
if in.Hooks.PullReqeust != nil {
|
|
||||||
repo.Hooks.PullRequest = *in.Hooks.PullReqeust
|
|
||||||
}
|
|
||||||
if in.Trusted != nil && user.Admin {
|
|
||||||
repo.Trusted = *in.Trusted
|
|
||||||
}
|
|
||||||
if in.Timeout != nil && user.Admin {
|
|
||||||
repo.Timeout = *in.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.SetRepo(repo)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := repoResp{repo, perm, nil, nil, false}
|
|
||||||
data.Params = repo.Params
|
|
||||||
data.Keypair = repo.Keys
|
|
||||||
data.Starred, _ = store.Starred(user, repo)
|
|
||||||
|
|
||||||
c.JSON(200, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepo accepts a request to delete the named
|
|
||||||
// repository.
|
|
||||||
//
|
|
||||||
// DEL /api/repos/:owner/:name
|
|
||||||
//
|
|
||||||
func DeleteRepo(c *gin.Context) {
|
|
||||||
ds := ToDatastore(c)
|
|
||||||
u := ToUser(c)
|
|
||||||
r := ToRepo(c)
|
|
||||||
|
|
||||||
link := fmt.Sprintf(
|
|
||||||
"%s/api/hook",
|
|
||||||
httputil.GetURL(c.Request),
|
|
||||||
)
|
|
||||||
|
|
||||||
remote := ToRemote(c)
|
|
||||||
err := remote.Deactivate(u, r, link)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ds.DelRepo(r)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
}
|
|
||||||
c.Writer.WriteHeader(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostRepo accapets a request to activate the named repository
|
|
||||||
// in the datastore. It returns a 201 status created if successful
|
|
||||||
//
|
|
||||||
// POST /api/repos/:owner/:name
|
|
||||||
//
|
|
||||||
func PostRepo(c *gin.Context) {
|
|
||||||
user := ToUser(c)
|
|
||||||
store := ToDatastore(c)
|
|
||||||
owner := c.Params.ByName("owner")
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
|
|
||||||
// get the repository and user permissions
|
|
||||||
// from the remote system.
|
|
||||||
remote := ToRemote(c)
|
|
||||||
r, err := remote.Repo(user, owner, name)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
}
|
|
||||||
m, err := remote.Perm(user, owner, name)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !m.Admin {
|
|
||||||
c.Fail(403, fmt.Errorf("must be repository admin"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// error if the repository already exists
|
|
||||||
_, err = store.RepoName(owner, name)
|
|
||||||
if err == nil {
|
|
||||||
c.String(409, "Repository already exists")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the repository owner to the
|
|
||||||
// currently authenticated user.
|
|
||||||
r.UserID = user.ID
|
|
||||||
r.Hooks = new(common.Hooks)
|
|
||||||
r.Hooks.Push = true
|
|
||||||
r.Hooks.PullRequest = true
|
|
||||||
r.Timeout = 60 // 1 hour default build time
|
|
||||||
r.Hash = common.GenerateToken()
|
|
||||||
r.Self = fmt.Sprintf(
|
|
||||||
"%s/%s",
|
|
||||||
httputil.GetURL(c.Request),
|
|
||||||
r.FullName,
|
|
||||||
)
|
|
||||||
|
|
||||||
// crates the jwt token used to verify the repository
|
|
||||||
t := token.New(token.HookToken, r.FullName)
|
|
||||||
sig, err := t.Sign(r.Hash)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
link := fmt.Sprintf(
|
|
||||||
"%s/api/hook?access_token=%s",
|
|
||||||
httputil.GetURL(c.Request),
|
|
||||||
sig,
|
|
||||||
)
|
|
||||||
|
|
||||||
// generate an RSA key and add to the repo
|
|
||||||
key, err := sshutil.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Keys = new(common.Keypair)
|
|
||||||
r.Keys.Public = string(sshutil.MarshalPublicKey(&key.PublicKey))
|
|
||||||
r.Keys.Private = string(sshutil.MarshalPrivateKey(key))
|
|
||||||
|
|
||||||
// activate the repository before we make any
|
|
||||||
// local changes to the database.
|
|
||||||
err = remote.Activate(user, r, r.Keys, link)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist the repository
|
|
||||||
err = store.AddRepo(r)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
store.AddStar(user, r)
|
|
||||||
|
|
||||||
c.JSON(200, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt accapets a request to encrypt the
|
|
||||||
// body of the request using the repository secret
|
|
||||||
// key.
|
|
||||||
//
|
|
||||||
// POST /api/repos/:owner/:name/encrypt
|
|
||||||
//
|
|
||||||
func Encrypt(c *gin.Context) {
|
|
||||||
repo := ToRepo(c)
|
|
||||||
in, err := ioutil.ReadAll(c.Request.Body)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in, err = base64.StdEncoding.DecodeString(string(in))
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we found some strange characters included in
|
|
||||||
// the yaml file when entered into a browser textarea.
|
|
||||||
// these need to be removed
|
|
||||||
in = bytes.Replace(in, []byte{'\xA0'}, []byte{' '}, -1)
|
|
||||||
|
|
||||||
// make sure the Yaml is valid format to prevent
|
|
||||||
// a malformed value from being used in the build
|
|
||||||
err = yaml.Unmarshal(in, &yaml.MapSlice{})
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypts using go-jose
|
|
||||||
out, err := secure.Encrypt(string(in), repo.Keys.Private)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Writer.Write([]byte(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe accapets a request to unsubscribe the
|
|
||||||
// currently authenticated user to the repository.
|
|
||||||
//
|
|
||||||
// DEL /api/subscribers/:owner/:name
|
|
||||||
//
|
|
||||||
func Unsubscribe(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
|
|
||||||
err := store.DelStar(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
} else {
|
|
||||||
c.Writer.WriteHeader(200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe accapets a request to subscribe the
|
|
||||||
// currently authenticated user to the repository.
|
|
||||||
//
|
|
||||||
// POST /api/subscriber/:owner/:name
|
|
||||||
//
|
|
||||||
func Subscribe(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
|
|
||||||
err := store.AddStar(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
} else {
|
|
||||||
c.Writer.WriteHeader(200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// perms is a helper function that returns user permissions
|
|
||||||
// for a particular repository.
|
|
||||||
func perms(remote remote.Remote, u *common.User, r *common.Repo) *common.Perm {
|
|
||||||
switch {
|
|
||||||
case u == nil && r.Private:
|
|
||||||
return &common.Perm{}
|
|
||||||
case u == nil && r.Private == false:
|
|
||||||
return &common.Perm{Pull: true}
|
|
||||||
case u.Admin:
|
|
||||||
return &common.Perm{Pull: true, Push: true, Admin: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := remote.Perm(u, r.Owner, r.Name)
|
|
||||||
if err != nil {
|
|
||||||
return &common.Perm{}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
|
@ -1,261 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/bus"
|
|
||||||
"github.com/drone/drone/pkg/queue"
|
|
||||||
"github.com/drone/drone/pkg/remote"
|
|
||||||
"github.com/drone/drone/pkg/runner"
|
|
||||||
"github.com/drone/drone/pkg/store"
|
|
||||||
"github.com/drone/drone/pkg/token"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetQueue(q queue.Queue) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set("queue", q)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToQueue(c *gin.Context) queue.Queue {
|
|
||||||
v, ok := c.Get("queue")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(queue.Queue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBus(r bus.Bus) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set("bus", r)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToBus(c *gin.Context) bus.Bus {
|
|
||||||
v, ok := c.Get("bus")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(bus.Bus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToRemote(c *gin.Context) remote.Remote {
|
|
||||||
v, ok := c.Get("remote")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(remote.Remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetRemote(r remote.Remote) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set("remote", r)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToRunner(c *gin.Context) runner.Runner {
|
|
||||||
v, ok := c.Get("runner")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(runner.Runner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetRunner(r runner.Runner) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set("runner", r)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToPerm(c *gin.Context) *common.Perm {
|
|
||||||
v, ok := c.Get("perm")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(*common.Perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToUser(c *gin.Context) *common.User {
|
|
||||||
v, ok := c.Get("user")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(*common.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToRepo(c *gin.Context) *common.Repo {
|
|
||||||
v, ok := c.Get("repo")
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v.(*common.Repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToDatastore(c *gin.Context) store.Store {
|
|
||||||
return c.MustGet("datastore").(store.Store)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetDatastore(ds store.Store) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set("datastore", ds)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetUser() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
|
|
||||||
var store = ToDatastore(c)
|
|
||||||
var user *common.User
|
|
||||||
|
|
||||||
_, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
|
||||||
var err error
|
|
||||||
user, err = store.UserLogin(t.Text)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return user.Hash, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil && user != nil && user.ID != 0 {
|
|
||||||
c.Set("user", user)
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetRepo() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
ds := ToDatastore(c)
|
|
||||||
u := ToUser(c)
|
|
||||||
owner := c.Params.ByName("owner")
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
r, err := ds.RepoName(owner, name)
|
|
||||||
switch {
|
|
||||||
case err != nil && u != nil:
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
case err != nil && u == nil:
|
|
||||||
c.Fail(401, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Set("repo", r)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetPerm() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
remote := ToRemote(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
perm := perms(remote, user, repo)
|
|
||||||
c.Set("perm", perm)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustUser() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
u := ToUser(c)
|
|
||||||
if u == nil {
|
|
||||||
c.AbortWithStatus(401)
|
|
||||||
} else {
|
|
||||||
c.Set("user", u)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustAdmin() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
u := ToUser(c)
|
|
||||||
if u == nil {
|
|
||||||
c.AbortWithStatus(401)
|
|
||||||
} else if !u.Admin {
|
|
||||||
c.AbortWithStatus(403)
|
|
||||||
} else {
|
|
||||||
c.Set("user", u)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckPull() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
u := ToUser(c)
|
|
||||||
m := ToPerm(c)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case u == nil && m == nil:
|
|
||||||
c.AbortWithStatus(401)
|
|
||||||
case u == nil && m.Pull == false:
|
|
||||||
c.AbortWithStatus(401)
|
|
||||||
case u != nil && m.Pull == false:
|
|
||||||
c.AbortWithStatus(404)
|
|
||||||
default:
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckPush() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
switch c.Request.Method {
|
|
||||||
case "GET", "OPTIONS":
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u := ToUser(c)
|
|
||||||
m := ToPerm(c)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case u == nil && m.Push == false:
|
|
||||||
c.AbortWithStatus(401)
|
|
||||||
case u != nil && m.Push == false:
|
|
||||||
c.AbortWithStatus(404)
|
|
||||||
default:
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetHeaders() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
|
|
||||||
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
|
|
||||||
c.Writer.Header().Add("X-Frame-Options", "DENY")
|
|
||||||
c.Writer.Header().Add("X-Content-Type-Options", "nosniff")
|
|
||||||
c.Writer.Header().Add("X-XSS-Protection", "1; mode=block")
|
|
||||||
c.Writer.Header().Add("Cache-Control", "no-cache")
|
|
||||||
c.Writer.Header().Add("Cache-Control", "no-store")
|
|
||||||
c.Writer.Header().Add("Cache-Control", "max-age=0")
|
|
||||||
c.Writer.Header().Add("Cache-Control", "must-revalidate")
|
|
||||||
c.Writer.Header().Add("Cache-Control", "value")
|
|
||||||
c.Writer.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
|
||||||
c.Writer.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
|
||||||
if c.Request.TLS != nil {
|
|
||||||
c.Writer.Header().Add("Strict-Transport-Security", "max-age=31536000")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization")
|
|
||||||
c.Writer.Header().Set("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
|
|
||||||
c.Writer.Header().Set("Content-Type", "application/json")
|
|
||||||
c.Writer.WriteHeader(200)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
123
controller/stream.go
Normal file
123
controller/stream.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
/*
|
||||||
|
stream.Get("/:owner/:name", controller.GetRepoEvents)
|
||||||
|
stream.Get("/:owner/:name/:build/:number", controller.GetStream)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/drone/drone/engine"
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/manucorporat/sse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRepoEvents will upgrade the connection to a Websocket and will stream
|
||||||
|
// event updates to the browser.
|
||||||
|
func GetRepoEvents(c *gin.Context) {
|
||||||
|
engine_ := context.Engine(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
|
||||||
|
eventc := make(chan *engine.Event, 1)
|
||||||
|
engine_.Subscribe(eventc)
|
||||||
|
defer func() {
|
||||||
|
engine_.Unsubscribe(eventc)
|
||||||
|
close(eventc)
|
||||||
|
log.Infof("closed event stream")
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
select {
|
||||||
|
case event := <-eventc:
|
||||||
|
if event == nil {
|
||||||
|
log.Infof("nil event received")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if event.Name == repo.FullName {
|
||||||
|
log.Debugf("received message %s", event.Name)
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Event: "message",
|
||||||
|
Data: string(event.Msg),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case <-c.Writer.CloseNotify():
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStream(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
engine_ := context.Engine(c)
|
||||||
|
repo := session.Repo(c)
|
||||||
|
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||||
|
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||||
|
|
||||||
|
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
|
||||||
|
build, err := model.GetBuildNumber(db, repo, buildn)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugln("stream cannot get build number.", err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
job, err := model.GetJobNumber(db, build, jobn)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugln("stream cannot get job number.", err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node, err := model.GetNode(db, job.NodeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugln("stream cannot get node.", err)
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := engine_.Stream(build.ID, job.ID, node)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(404, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
rc.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-c.Writer.CloseNotify()
|
||||||
|
rc.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
rw := &StreamWriter{c.Writer, 0}
|
||||||
|
|
||||||
|
stdcopy.StdCopy(rw, rw, rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamWriter struct {
|
||||||
|
writer gin.ResponseWriter
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *StreamWriter) Write(data []byte) (int, error) {
|
||||||
|
var err = sse.Encode(w.writer, sse.Event{
|
||||||
|
Id: strconv.Itoa(w.count),
|
||||||
|
Event: "message",
|
||||||
|
Data: string(data),
|
||||||
|
})
|
||||||
|
w.writer.Flush()
|
||||||
|
w.count += len(data)
|
||||||
|
return len(data), err
|
||||||
|
}
|
|
@ -1,103 +1,82 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "crypto/sha1"
|
"net/http"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/token"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUserCurr accepts a request to retrieve the
|
var cache *lru.Cache
|
||||||
// currently authenticated user from the datastore
|
|
||||||
// and return in JSON format.
|
|
||||||
//
|
|
||||||
// GET /api/user
|
|
||||||
//
|
|
||||||
func GetUserCurr(c *gin.Context) {
|
|
||||||
u := ToUser(c)
|
|
||||||
// f := fmt.Printf("% x", sha1.Sum(u.Hash))
|
|
||||||
|
|
||||||
// v := struct {
|
func init() {
|
||||||
// *types.User
|
var err error
|
||||||
|
cache, err = lru.New(1028)
|
||||||
// // token fingerprint
|
if err != nil {
|
||||||
// Token string `json:"token"`
|
panic(err)
|
||||||
// }{u, f}
|
}
|
||||||
|
|
||||||
c.JSON(200, u)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutUserCurr accepts a request to update the currently
|
func GetSelf(c *gin.Context) {
|
||||||
// authenticated User profile.
|
c.IndentedJSON(200, session.User(c))
|
||||||
//
|
}
|
||||||
// PUT /api/user
|
|
||||||
//
|
|
||||||
func PutUserCurr(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
|
|
||||||
in := &types.User{}
|
func GetFeed(c *gin.Context) {
|
||||||
if !c.BindWith(in, binding.JSON) {
|
user := session.User(c)
|
||||||
|
db := context.Database(c)
|
||||||
|
feed, err := model.GetUserFeed(db, user, 25, 0)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: we are no longer auto-generating avatar
|
c.IndentedJSON(http.StatusOK, feed)
|
||||||
user.Email = in.Email
|
|
||||||
user.Avatar = gravatar.Hash(in.Email)
|
|
||||||
err := store.SetUser(user)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(200, user)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserRepos accepts a request to get the currently
|
func GetRepos(c *gin.Context) {
|
||||||
// authenticated user's repository list from the datastore,
|
user := session.User(c)
|
||||||
// encoded and returned in JSON format.
|
db := context.Database(c)
|
||||||
//
|
repos, err := model.GetRepoList(db, user)
|
||||||
// GET /api/user/repos
|
|
||||||
//
|
|
||||||
func GetUserRepos(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
user := ToUser(c)
|
|
||||||
repos, err := store.RepoList(user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(400, err)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, &repos)
|
|
||||||
}
|
}
|
||||||
|
c.IndentedJSON(http.StatusOK, repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserFeed accepts a request to get the currently
|
func GetRemoteRepos(c *gin.Context) {
|
||||||
// authenticated user's build feed from the datastore,
|
user := session.User(c)
|
||||||
// encoded and returned in JSON format.
|
remote := context.Remote(c)
|
||||||
//
|
|
||||||
// GET /api/user/feed
|
// attempt to get the repository list from the
|
||||||
//
|
// cache since the operation is expensive
|
||||||
func GetUserFeed(c *gin.Context) {
|
v, ok := cache.Get(user.Login)
|
||||||
store := ToDatastore(c)
|
if ok {
|
||||||
user := ToUser(c)
|
c.IndentedJSON(http.StatusOK, v)
|
||||||
feed, err := store.UserFeed(user, 25, 0)
|
return
|
||||||
if err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(200, &feed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repos, err := remote.Repos(user)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cache.Add(user.Login, repos)
|
||||||
|
c.IndentedJSON(http.StatusOK, repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/user/token
|
func PostToken(c *gin.Context) {
|
||||||
func PostUserToken(c *gin.Context) {
|
user := session.User(c)
|
||||||
user := ToUser(c)
|
|
||||||
|
|
||||||
token := token.New(token.UserToken, user.Login)
|
token := token.New(token.UserToken, user.Login)
|
||||||
tokenstr, err := token.Sign(user.Hash)
|
tokenstr, err := token.Sign(user.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(500, err)
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
} else {
|
} else {
|
||||||
c.String(200, tokenstr)
|
c.String(http.StatusOK, tokenstr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
"github.com/drone/drone/pkg/server/recorder"
|
|
||||||
"github.com/drone/drone/pkg/store/mock"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUser(t *testing.T) {
|
|
||||||
store := new(mocks.Store)
|
|
||||||
|
|
||||||
g := Goblin(t)
|
|
||||||
g.Describe("User", func() {
|
|
||||||
|
|
||||||
g.It("should get", func() {
|
|
||||||
rw := recorder.New()
|
|
||||||
ctx := &gin.Context{Engine: gin.Default(), Writer: rw}
|
|
||||||
|
|
||||||
user := &common.User{Login: "octocat"}
|
|
||||||
ctx.Set("user", user)
|
|
||||||
|
|
||||||
GetUserCurr(ctx)
|
|
||||||
|
|
||||||
out := &common.User{}
|
|
||||||
json.NewDecoder(rw.Body).Decode(out)
|
|
||||||
g.Assert(rw.Code).Equal(200)
|
|
||||||
g.Assert(out).Equal(user)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should put", func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
in := &common.User{Email: "octocat@github.com"}
|
|
||||||
json.NewEncoder(&buf).Encode(in)
|
|
||||||
|
|
||||||
rw := recorder.New()
|
|
||||||
ctx := &gin.Context{Engine: gin.Default(), Writer: rw}
|
|
||||||
ctx.Request = &http.Request{Body: ioutil.NopCloser(&buf)}
|
|
||||||
ctx.Request.Header = http.Header{}
|
|
||||||
ctx.Request.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
user := &common.User{Login: "octocat"}
|
|
||||||
ctx.Set("user", user)
|
|
||||||
ctx.Set("datastore", store)
|
|
||||||
store.On("SetUser", user).Return(nil).Once()
|
|
||||||
|
|
||||||
PutUserCurr(ctx)
|
|
||||||
|
|
||||||
out := &common.User{}
|
|
||||||
json.NewDecoder(rw.Body).Decode(out)
|
|
||||||
g.Assert(rw.Code).Equal(200)
|
|
||||||
g.Assert(out.Login).Equal(user.Login)
|
|
||||||
g.Assert(out.Email).Equal(in.Email)
|
|
||||||
g.Assert(out.Avatar).Equal("7194e8d48fa1d2b689f99443b767316c")
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should put, error", func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
in := &common.User{Email: "octocat@github.com"}
|
|
||||||
json.NewEncoder(&buf).Encode(in)
|
|
||||||
|
|
||||||
rw := recorder.New()
|
|
||||||
ctx := &gin.Context{Engine: gin.Default(), Writer: rw}
|
|
||||||
ctx.Request = &http.Request{Body: ioutil.NopCloser(&buf)}
|
|
||||||
ctx.Request.Header = http.Header{}
|
|
||||||
ctx.Request.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
user := &common.User{Login: "octocat"}
|
|
||||||
ctx.Set("user", user)
|
|
||||||
ctx.Set("datastore", store)
|
|
||||||
store.On("SetUser", user).Return(errors.New("error")).Once()
|
|
||||||
|
|
||||||
PutUserCurr(ctx)
|
|
||||||
|
|
||||||
out := &common.User{}
|
|
||||||
json.NewDecoder(rw.Body).Decode(out)
|
|
||||||
g.Assert(rw.Code).Equal(400)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,131 +1,118 @@
|
||||||
package server
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
"net/http"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/shared/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUsers accepts a request to retrieve all users
|
|
||||||
// from the datastore and return encoded in JSON format.
|
|
||||||
//
|
|
||||||
// GET /api/users
|
|
||||||
//
|
|
||||||
func GetUsers(c *gin.Context) {
|
func GetUsers(c *gin.Context) {
|
||||||
store := ToDatastore(c)
|
db := context.Database(c)
|
||||||
users, err := store.UserList()
|
users, err := model.GetUserList(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(400, err)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, users)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostUser accepts a request to create a new user in the
|
|
||||||
// system. The created user account is returned in JSON
|
|
||||||
// format if successful.
|
|
||||||
//
|
|
||||||
// POST /api/users
|
|
||||||
//
|
|
||||||
func PostUser(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
user := &types.User{Login: name}
|
|
||||||
user.Token = c.Request.FormValue("token")
|
|
||||||
user.Secret = c.Request.FormValue("secret")
|
|
||||||
user.Hash = c.Request.FormValue("hash")
|
|
||||||
if len(user.Hash) == 0 {
|
|
||||||
user.Hash = types.GenerateToken()
|
|
||||||
}
|
|
||||||
if err := store.AddUser(user); err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
} else {
|
|
||||||
c.JSON(201, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser accepts a request to retrieve a user by hostname
|
|
||||||
// and login from the datastore and return encoded in JSON
|
|
||||||
// format.
|
|
||||||
//
|
|
||||||
// GET /api/users/:name
|
|
||||||
//
|
|
||||||
func GetUser(c *gin.Context) {
|
func GetUser(c *gin.Context) {
|
||||||
store := ToDatastore(c)
|
db := context.Database(c)
|
||||||
name := c.Params.ByName("name")
|
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||||
user, err := store.UserLogin(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(404, err)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutUser accepts a request to update an existing user in
|
func PatchUser(c *gin.Context) {
|
||||||
// the system. The modified user account is returned in JSON
|
me := session.User(c)
|
||||||
// format if successful.
|
db := context.Database(c)
|
||||||
//
|
in := &model.User{}
|
||||||
// PUT /api/users/:name
|
err := c.Bind(in)
|
||||||
//
|
|
||||||
func PutUser(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
me := ToUser(c)
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
user, err := store.UserLogin(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(404, err)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
in := &types.User{}
|
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||||
if !c.BindWith(in, binding.JSON) {
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user.Admin = in.Admin
|
||||||
|
user.Active = in.Active
|
||||||
|
|
||||||
|
// cannot update self
|
||||||
|
if me.ID == user.ID {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = model.UpdateUser(db, user)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostUser(c *gin.Context) {
|
||||||
|
db := context.Database(c)
|
||||||
|
in := &model.User{}
|
||||||
|
err := c.Bind(in)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &model.User{}
|
||||||
|
user.Login = in.Login
|
||||||
user.Email = in.Email
|
user.Email = in.Email
|
||||||
user.Avatar = gravatar.Hash(user.Email)
|
user.Admin = in.Admin
|
||||||
|
user.Avatar = in.Avatar
|
||||||
|
user.Active = true
|
||||||
|
user.Hash = crypto.Rand()
|
||||||
|
|
||||||
// an administrator must not be able to
|
err = model.CreateUser(db, user)
|
||||||
// downgrade her own account.
|
|
||||||
if me.Login != user.Login {
|
|
||||||
user.Admin = in.Admin
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.SetUser(user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(400, err)
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
} else {
|
return
|
||||||
c.JSON(200, user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.IndentedJSON(http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser accepts a request to delete the specified
|
|
||||||
// user account from the system. A successful request will
|
|
||||||
// respond with an OK 200 status.
|
|
||||||
//
|
|
||||||
// DELETE /api/users/:name
|
|
||||||
//
|
|
||||||
func DeleteUser(c *gin.Context) {
|
func DeleteUser(c *gin.Context) {
|
||||||
store := ToDatastore(c)
|
me := session.User(c)
|
||||||
me := ToUser(c)
|
db := context.Database(c)
|
||||||
name := c.Params.ByName("name")
|
|
||||||
user, err := store.UserLogin(name)
|
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fail(404, err)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// an administrator must not be able to
|
// cannot delete self
|
||||||
// delete her own account.
|
if me.ID == user.ID {
|
||||||
if user.Login == me.Login {
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
c.Writer.WriteHeader(403)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := store.DelUser(user); err != nil {
|
err = model.DeleteUser(db, user)
|
||||||
c.Fail(400, err)
|
if err != nil {
|
||||||
} else {
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
c.Writer.WriteHeader(204)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Writer.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
105
controller/ws.go
105
controller/ws.go
|
@ -1,105 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/bus"
|
|
||||||
|
|
||||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/manucorporat/sse"
|
|
||||||
"github.com/drone/drone/pkg/docker"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetRepoEvents will upgrade the connection to a Websocket and will stream
|
|
||||||
// event updates to the browser.
|
|
||||||
func GetRepoEvents(c *gin.Context) {
|
|
||||||
bus_ := ToBus(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
|
||||||
|
|
||||||
eventc := make(chan *bus.Event, 1)
|
|
||||||
bus_.Subscribe(eventc)
|
|
||||||
defer func() {
|
|
||||||
bus_.Unsubscribe(eventc)
|
|
||||||
close(eventc)
|
|
||||||
log.Infof("closed event stream")
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.Stream(func(w io.Writer) bool {
|
|
||||||
select {
|
|
||||||
case event := <-eventc:
|
|
||||||
if event == nil {
|
|
||||||
log.Infof("nil event received")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if event.Kind == bus.EventRepo &&
|
|
||||||
event.Name == repo.FullName {
|
|
||||||
sse.Encode(w, sse.Event{
|
|
||||||
Event: "message",
|
|
||||||
Data: string(event.Msg),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case <-c.Writer.CloseNotify():
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStream(c *gin.Context) {
|
|
||||||
store := ToDatastore(c)
|
|
||||||
repo := ToRepo(c)
|
|
||||||
runner := ToRunner(c)
|
|
||||||
commitseq, _ := strconv.Atoi(c.Params.ByName("build"))
|
|
||||||
jobnum, _ := strconv.Atoi(c.Params.ByName("number"))
|
|
||||||
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
|
||||||
|
|
||||||
build, err := store.BuildNumber(repo, commitseq)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
job, err := store.JobNumber(build, jobnum)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, err := runner.Logs(job)
|
|
||||||
if err != nil {
|
|
||||||
c.Fail(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
rc.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-c.Writer.CloseNotify()
|
|
||||||
rc.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
rw := &StreamWriter{c.Writer, 0}
|
|
||||||
|
|
||||||
docker.StdCopy(rw, rw, rc)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamWriter struct {
|
|
||||||
writer gin.ResponseWriter
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *StreamWriter) Write(data []byte) (int, error) {
|
|
||||||
var err = sse.Encode(w.writer, sse.Event{
|
|
||||||
Id: strconv.Itoa(w.count),
|
|
||||||
Event: "message",
|
|
||||||
Data: string(data),
|
|
||||||
})
|
|
||||||
w.writer.Flush()
|
|
||||||
w.count += len(data)
|
|
||||||
return len(data), err
|
|
||||||
}
|
|
51
drone.go
Normal file
51
drone.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"github.com/drone/drone/engine"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
|
"github.com/drone/drone/router"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/drone/drone/shared/envconfig"
|
||||||
|
"github.com/drone/drone/shared/server"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dotenv = flag.String("config", ".env", "")
|
||||||
|
debug = flag.Bool("debug", true, "")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// debug level if requested by user
|
||||||
|
if *debug {
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the configuration from env file
|
||||||
|
env := envconfig.Load(*dotenv)
|
||||||
|
|
||||||
|
// Setup the database driver
|
||||||
|
database_ := database.Load(env)
|
||||||
|
|
||||||
|
// setup the remote driver
|
||||||
|
remote_ := remote.Load(env)
|
||||||
|
|
||||||
|
// setup the runner
|
||||||
|
engine_ := engine.Load(database_, remote_)
|
||||||
|
|
||||||
|
// setup the server and start the listener
|
||||||
|
server_ := server.Load(env)
|
||||||
|
server_.Run(
|
||||||
|
router.Load(
|
||||||
|
context.SetDatabase(database_),
|
||||||
|
context.SetRemote(remote_),
|
||||||
|
context.SetEngine(engine_),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,28 +1,26 @@
|
||||||
package builtin
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/bus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bus struct {
|
type eventbus struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
subs map[chan *bus.Event]bool
|
subs map[chan *Event]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Bus that manages a list of
|
// New creates a new eventbus that manages a list of
|
||||||
// subscribers to which events are published.
|
// subscribers to which events are published.
|
||||||
func New() *Bus {
|
func newEventbus() *eventbus {
|
||||||
return &Bus{
|
return &eventbus{
|
||||||
subs: make(map[chan *bus.Event]bool),
|
subs: make(map[chan *Event]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe adds the channel to the list of
|
// Subscribe adds the channel to the list of
|
||||||
// subscribers. Each subscriber in the list will
|
// subscribers. Each subscriber in the list will
|
||||||
// receive broadcast events.
|
// receive broadcast events.
|
||||||
func (b *Bus) Subscribe(c chan *bus.Event) {
|
func (b *eventbus) subscribe(c chan *Event) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
b.subs[c] = true
|
b.subs[c] = true
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
|
@ -30,19 +28,19 @@ func (b *Bus) Subscribe(c chan *bus.Event) {
|
||||||
|
|
||||||
// Unsubscribe removes the channel from the
|
// Unsubscribe removes the channel from the
|
||||||
// list of subscribers.
|
// list of subscribers.
|
||||||
func (b *Bus) Unsubscribe(c chan *bus.Event) {
|
func (b *eventbus) unsubscribe(c chan *Event) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
delete(b.subs, c)
|
delete(b.subs, c)
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send dispatches a message to all subscribers.
|
// Send dispatches a message to all subscribers.
|
||||||
func (b *Bus) Send(event *bus.Event) {
|
func (b *eventbus) send(event *Event) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
for s := range b.subs {
|
for s := range b.subs {
|
||||||
go func(c chan *bus.Event) {
|
go func(c chan *Event) {
|
||||||
defer recover()
|
defer recover()
|
||||||
c <- event
|
c <- event
|
||||||
}(s)
|
}(s)
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
package builtin
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
. "github.com/franela/goblin"
|
||||||
"github.com/drone/drone/pkg/bus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
func TestBus(t *testing.T) {
|
||||||
g := Goblin(t)
|
g := Goblin(t)
|
||||||
g.Describe("Bus", func() {
|
g.Describe("Event bus", func() {
|
||||||
|
|
||||||
g.It("Should unsubscribe", func() {
|
g.It("Should unsubscribe", func() {
|
||||||
c1 := make(chan *bus.Event)
|
c1 := make(chan *Event)
|
||||||
c2 := make(chan *bus.Event)
|
c2 := make(chan *Event)
|
||||||
b := New()
|
b := newEventbus()
|
||||||
b.Subscribe(c1)
|
b.subscribe(c1)
|
||||||
b.Subscribe(c2)
|
b.subscribe(c2)
|
||||||
g.Assert(len(b.subs)).Equal(2)
|
g.Assert(len(b.subs)).Equal(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should subscribe", func() {
|
g.It("Should subscribe", func() {
|
||||||
c1 := make(chan *bus.Event)
|
c1 := make(chan *Event)
|
||||||
c2 := make(chan *bus.Event)
|
c2 := make(chan *Event)
|
||||||
b := New()
|
b := newEventbus()
|
||||||
b.Subscribe(c1)
|
b.subscribe(c1)
|
||||||
b.Subscribe(c2)
|
b.subscribe(c2)
|
||||||
g.Assert(len(b.subs)).Equal(2)
|
g.Assert(len(b.subs)).Equal(2)
|
||||||
b.Unsubscribe(c1)
|
b.unsubscribe(c1)
|
||||||
b.Unsubscribe(c2)
|
b.unsubscribe(c2)
|
||||||
g.Assert(len(b.subs)).Equal(0)
|
g.Assert(len(b.subs)).Equal(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should send", func() {
|
g.It("Should send", func() {
|
||||||
em := map[string]bool{"foo": true, "bar": true}
|
em := map[string]bool{"foo": true, "bar": true}
|
||||||
e1 := &bus.Event{Name: "foo"}
|
e1 := &Event{Name: "foo"}
|
||||||
e2 := &bus.Event{Name: "bar"}
|
e2 := &Event{Name: "bar"}
|
||||||
c := make(chan *bus.Event)
|
c := make(chan *Event)
|
||||||
b := New()
|
b := newEventbus()
|
||||||
b.Subscribe(c)
|
b.subscribe(c)
|
||||||
b.Send(e1)
|
b.send(e1)
|
||||||
b.Send(e2)
|
b.send(e2)
|
||||||
r1 := <-c
|
r1 := <-c
|
||||||
r2 := <-c
|
r2 := <-c
|
||||||
g.Assert(em[r1.Name]).Equal(true)
|
g.Assert(em[r1.Name]).Equal(true)
|
||||||
|
|
392
engine/engine.go
Normal file
392
engine/engine.go
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
|
"github.com/drone/drone/shared/docker"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Engine interface {
|
||||||
|
Schedule(*Task)
|
||||||
|
Cancel(int64, int64, *model.Node) error
|
||||||
|
Stream(int64, int64, *model.Node) (io.ReadCloser, error)
|
||||||
|
Deallocate(*model.Node)
|
||||||
|
Allocate(*model.Node) bool
|
||||||
|
Subscribe(chan *Event)
|
||||||
|
Unsubscribe(chan *Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// options to fetch the stdout and stderr logs
|
||||||
|
logOpts = &dockerclient.LogOptions{
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// options to fetch the stdout and stderr logs
|
||||||
|
// by tailing the output.
|
||||||
|
logOptsTail = &dockerclient.LogOptions{
|
||||||
|
Follow: true,
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// error when the system cannot find logs
|
||||||
|
errLogging = errors.New("Logs not available")
|
||||||
|
)
|
||||||
|
|
||||||
|
type engine struct {
|
||||||
|
db *sql.DB
|
||||||
|
bus *eventbus
|
||||||
|
updater *updater
|
||||||
|
pool *pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load creates a new build engine, loaded with registered nodes from the
|
||||||
|
// database. The registered nodes are added to the pool of nodes to immediately
|
||||||
|
// start accepting workloads.
|
||||||
|
func Load(db *sql.DB, remote remote.Remote) Engine {
|
||||||
|
engine := &engine{}
|
||||||
|
engine.bus = newEventbus()
|
||||||
|
engine.pool = newPool()
|
||||||
|
engine.db = db
|
||||||
|
engine.updater = &updater{engine.bus, db, remote}
|
||||||
|
|
||||||
|
nodes, err := model.GetNodeList(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get nodes from database. %s", err)
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
engine.pool.allocate(node)
|
||||||
|
log.Infof("registered docker daemon %s", node.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel cancels the job running on the specified Node.
|
||||||
|
func (e *engine) Cancel(build, job int64, node *model.Node) error {
|
||||||
|
client, err := dockerclient.NewDockerClient(node.Addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := fmt.Sprintf("drone_build_%d_job_%d", build, job)
|
||||||
|
return client.StopContainer(id, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream streams the job output from the specified Node.
|
||||||
|
func (e *engine) Stream(build, job int64, node *model.Node) (io.ReadCloser, error) {
|
||||||
|
client, err := dockerclient.NewDockerClient(node.Addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cannot create Docker client for node %s", node.Addr)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := fmt.Sprintf("drone_build_%d_job_%d", build, job)
|
||||||
|
log.Debugf("streaming container logs %s", id)
|
||||||
|
return client.ContainerLogs(id, logOptsTail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the channel to all build events.
|
||||||
|
func (e *engine) Subscribe(c chan *Event) {
|
||||||
|
e.bus.subscribe(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe unsubscribes the channel from all build events.
|
||||||
|
func (e *engine) Unsubscribe(c chan *Event) {
|
||||||
|
e.bus.unsubscribe(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) Allocate(node *model.Node) bool {
|
||||||
|
log.Infof("registered docker daemon %s", node.Addr)
|
||||||
|
return e.pool.allocate(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) Deallocate(n *model.Node) {
|
||||||
|
nodes := e.pool.list()
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.ID == n.ID {
|
||||||
|
log.Infof("un-registered docker daemon %s", node.Addr)
|
||||||
|
e.pool.deallocate(node)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *engine) Schedule(req *Task) {
|
||||||
|
node := <-e.pool.reserve()
|
||||||
|
|
||||||
|
// since we are probably running in a go-routine
|
||||||
|
// make sure we recover from any panics so that
|
||||||
|
// a bug doesn't crash the whole system.
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
log.Printf("panic running build: %v\n%s", err, buf)
|
||||||
|
}
|
||||||
|
e.pool.release(node)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// update the node that was allocated to each job
|
||||||
|
func(id int64) {
|
||||||
|
tx, err := e.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error updating job to persist node. %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Commit()
|
||||||
|
for _, job := range req.Jobs {
|
||||||
|
job.NodeID = id
|
||||||
|
model.UpdateJob(e.db, job)
|
||||||
|
}
|
||||||
|
}(node.ID)
|
||||||
|
|
||||||
|
// run the full build!
|
||||||
|
client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error creating docker client", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the build state if any of the sub-tasks
|
||||||
|
// had a non-success status
|
||||||
|
req.Build.Started = time.Now().UTC().Unix()
|
||||||
|
req.Build.Status = model.StatusRunning
|
||||||
|
e.updater.SetBuild(req)
|
||||||
|
|
||||||
|
// run all bulid jobs
|
||||||
|
for _, job := range req.Jobs {
|
||||||
|
req.Job = job
|
||||||
|
runJob(req, e.updater, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
req.Build.Status = model.StatusSuccess
|
||||||
|
for _, job := range req.Jobs {
|
||||||
|
if job.Status != model.StatusSuccess {
|
||||||
|
req.Build.Status = job.Status
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Build.Finished = time.Now().UTC().Unix()
|
||||||
|
err = e.updater.SetBuild(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error updating build completion status. %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run notifications!!!
|
||||||
|
// for _ = range req.Jobs {
|
||||||
|
// err := runJobNotify(req, client)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Errorf("error executing notification step. %s", err)
|
||||||
|
// }
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDockerClient(addr, cert, key, ca string) (dockerclient.Client, error) {
|
||||||
|
var tlc *tls.Config
|
||||||
|
|
||||||
|
// create the Docket client TLS config
|
||||||
|
if len(cert) != 0 {
|
||||||
|
cert_, err := tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return dockerclient.NewDockerClient(addr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the TLS configuration for secure
|
||||||
|
// docker communications.
|
||||||
|
tlc = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert_},
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the certificate authority if provided.
|
||||||
|
// else don't use a certificate authority and set
|
||||||
|
// skip verify to true
|
||||||
|
if len(ca) != 0 {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
pool.AppendCertsFromPEM([]byte(ca))
|
||||||
|
tlc.RootCAs = pool
|
||||||
|
|
||||||
|
} else {
|
||||||
|
tlc.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the Docker client. In this version of Drone (alpha)
|
||||||
|
// we do not spread builds across clients, but this can and
|
||||||
|
// (probably) will change in the future.
|
||||||
|
return dockerclient.NewDockerClient(addr, tlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runJob(r *Task, updater *updater, client dockerclient.Client) error {
|
||||||
|
|
||||||
|
name := fmt.Sprintf("drone_build_%d_job_%d", r.Build.ID, r.Job.ID)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r.Job.Status == model.StatusRunning {
|
||||||
|
r.Job.Status = model.StatusError
|
||||||
|
r.Job.Finished = time.Now().UTC().Unix()
|
||||||
|
r.Job.ExitCode = 255
|
||||||
|
}
|
||||||
|
if r.Job.Status == model.StatusPending {
|
||||||
|
r.Job.Status = model.StatusError
|
||||||
|
r.Job.Started = time.Now().UTC().Unix()
|
||||||
|
r.Job.Finished = time.Now().UTC().Unix()
|
||||||
|
r.Job.ExitCode = 255
|
||||||
|
}
|
||||||
|
updater.SetJob(r)
|
||||||
|
|
||||||
|
client.KillContainer(name, "9")
|
||||||
|
client.RemoveContainer(name, true, true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// marks the task as running
|
||||||
|
r.Job.Status = model.StatusRunning
|
||||||
|
r.Job.Started = time.Now().UTC().Unix()
|
||||||
|
|
||||||
|
// encode the build payload to write to stdin
|
||||||
|
// when launching the build container
|
||||||
|
in, err := encodeToLegacyFormat(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to marshal work. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE AND START BUILD
|
||||||
|
args := DefaultBuildArgs
|
||||||
|
if r.Build.Event == model.EventPull {
|
||||||
|
args = DefaultPullRequestArgs
|
||||||
|
}
|
||||||
|
args = append(args, "--")
|
||||||
|
args = append(args, string(in))
|
||||||
|
|
||||||
|
conf := &dockerclient.ContainerConfig{
|
||||||
|
Image: DefaultAgent,
|
||||||
|
Entrypoint: DefaultEntrypoint,
|
||||||
|
Cmd: args,
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
||||||
|
},
|
||||||
|
Volumes: map[string]struct{}{
|
||||||
|
"/var/run/docker.sock": struct{}{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// w.client.PullImage(conf.Image, nil)
|
||||||
|
|
||||||
|
_, err = docker.RunDaemon(client, conf, name)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error starting build container. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE STATUS
|
||||||
|
|
||||||
|
err = updater.SetJob(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error updating job status as running. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WAIT FOR OUTPUT
|
||||||
|
info, builderr := docker.Wait(client, name)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case info.State.ExitCode == 128:
|
||||||
|
r.Job.ExitCode = info.State.ExitCode
|
||||||
|
r.Job.Status = model.StatusKilled
|
||||||
|
case info.State.ExitCode == 130:
|
||||||
|
r.Job.ExitCode = info.State.ExitCode
|
||||||
|
r.Job.Status = model.StatusKilled
|
||||||
|
case builderr != nil:
|
||||||
|
r.Job.Status = model.StatusError
|
||||||
|
case info.State.ExitCode != 0:
|
||||||
|
r.Job.ExitCode = info.State.ExitCode
|
||||||
|
r.Job.Status = model.StatusFailure
|
||||||
|
default:
|
||||||
|
r.Job.Status = model.StatusSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the logs to the datastore
|
||||||
|
var buf bytes.Buffer
|
||||||
|
rc, err := client.ContainerLogs(name, docker.LogOpts)
|
||||||
|
if err != nil && builderr != nil {
|
||||||
|
buf.WriteString("Error launching build")
|
||||||
|
buf.WriteString(builderr.Error())
|
||||||
|
} else if err != nil {
|
||||||
|
buf.WriteString("Error launching build")
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
log.Errorf("error opening connection to logs. %s", err)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer rc.Close()
|
||||||
|
stdcopy.StdCopy(&buf, &buf, io.LimitReader(rc, 5000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the task in the datastore
|
||||||
|
r.Job.Finished = time.Now().UTC().Unix()
|
||||||
|
err = updater.SetJob(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error updating job after completion. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updater.SetLogs(r, ioutil.NopCloser(&buf))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error updating logs. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("completed job %d with status %s.", r.Job.ID, r.Job.Status)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runJobNotify(r *Task, client dockerclient.Client) error {
|
||||||
|
|
||||||
|
name := fmt.Sprintf("drone_build_%d_notify", r.Build.ID)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
client.KillContainer(name, "9")
|
||||||
|
client.RemoveContainer(name, true, true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// encode the build payload to write to stdin
|
||||||
|
// when launching the build container
|
||||||
|
in, err := encodeToLegacyFormat(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failure to marshal work. %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := DefaultNotifyArgs
|
||||||
|
args = append(args, "--")
|
||||||
|
args = append(args, string(in))
|
||||||
|
|
||||||
|
conf := &dockerclient.ContainerConfig{
|
||||||
|
Image: DefaultAgent,
|
||||||
|
Entrypoint: DefaultEntrypoint,
|
||||||
|
Cmd: args,
|
||||||
|
HostConfig: dockerclient.HostConfig{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = docker.Run(client, conf, name)
|
||||||
|
return err
|
||||||
|
}
|
86
engine/pool.go
Normal file
86
engine/pool.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pool struct {
|
||||||
|
sync.Mutex
|
||||||
|
nodes map[*model.Node]bool
|
||||||
|
nodec chan *model.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPool() *pool {
|
||||||
|
return &pool{
|
||||||
|
nodes: make(map[*model.Node]bool),
|
||||||
|
nodec: make(chan *model.Node, 999),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate allocates a node to the pool to
|
||||||
|
// be available to accept work.
|
||||||
|
func (p *pool) allocate(n *model.Node) bool {
|
||||||
|
if p.isAllocated(n) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Lock()
|
||||||
|
p.nodes[n] = true
|
||||||
|
p.Unlock()
|
||||||
|
|
||||||
|
p.nodec <- n
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllocated is a helper function that returns
|
||||||
|
// true if the node is currently allocated to
|
||||||
|
// the pool.
|
||||||
|
func (p *pool) isAllocated(n *model.Node) bool {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
_, ok := p.nodes[n]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate removes the node from the pool of
|
||||||
|
// available nodes. If the node is currently
|
||||||
|
// reserved and performing work it will finish,
|
||||||
|
// but no longer be given new work.
|
||||||
|
func (p *pool) deallocate(n *model.Node) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
delete(p.nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of all model.Nodes currently
|
||||||
|
// allocated to the pool.
|
||||||
|
func (p *pool) list() []*model.Node {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
var nodes []*model.Node
|
||||||
|
for n := range p.nodes {
|
||||||
|
nodes = append(nodes, n)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve reserves the next available node to
|
||||||
|
// start doing work. Once work is complete, the
|
||||||
|
// node should be released back to the pool.
|
||||||
|
func (p *pool) reserve() <-chan *model.Node {
|
||||||
|
return p.nodec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the node back to the pool
|
||||||
|
// of available nodes.
|
||||||
|
func (p *pool) release(n *model.Node) bool {
|
||||||
|
if !p.isAllocated(n) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nodec <- n
|
||||||
|
return true
|
||||||
|
}
|
89
engine/pool_test.go
Normal file
89
engine/pool_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPool(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Pool", func() {
|
||||||
|
|
||||||
|
g.It("Should allocate nodes", func() {
|
||||||
|
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
pool.allocate(n)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(1)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(1)
|
||||||
|
g.Assert(pool.nodes[n]).Equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not re-allocate an allocated node", func() {
|
||||||
|
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
g.Assert(pool.allocate(n)).Equal(true)
|
||||||
|
g.Assert(pool.allocate(n)).Equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should reserve a node", func() {
|
||||||
|
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
pool.allocate(n)
|
||||||
|
g.Assert(<-pool.reserve()).Equal(n)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should release a node", func() {
|
||||||
|
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
pool.allocate(n)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(1)
|
||||||
|
g.Assert(<-pool.reserve()).Equal(n)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(0)
|
||||||
|
pool.release(n)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(1)
|
||||||
|
g.Assert(<-pool.reserve()).Equal(n)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not release an unallocated node", func() {
|
||||||
|
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
g.Assert(len(pool.nodes)).Equal(0)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(0)
|
||||||
|
pool.release(n)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(0)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(0)
|
||||||
|
pool.release(nil)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(0)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should list all allocated nodes", func() {
|
||||||
|
n1 := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
n2 := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
pool.allocate(n1)
|
||||||
|
pool.allocate(n2)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(2)
|
||||||
|
g.Assert(len(pool.nodec)).Equal(2)
|
||||||
|
g.Assert(len(pool.list())).Equal(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should remove a node", func() {
|
||||||
|
n1 := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
n2 := &model.Node{Addr: "unix:///var/run/docker.sock"}
|
||||||
|
pool := newPool()
|
||||||
|
pool.allocate(n1)
|
||||||
|
pool.allocate(n2)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(2)
|
||||||
|
pool.deallocate(n1)
|
||||||
|
pool.deallocate(n2)
|
||||||
|
g.Assert(len(pool.nodes)).Equal(0)
|
||||||
|
g.Assert(len(pool.list())).Equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
123
engine/queue.go
123
engine/queue.go
|
@ -1,123 +0,0 @@
|
||||||
package builtin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/queue"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrNotFound = errors.New("work item not found")
|
|
||||||
|
|
||||||
type Queue struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
acks map[*queue.Work]struct{}
|
|
||||||
items map[*queue.Work]struct{}
|
|
||||||
itemc chan *queue.Work
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *Queue {
|
|
||||||
return &Queue{
|
|
||||||
acks: make(map[*queue.Work]struct{}),
|
|
||||||
items: make(map[*queue.Work]struct{}),
|
|
||||||
itemc: make(chan *queue.Work, 999),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish inserts work at the tail of this queue, waiting for
|
|
||||||
// space to become available if the queue is full.
|
|
||||||
func (q *Queue) Publish(work *queue.Work) error {
|
|
||||||
q.Lock()
|
|
||||||
q.items[work] = struct{}{}
|
|
||||||
q.Unlock()
|
|
||||||
q.itemc <- work
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes the specified work item from this queue,
|
|
||||||
// if it is present.
|
|
||||||
func (q *Queue) Remove(work *queue.Work) error {
|
|
||||||
q.Lock()
|
|
||||||
defer q.Unlock()
|
|
||||||
|
|
||||||
_, ok := q.items[work]
|
|
||||||
if !ok {
|
|
||||||
return ErrNotFound
|
|
||||||
}
|
|
||||||
var items []*queue.Work
|
|
||||||
|
|
||||||
// loop through and drain all items
|
|
||||||
// from the queue.
|
|
||||||
drain:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case item := <-q.itemc:
|
|
||||||
items = append(items, item)
|
|
||||||
default:
|
|
||||||
break drain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-add all items to the queue except
|
|
||||||
// the item we're trying to remove
|
|
||||||
for _, item := range items {
|
|
||||||
if item == work {
|
|
||||||
delete(q.items, work)
|
|
||||||
delete(q.acks, work)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
q.itemc <- item
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull retrieves and removes the head of this queue, waiting
|
|
||||||
// if necessary until work becomes available.
|
|
||||||
func (q *Queue) Pull() *queue.Work {
|
|
||||||
work := <-q.itemc
|
|
||||||
q.Lock()
|
|
||||||
delete(q.items, work)
|
|
||||||
q.acks[work] = struct{}{}
|
|
||||||
q.Unlock()
|
|
||||||
return work
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullClose retrieves and removes the head of this queue,
|
|
||||||
// waiting if necessary until work becomes available. The
|
|
||||||
// CloseNotifier should be provided to clone the channel
|
|
||||||
// if the subscribing client terminates its connection.
|
|
||||||
func (q *Queue) PullClose(cn queue.CloseNotifier) *queue.Work {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-cn.CloseNotify():
|
|
||||||
return nil
|
|
||||||
case work := <-q.itemc:
|
|
||||||
q.Lock()
|
|
||||||
delete(q.items, work)
|
|
||||||
q.acks[work] = struct{}{}
|
|
||||||
q.Unlock()
|
|
||||||
return work
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ack acknowledges an item in the queue was processed.
|
|
||||||
func (q *Queue) Ack(work *queue.Work) error {
|
|
||||||
q.Lock()
|
|
||||||
delete(q.acks, work)
|
|
||||||
q.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items returns a slice containing all of the work in this
|
|
||||||
// queue, in proper sequence.
|
|
||||||
func (q *Queue) Items() []*queue.Work {
|
|
||||||
q.Lock()
|
|
||||||
defer q.Unlock()
|
|
||||||
items := []*queue.Work{}
|
|
||||||
for work := range q.items {
|
|
||||||
items = append(items, work)
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package builtin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
|
||||||
"github.com/drone/drone/pkg/queue"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
|
||||||
g := Goblin(t)
|
|
||||||
g.Describe("Queue", func() {
|
|
||||||
|
|
||||||
g.It("Should publish item", func() {
|
|
||||||
w1 := &queue.Work{}
|
|
||||||
w2 := &queue.Work{}
|
|
||||||
q := New()
|
|
||||||
q.Publish(w1)
|
|
||||||
q.Publish(w2)
|
|
||||||
g.Assert(len(q.items)).Equal(2)
|
|
||||||
g.Assert(len(q.itemc)).Equal(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should remove item", func() {
|
|
||||||
w1 := &queue.Work{}
|
|
||||||
w2 := &queue.Work{}
|
|
||||||
w3 := &queue.Work{}
|
|
||||||
q := New()
|
|
||||||
q.Publish(w1)
|
|
||||||
q.Publish(w2)
|
|
||||||
q.Publish(w3)
|
|
||||||
q.Remove(w2)
|
|
||||||
g.Assert(len(q.items)).Equal(2)
|
|
||||||
g.Assert(len(q.itemc)).Equal(2)
|
|
||||||
g.Assert(q.Pull()).Equal(w1)
|
|
||||||
g.Assert(q.Pull()).Equal(w3)
|
|
||||||
g.Assert(q.Remove(w2)).Equal(ErrNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should pull item", func() {
|
|
||||||
w1 := &queue.Work{}
|
|
||||||
w2 := &queue.Work{}
|
|
||||||
q := New()
|
|
||||||
c := new(closeNotifier)
|
|
||||||
q.Publish(w1)
|
|
||||||
q.Publish(w2)
|
|
||||||
g.Assert(q.Pull()).Equal(w1)
|
|
||||||
g.Assert(q.PullClose(c)).Equal(w2)
|
|
||||||
g.Assert(q.acks[w1]).Equal(struct{}{})
|
|
||||||
g.Assert(q.acks[w2]).Equal(struct{}{})
|
|
||||||
g.Assert(len(q.acks)).Equal(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should cancel pulling item", func() {
|
|
||||||
q := New()
|
|
||||||
c := new(closeNotifier)
|
|
||||||
c.closec = make(chan bool, 1)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
go func() {
|
|
||||||
wg.Add(1)
|
|
||||||
g.Assert(q.PullClose(c) == nil).IsTrue()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
c.closec <- true
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should ack item", func() {
|
|
||||||
w := &queue.Work{}
|
|
||||||
c := new(closeNotifier)
|
|
||||||
q := New()
|
|
||||||
q.Publish(w)
|
|
||||||
g.Assert(q.PullClose(c)).Equal(w)
|
|
||||||
g.Assert(len(q.acks)).Equal(1)
|
|
||||||
g.Assert(q.Ack(w)).Equal(nil)
|
|
||||||
g.Assert(len(q.acks)).Equal(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should get all items", func() {
|
|
||||||
q := New()
|
|
||||||
q.Publish(&queue.Work{})
|
|
||||||
q.Publish(&queue.Work{})
|
|
||||||
q.Publish(&queue.Work{})
|
|
||||||
g.Assert(len(q.Items())).Equal(3)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type closeNotifier struct {
|
|
||||||
closec chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *closeNotifier) CloseNotify() <-chan bool {
|
|
||||||
return c.closec
|
|
||||||
}
|
|
318
engine/runner.go
318
engine/runner.go
|
@ -1,318 +0,0 @@
|
||||||
package builtin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/samalba/dockerclient"
|
|
||||||
"github.com/drone/drone/pkg/docker"
|
|
||||||
"github.com/drone/drone/pkg/queue"
|
|
||||||
"github.com/drone/drone/pkg/types"
|
|
||||||
|
|
||||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Defult docker host address
|
|
||||||
DefaultHost = "unix:///var/run/docker.sock"
|
|
||||||
|
|
||||||
// Docker host address from environment variable
|
|
||||||
DockerHost = os.Getenv("DOCKER_HOST")
|
|
||||||
|
|
||||||
// Docker TLS variables
|
|
||||||
DockerHostCa = os.Getenv("DOCKER_CA")
|
|
||||||
DockerHostKey = os.Getenv("DOCKER_KEY")
|
|
||||||
DockerHostCert = os.Getenv("DOCKER_CERT")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// if the environment doesn't specify a DOCKER_HOST
|
|
||||||
// we should use the default Docker socket.
|
|
||||||
if len(DockerHost) == 0 {
|
|
||||||
DockerHost = DefaultHost
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Runner struct {
|
|
||||||
Updater
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDockerClient() (dockerclient.Client, error) {
|
|
||||||
var tlc *tls.Config
|
|
||||||
|
|
||||||
// create the Docket client TLS config
|
|
||||||
if len(DockerHostCert) > 0 && len(DockerHostKey) > 0 && len(DockerHostCa) > 0 {
|
|
||||||
cert, err := tls.LoadX509KeyPair(DockerHostCert, DockerHostKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to load SSL cert and key. %s", err)
|
|
||||||
return dockerclient.NewDockerClient(DockerHost, nil)
|
|
||||||
}
|
|
||||||
caCert, err := ioutil.ReadFile(DockerHostCa)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to load SSL CA cert. %s", err)
|
|
||||||
return dockerclient.NewDockerClient(DockerHost, nil)
|
|
||||||
}
|
|
||||||
caCertPool := x509.NewCertPool()
|
|
||||||
caCertPool.AppendCertsFromPEM(caCert)
|
|
||||||
tlc = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
RootCAs: caCertPool,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the Docker client. In this version of Drone (alpha)
|
|
||||||
// we do not spread builds across clients, but this can and
|
|
||||||
// (probably) will change in the future.
|
|
||||||
return dockerclient.NewDockerClient(DockerHost, tlc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runner) Run(w *queue.Work) error {
|
|
||||||
var workers []*worker
|
|
||||||
var client dockerclient.Client
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
|
|
||||||
// ensures that all containers have been removed
|
|
||||||
// from the host machine.
|
|
||||||
for _, worker := range workers {
|
|
||||||
worker.Remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
// if any part of the commit fails and leaves
|
|
||||||
// behind orphan sub-builds we need to cleanup
|
|
||||||
// after ourselves.
|
|
||||||
if w.Build.Status == types.StateRunning {
|
|
||||||
// if any tasks are running or pending
|
|
||||||
// we should mark them as complete.
|
|
||||||
for _, b := range w.Build.Jobs {
|
|
||||||
if b.Status == types.StateRunning {
|
|
||||||
b.Status = types.StateError
|
|
||||||
b.Finished = time.Now().UTC().Unix()
|
|
||||||
b.ExitCode = 255
|
|
||||||
}
|
|
||||||
if b.Status == types.StatePending {
|
|
||||||
b.Status = types.StateError
|
|
||||||
b.Started = time.Now().UTC().Unix()
|
|
||||||
b.Finished = time.Now().UTC().Unix()
|
|
||||||
b.ExitCode = 255
|
|
||||||
}
|
|
||||||
r.SetJob(w.Repo, w.Build, b)
|
|
||||||
}
|
|
||||||
// must populate build start
|
|
||||||
if w.Build.Started == 0 {
|
|
||||||
w.Build.Started = time.Now().UTC().Unix()
|
|
||||||
}
|
|
||||||
// mark the build as complete (with error)
|
|
||||||
w.Build.Status = types.StateError
|
|
||||||
w.Build.Finished = time.Now().UTC().Unix()
|
|
||||||
r.SetBuild(w.User, w.Repo, w.Build)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// marks the build as running
|
|
||||||
w.Build.Started = time.Now().UTC().Unix()
|
|
||||||
w.Build.Status = types.StateRunning
|
|
||||||
err := r.SetBuild(w.User, w.Repo, w.Build)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to set build. %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the Docker client. In this version of Drone (alpha)
|
|
||||||
// we do not spread builds across clients, but this can and
|
|
||||||
// (probably) will change in the future.
|
|
||||||
client, err = newDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to connect to docker. %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through and execute the build and
|
|
||||||
// clone steps for each build job.
|
|
||||||
for _, job := range w.Build.Jobs {
|
|
||||||
|
|
||||||
// marks the task as running
|
|
||||||
job.Status = types.StateRunning
|
|
||||||
job.Started = time.Now().UTC().Unix()
|
|
||||||
err = r.SetJob(w.Repo, w.Build, job)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to set job. %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
work := &work{
|
|
||||||
System: w.System,
|
|
||||||
Workspace: &types.Workspace{Netrc: w.Netrc, Keys: w.Keys},
|
|
||||||
Repo: w.Repo,
|
|
||||||
Build: w.Build,
|
|
||||||
Job: job,
|
|
||||||
Secret: string(w.Secret),
|
|
||||||
Config: string(w.Config),
|
|
||||||
}
|
|
||||||
in, err := json.Marshal(work)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to marshalise work. %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
worker := newWorker(client)
|
|
||||||
workers = append(workers, worker)
|
|
||||||
cname := cname(job)
|
|
||||||
pullrequest := (w.Build.PullRequest != nil && w.Build.PullRequest.Number != 0)
|
|
||||||
state, builderr := worker.Build(cname, in, pullrequest)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case state == 128:
|
|
||||||
job.ExitCode = state
|
|
||||||
job.Status = types.StateKilled
|
|
||||||
case state == 130:
|
|
||||||
job.ExitCode = state
|
|
||||||
job.Status = types.StateKilled
|
|
||||||
case builderr != nil:
|
|
||||||
job.Status = types.StateError
|
|
||||||
case state != 0:
|
|
||||||
job.ExitCode = state
|
|
||||||
job.Status = types.StateFailure
|
|
||||||
default:
|
|
||||||
job.Status = types.StateSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the logs to the datastore
|
|
||||||
var buf bytes.Buffer
|
|
||||||
rc, err := worker.Logs()
|
|
||||||
if err != nil && builderr != nil {
|
|
||||||
buf.WriteString("001 Error launching build")
|
|
||||||
buf.WriteString(builderr.Error())
|
|
||||||
} else if err != nil {
|
|
||||||
buf.WriteString("002 Error launching build")
|
|
||||||
buf.WriteString(err.Error())
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
defer rc.Close()
|
|
||||||
docker.StdCopy(&buf, &buf, rc)
|
|
||||||
}
|
|
||||||
err = r.SetLogs(w.Repo, w.Build, job, ioutil.NopCloser(&buf))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the task in the datastore
|
|
||||||
job.Finished = time.Now().UTC().Unix()
|
|
||||||
err = r.SetJob(w.Repo, w.Build, job)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the build state if any of the sub-tasks
|
|
||||||
// had a non-success status
|
|
||||||
w.Build.Status = types.StateSuccess
|
|
||||||
for _, job := range w.Build.Jobs {
|
|
||||||
if job.Status != types.StateSuccess {
|
|
||||||
w.Build.Status = job.Status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = r.SetBuild(w.User, w.Repo, w.Build)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through and execute the notifications and
|
|
||||||
// the destroy all containers afterward.
|
|
||||||
for i, job := range w.Build.Jobs {
|
|
||||||
work := &work{
|
|
||||||
System: w.System,
|
|
||||||
Workspace: &types.Workspace{Netrc: w.Netrc, Keys: w.Keys},
|
|
||||||
Repo: w.Repo,
|
|
||||||
Build: w.Build,
|
|
||||||
Job: job,
|
|
||||||
Secret: string(w.Secret),
|
|
||||||
Config: string(w.Config),
|
|
||||||
}
|
|
||||||
in, err := json.Marshal(work)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
workers[i].Notify(in)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runner) Cancel(job *types.Job) error {
|
|
||||||
client, err := newDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.StopContainer(cname(job), 30)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runner) Logs(job *types.Job) (io.ReadCloser, error) {
|
|
||||||
client, err := newDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// make sure this container actually exists
|
|
||||||
info, err := client.InspectContainer(cname(job))
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
// add a small exponential backoff since there
|
|
||||||
// is a small window when the container hasn't
|
|
||||||
// been created yet, but the build is about to start
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
info, err = client.InspectContainer(cname(job))
|
|
||||||
if err != nil && i == 5 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify the container is running. if not we'll
|
|
||||||
// do an exponential backoff and attempt to wait
|
|
||||||
if !info.State.Running {
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
info, err = client.InspectContainer(info.Id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if info.State.Running {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i == 5 {
|
|
||||||
return nil, dockerclient.ErrNotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.ContainerLogs(info.Id, logOptsTail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cname(job *types.Job) string {
|
|
||||||
return fmt.Sprintf("drone-%d", job.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Runner) Poll(q queue.Queue) {
|
|
||||||
for {
|
|
||||||
w := q.Pull()
|
|
||||||
q.Ack(w)
|
|
||||||
err := r.Run(w)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
24
engine/types.go
Normal file
24
engine/types.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Name string
|
||||||
|
Msg []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
User *model.User `json:"-"`
|
||||||
|
Repo *model.Repo `json:"repo"`
|
||||||
|
Build *model.Build `json:"build"`
|
||||||
|
BuildPrev *model.Build `json:"build_last"`
|
||||||
|
Jobs []*model.Job `json:"jobs"`
|
||||||
|
Job *model.Job `json:"job"`
|
||||||
|
Keys *model.Key `json:"keys"`
|
||||||
|
Netrc *model.Netrc `json:"netrc"`
|
||||||
|
Config string `json:"config"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
System *model.System `json:"system"`
|
||||||
|
}
|
|
@ -1,94 +1,67 @@
|
||||||
package builtin
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/bus"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/pkg/store"
|
|
||||||
"github.com/drone/drone/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Updater interface {
|
|
||||||
SetBuild(*types.User, *types.Repo, *types.Build) error
|
|
||||||
SetJob(*types.Repo, *types.Build, *types.Job) error
|
|
||||||
SetLogs(*types.Repo, *types.Build, *types.Job, io.ReadCloser) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUpdater returns an implementation of the Updater interface
|
|
||||||
// that directly modifies the database and sends messages to the bus.
|
|
||||||
func NewUpdater(bus bus.Bus, store store.Store, rem remote.Remote) Updater {
|
|
||||||
return &updater{bus, store, rem}
|
|
||||||
}
|
|
||||||
|
|
||||||
type updater struct {
|
type updater struct {
|
||||||
bus bus.Bus
|
bus *eventbus
|
||||||
store store.Store
|
db *sql.DB
|
||||||
remote remote.Remote
|
remote remote.Remote
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *updater) SetBuild(user *types.User, r *types.Repo, c *types.Build) error {
|
func (u *updater) SetBuild(r *Task) error {
|
||||||
err := u.store.SetBuild(c)
|
err := model.UpdateBuild(u.db, r.Build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.remote.Status(user, r, c)
|
err = u.remote.Status(r.User, r.Repo, r.Build, fmt.Sprintf("%s/%s/%d", r.System.Link, r.Repo.FullName, r.Build.Number))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log err
|
// log err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need this because builds coming from
|
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
|
||||||
// a remote agent won't have the embedded
|
|
||||||
// build list. we should probably just rethink
|
|
||||||
// the messaging instead of this hack.
|
|
||||||
if c.Jobs == nil || len(c.Jobs) == 0 {
|
|
||||||
c.Jobs, _ = u.store.JobList(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.bus.Send(&bus.Event{
|
u.bus.send(&Event{
|
||||||
Name: r.FullName,
|
Name: r.Repo.FullName,
|
||||||
Kind: bus.EventRepo,
|
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *updater) SetJob(r *types.Repo, c *types.Build, j *types.Job) error {
|
func (u *updater) SetJob(r *Task) error {
|
||||||
err := u.store.SetJob(j)
|
err := model.UpdateJob(u.db, r.Job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need this because builds coming from
|
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
|
||||||
// a remote agent won't have the embedded
|
|
||||||
// build list. we should probably just rethink
|
|
||||||
// the messaging instead of this hack.
|
|
||||||
if c.Jobs == nil || len(c.Jobs) == 0 {
|
|
||||||
c.Jobs, _ = u.store.JobList(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.bus.Send(&bus.Event{
|
u.bus.send(&Event{
|
||||||
Name: r.FullName,
|
Name: r.Repo.FullName,
|
||||||
Kind: bus.EventRepo,
|
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *updater) SetLogs(r *types.Repo, c *types.Build, j *types.Job, rc io.ReadCloser) error {
|
func (u *updater) SetLogs(r *Task, rc io.ReadCloser) error {
|
||||||
path := fmt.Sprintf("/logs/%s/%v/%v", r.FullName, c.Number, j.Number)
|
return model.SetLog(u.db, r.Job, rc)
|
||||||
return u.store.SetBlobReader(path, rc)
|
}
|
||||||
|
|
||||||
|
type payload struct {
|
||||||
|
*model.Build
|
||||||
|
Jobs []*model.Job `json:"jobs"`
|
||||||
}
|
}
|
||||||
|
|
35
engine/util.go
Normal file
35
engine/util.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeToLegacyFormat(t *Task) ([]byte, error) {
|
||||||
|
t.System.Plugins = append(t.System.Plugins, "plugins/*")
|
||||||
|
|
||||||
|
s := map[string]interface{}{}
|
||||||
|
s["repo"] = t.Repo
|
||||||
|
s["config"] = t.Config
|
||||||
|
s["secret"] = t.Secret
|
||||||
|
s["job"] = t.Job
|
||||||
|
s["system"] = t.System
|
||||||
|
s["workspace"] = map[string]interface{}{
|
||||||
|
"netrc": t.Netrc,
|
||||||
|
"keys": t.Keys,
|
||||||
|
}
|
||||||
|
s["build"] = map[string]interface{}{
|
||||||
|
"number": t.Build.Number,
|
||||||
|
"status": t.Build.Status,
|
||||||
|
"head_commit": map[string]interface{}{
|
||||||
|
"sha": t.Build.Commit,
|
||||||
|
"ref": t.Build.Ref,
|
||||||
|
"branch": t.Build.Branch,
|
||||||
|
"message": t.Build.Message,
|
||||||
|
"author": map[string]interface{}{
|
||||||
|
"login": t.Build.Author,
|
||||||
|
"email": t.Build.Email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return json.Marshal(&s)
|
||||||
|
}
|
140
engine/worker.go
140
engine/worker.go
|
@ -1,30 +1,10 @@
|
||||||
package builtin
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/samalba/dockerclient"
|
"github.com/drone/drone/shared/docker"
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/samalba/dockerclient"
|
||||||
)
|
|
||||||
|
|
||||||
var ErrLogging = errors.New("Logs not available")
|
|
||||||
|
|
||||||
var (
|
|
||||||
// options to fetch the stdout and stderr logs
|
|
||||||
logOpts = &dockerclient.LogOptions{
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// options to fetch the stdout and stderr logs
|
|
||||||
// by tailing the output.
|
|
||||||
logOptsTail = &dockerclient.LogOptions{
|
|
||||||
Follow: true,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -35,26 +15,15 @@ var (
|
||||||
DefaultEntrypoint = []string{"/bin/drone-exec"}
|
DefaultEntrypoint = []string{"/bin/drone-exec"}
|
||||||
|
|
||||||
// default argument to invoke build steps
|
// default argument to invoke build steps
|
||||||
DefaultBuildArgs = []string{"--pull", "--cache", "--clone", "--build", "--deploy"}
|
DefaultBuildArgs = []string{"--cache", "--debug", "--clone", "--build", "--deploy"}
|
||||||
|
|
||||||
// default argument to invoke build steps
|
// default argument to invoke build steps
|
||||||
DefaultPullRequestArgs = []string{"--pull", "--cache", "--clone", "--build"}
|
DefaultPullRequestArgs = []string{"--cache", "--clone", "--build"}
|
||||||
|
|
||||||
// default arguments to invoke notify steps
|
// default arguments to invoke notify steps
|
||||||
DefaultNotifyArgs = []string{"--pull", "--notify"}
|
DefaultNotifyArgs = []string{"--notify"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type work struct {
|
|
||||||
Repo *types.Repo `json:"repo"`
|
|
||||||
Build *types.Build `json:"build"`
|
|
||||||
BuildLast *types.Build `json:"build_last"`
|
|
||||||
Job *types.Job `json:"job"`
|
|
||||||
System *types.System `json:"system"`
|
|
||||||
Workspace *types.Workspace `json:"workspace"`
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
Config string `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type worker struct {
|
type worker struct {
|
||||||
client dockerclient.Client
|
client dockerclient.Client
|
||||||
build *dockerclient.ContainerInfo
|
build *dockerclient.ContainerInfo
|
||||||
|
@ -84,7 +53,6 @@ func (w *worker) Build(name string, stdin []byte, pr bool) (_ int, err error) {
|
||||||
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
||||||
},
|
},
|
||||||
Volumes: map[string]struct{}{
|
Volumes: map[string]struct{}{
|
||||||
"/drone": struct{}{},
|
|
||||||
"/var/run/docker.sock": struct{}{},
|
"/var/run/docker.sock": struct{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -92,9 +60,9 @@ func (w *worker) Build(name string, stdin []byte, pr bool) (_ int, err error) {
|
||||||
// TEMPORARY: always try to pull the new image for now
|
// TEMPORARY: always try to pull the new image for now
|
||||||
// since we'll be frequently updating the build image
|
// since we'll be frequently updating the build image
|
||||||
// for the next few weeks
|
// for the next few weeks
|
||||||
w.client.PullImage(conf.Image, nil)
|
// w.client.PullImage(conf.Image, nil)
|
||||||
|
|
||||||
w.build, err = run(w.client, conf, name)
|
w.build, err = docker.Run(w.client, conf, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
|
@ -103,16 +71,7 @@ func (w *worker) Build(name string, stdin []byte, pr bool) (_ int, err error) {
|
||||||
|
|
||||||
// Notify executes the notification steps.
|
// Notify executes the notification steps.
|
||||||
func (w *worker) Notify(stdin []byte) error {
|
func (w *worker) Notify(stdin []byte) error {
|
||||||
// use the affinity parameter in case we are
|
|
||||||
// using Docker swarm as a backend.
|
|
||||||
environment := []string{"affinity:container==" + w.build.Id}
|
|
||||||
|
|
||||||
// the build container is acting as an ambassador container
|
|
||||||
// with a shared filesystem .
|
|
||||||
volume := []string{w.build.Id}
|
|
||||||
|
|
||||||
// the command line arguments passed into the
|
|
||||||
// build agent container.
|
|
||||||
args := DefaultNotifyArgs
|
args := DefaultNotifyArgs
|
||||||
args = append(args, "--")
|
args = append(args, "--")
|
||||||
args = append(args, string(stdin))
|
args = append(args, string(stdin))
|
||||||
|
@ -121,14 +80,11 @@ func (w *worker) Notify(stdin []byte) error {
|
||||||
Image: DefaultAgent,
|
Image: DefaultAgent,
|
||||||
Entrypoint: DefaultEntrypoint,
|
Entrypoint: DefaultEntrypoint,
|
||||||
Cmd: args,
|
Cmd: args,
|
||||||
Env: environment,
|
HostConfig: dockerclient.HostConfig{},
|
||||||
HostConfig: dockerclient.HostConfig{
|
|
||||||
VolumesFrom: volume,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
w.notify, err = run(w.client, conf, "")
|
w.notify, err = docker.Run(w.client, conf, "")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +92,7 @@ func (w *worker) Notify(stdin []byte) error {
|
||||||
// from the build and deploy agents.
|
// from the build and deploy agents.
|
||||||
func (w *worker) Logs() (io.ReadCloser, error) {
|
func (w *worker) Logs() (io.ReadCloser, error) {
|
||||||
if w.build == nil {
|
if w.build == nil {
|
||||||
return nil, ErrLogging
|
return nil, errLogging
|
||||||
}
|
}
|
||||||
return w.client.ContainerLogs(w.build.Id, logOpts)
|
return w.client.ContainerLogs(w.build.Id, logOpts)
|
||||||
}
|
}
|
||||||
|
@ -153,77 +109,3 @@ func (w *worker) Remove() {
|
||||||
w.client.RemoveContainer(w.build.Id, true, true)
|
w.client.RemoveContainer(w.build.Id, true, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run is a helper function that creates and starts a container,
|
|
||||||
// blocking until either complete.
|
|
||||||
func run(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
|
|
||||||
|
|
||||||
// attempts to create the contianer
|
|
||||||
id, err := client.CreateContainer(conf, name)
|
|
||||||
if err != nil {
|
|
||||||
// and pull the image and re-create if that fails
|
|
||||||
client.PullImage(conf.Image, nil)
|
|
||||||
id, err = client.CreateContainer(conf, name)
|
|
||||||
// make sure the container is removed in
|
|
||||||
// the event of a creation error.
|
|
||||||
if err != nil && len(id) != 0 {
|
|
||||||
client.RemoveContainer(id, true, true)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensures the container is always stopped
|
|
||||||
// and ready to be removed.
|
|
||||||
defer func() {
|
|
||||||
client.StopContainer(id, 5)
|
|
||||||
client.KillContainer(id, "9")
|
|
||||||
}()
|
|
||||||
|
|
||||||
// fetches the container information.
|
|
||||||
info, err := client.InspectContainer(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel listening for errors while the
|
|
||||||
// container is running async.
|
|
||||||
errc := make(chan error, 1)
|
|
||||||
infoc := make(chan *dockerclient.ContainerInfo, 1)
|
|
||||||
go func() {
|
|
||||||
|
|
||||||
// starts the container
|
|
||||||
err := client.StartContainer(id, &conf.HostConfig)
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// blocks and waits for the container to finish
|
|
||||||
// by streaming the logs (to /dev/null). Ideally
|
|
||||||
// we could use the `wait` function instead
|
|
||||||
rc, err := client.ContainerLogs(id, logOptsTail)
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
io.Copy(ioutil.Discard, rc)
|
|
||||||
rc.Close()
|
|
||||||
|
|
||||||
// fetches the container information
|
|
||||||
info, err := client.InspectContainer(id)
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
infoc <- info
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case info := <-infoc:
|
|
||||||
return info, nil
|
|
||||||
case err := <-errc:
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
426
make.go
426
make.go
|
@ -1,426 +0,0 @@
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
// This program builds Drone.
|
|
||||||
// $ go run make.go deps bindata build test
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version = "0.4"
|
|
||||||
sha = rev()
|
|
||||||
)
|
|
||||||
|
|
||||||
// list of all posible steps that can be executed
|
|
||||||
// as part of the build process.
|
|
||||||
var steps = map[string]step{
|
|
||||||
"deps": executeDeps,
|
|
||||||
"json": executeJson,
|
|
||||||
"embed": executeEmbed,
|
|
||||||
"scripts": executeScripts,
|
|
||||||
"styles": executeStyles,
|
|
||||||
"vet": executeVet,
|
|
||||||
"fmt": executeFmt,
|
|
||||||
"test": executeTest,
|
|
||||||
"build": executeBuild,
|
|
||||||
"install": executeInstall,
|
|
||||||
"image": executeImage,
|
|
||||||
"bindata": executeBindata,
|
|
||||||
"clean": executeClean,
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
for _, arg := range os.Args[1:] {
|
|
||||||
step, ok := steps[arg]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
fmt.Println("Error: Invalid step", arg)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := step()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error: Failed step", arg)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type step func() error
|
|
||||||
|
|
||||||
func executeDeps() error {
|
|
||||||
deps := []string{
|
|
||||||
"github.com/jteeuwen/go-bindata/...",
|
|
||||||
"golang.org/x/tools/cmd/cover",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dep := range deps {
|
|
||||||
err := run(
|
|
||||||
"go",
|
|
||||||
"get",
|
|
||||||
"-u",
|
|
||||||
dep)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// json step generates optimized json marshal and
|
|
||||||
// unmarshal functions to override defaults.
|
|
||||||
func executeJson() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// embed step embeds static files in .go files.
|
|
||||||
func executeEmbed() error {
|
|
||||||
// embed drone.{revision}.css
|
|
||||||
// embed drone.{revision}.js
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scripts step concatinates all javascript files.
|
|
||||||
func executeScripts() error {
|
|
||||||
files := []string{
|
|
||||||
"cmd/drone-server/static/scripts/term.js",
|
|
||||||
"cmd/drone-server/static/scripts/drone.js",
|
|
||||||
"cmd/drone-server/static/scripts/controllers/repos.js",
|
|
||||||
"cmd/drone-server/static/scripts/controllers/builds.js",
|
|
||||||
"cmd/drone-server/static/scripts/controllers/users.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/repos.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/builds.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/users.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/logs.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/tokens.js",
|
|
||||||
"cmd/drone-server/static/scripts/services/feed.js",
|
|
||||||
"cmd/drone-server/static/scripts/filters/filter.js",
|
|
||||||
"cmd/drone-server/static/scripts/filters/gravatar.js",
|
|
||||||
"cmd/drone-server/static/scripts/filters/time.js",
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(
|
|
||||||
"cmd/drone-server/static/scripts/drone.min.js",
|
|
||||||
os.O_CREATE|os.O_RDWR|os.O_TRUNC,
|
|
||||||
0660)
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to open output file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range files {
|
|
||||||
content, err := ioutil.ReadFile(input)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Write(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// styles step concatinates the stylesheet files.
|
|
||||||
func executeStyles() error {
|
|
||||||
files := []string{
|
|
||||||
"cmd/drone-server/static/styles/reset.css",
|
|
||||||
"cmd/drone-server/static/styles/fonts.css",
|
|
||||||
"cmd/drone-server/static/styles/alert.css",
|
|
||||||
"cmd/drone-server/static/styles/blankslate.css",
|
|
||||||
"cmd/drone-server/static/styles/list.css",
|
|
||||||
"cmd/drone-server/static/styles/label.css",
|
|
||||||
"cmd/drone-server/static/styles/range.css",
|
|
||||||
"cmd/drone-server/static/styles/switch.css",
|
|
||||||
"cmd/drone-server/static/styles/main.css",
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(
|
|
||||||
"cmd/drone-server/static/styles/drone.min.css",
|
|
||||||
os.O_CREATE|os.O_RDWR|os.O_TRUNC,
|
|
||||||
0660)
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to open output file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range files {
|
|
||||||
content, err := ioutil.ReadFile(input)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Write(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// vet step executes the `go vet` command
|
|
||||||
func executeVet() error {
|
|
||||||
return run(
|
|
||||||
"go",
|
|
||||||
"vet",
|
|
||||||
"github.com/drone/drone/pkg/...",
|
|
||||||
"github.com/drone/drone/cmd/...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt step executes the `go fmt` command
|
|
||||||
func executeFmt() error {
|
|
||||||
return run(
|
|
||||||
"go",
|
|
||||||
"fmt",
|
|
||||||
"github.com/drone/drone/pkg/...",
|
|
||||||
"github.com/drone/drone/cmd/...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// test step executes unit tests and coverage.
|
|
||||||
func executeTest() error {
|
|
||||||
ldf := fmt.Sprintf(
|
|
||||||
"-X main.revision=%s -X main.version=%s",
|
|
||||||
sha,
|
|
||||||
version)
|
|
||||||
|
|
||||||
return run(
|
|
||||||
"go",
|
|
||||||
"test",
|
|
||||||
"-cover",
|
|
||||||
"-ldflags",
|
|
||||||
ldf,
|
|
||||||
"github.com/drone/drone/pkg/...",
|
|
||||||
"github.com/drone/drone/cmd/...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// install step installs the application binaries.
|
|
||||||
func executeInstall() error {
|
|
||||||
var bins = []struct {
|
|
||||||
input string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"github.com/drone/drone/cmd/drone-server",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bin := range bins {
|
|
||||||
ldf := fmt.Sprintf(
|
|
||||||
"-X main.revision=%s -X main.version=%s",
|
|
||||||
sha,
|
|
||||||
version)
|
|
||||||
|
|
||||||
err := run(
|
|
||||||
"go",
|
|
||||||
"install",
|
|
||||||
"-ldflags",
|
|
||||||
ldf,
|
|
||||||
bin.input)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// build step creates the application binaries.
|
|
||||||
func executeBuild() error {
|
|
||||||
var bins = []struct {
|
|
||||||
input string
|
|
||||||
output string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"github.com/drone/drone/cmd/drone-server",
|
|
||||||
"bin/drone",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bin := range bins {
|
|
||||||
ldf := fmt.Sprintf(
|
|
||||||
"-X main.revision=%s -X main.version=%s",
|
|
||||||
sha,
|
|
||||||
version)
|
|
||||||
|
|
||||||
err := run(
|
|
||||||
"go",
|
|
||||||
"build",
|
|
||||||
"-o",
|
|
||||||
bin.output,
|
|
||||||
"-ldflags",
|
|
||||||
ldf,
|
|
||||||
bin.input)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// image step builds docker images.
|
|
||||||
func executeImage() error {
|
|
||||||
var images = []struct {
|
|
||||||
dir string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"bin/drone-server",
|
|
||||||
"drone/drone",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, image := range images {
|
|
||||||
path := filepath.Join(
|
|
||||||
image.dir,
|
|
||||||
"Dockerfile")
|
|
||||||
|
|
||||||
name := fmt.Sprintf("%s:%s",
|
|
||||||
image.name,
|
|
||||||
version)
|
|
||||||
|
|
||||||
err := run(
|
|
||||||
"docker",
|
|
||||||
"build",
|
|
||||||
"-rm",
|
|
||||||
path,
|
|
||||||
name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// bindata step generates go-bindata package.
|
|
||||||
func executeBindata() error {
|
|
||||||
var paths = []struct {
|
|
||||||
input string
|
|
||||||
output string
|
|
||||||
pkg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"cmd/drone-server/static/...",
|
|
||||||
"cmd/drone-server/drone_bindata.go",
|
|
||||||
"main",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
binErr := run(
|
|
||||||
"go-bindata",
|
|
||||||
fmt.Sprintf("-o=%s", path.output),
|
|
||||||
fmt.Sprintf("-pkg=%s", path.pkg),
|
|
||||||
path.input)
|
|
||||||
|
|
||||||
if binErr != nil {
|
|
||||||
return binErr
|
|
||||||
}
|
|
||||||
|
|
||||||
fmtErr := run(
|
|
||||||
"go",
|
|
||||||
"fmt",
|
|
||||||
path.output)
|
|
||||||
|
|
||||||
if fmtErr != nil {
|
|
||||||
return fmtErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean step removes all generated files.
|
|
||||||
func executeClean() error {
|
|
||||||
err := filepath.Walk(".", func(path string, f os.FileInfo, err error) error {
|
|
||||||
suffixes := []string{
|
|
||||||
".out",
|
|
||||||
"_bindata.go",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, suffix := range suffixes {
|
|
||||||
if strings.HasSuffix(path, suffix) {
|
|
||||||
if err := os.Remove(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []string{
|
|
||||||
"bin/drone",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Remove(file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// run is a helper function that executes commands
|
|
||||||
// and assigns stdout and stderr targets
|
|
||||||
func run(command string, args ...string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
trace(cmd.Args)
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to parse the git revision
|
|
||||||
func rev() string {
|
|
||||||
cmd := exec.Command(
|
|
||||||
"git",
|
|
||||||
"rev-parse",
|
|
||||||
"--short",
|
|
||||||
"HEAD")
|
|
||||||
|
|
||||||
raw, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "HEAD"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Trim(string(raw), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// trace is a helper function that writes a command
|
|
||||||
// to stdout similar to bash +x
|
|
||||||
func trace(args []string) {
|
|
||||||
print("+ ")
|
|
||||||
println(strings.Join(args, " "))
|
|
||||||
}
|
|
172
model/build.go
172
model/build.go
|
@ -1,48 +1,146 @@
|
||||||
package types
|
package model
|
||||||
|
|
||||||
const (
|
import (
|
||||||
StatePending = "pending"
|
"time"
|
||||||
StateRunning = "running"
|
|
||||||
StateSuccess = "success"
|
"github.com/drone/drone/shared/database"
|
||||||
StateFailure = "failure"
|
"github.com/russross/meddler"
|
||||||
StateKilled = "killed"
|
|
||||||
StateError = "error"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Build struct {
|
type Build struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id" meddler:"build_id,pk"`
|
||||||
RepoID int64 `json:"-" sql:"unique:ux_build_number,index:ix_build_repo_id"`
|
RepoID int64 `json:"-" meddler:"build_repo_id"`
|
||||||
Number int `json:"number" sql:"unique:ux_build_number"`
|
Number int `json:"number" meddler:"build_number"`
|
||||||
Event string `json:"event"`
|
Event string `json:"event" meddler:"build_event"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status" meddler:"build_status"`
|
||||||
Started int64 `json:"started_at"`
|
Created int64 `json:"created_at" meddler:"build_created"`
|
||||||
Finished int64 `json:"finished_at"`
|
Started int64 `json:"started_at" meddler:"build_started"`
|
||||||
|
Finished int64 `json:"finished_at" meddler:"build_finished"`
|
||||||
Commit *Commit `json:"head_commit"`
|
Commit string `json:"commit" meddler:"build_commit"`
|
||||||
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
Branch string `json:"branch" meddler:"build_branch"`
|
||||||
|
Ref string `json:"ref" meddler:"build_ref"`
|
||||||
Jobs []*Job `json:"jobs,omitempty" sql:"-"`
|
Refspec string `json:"refspec" meddler:"build_refspec"`
|
||||||
|
Remote string `json:"remote" meddler:"build_remote"`
|
||||||
|
Title string `json:"title" meddler:"build_title"`
|
||||||
|
Message string `json:"message" meddler:"build_message"`
|
||||||
|
Timestamp string `json:"timestamp" meddler:"build_timestamp"`
|
||||||
|
Author string `json:"author" meddler:"build_author"`
|
||||||
|
Avatar string `json:"author_avatar" meddler:"build_avatar"`
|
||||||
|
Email string `json:"author_email" meddler:"build_email"`
|
||||||
|
Link string `json:"link_url" meddler:"build_link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PullRequest struct {
|
type BuildGroup struct {
|
||||||
Number int `json:"number,omitempty"`
|
Date string
|
||||||
Title string `json:"title,omitempty"`
|
Builds []*Build
|
||||||
Link string `json:"link_url,omitempty"`
|
|
||||||
Base *Commit `json:"base_commit,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commit struct {
|
func GetBuild(db meddler.DB, id int64) (*Build, error) {
|
||||||
Sha string `json:"sha"`
|
var build = new(Build)
|
||||||
Ref string `json:"ref"`
|
var err = meddler.Load(db, buildTable, build, id)
|
||||||
Link string `json:"link_url,omitempty"`
|
return build, err
|
||||||
Branch string `json:"branch" sql:"index:ix_commit_branch"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Timestamp string `json:"timestamp,omitempty"`
|
|
||||||
Remote string `json:"remote,omitempty"`
|
|
||||||
Author *Author `json:"author,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author struct {
|
func GetBuildNumber(db meddler.DB, repo *Repo, number int) (*Build, error) {
|
||||||
Login string `json:"login,omitempty"`
|
var build = new(Build)
|
||||||
Email string `json:"email,omitempty"`
|
var err = meddler.QueryRow(db, build, database.Rebind(buildNumberQuery), repo.ID, number)
|
||||||
|
return build, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBuildRef(db meddler.DB, repo *Repo, ref string) (*Build, error) {
|
||||||
|
var build = new(Build)
|
||||||
|
var err = meddler.QueryRow(db, build, database.Rebind(buildRefQuery), repo.ID, ref)
|
||||||
|
return build, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuildCommit(db meddler.DB, repo *Repo, sha, branch string) (*Build, error) {
|
||||||
|
var build = new(Build)
|
||||||
|
var err = meddler.QueryRow(db, build, database.Rebind(buildCommitQuery), repo.ID, sha, branch)
|
||||||
|
return build, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuildLast(db meddler.DB, repo *Repo, branch string) (*Build, error) {
|
||||||
|
var build = new(Build)
|
||||||
|
var err = meddler.QueryRow(db, build, database.Rebind(buildLastQuery), repo.ID, branch)
|
||||||
|
return build, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBuildList(db meddler.DB, repo *Repo) ([]*Build, error) {
|
||||||
|
var builds = []*Build{}
|
||||||
|
var err = meddler.QueryAll(db, &builds, database.Rebind(buildListQuery), repo.ID)
|
||||||
|
return builds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBuild(db meddler.DB, build *Build, jobs ...*Job) error {
|
||||||
|
var number int
|
||||||
|
db.QueryRow(buildNumberLast, build.RepoID).Scan(&number)
|
||||||
|
build.Number = number + 1
|
||||||
|
build.Created = time.Now().UTC().Unix()
|
||||||
|
err := meddler.Insert(db, buildTable, build)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, job := range jobs {
|
||||||
|
job.BuildID = build.ID
|
||||||
|
job.Number = i + 1
|
||||||
|
err = InsertJob(db, job)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateBuild(db meddler.DB, build *Build) error {
|
||||||
|
return meddler.Update(db, buildTable, build)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTable = "builds"
|
||||||
|
|
||||||
|
const buildListQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
ORDER BY build_number DESC
|
||||||
|
LIMIT 50
|
||||||
|
`
|
||||||
|
|
||||||
|
const buildNumberQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
AND build_number = ?
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
const buildLastQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
AND build_branch = ?
|
||||||
|
ORDER BY build_number DESC
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const buildCommitQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
AND build_commit = ?
|
||||||
|
AND build_branch = ?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const buildRefQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
AND build_ref = ?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const buildNumberLast = `
|
||||||
|
SELECT MAX(build_number)
|
||||||
|
FROM builds
|
||||||
|
WHERE build_repo_id = ?
|
||||||
|
`
|
||||||
|
|
207
model/build_test.go
Normal file
207
model/build_test.go
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuild(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Builds", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM builds")
|
||||||
|
db.Exec("DELETE FROM jobs")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Post a Build", func() {
|
||||||
|
build := Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||||
|
}
|
||||||
|
err := CreateBuild(db, &build, []*Job{}...)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(build.ID != 0).IsTrue()
|
||||||
|
g.Assert(build.Number).Equal(1)
|
||||||
|
g.Assert(build.Commit).Equal("85f8c029b902ed9400bc600bac301a0aadb144ac")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Put a Build", func() {
|
||||||
|
build := Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Number: 5,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||||
|
}
|
||||||
|
CreateBuild(db, &build, []*Job{}...)
|
||||||
|
build.Status = StatusRunning
|
||||||
|
err1 := UpdateBuild(db, &build)
|
||||||
|
getbuild, err2 := GetBuild(db, build.ID)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(build.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build.Status).Equal(getbuild.Status)
|
||||||
|
g.Assert(build.Number).Equal(getbuild.Number)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build", func() {
|
||||||
|
build := Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
CreateBuild(db, &build, []*Job{}...)
|
||||||
|
getbuild, err := GetBuild(db, build.ID)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(build.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build.Status).Equal(getbuild.Status)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build by Number", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
}
|
||||||
|
err1 := CreateBuild(db, build1, []*Job{}...)
|
||||||
|
err2 := CreateBuild(db, build2, []*Job{}...)
|
||||||
|
getbuild, err3 := GetBuildNumber(db, &Repo{ID: 1}, build2.Number)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(build2.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build2.Number).Equal(getbuild.Number)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build by Ref", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Ref: "refs/pull/5",
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Ref: "refs/pull/6",
|
||||||
|
}
|
||||||
|
err1 := CreateBuild(db, build1, []*Job{}...)
|
||||||
|
err2 := CreateBuild(db, build2, []*Job{}...)
|
||||||
|
getbuild, err3 := GetBuildRef(db, &Repo{ID: 1}, "refs/pull/6")
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(build2.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build2.Number).Equal(getbuild.Number)
|
||||||
|
g.Assert(build2.Ref).Equal(getbuild.Ref)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build by Ref", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Ref: "refs/pull/5",
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Ref: "refs/pull/6",
|
||||||
|
}
|
||||||
|
err1 := CreateBuild(db, build1, []*Job{}...)
|
||||||
|
err2 := CreateBuild(db, build2, []*Job{}...)
|
||||||
|
getbuild, err3 := GetBuildRef(db, &Repo{ID: 1}, "refs/pull/6")
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(build2.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build2.Number).Equal(getbuild.Number)
|
||||||
|
g.Assert(build2.Ref).Equal(getbuild.Ref)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build by Commit", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Branch: "master",
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusPending,
|
||||||
|
Branch: "dev",
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa",
|
||||||
|
}
|
||||||
|
err1 := CreateBuild(db, build1, []*Job{}...)
|
||||||
|
err2 := CreateBuild(db, build2, []*Job{}...)
|
||||||
|
getbuild, err3 := GetBuildCommit(db, &Repo{ID: 1}, build2.Commit, build2.Branch)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(build2.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build2.Number).Equal(getbuild.Number)
|
||||||
|
g.Assert(build2.Commit).Equal(getbuild.Commit)
|
||||||
|
g.Assert(build2.Branch).Equal(getbuild.Branch)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Build by Commit", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusFailure,
|
||||||
|
Branch: "master",
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
Branch: "master",
|
||||||
|
Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa",
|
||||||
|
}
|
||||||
|
err1 := CreateBuild(db, build1, []*Job{}...)
|
||||||
|
err2 := CreateBuild(db, build2, []*Job{}...)
|
||||||
|
getbuild, err3 := GetBuildLast(db, &Repo{ID: 1}, build2.Branch)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(build2.ID).Equal(getbuild.ID)
|
||||||
|
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
|
||||||
|
g.Assert(build2.Number).Equal(getbuild.Number)
|
||||||
|
g.Assert(build2.Status).Equal(getbuild.Status)
|
||||||
|
g.Assert(build2.Branch).Equal(getbuild.Branch)
|
||||||
|
g.Assert(build2.Commit).Equal(getbuild.Commit)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get recent Builds", func() {
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusFailure,
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
CreateBuild(db, build1, []*Job{}...)
|
||||||
|
CreateBuild(db, build2, []*Job{}...)
|
||||||
|
builds, err := GetBuildList(db, &Repo{ID: 1})
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(builds)).Equal(2)
|
||||||
|
g.Assert(builds[0].ID).Equal(build2.ID)
|
||||||
|
g.Assert(builds[0].RepoID).Equal(build2.RepoID)
|
||||||
|
g.Assert(builds[0].Status).Equal(build2.Status)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
package ccmenu
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CCProjects struct {
|
type CCProjects struct {
|
||||||
|
@ -23,10 +21,10 @@ type CCProject struct {
|
||||||
WebURL string `xml:"webUrl,attr"`
|
WebURL string `xml:"webUrl,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCC(r *types.Repo, b *types.Build) *CCProjects {
|
func NewCC(r *Repo, b *Build, link string) *CCProjects {
|
||||||
proj := &CCProject{
|
proj := &CCProject{
|
||||||
Name: r.Owner + "/" + r.Name,
|
Name: r.FullName,
|
||||||
WebURL: r.Self,
|
WebURL: link,
|
||||||
Activity: "Building",
|
Activity: "Building",
|
||||||
LastBuildStatus: "Unknown",
|
LastBuildStatus: "Unknown",
|
||||||
LastBuildLabel: "Unknown",
|
LastBuildLabel: "Unknown",
|
||||||
|
@ -34,24 +32,22 @@ func NewCC(r *types.Repo, b *types.Build) *CCProjects {
|
||||||
|
|
||||||
// if the build is not currently running then
|
// if the build is not currently running then
|
||||||
// we can return the latest build status.
|
// we can return the latest build status.
|
||||||
if b.Status != types.StatePending &&
|
if b.Status != StatusPending &&
|
||||||
b.Status != types.StateRunning {
|
b.Status != StatusRunning {
|
||||||
proj.Activity = "Sleeping"
|
proj.Activity = "Sleeping"
|
||||||
proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339)
|
proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339)
|
||||||
proj.LastBuildLabel = strconv.Itoa(b.Number)
|
proj.LastBuildLabel = strconv.Itoa(b.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the last build state accepts a valid
|
// ensure the last build Status accepts a valid
|
||||||
// ccmenu enumeration
|
// ccmenu enumeration
|
||||||
switch b.Status {
|
switch b.Status {
|
||||||
case types.StateError, types.StateKilled:
|
case StatusError, StatusKilled:
|
||||||
proj.LastBuildStatus = "Exception"
|
proj.LastBuildStatus = "Exception"
|
||||||
case types.StateSuccess:
|
case StatusSuccess:
|
||||||
proj.LastBuildStatus = "Success"
|
proj.LastBuildStatus = "Success"
|
||||||
case types.StateFailure:
|
case StatusFailure:
|
||||||
proj.LastBuildStatus = "Failure"
|
proj.LastBuildStatus = "Failure"
|
||||||
default:
|
|
||||||
proj.LastBuildStatus = "Unknown"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CCProjects{Project: proj}
|
return &CCProjects{Project: proj}
|
83
model/cc_test.go
Normal file
83
model/cc_test.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCC(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("CC", func() {
|
||||||
|
|
||||||
|
g.It("Should create a project", func() {
|
||||||
|
|
||||||
|
r := &Repo{
|
||||||
|
FullName: "foo/bar",
|
||||||
|
}
|
||||||
|
b := &Build{
|
||||||
|
Status: StatusSuccess,
|
||||||
|
Number: 1,
|
||||||
|
Started: 1442872675,
|
||||||
|
}
|
||||||
|
cc := NewCC(r, b, "http://localhost/foo/bar/1")
|
||||||
|
|
||||||
|
g.Assert(cc.Project.Name).Equal("foo/bar")
|
||||||
|
g.Assert(cc.Project.Activity).Equal("Sleeping")
|
||||||
|
g.Assert(cc.Project.LastBuildStatus).Equal("Success")
|
||||||
|
g.Assert(cc.Project.LastBuildLabel).Equal("1")
|
||||||
|
g.Assert(cc.Project.LastBuildTime).Equal("2015-09-21T14:57:55-07:00")
|
||||||
|
g.Assert(cc.Project.WebURL).Equal("http://localhost/foo/bar/1")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should properly label exceptions", func() {
|
||||||
|
r := &Repo{FullName: "foo/bar"}
|
||||||
|
b := &Build{
|
||||||
|
Status: StatusError,
|
||||||
|
Number: 1,
|
||||||
|
Started: 1257894000,
|
||||||
|
}
|
||||||
|
cc := NewCC(r, b, "http://localhost/foo/bar/1")
|
||||||
|
g.Assert(cc.Project.LastBuildStatus).Equal("Exception")
|
||||||
|
g.Assert(cc.Project.Activity).Equal("Sleeping")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should properly label success", func() {
|
||||||
|
r := &Repo{FullName: "foo/bar"}
|
||||||
|
b := &Build{
|
||||||
|
Status: StatusSuccess,
|
||||||
|
Number: 1,
|
||||||
|
Started: 1257894000,
|
||||||
|
}
|
||||||
|
cc := NewCC(r, b, "http://localhost/foo/bar/1")
|
||||||
|
g.Assert(cc.Project.LastBuildStatus).Equal("Success")
|
||||||
|
g.Assert(cc.Project.Activity).Equal("Sleeping")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should properly label failure", func() {
|
||||||
|
r := &Repo{FullName: "foo/bar"}
|
||||||
|
b := &Build{
|
||||||
|
Status: StatusFailure,
|
||||||
|
Number: 1,
|
||||||
|
Started: 1257894000,
|
||||||
|
}
|
||||||
|
cc := NewCC(r, b, "http://localhost/foo/bar/1")
|
||||||
|
g.Assert(cc.Project.LastBuildStatus).Equal("Failure")
|
||||||
|
g.Assert(cc.Project.Activity).Equal("Sleeping")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should properly label running", func() {
|
||||||
|
r := &Repo{FullName: "foo/bar"}
|
||||||
|
b := &Build{
|
||||||
|
Status: StatusRunning,
|
||||||
|
Number: 1,
|
||||||
|
Started: 1257894000,
|
||||||
|
}
|
||||||
|
cc := NewCC(r, b, "http://localhost/foo/bar/1")
|
||||||
|
g.Assert(cc.Project.Activity).Equal("Building")
|
||||||
|
g.Assert(cc.Project.LastBuildStatus).Equal("Unknown")
|
||||||
|
g.Assert(cc.Project.LastBuildLabel).Equal("Unknown")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
126
model/config.go
126
model/config.go
|
@ -1,126 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents a repository build configuration.
|
|
||||||
type Config struct {
|
|
||||||
Cache *Step
|
|
||||||
Setup *Step
|
|
||||||
Clone *Step
|
|
||||||
Build *Step
|
|
||||||
|
|
||||||
Compose map[string]*Step
|
|
||||||
Publish map[string]*Step
|
|
||||||
Deploy map[string]*Step
|
|
||||||
Notify map[string]*Step
|
|
||||||
|
|
||||||
Matrix Matrix
|
|
||||||
Axis Axis
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matrix represents the build matrix.
|
|
||||||
type Matrix map[string][]string
|
|
||||||
|
|
||||||
// Axis represents a single permutation of entries
|
|
||||||
// from the build matrix.
|
|
||||||
type Axis map[string]string
|
|
||||||
|
|
||||||
// String returns a string representation of an Axis as
|
|
||||||
// a comma-separated list of environment variables.
|
|
||||||
func (a Axis) String() string {
|
|
||||||
var envs []string
|
|
||||||
for k, v := range a {
|
|
||||||
envs = append(envs, k+"="+v)
|
|
||||||
}
|
|
||||||
return strings.Join(envs, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step represents a step in the build process, including
|
|
||||||
// the execution environment and parameters.
|
|
||||||
type Step struct {
|
|
||||||
Image string
|
|
||||||
Pull bool
|
|
||||||
Privileged bool
|
|
||||||
Environment []string
|
|
||||||
Entrypoint []string
|
|
||||||
Command []string
|
|
||||||
Volumes []string
|
|
||||||
Cache []string
|
|
||||||
WorkingDir string `yaml:"working_dir"`
|
|
||||||
NetworkMode string `yaml:"net"`
|
|
||||||
|
|
||||||
// Condition represents a set of conditions that must
|
|
||||||
// be met in order to execute this step.
|
|
||||||
Condition *Condition `yaml:"when"`
|
|
||||||
|
|
||||||
// Config represents the unique configuration details
|
|
||||||
// for each plugin.
|
|
||||||
Config map[string]interface{} `yaml:"config,inline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition represents a set of conditions that must
|
|
||||||
// be met in order to proceed with a build or build step.
|
|
||||||
type Condition struct {
|
|
||||||
Owner string // Indicates the step should run only for this repo (useful for forks)
|
|
||||||
Branch string // Indicates the step should run only for this branch
|
|
||||||
Event string
|
|
||||||
Success string
|
|
||||||
Failure string
|
|
||||||
|
|
||||||
// Indicates the step should only run when the following
|
|
||||||
// matrix values are present for the sub-build.
|
|
||||||
Matrix map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchBranch is a helper function that returns true
|
|
||||||
// if all_branches is true. Else it returns false if a
|
|
||||||
// branch condition is specified, and the branch does
|
|
||||||
// not match.
|
|
||||||
func (c *Condition) MatchBranch(branch string) bool {
|
|
||||||
if len(c.Branch) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(branch, "refs/heads/") {
|
|
||||||
branch = branch[11:]
|
|
||||||
}
|
|
||||||
match, _ := filepath.Match(c.Branch, branch)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchOwner is a helper function that returns false
|
|
||||||
// if an owner condition is specified and the repository
|
|
||||||
// owner does not match.
|
|
||||||
//
|
|
||||||
// This is useful when you want to prevent forks from
|
|
||||||
// executing deployment, publish or notification steps.
|
|
||||||
func (c *Condition) MatchOwner(owner string) bool {
|
|
||||||
if len(c.Owner) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
parts := strings.Split(owner, "/")
|
|
||||||
switch len(parts) {
|
|
||||||
case 2:
|
|
||||||
return c.Owner == parts[0]
|
|
||||||
case 3:
|
|
||||||
return c.Owner == parts[1]
|
|
||||||
default:
|
|
||||||
return c.Owner == owner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchMatrix is a helper function that returns false
|
|
||||||
// to limit steps to only certain matrix axis.
|
|
||||||
func (c *Condition) MatchMatrix(matrix map[string]string) bool {
|
|
||||||
if len(c.Matrix) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for k, v := range c.Matrix {
|
|
||||||
if matrix[k] != v {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
18
model/const.go
Normal file
18
model/const.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventPush = "push"
|
||||||
|
EventPull = "pull_request"
|
||||||
|
EventTag = "tag"
|
||||||
|
EventDeploy = "deploy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusSkipped = "skipped"
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusRunning = "running"
|
||||||
|
StatusSuccess = "success"
|
||||||
|
StatusFailure = "failure"
|
||||||
|
StatusKilled = "killed"
|
||||||
|
StatusError = "error"
|
||||||
|
)
|
23
model/feed.go
Normal file
23
model/feed.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Feed struct {
|
||||||
|
Owner string `json:"owner" meddler:"repo_owner"`
|
||||||
|
Name string `json:"name" meddler:"repo_name"`
|
||||||
|
FullName string `json:"full_name" meddler:"repo_full_name"`
|
||||||
|
Avatar string `json:"avatar_url" meddler:"repo_avatar"`
|
||||||
|
|
||||||
|
Number int `json:"number" meddler:"build_number"`
|
||||||
|
Event string `json:"event" meddler:"build_event"`
|
||||||
|
Status string `json:"status" meddler:"build_status"`
|
||||||
|
Started int64 `json:"started_at" meddler:"build_started"`
|
||||||
|
Finished int64 `json:"finished_at" meddler:"build_finished"`
|
||||||
|
Commit string `json:"commit" meddler:"build_commit"`
|
||||||
|
Branch string `json:"branch" meddler:"build_branch"`
|
||||||
|
Ref string `json:"ref" meddler:"build_ref"`
|
||||||
|
Refspec string `json:"refspec" meddler:"build_refspec"`
|
||||||
|
Remote string `json:"remote" meddler:"build_remote"`
|
||||||
|
Title string `json:"title" meddler:"build_title"`
|
||||||
|
Message string `json:"message" meddler:"build_message"`
|
||||||
|
Author string `json:"author" meddler:"build_author"`
|
||||||
|
Email string `json:"author_email" meddler:"build_email"`
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Hook struct {
|
|
||||||
Event string
|
|
||||||
Repo *Repo
|
|
||||||
Commit *Commit
|
|
||||||
PullRequest *PullRequest
|
|
||||||
}
|
|
67
model/job.go
67
model/job.go
|
@ -1,13 +1,62 @@
|
||||||
package types
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id" meddler:"job_id,pk"`
|
||||||
BuildID int64 `json:"-" sql:"unique:ux_build_number,index:ix_job_build_id"`
|
BuildID int64 `json:"-" meddler:"job_build_id"`
|
||||||
Number int `json:"number" sql:"unique:ux_build_number"`
|
NodeID int64 `json:"-" meddler:"job_node_id"`
|
||||||
Status string `json:"status"`
|
Number int `json:"number" meddler:"job_number"`
|
||||||
ExitCode int `json:"exit_code"`
|
Status string `json:"status" meddler:"job_status"`
|
||||||
Started int64 `json:"started_at"`
|
ExitCode int `json:"exit_code" meddler:"job_exit_code"`
|
||||||
Finished int64 `json:"finished_at"`
|
Started int64 `json:"started_at" meddler:"job_started"`
|
||||||
|
Finished int64 `json:"finished_at" meddler:"job_finished"`
|
||||||
|
|
||||||
Environment map[string]string `json:"environment" sql:"type:varchar,size:2048"`
|
Environment map[string]string `json:"environment" meddler:"job_environment,json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetJob(db meddler.DB, id int64) (*Job, error) {
|
||||||
|
var job = new(Job)
|
||||||
|
var err = meddler.Load(db, jobTable, job, id)
|
||||||
|
return job, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJobNumber(db meddler.DB, build *Build, number int) (*Job, error) {
|
||||||
|
var job = new(Job)
|
||||||
|
var err = meddler.QueryRow(db, job, database.Rebind(jobNumberQuery), build.ID, number)
|
||||||
|
return job, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJobList(db meddler.DB, build *Build) ([]*Job, error) {
|
||||||
|
var jobs = []*Job{}
|
||||||
|
var err = meddler.QueryAll(db, &jobs, database.Rebind(jobListQuery), build.ID)
|
||||||
|
return jobs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertJob(db meddler.DB, job *Job) error {
|
||||||
|
return meddler.Insert(db, jobTable, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateJob(db meddler.DB, job *Job) error {
|
||||||
|
return meddler.Update(db, jobTable, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobTable = "jobs"
|
||||||
|
|
||||||
|
const jobListQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM jobs
|
||||||
|
WHERE job_build_id = ?
|
||||||
|
ORDER BY job_number ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
const jobNumberQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM jobs
|
||||||
|
WHERE job_build_id = ?
|
||||||
|
AND job_number = ?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
117
model/job_test.go
Normal file
117
model/job_test.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJob(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Job", func() {
|
||||||
|
|
||||||
|
// before each test we purge the package table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM jobs")
|
||||||
|
db.Exec("DELETE FROM builds")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Set a job", func() {
|
||||||
|
job := &Job{
|
||||||
|
BuildID: 1,
|
||||||
|
Status: "pending",
|
||||||
|
ExitCode: 0,
|
||||||
|
Number: 1,
|
||||||
|
}
|
||||||
|
err1 := InsertJob(db, job)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(job.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
job.Status = "started"
|
||||||
|
err2 := UpdateJob(db, job)
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
|
||||||
|
getjob, err3 := GetJob(db, job.ID)
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(getjob.Status).Equal(job.Status)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Job by ID", func() {
|
||||||
|
job := &Job{
|
||||||
|
BuildID: 1,
|
||||||
|
Status: "pending",
|
||||||
|
ExitCode: 1,
|
||||||
|
Number: 1,
|
||||||
|
Environment: map[string]string{"foo": "bar"},
|
||||||
|
}
|
||||||
|
err1 := InsertJob(db, job)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(job.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
getjob, err2 := GetJob(db, job.ID)
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(getjob.ID).Equal(job.ID)
|
||||||
|
g.Assert(getjob.Status).Equal(job.Status)
|
||||||
|
g.Assert(getjob.ExitCode).Equal(job.ExitCode)
|
||||||
|
g.Assert(getjob.Environment).Equal(job.Environment)
|
||||||
|
g.Assert(getjob.Environment["foo"]).Equal("bar")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Job by Number", func() {
|
||||||
|
job := &Job{
|
||||||
|
BuildID: 1,
|
||||||
|
Status: "pending",
|
||||||
|
ExitCode: 1,
|
||||||
|
Number: 1,
|
||||||
|
}
|
||||||
|
err1 := InsertJob(db, job)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(job.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
getjob, err2 := GetJobNumber(db, &Build{ID: 1}, 1)
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(getjob.ID).Equal(job.ID)
|
||||||
|
g.Assert(getjob.Status).Equal(job.Status)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a List of Jobs by Commit", func() {
|
||||||
|
|
||||||
|
build := Build{
|
||||||
|
RepoID: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
jobs := []*Job{
|
||||||
|
&Job{
|
||||||
|
BuildID: 1,
|
||||||
|
Status: "success",
|
||||||
|
ExitCode: 0,
|
||||||
|
Number: 1,
|
||||||
|
},
|
||||||
|
&Job{
|
||||||
|
BuildID: 3,
|
||||||
|
Status: "error",
|
||||||
|
ExitCode: 1,
|
||||||
|
Number: 2,
|
||||||
|
},
|
||||||
|
&Job{
|
||||||
|
BuildID: 5,
|
||||||
|
Status: "pending",
|
||||||
|
ExitCode: 0,
|
||||||
|
Number: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
//
|
||||||
|
err1 := CreateBuild(db, &build, jobs...)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
getjobs, err2 := GetJobList(db, &build)
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(len(getjobs)).Equal(3)
|
||||||
|
g.Assert(getjobs[0].Number).Equal(1)
|
||||||
|
g.Assert(getjobs[0].Status).Equal(StatusSuccess)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
46
model/key.go
Normal file
46
model/key.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
ID int64 `json:"-" meddler:"key_id,pk"`
|
||||||
|
RepoID int64 `json:"-" meddler:"key_repo_id"`
|
||||||
|
Public string `json:"public" meddler:"key_public"`
|
||||||
|
Private string `json:"private" meddler:"key_private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKey(db meddler.DB, repo *Repo) (*Key, error) {
|
||||||
|
var key = new(Key)
|
||||||
|
var err = meddler.QueryRow(db, key, database.Rebind(keyQuery), repo.ID)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateKey(db meddler.DB, key *Key) error {
|
||||||
|
return meddler.Save(db, keyTable, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateKey(db meddler.DB, key *Key) error {
|
||||||
|
return meddler.Save(db, keyTable, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteKey(db meddler.DB, repo *Repo) error {
|
||||||
|
var _, err = db.Exec(database.Rebind(keyDeleteStmt), repo.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyTable = "keys"
|
||||||
|
|
||||||
|
const keyQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM keys
|
||||||
|
WHERE key_repo_id=?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const keyDeleteStmt = `
|
||||||
|
DELETE FROM keys
|
||||||
|
WHERE key_repo_id=?
|
||||||
|
`
|
113
model/key_test.go
Normal file
113
model/key_test.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKey(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Keys", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM keys")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should create a key", func() {
|
||||||
|
key := Key{
|
||||||
|
RepoID: 1,
|
||||||
|
Public: fakePublicKey,
|
||||||
|
Private: fakePrivateKey,
|
||||||
|
}
|
||||||
|
err := CreateKey(db, &key)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(key.ID != 0).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should update a key", func() {
|
||||||
|
key := Key{
|
||||||
|
RepoID: 1,
|
||||||
|
Public: fakePublicKey,
|
||||||
|
Private: fakePrivateKey,
|
||||||
|
}
|
||||||
|
err := CreateKey(db, &key)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(key.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
key.Private = ""
|
||||||
|
key.Public = ""
|
||||||
|
|
||||||
|
err1 := UpdateKey(db, &key)
|
||||||
|
getkey, err2 := GetKey(db, &Repo{ID: 1})
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(key.ID).Equal(getkey.ID)
|
||||||
|
g.Assert(key.Public).Equal(getkey.Public)
|
||||||
|
g.Assert(key.Private).Equal(getkey.Private)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get a key", func() {
|
||||||
|
key := Key{
|
||||||
|
RepoID: 1,
|
||||||
|
Public: fakePublicKey,
|
||||||
|
Private: fakePrivateKey,
|
||||||
|
}
|
||||||
|
err := CreateKey(db, &key)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(key.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
getkey, err := GetKey(db, &Repo{ID: 1})
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(key.ID).Equal(getkey.ID)
|
||||||
|
g.Assert(key.Public).Equal(getkey.Public)
|
||||||
|
g.Assert(key.Private).Equal(getkey.Private)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should delete a key", func() {
|
||||||
|
key := Key{
|
||||||
|
RepoID: 1,
|
||||||
|
Public: fakePublicKey,
|
||||||
|
Private: fakePrivateKey,
|
||||||
|
}
|
||||||
|
err1 := CreateKey(db, &key)
|
||||||
|
err2 := DeleteKey(db, &Repo{ID: 1})
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
|
||||||
|
_, err := GetKey(db, &Repo{ID: 1})
|
||||||
|
g.Assert(err == nil).IsFalse()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var fakePublicKey = `
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0
|
||||||
|
FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/
|
||||||
|
3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var fakePrivateKey = `
|
||||||
|
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
|
||||||
|
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
|
||||||
|
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
|
||||||
|
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
|
||||||
|
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
|
||||||
|
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
|
||||||
|
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
|
||||||
|
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
|
||||||
|
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
|
||||||
|
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
|
||||||
|
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
42
model/log.go
Normal file
42
model/log.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
ID int64 `meddler:"log_id,pk"`
|
||||||
|
JobID int64 `meddler:"log_job_id"`
|
||||||
|
Data []byte `meddler:"log_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLog(db meddler.DB, job *Job) (io.ReadCloser, error) {
|
||||||
|
var log = new(Log)
|
||||||
|
var err = meddler.QueryRow(db, log, database.Rebind(logQuery), job.ID)
|
||||||
|
var buf = bytes.NewBuffer(log.Data)
|
||||||
|
return ioutil.NopCloser(buf), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLog(db meddler.DB, job *Job, r io.Reader) error {
|
||||||
|
var log = new(Log)
|
||||||
|
var err = meddler.QueryRow(db, log, database.Rebind(logQuery), job.ID)
|
||||||
|
if err != nil {
|
||||||
|
log = &Log{JobID: job.ID}
|
||||||
|
}
|
||||||
|
log.Data, _ = ioutil.ReadAll(r)
|
||||||
|
return meddler.Save(db, logTable, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
const logTable = "logs"
|
||||||
|
|
||||||
|
const logQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM logs
|
||||||
|
WHERE log_job_id=?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
59
model/log_test.go
Normal file
59
model/log_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLog(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Logs", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM logs")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should create a log", func() {
|
||||||
|
job := Job{
|
||||||
|
ID: 1,
|
||||||
|
}
|
||||||
|
buf := bytes.NewBufferString("echo hi")
|
||||||
|
err := SetLog(db, &job, buf)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
|
||||||
|
rc, err := GetLog(db, &job)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
defer rc.Close()
|
||||||
|
out, _ := ioutil.ReadAll(rc)
|
||||||
|
g.Assert(string(out)).Equal("echo hi")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should update a log", func() {
|
||||||
|
job := Job{
|
||||||
|
ID: 1,
|
||||||
|
}
|
||||||
|
buf1 := bytes.NewBufferString("echo hi")
|
||||||
|
buf2 := bytes.NewBufferString("echo allo?")
|
||||||
|
err1 := SetLog(db, &job, buf1)
|
||||||
|
err2 := SetLog(db, &job, buf2)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
|
||||||
|
rc, err := GetLog(db, &job)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
defer rc.Close()
|
||||||
|
out, _ := ioutil.ReadAll(rc)
|
||||||
|
g.Assert(string(out)).Equal("echo allo?")
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
7
model/netrc.go
Normal file
7
model/netrc.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Netrc struct {
|
||||||
|
Machine string `json:"machine"`
|
||||||
|
Login string `json:"login"`
|
||||||
|
Password string `json:"user"`
|
||||||
|
}
|
79
model/node.go
Normal file
79
model/node.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
ID int64 `meddler:"node_id,pk" json:"id"`
|
||||||
|
Addr string `meddler:"node_addr" json:"address"`
|
||||||
|
Arch string `meddler:"node_arch" json:"architecture"`
|
||||||
|
Cert string `meddler:"node_cert" json:"-"`
|
||||||
|
Key string `meddler:"node_key" json:"-"`
|
||||||
|
CA string `meddler:"node_ca" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNode(db meddler.DB, id int64) (*Node, error) {
|
||||||
|
var node = new(Node)
|
||||||
|
var err = meddler.Load(db, nodeTable, node, id)
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeList(db meddler.DB) ([]*Node, error) {
|
||||||
|
var nodes = []*Node{}
|
||||||
|
var err = meddler.QueryAll(db, &nodes, database.Rebind(nodeListQuery))
|
||||||
|
return nodes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertNode(db meddler.DB, node *Node) error {
|
||||||
|
return meddler.Insert(db, nodeTable, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateNode(db meddler.DB, node *Node) error {
|
||||||
|
return meddler.Update(db, nodeTable, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteNode(db meddler.DB, node *Node) error {
|
||||||
|
var _, err = db.Exec(database.Rebind(nodeDeleteStmt), node.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTable = "nodes"
|
||||||
|
|
||||||
|
const nodeListQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM nodes
|
||||||
|
ORDER BY node_addr
|
||||||
|
`
|
||||||
|
|
||||||
|
const nodeDeleteStmt = `
|
||||||
|
DELETE FROM nodes
|
||||||
|
WHERE node_id=?
|
||||||
|
`
|
||||||
|
|
||||||
|
const (
|
||||||
|
Freebsd_386 uint = iota
|
||||||
|
Freebsd_amd64
|
||||||
|
Freebsd_arm
|
||||||
|
Linux_386
|
||||||
|
Linux_amd64
|
||||||
|
Linux_arm
|
||||||
|
Linux_arm64
|
||||||
|
Solaris_amd64
|
||||||
|
Windows_386
|
||||||
|
Windows_amd64
|
||||||
|
)
|
||||||
|
|
||||||
|
var Archs = map[string]uint{
|
||||||
|
"freebsd_386": Freebsd_386,
|
||||||
|
"freebsd_amd64": Freebsd_amd64,
|
||||||
|
"freebsd_arm": Freebsd_arm,
|
||||||
|
"linux_386": Linux_386,
|
||||||
|
"linux_amd64": Linux_amd64,
|
||||||
|
"linux_arm": Linux_arm,
|
||||||
|
"linux_arm64": Linux_arm64,
|
||||||
|
"solaris_amd64": Solaris_amd64,
|
||||||
|
"windows_386": Windows_386,
|
||||||
|
"windows_amd64": Windows_amd64,
|
||||||
|
}
|
100
model/node_test.go
Normal file
100
model/node_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNode(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Nodes", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM nodes")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should create a node", func() {
|
||||||
|
node := Node{
|
||||||
|
Addr: "unix:///var/run/docker/docker.sock",
|
||||||
|
Arch: "linux_amd64",
|
||||||
|
}
|
||||||
|
err := InsertNode(db, &node)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(node.ID != 0).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should update a node", func() {
|
||||||
|
node := Node{
|
||||||
|
Addr: "unix:///var/run/docker/docker.sock",
|
||||||
|
Arch: "linux_amd64",
|
||||||
|
}
|
||||||
|
err := InsertNode(db, &node)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(node.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
node.Addr = "unix:///var/run/docker.sock"
|
||||||
|
|
||||||
|
err1 := UpdateNode(db, &node)
|
||||||
|
getnode, err2 := GetNode(db, node.ID)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(node.ID).Equal(getnode.ID)
|
||||||
|
g.Assert(node.Addr).Equal(getnode.Addr)
|
||||||
|
g.Assert(node.Arch).Equal(getnode.Arch)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get a node", func() {
|
||||||
|
node := Node{
|
||||||
|
Addr: "unix:///var/run/docker/docker.sock",
|
||||||
|
Arch: "linux_amd64",
|
||||||
|
}
|
||||||
|
err := InsertNode(db, &node)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(node.ID != 0).IsTrue()
|
||||||
|
|
||||||
|
getnode, err := GetNode(db, node.ID)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(node.ID).Equal(getnode.ID)
|
||||||
|
g.Assert(node.Addr).Equal(getnode.Addr)
|
||||||
|
g.Assert(node.Arch).Equal(getnode.Arch)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get a node list", func() {
|
||||||
|
node1 := Node{
|
||||||
|
Addr: "unix:///var/run/docker/docker.sock",
|
||||||
|
Arch: "linux_amd64",
|
||||||
|
}
|
||||||
|
node2 := Node{
|
||||||
|
Addr: "unix:///var/run/docker.sock",
|
||||||
|
Arch: "linux_386",
|
||||||
|
}
|
||||||
|
InsertNode(db, &node1)
|
||||||
|
InsertNode(db, &node2)
|
||||||
|
|
||||||
|
nodes, err := GetNodeList(db)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(nodes)).Equal(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should delete a node", func() {
|
||||||
|
node := Node{
|
||||||
|
Addr: "unix:///var/run/docker/docker.sock",
|
||||||
|
Arch: "linux_amd64",
|
||||||
|
}
|
||||||
|
err1 := InsertNode(db, &node)
|
||||||
|
err2 := DeleteNode(db, &node)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
|
||||||
|
_, err := GetNode(db, node.ID)
|
||||||
|
g.Assert(err == nil).IsFalse()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
7
model/perm.go
Normal file
7
model/perm.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Perm struct {
|
||||||
|
Pull bool `json:"pull"`
|
||||||
|
Push bool `json:"push"`
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
}
|
136
model/repo.go
136
model/repo.go
|
@ -1,77 +1,89 @@
|
||||||
package types
|
package model
|
||||||
|
|
||||||
type Repo struct {
|
import (
|
||||||
ID int64 `json:"id"`
|
"github.com/drone/drone/shared/database"
|
||||||
UserID int64 `json:"-" sql:"index:ix_repo_user_id"`
|
"github.com/russross/meddler"
|
||||||
Owner string `json:"owner" sql:"unique:ux_repo_owner_name"`
|
)
|
||||||
Name string `json:"name" sql:"unique:ux_repo_owner_name"`
|
|
||||||
FullName string `json:"full_name" sql:"unique:ux_repo_full_name"`
|
|
||||||
Avatar string `json:"avatar_url"`
|
|
||||||
Self string `json:"self_url"`
|
|
||||||
Link string `json:"link_url"`
|
|
||||||
Clone string `json:"clone_url"`
|
|
||||||
Branch string `json:"default_branch"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
Trusted bool `json:"trusted"`
|
|
||||||
Timeout int64 `json:"timeout"`
|
|
||||||
|
|
||||||
Keys *Keypair `json:"-"`
|
|
||||||
Hooks *Hooks `json:"hooks"`
|
|
||||||
|
|
||||||
// Perms are the current user's permissions to push,
|
|
||||||
// pull, and administer this repository. The permissions
|
|
||||||
// are sourced from the version control system (ie GitHub)
|
|
||||||
Perms *Perm `json:"perms,omitempty" sql:"-"`
|
|
||||||
|
|
||||||
// Params are private environment parameters that are
|
|
||||||
// considered secret and are therefore stored external
|
|
||||||
// to the source code repository inside Drone.
|
|
||||||
Params map[string]string `json:"-"`
|
|
||||||
|
|
||||||
// randomly generated hash used to sign repository
|
|
||||||
// tokens and encrypt and decrypt private variables.
|
|
||||||
Hash string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RepoLite struct {
|
type RepoLite struct {
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"-"`
|
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
Language string `json:"language"`
|
Avatar string `json:"avatar_url"`
|
||||||
Private bool `json:"private"`
|
|
||||||
Created int64 `json:"created_at"`
|
|
||||||
Updated int64 `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoCommit struct {
|
type Repo struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id" meddler:"repo_id,pk"`
|
||||||
Owner string `json:"owner"`
|
UserID int64 `json:"-" meddler:"repo_user_id"`
|
||||||
Name string `json:"name"`
|
Owner string `json:"owner" meddler:"repo_owner"`
|
||||||
FullName string `json:"full_name"`
|
Name string `json:"name" meddler:"repo_name"`
|
||||||
Number int `json:"number"`
|
FullName string `json:"full_name" meddler:"repo_full_name"`
|
||||||
Status string `json:"status"`
|
Avatar string `json:"avatar_url" meddler:"repo_avatar"`
|
||||||
Started int64 `json:"started_at"`
|
Link string `json:"link_url" meddler:"repo_link"`
|
||||||
Finished int64 `json:"finished_at"`
|
Clone string `json:"clone_url" meddler:"repo_clone"`
|
||||||
|
Branch string `json:"default_branch" meddler:"repo_branch"`
|
||||||
|
Timeout int64 `json:"timeout" meddler:"repo_timeout"`
|
||||||
|
IsPrivate bool `json:"private" meddler:"repo_private"`
|
||||||
|
IsTrusted bool `json:"trusted" meddler:"repo_trusted"`
|
||||||
|
IsStarred bool `json:"starred,omitempty" meddler:"-"`
|
||||||
|
AllowPull bool `json:"allow_pr" meddler:"repo_allow_pr"`
|
||||||
|
AllowPush bool `json:"allow_push" meddler:"repo_allow_push"`
|
||||||
|
AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"`
|
||||||
|
AllowTag bool `json:"allow_tags" meddler:"repo_allow_tags"`
|
||||||
|
Hash string `json:"-" meddler:"repo_hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Perm struct {
|
func GetRepo(db meddler.DB, id int64) (*Repo, error) {
|
||||||
Pull bool `json:"pull" sql:"-"`
|
var repo = new(Repo)
|
||||||
Push bool `json:"push" sql:"-"`
|
var err = meddler.Load(db, repoTable, repo, id)
|
||||||
Admin bool `json:"admin" sql:"-"`
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hooks struct {
|
func GetRepoName(db meddler.DB, owner, name string) (*Repo, error) {
|
||||||
PullRequest bool `json:"pull_request"`
|
var repo = new(Repo)
|
||||||
Push bool `json:"push"`
|
var err = meddler.QueryRow(db, repo, database.Rebind(repoNameQuery), owner, name)
|
||||||
Tags bool `json:"tags"`
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keypair represents an RSA public and private key
|
func GetRepoList(db meddler.DB, user *User) ([]*Repo, error) {
|
||||||
// assigned to a repository. It may be used to clone
|
var repos = []*Repo{}
|
||||||
// private repositories, or as a deployment key.
|
var err = meddler.QueryAll(db, &repos, database.Rebind(repoListQuery), user.ID)
|
||||||
type Keypair struct {
|
return repos, err
|
||||||
Public string `json:"public,omitempty"`
|
|
||||||
Private string `json:"private,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateRepo(db meddler.DB, repo *Repo) error {
|
||||||
|
return meddler.Insert(db, repoTable, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRepo(db meddler.DB, repo *Repo) error {
|
||||||
|
return meddler.Update(db, repoTable, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRepo(db meddler.DB, repo *Repo) error {
|
||||||
|
var _, err = db.Exec(database.Rebind(repoDeleteStmt), repo.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoTable = "repos"
|
||||||
|
|
||||||
|
const repoNameQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM repos
|
||||||
|
WHERE repo_owner = ?
|
||||||
|
AND repo_name = ?
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
const repoListQuery = `
|
||||||
|
SELECT r.*
|
||||||
|
FROM
|
||||||
|
repos r
|
||||||
|
,stars s
|
||||||
|
WHERE r.repo_id = s.star_repo_id
|
||||||
|
AND s.star_user_id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
const repoDeleteStmt = `
|
||||||
|
DELETE FROM repos
|
||||||
|
WHERE repo_id = ?
|
||||||
|
`
|
||||||
|
|
148
model/repo_test.go
Normal file
148
model/repo_test.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepostore(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Repo", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM stars")
|
||||||
|
db.Exec("DELETE FROM repos")
|
||||||
|
db.Exec("DELETE FROM users")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Set a Repo", func() {
|
||||||
|
repo := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
err1 := CreateRepo(db, &repo)
|
||||||
|
err2 := UpdateRepo(db, &repo)
|
||||||
|
getrepo, err3 := GetRepo(db, repo.ID)
|
||||||
|
if err3 != nil {
|
||||||
|
println("Get Repo Error")
|
||||||
|
println(err3.Error())
|
||||||
|
}
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Add a Repo", func() {
|
||||||
|
repo := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
err := CreateRepo(db, &repo)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(repo.ID != 0).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Repo by ID", func() {
|
||||||
|
repo := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
CreateRepo(db, &repo)
|
||||||
|
getrepo, err := GetRepo(db, repo.ID)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||||
|
g.Assert(repo.UserID).Equal(getrepo.UserID)
|
||||||
|
g.Assert(repo.Owner).Equal(getrepo.Owner)
|
||||||
|
g.Assert(repo.Name).Equal(getrepo.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Repo by Name", func() {
|
||||||
|
repo := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
CreateRepo(db, &repo)
|
||||||
|
getrepo, err := GetRepoName(db, repo.Owner, repo.Name)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||||
|
g.Assert(repo.UserID).Equal(getrepo.UserID)
|
||||||
|
g.Assert(repo.Owner).Equal(getrepo.Owner)
|
||||||
|
g.Assert(repo.Name).Equal(getrepo.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a Repo List by User", func() {
|
||||||
|
repo1 := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
repo2 := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone-dart",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone-dart",
|
||||||
|
}
|
||||||
|
CreateRepo(db, &repo1)
|
||||||
|
CreateRepo(db, &repo2)
|
||||||
|
CreateStar(db, &User{ID: 1}, &repo1)
|
||||||
|
repos, err := GetRepoList(db, &User{ID: 1})
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(repos)).Equal(1)
|
||||||
|
g.Assert(repos[0].UserID).Equal(repo1.UserID)
|
||||||
|
g.Assert(repos[0].Owner).Equal(repo1.Owner)
|
||||||
|
g.Assert(repos[0].Name).Equal(repo1.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Delete a Repo", func() {
|
||||||
|
repo := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
CreateRepo(db, &repo)
|
||||||
|
_, err1 := GetRepo(db, repo.ID)
|
||||||
|
err2 := DeleteRepo(db, &repo)
|
||||||
|
_, err3 := GetRepo(db, repo.ID)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Enforce Unique Repo Name", func() {
|
||||||
|
repo1 := Repo{
|
||||||
|
UserID: 1,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
repo2 := Repo{
|
||||||
|
UserID: 2,
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
}
|
||||||
|
err1 := CreateRepo(db, &repo1)
|
||||||
|
err2 := CreateRepo(db, &repo2)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsFalse()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
44
model/star.go
Normal file
44
model/star.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Star struct {
|
||||||
|
ID int64 `meddler:"star_id,pk"`
|
||||||
|
RepoID int64 `meddler:"star_repo_id"`
|
||||||
|
UserID int64 `meddler:"star_user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStar(db meddler.DB, user *User, repo *Repo) (bool, error) {
|
||||||
|
var star = new(Star)
|
||||||
|
err := meddler.QueryRow(db, star, database.Rebind(starQuery), user.ID, repo.ID)
|
||||||
|
return (err == nil), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateStar(db meddler.DB, user *User, repo *Repo) error {
|
||||||
|
var star = &Star{UserID: user.ID, RepoID: repo.ID}
|
||||||
|
return meddler.Insert(db, starTable, star)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteStar(db meddler.DB, user *User, repo *Repo) error {
|
||||||
|
var _, err = db.Exec(database.Rebind(starDeleteStmt), user.ID, repo.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const starTable = "stars"
|
||||||
|
|
||||||
|
const starQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM stars
|
||||||
|
WHERE star_user_id=?
|
||||||
|
AND star_repo_id=?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const starDeleteStmt = `
|
||||||
|
DELETE FROM stars
|
||||||
|
WHERE star_user_id=?
|
||||||
|
AND star_repo_id=?
|
||||||
|
`
|
59
model/star_test.go
Normal file
59
model/star_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStarstore(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Stars", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM stars")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Add a Star", func() {
|
||||||
|
user := User{ID: 1}
|
||||||
|
repo := Repo{ID: 2}
|
||||||
|
err := CreateStar(db, &user, &repo)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get Starred", func() {
|
||||||
|
user := User{ID: 1}
|
||||||
|
repo := Repo{ID: 2}
|
||||||
|
CreateStar(db, &user, &repo)
|
||||||
|
ok, err := GetStar(db, &user, &repo)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(ok).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Not Get Starred", func() {
|
||||||
|
user := User{ID: 1}
|
||||||
|
repo := Repo{ID: 2}
|
||||||
|
ok, err := GetStar(db, &user, &repo)
|
||||||
|
g.Assert(err != nil).IsTrue()
|
||||||
|
g.Assert(ok).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Del a Star", func() {
|
||||||
|
user := User{ID: 1}
|
||||||
|
repo := Repo{ID: 2}
|
||||||
|
CreateStar(db, &user, &repo)
|
||||||
|
_, err1 := GetStar(db, &user, &repo)
|
||||||
|
err2 := DeleteStar(db, &user, &repo)
|
||||||
|
_, err3 := GetStar(db, &user, &repo)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsFalse()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Status struct {
|
|
||||||
ID int64 `json:"-"`
|
|
||||||
CommitID int64 `json:"-"`
|
|
||||||
State string `json:"status"`
|
|
||||||
Link string `json:"target_url"`
|
|
||||||
Desc string `json:"description"`
|
|
||||||
Context string `json:"context"`
|
|
||||||
}
|
|
8
model/sys.go
Normal file
8
model/sys.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type System struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Link string `json:"link_url"`
|
||||||
|
Plugins []string `json:"plugins"`
|
||||||
|
Globals []string `json:"globals"`
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
// System provides important information about the Drone
|
|
||||||
// server to the plugin.
|
|
||||||
type System struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Link string `json:"link_url"`
|
|
||||||
Plugins []string `json:"plugins"`
|
|
||||||
Globals []string `json:"globals"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workspace defines the build's workspace inside the
|
|
||||||
// container. This helps the plugin locate the source
|
|
||||||
// code directory.
|
|
||||||
type Workspace struct {
|
|
||||||
Root string `json:"root"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
|
|
||||||
Netrc *Netrc `json:"netrc"`
|
|
||||||
Keys *Keypair `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Netrc struct {
|
|
||||||
Machine string `json:"machine"`
|
|
||||||
Login string `json:"login"`
|
|
||||||
Password string `json:"user"`
|
|
||||||
}
|
|
127
model/user.go
127
model/user.go
|
@ -1,16 +1,117 @@
|
||||||
package types
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id" meddler:"user_id,pk"`
|
||||||
Login string `json:"login,omitempty" sql:"unique:ux_user_login"`
|
Login string `json:"login" meddler:"user_login"`
|
||||||
Token string `json:"-"`
|
Token string `json:"-" meddler:"user_token"`
|
||||||
Secret string `json:"-"`
|
Secret string `json:"-" meddler:"user_secret"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email" meddler:"user_email"`
|
||||||
Avatar string `json:"avatar_url,omitempty"`
|
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active," meddler:"user_active"`
|
||||||
Admin bool `json:"admin,omitempty"`
|
Admin bool `json:"admin," meddler:"user_admin"`
|
||||||
|
Hash string `json:"-" meddler:"user_hash"`
|
||||||
// randomly generated hash used to sign user
|
|
||||||
// session and application tokens.
|
|
||||||
Hash string `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUser(db meddler.DB, id int64) (*User, error) {
|
||||||
|
var usr = new(User)
|
||||||
|
var err = meddler.Load(db, userTable, usr, id)
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserLogin(db meddler.DB, login string) (*User, error) {
|
||||||
|
var usr = new(User)
|
||||||
|
var err = meddler.QueryRow(db, usr, database.Rebind(userLoginQuery), login)
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserList(db meddler.DB) ([]*User, error) {
|
||||||
|
var users = []*User{}
|
||||||
|
var err = meddler.QueryAll(db, &users, database.Rebind(userListQuery))
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserFeed(db meddler.DB, user *User, limit, offset int) ([]*Feed, error) {
|
||||||
|
var feed = []*Feed{}
|
||||||
|
var err = meddler.QueryAll(db, &feed, database.Rebind(userFeedQuery), user.ID, limit, offset)
|
||||||
|
return feed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserCount(db meddler.DB) (int, error) {
|
||||||
|
var count int
|
||||||
|
var err = db.QueryRow(database.Rebind(userCountQuery)).Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUser(db meddler.DB, user *User) error {
|
||||||
|
return meddler.Insert(db, userTable, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUser(db meddler.DB, user *User) error {
|
||||||
|
return meddler.Update(db, userTable, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteUser(db meddler.DB, user *User) error {
|
||||||
|
var _, err = db.Exec(database.Rebind(userDeleteStmt), user.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const userTable = "users"
|
||||||
|
|
||||||
|
const userLoginQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
WHERE user_login=?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const userListQuery = `
|
||||||
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
ORDER BY user_login ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
const userCountQuery = `
|
||||||
|
SELECT count(1)
|
||||||
|
FROM users
|
||||||
|
`
|
||||||
|
|
||||||
|
const userDeleteStmt = `
|
||||||
|
DELETE FROM users
|
||||||
|
WHERE user_id=?
|
||||||
|
`
|
||||||
|
|
||||||
|
const userFeedQuery = `
|
||||||
|
SELECT
|
||||||
|
repo_owner
|
||||||
|
,repo_name
|
||||||
|
,repo_full_name
|
||||||
|
,repo_avatar
|
||||||
|
,build_number
|
||||||
|
,build_event
|
||||||
|
,build_status
|
||||||
|
,build_started
|
||||||
|
,build_finished
|
||||||
|
,build_commit
|
||||||
|
,build_branch
|
||||||
|
,build_ref
|
||||||
|
,build_refspec
|
||||||
|
,build_remote
|
||||||
|
,build_title
|
||||||
|
,build_message
|
||||||
|
,build_author
|
||||||
|
,build_email
|
||||||
|
FROM
|
||||||
|
builds b
|
||||||
|
,repos r
|
||||||
|
,stars s
|
||||||
|
WHERE b.build_repo_id = r.repo_id
|
||||||
|
AND r.repo_id = s.star_repo_id
|
||||||
|
AND s.star_user_id = ?
|
||||||
|
ORDER BY b.build_number DESC
|
||||||
|
LIMIT ? OFFSET ?
|
||||||
|
`
|
||||||
|
|
207
model/user_test.go
Normal file
207
model/user_test.go
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/database"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserstore(t *testing.T) {
|
||||||
|
db := database.Open("sqlite3", ":memory:")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("User", func() {
|
||||||
|
|
||||||
|
// before each test be sure to purge the package
|
||||||
|
// table data from the database.
|
||||||
|
g.BeforeEach(func() {
|
||||||
|
db.Exec("DELETE FROM users")
|
||||||
|
db.Exec("DELETE FROM stars")
|
||||||
|
db.Exec("DELETE FROM repos")
|
||||||
|
db.Exec("DELETE FROM builds")
|
||||||
|
db.Exec("DELETE FROM jobs")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Update a User", func() {
|
||||||
|
user := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
err1 := CreateUser(db, &user)
|
||||||
|
err2 := UpdateUser(db, &user)
|
||||||
|
getuser, err3 := GetUser(db, user.ID)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsTrue()
|
||||||
|
g.Assert(user.ID).Equal(getuser.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Add a new User", func() {
|
||||||
|
user := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
err := CreateUser(db, &user)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(user.ID != 0).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a User", func() {
|
||||||
|
user := User{
|
||||||
|
Login: "joe",
|
||||||
|
Token: "f0b461ca586c27872b43a0685cbc2847",
|
||||||
|
Secret: "976f22a5eef7caacb7e678d6c52f49b1",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8",
|
||||||
|
Active: true,
|
||||||
|
Admin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateUser(db, &user)
|
||||||
|
getuser, err := GetUser(db, user.ID)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(user.ID).Equal(getuser.ID)
|
||||||
|
g.Assert(user.Login).Equal(getuser.Login)
|
||||||
|
g.Assert(user.Token).Equal(getuser.Token)
|
||||||
|
g.Assert(user.Secret).Equal(getuser.Secret)
|
||||||
|
g.Assert(user.Email).Equal(getuser.Email)
|
||||||
|
g.Assert(user.Avatar).Equal(getuser.Avatar)
|
||||||
|
g.Assert(user.Active).Equal(getuser.Active)
|
||||||
|
g.Assert(user.Admin).Equal(getuser.Admin)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a User By Login", func() {
|
||||||
|
user := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
CreateUser(db, &user)
|
||||||
|
getuser, err := GetUserLogin(db, user.Login)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(user.ID).Equal(getuser.ID)
|
||||||
|
g.Assert(user.Login).Equal(getuser.Login)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Enforce Unique User Login", func() {
|
||||||
|
user1 := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
user2 := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||||
|
}
|
||||||
|
err1 := CreateUser(db, &user1)
|
||||||
|
err2 := CreateUser(db, &user2)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a User List", func() {
|
||||||
|
user1 := User{
|
||||||
|
Login: "jane",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||||
|
}
|
||||||
|
user2 := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
CreateUser(db, &user1)
|
||||||
|
CreateUser(db, &user2)
|
||||||
|
users, err := GetUserList(db)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(users)).Equal(2)
|
||||||
|
g.Assert(users[0].Login).Equal(user1.Login)
|
||||||
|
g.Assert(users[0].Email).Equal(user1.Email)
|
||||||
|
g.Assert(users[0].Token).Equal(user1.Token)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a User Count", func() {
|
||||||
|
user1 := User{
|
||||||
|
Login: "jane",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||||
|
}
|
||||||
|
user2 := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
CreateUser(db, &user1)
|
||||||
|
CreateUser(db, &user2)
|
||||||
|
count, err := GetUserCount(db)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(count).Equal(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Get a User Count Zero", func() {
|
||||||
|
count, err := GetUserCount(db)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(count).Equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should Del a User", func() {
|
||||||
|
user := User{
|
||||||
|
Login: "joe",
|
||||||
|
Email: "foo@bar.com",
|
||||||
|
Token: "e42080dddf012c718e476da161d21ad5",
|
||||||
|
}
|
||||||
|
CreateUser(db, &user)
|
||||||
|
_, err1 := GetUser(db, user.ID)
|
||||||
|
err2 := DeleteUser(db, &user)
|
||||||
|
_, err3 := GetUser(db, user.ID)
|
||||||
|
g.Assert(err1 == nil).IsTrue()
|
||||||
|
g.Assert(err2 == nil).IsTrue()
|
||||||
|
g.Assert(err3 == nil).IsFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get the Build feed for a User", func() {
|
||||||
|
repo1 := &Repo{
|
||||||
|
UserID: 1,
|
||||||
|
Owner: "bradrydzewski",
|
||||||
|
Name: "drone",
|
||||||
|
FullName: "bradrydzewski/drone",
|
||||||
|
}
|
||||||
|
repo2 := &Repo{
|
||||||
|
UserID: 2,
|
||||||
|
Owner: "drone",
|
||||||
|
Name: "drone",
|
||||||
|
FullName: "drone/drone",
|
||||||
|
}
|
||||||
|
CreateRepo(db, repo1)
|
||||||
|
CreateRepo(db, repo2)
|
||||||
|
CreateStar(db, &User{ID: 1}, repo1)
|
||||||
|
|
||||||
|
build1 := &Build{
|
||||||
|
RepoID: repo1.ID,
|
||||||
|
Status: StatusFailure,
|
||||||
|
}
|
||||||
|
build2 := &Build{
|
||||||
|
RepoID: repo1.ID,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
build3 := &Build{
|
||||||
|
RepoID: repo2.ID,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
CreateBuild(db, build1)
|
||||||
|
CreateBuild(db, build2)
|
||||||
|
CreateBuild(db, build3)
|
||||||
|
|
||||||
|
builds, err := GetUserFeed(db, &User{ID: 1}, 20, 0)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(builds)).Equal(2)
|
||||||
|
g.Assert(builds[0].Owner).Equal("bradrydzewski")
|
||||||
|
g.Assert(builds[0].Name).Equal("drone")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// standard characters allowed in token string.
|
|
||||||
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
|
||||||
|
|
||||||
// default token length
|
|
||||||
var length = 32
|
|
||||||
|
|
||||||
// GenerateToken generates random strings good for use in URIs to
|
|
||||||
// identify unique objects.
|
|
||||||
func GenerateToken() string {
|
|
||||||
b := make([]byte, length)
|
|
||||||
r := make([]byte, length+(length/4)) // storage for random bytes.
|
|
||||||
clen := byte(len(chars))
|
|
||||||
maxrb := byte(256 - (256 % len(chars)))
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
io.ReadFull(rand.Reader, r)
|
|
||||||
for _, c := range r {
|
|
||||||
if c >= maxrb {
|
|
||||||
// Skip this number to avoid modulo bias.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b[i] = chars[c%clen]
|
|
||||||
i++
|
|
||||||
if i == length {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_GenerateToken(t *testing.T) {
|
|
||||||
token := GenerateToken()
|
|
||||||
if len(token) != length {
|
|
||||||
t.Errorf("Want token length %d, got %d", length, len(token))
|
|
||||||
}
|
|
||||||
}
|
|
436
remote/github/github.go
Normal file
436
remote/github/github.go
Normal file
|
@ -0,0 +1,436 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/shared/envconfig"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/oauth2"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/google/go-github/github"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultURL = "https://github.com"
|
||||||
|
DefaultAPI = "https://api.github.com"
|
||||||
|
DefaultScope = "repo,repo:status,user:email"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Github struct {
|
||||||
|
URL string
|
||||||
|
API string
|
||||||
|
Client string
|
||||||
|
Secret string
|
||||||
|
Orgs []string
|
||||||
|
Open bool
|
||||||
|
PrivateMode bool
|
||||||
|
SkipVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(env envconfig.Env) *Github {
|
||||||
|
config := env.String("REMOTE_CONFIG", "")
|
||||||
|
|
||||||
|
// parse the remote DSN configuration string
|
||||||
|
url_, err := url.Parse(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("unable to parse remote dsn. %s", err)
|
||||||
|
}
|
||||||
|
params := url_.Query()
|
||||||
|
url_.Path = ""
|
||||||
|
url_.RawQuery = ""
|
||||||
|
|
||||||
|
// create the Githbub remote using parameters from
|
||||||
|
// the parsed DSN configuration string.
|
||||||
|
github := Github{}
|
||||||
|
github.URL = url_.String()
|
||||||
|
github.Client = params.Get("client_id")
|
||||||
|
github.Secret = params.Get("client_secret")
|
||||||
|
github.Orgs = params["orgs"]
|
||||||
|
github.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
|
||||||
|
github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
||||||
|
github.Open, _ = strconv.ParseBool(params.Get("open"))
|
||||||
|
|
||||||
|
if github.URL == DefaultURL {
|
||||||
|
github.API = DefaultAPI
|
||||||
|
} else {
|
||||||
|
github.API = github.URL + "/api/v3/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &github
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login authenticates the session and returns the
|
||||||
|
// remote user details.
|
||||||
|
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientId: g.Client,
|
||||||
|
ClientSecret: g.Secret,
|
||||||
|
Scope: DefaultScope,
|
||||||
|
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
|
||||||
|
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
|
||||||
|
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the OAuth code
|
||||||
|
var code = req.FormValue("code")
|
||||||
|
if len(code) == 0 {
|
||||||
|
var random = GetRandom()
|
||||||
|
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var trans = &oauth2.Transport{Config: config}
|
||||||
|
var token, err = trans.Exchange(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
|
||||||
|
var useremail, errr = GetUserEmail(client)
|
||||||
|
if errr != nil {
|
||||||
|
return nil, false, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Orgs) > 0 {
|
||||||
|
allowedOrg, err := UserBelongsToOrg(client, g.Orgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
|
||||||
|
}
|
||||||
|
if !allowedOrg {
|
||||||
|
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", g.Orgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{}
|
||||||
|
user.Login = *useremail.Login
|
||||||
|
user.Email = *useremail.Email
|
||||||
|
user.Token = token.AccessToken
|
||||||
|
user.Avatar = *useremail.AvatarURL
|
||||||
|
return &user, g.Open, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth authenticates the session and returns the remote user
|
||||||
|
// login for the given token and secret
|
||||||
|
func (g *Github) Auth(token, secret string) (string, error) {
|
||||||
|
client := NewClient(g.API, token, g.SkipVerify)
|
||||||
|
user, _, err := client.Users.Get("")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return *user.Login, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repo fetches the named repository from the remote system.
|
||||||
|
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
repo_, err := GetRepo(client, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &model.Repo{}
|
||||||
|
repo.Owner = owner
|
||||||
|
repo.Name = name
|
||||||
|
repo.FullName = *repo_.FullName
|
||||||
|
repo.Link = *repo_.HTMLURL
|
||||||
|
repo.IsPrivate = *repo_.Private
|
||||||
|
repo.Clone = *repo_.CloneURL
|
||||||
|
repo.Branch = "master"
|
||||||
|
repo.Avatar = *repo_.Owner.AvatarURL
|
||||||
|
|
||||||
|
if repo_.DefaultBranch != nil {
|
||||||
|
repo.Branch = *repo_.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.PrivateMode {
|
||||||
|
repo.IsPrivate = true
|
||||||
|
}
|
||||||
|
return repo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repos fetches a list of repos from the remote system.
|
||||||
|
func (g *Github) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
|
||||||
|
all, err := GetAllRepos(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var repos = []*model.RepoLite{}
|
||||||
|
for _, repo := range all {
|
||||||
|
repos = append(repos, &model.RepoLite{
|
||||||
|
Owner: *repo.Owner.Login,
|
||||||
|
Name: *repo.Name,
|
||||||
|
FullName: *repo.FullName,
|
||||||
|
Avatar: *repo.Owner.AvatarURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm fetches the named repository permissions from
|
||||||
|
// the remote system for the specified user.
|
||||||
|
func (g *Github) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||||
|
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
repo, err := GetRepo(client, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := &model.Perm{}
|
||||||
|
m.Admin = (*repo.Permissions)["admin"]
|
||||||
|
m.Push = (*repo.Permissions)["push"]
|
||||||
|
m.Pull = (*repo.Permissions)["pull"]
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script fetches the build script (.drone.yml) from the remote
|
||||||
|
// repository and returns in string format.
|
||||||
|
func (g *Github) Script(u *model.User, r *model.Repo, b *model.Build) ([]byte, []byte, error) {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
|
||||||
|
cfg, err := GetFile(client, r.Owner, r.Name, ".drone.yml", b.Commit)
|
||||||
|
sec, _ := GetFile(client, r.Owner, r.Name, ".drone.sec", b.Commit)
|
||||||
|
return cfg, sec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status sends the commit status to the remote system.
|
||||||
|
// An example would be the GitHub pull request status.
|
||||||
|
func (g *Github) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
|
||||||
|
status := getStatus(b.Status)
|
||||||
|
desc := getDesc(b.Status)
|
||||||
|
data := github.RepoStatus{
|
||||||
|
Context: github.String("Drone"),
|
||||||
|
State: github.String(status),
|
||||||
|
Description: github.String(desc),
|
||||||
|
TargetURL: github.String(link),
|
||||||
|
}
|
||||||
|
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, b.Commit, &data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
|
// private repositories from a remote system.
|
||||||
|
func (g *Github) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
|
url_, err := url.Parse(g.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netrc := &model.Netrc{}
|
||||||
|
netrc.Login = u.Token
|
||||||
|
netrc.Password = "x-oauth-basic"
|
||||||
|
netrc.Machine = url_.Host
|
||||||
|
return netrc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
|
// adding the SSH deploy key, if applicable.
|
||||||
|
func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
title, err := GetKeyTitle(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the CloneURL is using the SSHURL then we know that
|
||||||
|
// we need to add an SSH key to GitHub.
|
||||||
|
if r.IsPrivate || g.PrivateMode {
|
||||||
|
_, err = CreateUpdateKey(client, r.Owner, r.Name, title, k.Public)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = CreateUpdateHook(client, r.Owner, r.Name, link)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
|
// which are equal to link and removing the SSH deploy key.
|
||||||
|
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
title, err := GetKeyTitle(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the deploy-key if it is installed remote.
|
||||||
|
if r.IsPrivate || g.PrivateMode {
|
||||||
|
if err := DeleteKey(client, r.Owner, r.Name, title); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeleteHook(client, r.Owner, r.Name, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook parses the post-commit hook from the Request body
|
||||||
|
// and returns the required data in a standard format.
|
||||||
|
func (g *Github) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
|
|
||||||
|
switch r.Header.Get("X-Github-Event") {
|
||||||
|
case "pull_request":
|
||||||
|
return g.pullRequest(r)
|
||||||
|
case "push":
|
||||||
|
return g.push(r)
|
||||||
|
default:
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// push parses a hook with event type `push` and returns
|
||||||
|
// the commit data.
|
||||||
|
func (g *Github) push(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
|
payload := GetPayload(r)
|
||||||
|
hook := &pushHook{}
|
||||||
|
err := json.Unmarshal(payload, hook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if hook.Deleted {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &model.Repo{}
|
||||||
|
repo.Owner = hook.Repo.Owner.Login
|
||||||
|
if len(repo.Owner) == 0 {
|
||||||
|
repo.Owner = hook.Repo.Owner.Name
|
||||||
|
}
|
||||||
|
repo.Name = hook.Repo.Name
|
||||||
|
// Generating rather than using hook.Repo.FullName as it's
|
||||||
|
// not always present
|
||||||
|
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||||
|
repo.Link = hook.Repo.HTMLURL
|
||||||
|
repo.IsPrivate = hook.Repo.Private
|
||||||
|
repo.Clone = hook.Repo.CloneURL
|
||||||
|
repo.Branch = hook.Repo.DefaultBranch
|
||||||
|
|
||||||
|
build := &model.Build{}
|
||||||
|
build.Event = model.EventPush
|
||||||
|
build.Commit = hook.Head.ID
|
||||||
|
build.Ref = hook.Ref
|
||||||
|
build.Link = hook.Head.URL
|
||||||
|
build.Branch = strings.Replace(build.Ref, "refs/heads/", "", -1)
|
||||||
|
build.Message = hook.Head.Message
|
||||||
|
// build.Timestamp = hook.Head.Timestamp
|
||||||
|
// build.Email = hook.Head.Author.Email
|
||||||
|
build.Avatar = hook.Sender.Avatar
|
||||||
|
build.Author = hook.Sender.Login
|
||||||
|
build.Remote = hook.Repo.CloneURL
|
||||||
|
|
||||||
|
// we should ignore github pages
|
||||||
|
if build.Ref == "refs/heads/gh-pages" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo, build, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pullRequest parses a hook with event type `pullRequest`
|
||||||
|
// and returns the commit data.
|
||||||
|
func (g *Github) pullRequest(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
|
payload := GetPayload(r)
|
||||||
|
hook := &struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
PullRequest *github.PullRequest `json:"pull_request"`
|
||||||
|
Repo *github.Repository `json:"repository"`
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(payload, hook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore these
|
||||||
|
if hook.Action != "opened" && hook.Action != "synchronize" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
if *hook.PullRequest.State != "open" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &model.Repo{}
|
||||||
|
repo.Owner = *hook.Repo.Owner.Login
|
||||||
|
repo.Name = *hook.Repo.Name
|
||||||
|
repo.FullName = *hook.Repo.FullName
|
||||||
|
repo.Link = *hook.Repo.HTMLURL
|
||||||
|
repo.IsPrivate = *hook.Repo.Private
|
||||||
|
repo.Clone = *hook.Repo.CloneURL
|
||||||
|
repo.Branch = "master"
|
||||||
|
if hook.Repo.DefaultBranch != nil {
|
||||||
|
repo.Branch = *hook.Repo.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
build := &model.Build{}
|
||||||
|
build.Event = model.EventPull
|
||||||
|
build.Commit = *hook.PullRequest.Head.SHA
|
||||||
|
build.Ref = fmt.Sprintf("refs/pull/%d/merge", *hook.PullRequest.Number)
|
||||||
|
build.Link = *hook.PullRequest.HTMLURL
|
||||||
|
build.Branch = *hook.PullRequest.Head.Ref
|
||||||
|
build.Message = *hook.PullRequest.Title
|
||||||
|
build.Author = *hook.PullRequest.Head.User.Login
|
||||||
|
build.Avatar = *hook.PullRequest.Head.User.AvatarURL
|
||||||
|
build.Remote = *hook.PullRequest.Base.Repo.CloneURL
|
||||||
|
build.Title = *hook.PullRequest.Title
|
||||||
|
// build.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
||||||
|
|
||||||
|
return repo, build, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusSuccess = "success"
|
||||||
|
StatusFailure = "failure"
|
||||||
|
StatusError = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DescPending = "this build is pending"
|
||||||
|
DescSuccess = "the build was successful"
|
||||||
|
DescFailure = "the build failed"
|
||||||
|
DescError = "oops, something went wrong"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getStatus is a helper functin that converts a Drone
|
||||||
|
// status to a GitHub status.
|
||||||
|
func getStatus(status string) string {
|
||||||
|
switch status {
|
||||||
|
case model.StatusPending, model.StatusRunning:
|
||||||
|
return StatusPending
|
||||||
|
case model.StatusSuccess:
|
||||||
|
return StatusSuccess
|
||||||
|
case model.StatusFailure:
|
||||||
|
return StatusFailure
|
||||||
|
case model.StatusError, model.StatusKilled:
|
||||||
|
return StatusError
|
||||||
|
default:
|
||||||
|
return StatusError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDesc is a helper function that generates a description
|
||||||
|
// message for the build based on the status.
|
||||||
|
func getDesc(status string) string {
|
||||||
|
switch status {
|
||||||
|
case model.StatusPending, model.StatusRunning:
|
||||||
|
return DescPending
|
||||||
|
case model.StatusSuccess:
|
||||||
|
return DescSuccess
|
||||||
|
case model.StatusFailure:
|
||||||
|
return DescFailure
|
||||||
|
case model.StatusError, model.StatusKilled:
|
||||||
|
return DescError
|
||||||
|
default:
|
||||||
|
return DescError
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,478 +0,0 @@
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
|
|
||||||
"github.com/drone/drone/pkg/oauth2"
|
|
||||||
"github.com/drone/drone/pkg/remote"
|
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
"github.com/drone/drone/pkg/utils/httputil"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultURL = "https://github.com"
|
|
||||||
DefaultAPI = "https://api.github.com"
|
|
||||||
DefaultScope = "repo,repo:status,user:email"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GitHub struct {
|
|
||||||
URL string
|
|
||||||
API string
|
|
||||||
Client string
|
|
||||||
Secret string
|
|
||||||
AllowedOrgs []string
|
|
||||||
Open bool
|
|
||||||
PrivateMode bool
|
|
||||||
SkipVerify bool
|
|
||||||
|
|
||||||
cache *lru.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
remote.Register("github", NewDriver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDriver(config string) (remote.Remote, error) {
|
|
||||||
url_, err := url.Parse(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params := url_.Query()
|
|
||||||
url_.Path = ""
|
|
||||||
url_.RawQuery = ""
|
|
||||||
|
|
||||||
github := GitHub{}
|
|
||||||
github.URL = url_.String()
|
|
||||||
github.Client = params.Get("client_id")
|
|
||||||
github.Secret = params.Get("client_secret")
|
|
||||||
github.AllowedOrgs = params["orgs"]
|
|
||||||
github.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
|
|
||||||
github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
|
||||||
github.Open, _ = strconv.ParseBool(params.Get("open"))
|
|
||||||
|
|
||||||
if github.URL == DefaultURL {
|
|
||||||
github.API = DefaultAPI
|
|
||||||
} else {
|
|
||||||
github.API = github.URL + "/api/v3/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// here we cache permissions to avoid too many api
|
|
||||||
// calls. this should really be moved outise the
|
|
||||||
// remote plugin into the app
|
|
||||||
github.cache, err = lru.New(1028)
|
|
||||||
return &github, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GitHub) Login(token, secret string) (*common.User, error) {
|
|
||||||
client := NewClient(g.API, token, g.SkipVerify)
|
|
||||||
login, err := GetUserEmail(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user := common.User{}
|
|
||||||
user.Login = *login.Login
|
|
||||||
user.Email = *login.Email
|
|
||||||
user.Token = token
|
|
||||||
user.Secret = secret
|
|
||||||
user.Avatar = *login.AvatarURL
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Orgs fetches the organizations for the given user.
|
|
||||||
func (g *GitHub) Orgs(u *common.User) ([]string, error) {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
orgs_ := []string{}
|
|
||||||
orgs, err := GetOrgs(client)
|
|
||||||
if err != nil {
|
|
||||||
return orgs_, err
|
|
||||||
}
|
|
||||||
for _, org := range orgs {
|
|
||||||
orgs_ = append(orgs_, *org.Login)
|
|
||||||
}
|
|
||||||
return orgs_, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessor method, to allowed remote organizations field.
|
|
||||||
func (g *GitHub) GetOrgs() []string {
|
|
||||||
return g.AllowedOrgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessor method, to open field.
|
|
||||||
func (g *GitHub) GetOpen() bool {
|
|
||||||
return g.Open
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
|
||||||
func (g *GitHub) Repo(u *common.User, owner, name string) (*common.Repo, error) {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
repo_, err := GetRepo(client, owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &common.Repo{}
|
|
||||||
repo.Owner = owner
|
|
||||||
repo.Name = name
|
|
||||||
repo.FullName = *repo_.FullName
|
|
||||||
repo.Link = *repo_.HTMLURL
|
|
||||||
repo.Private = *repo_.Private
|
|
||||||
repo.Clone = *repo_.CloneURL
|
|
||||||
repo.Branch = "master"
|
|
||||||
repo.Avatar = *repo_.Owner.AvatarURL
|
|
||||||
|
|
||||||
if repo_.DefaultBranch != nil {
|
|
||||||
repo.Branch = *repo_.DefaultBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.PrivateMode {
|
|
||||||
repo.Private = true
|
|
||||||
}
|
|
||||||
return repo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perm fetches the named repository from the remote system.
|
|
||||||
func (g *GitHub) Perm(u *common.User, owner, name string) (*common.Perm, error) {
|
|
||||||
key := fmt.Sprintf("%s/%s/%s", u.Login, owner, name)
|
|
||||||
val, ok := g.cache.Get(key)
|
|
||||||
if ok {
|
|
||||||
return val.(*common.Perm), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
repo, err := GetRepo(client, owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := &common.Perm{}
|
|
||||||
m.Admin = (*repo.Permissions)["admin"]
|
|
||||||
m.Push = (*repo.Permissions)["push"]
|
|
||||||
m.Pull = (*repo.Permissions)["pull"]
|
|
||||||
g.cache.Add(key, m)
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script fetches the build script (.drone.yml) from the remote
|
|
||||||
// repository and returns in string format.
|
|
||||||
func (g *GitHub) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, []byte, error) {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
|
|
||||||
cfg, err := GetFile(client, r.Owner, r.Name, ".drone.yml", b.Commit.Sha)
|
|
||||||
sec, _ := GetFile(client, r.Owner, r.Name, ".drone.sec", b.Commit.Sha)
|
|
||||||
return cfg, sec, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
|
||||||
// private repositories from a remote system.
|
|
||||||
func (g *GitHub) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
|
|
||||||
url_, err := url.Parse(g.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
netrc := &common.Netrc{}
|
|
||||||
netrc.Login = u.Token
|
|
||||||
netrc.Password = "x-oauth-basic"
|
|
||||||
netrc.Machine = url_.Host
|
|
||||||
return netrc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
|
||||||
// adding the SSH deploy key, if applicable.
|
|
||||||
func (g *GitHub) Activate(u *common.User, r *common.Repo, k *common.Keypair, link string) error {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
title, err := GetKeyTitle(link)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the CloneURL is using the SSHURL then we know that
|
|
||||||
// we need to add an SSH key to GitHub.
|
|
||||||
if r.Private || g.PrivateMode {
|
|
||||||
_, err = CreateUpdateKey(client, r.Owner, r.Name, title, k.Public)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = CreateUpdateHook(client, r.Owner, r.Name, link)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
|
||||||
// which are equal to link and removing the SSH deploy key.
|
|
||||||
func (g *GitHub) Deactivate(u *common.User, r *common.Repo, link string) error {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
title, err := GetKeyTitle(link)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the deploy-key if it is installed remote.
|
|
||||||
if r.Private || g.PrivateMode {
|
|
||||||
if err := DeleteKey(client, r.Owner, r.Name, title); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DeleteHook(client, r.Owner, r.Name, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GitHub) Status(u *common.User, r *common.Repo, b *common.Build) error {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
|
||||||
|
|
||||||
link := fmt.Sprintf("%s/%v", r.Self, b.Number)
|
|
||||||
status := getStatus(b.Status)
|
|
||||||
desc := getDesc(b.Status)
|
|
||||||
data := github.RepoStatus{
|
|
||||||
Context: github.String("Drone"),
|
|
||||||
State: github.String(status),
|
|
||||||
Description: github.String(desc),
|
|
||||||
TargetURL: github.String(link),
|
|
||||||
}
|
|
||||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, b.Commit.Sha, &data)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
|
||||||
// and returns the required data in a standard format.
|
|
||||||
func (g *GitHub) Hook(r *http.Request) (*common.Hook, error) {
|
|
||||||
switch r.Header.Get("X-Github-Event") {
|
|
||||||
case "pull_request":
|
|
||||||
return g.pullRequest(r)
|
|
||||||
case "push":
|
|
||||||
return g.push(r)
|
|
||||||
default:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return default scope for GitHub
|
|
||||||
func (g *GitHub) Scope() string {
|
|
||||||
return DefaultScope
|
|
||||||
}
|
|
||||||
|
|
||||||
// push parses a hook with event type `push` and returns
|
|
||||||
// the commit data.
|
|
||||||
func (g *GitHub) push(r *http.Request) (*common.Hook, error) {
|
|
||||||
payload := GetPayload(r)
|
|
||||||
hook := &pushHook{}
|
|
||||||
err := json.Unmarshal(payload, hook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hook.Deleted {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &common.Repo{}
|
|
||||||
repo.Owner = hook.Repo.Owner.Login
|
|
||||||
if len(repo.Owner) == 0 {
|
|
||||||
repo.Owner = hook.Repo.Owner.Name
|
|
||||||
}
|
|
||||||
repo.Name = hook.Repo.Name
|
|
||||||
// Generating rather than using hook.Repo.FullName as it's
|
|
||||||
// not always present
|
|
||||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
|
||||||
repo.Link = hook.Repo.HTMLURL
|
|
||||||
repo.Private = hook.Repo.Private
|
|
||||||
repo.Clone = hook.Repo.CloneURL
|
|
||||||
repo.Branch = hook.Repo.DefaultBranch
|
|
||||||
|
|
||||||
commit := &common.Commit{}
|
|
||||||
commit.Sha = hook.Head.ID
|
|
||||||
commit.Ref = hook.Ref
|
|
||||||
commit.Link = hook.Head.URL
|
|
||||||
commit.Branch = strings.Replace(commit.Ref, "refs/heads/", "", -1)
|
|
||||||
commit.Message = hook.Head.Message
|
|
||||||
commit.Timestamp = hook.Head.Timestamp
|
|
||||||
commit.Author = &common.Author{}
|
|
||||||
commit.Author.Email = hook.Head.Author.Email
|
|
||||||
commit.Author.Login = hook.Head.Author.Username
|
|
||||||
commit.Remote = hook.Repo.CloneURL
|
|
||||||
|
|
||||||
// we should ignore github pages
|
|
||||||
if commit.Ref == "refs/heads/gh-pages" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &common.Hook{Event: "push", Repo: repo, Commit: commit}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ¯\_(ツ)_/¯
|
|
||||||
func (g *GitHub) Oauth2Transport(r *http.Request) *oauth2.Transport {
|
|
||||||
return &oauth2.Transport{
|
|
||||||
Config: &oauth2.Config{
|
|
||||||
ClientId: g.Client,
|
|
||||||
ClientSecret: g.Secret,
|
|
||||||
Scope: DefaultScope,
|
|
||||||
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
|
|
||||||
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
|
|
||||||
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(r)),
|
|
||||||
//settings.Server.Scheme, settings.Server.Hostname),
|
|
||||||
},
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pullRequest parses a hook with event type `pullRequest`
|
|
||||||
// and returns the commit data.
|
|
||||||
func (g *GitHub) pullRequest(r *http.Request) (*common.Hook, error) {
|
|
||||||
payload := GetPayload(r)
|
|
||||||
hook := &struct {
|
|
||||||
Action string `json:"action"`
|
|
||||||
PullRequest *github.PullRequest `json:"pull_request"`
|
|
||||||
Repo *github.Repository `json:"repository"`
|
|
||||||
}{}
|
|
||||||
err := json.Unmarshal(payload, hook)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore these
|
|
||||||
if hook.Action != "opened" && hook.Action != "synchronize" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if *hook.PullRequest.State != "open" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := &common.Repo{}
|
|
||||||
repo.Owner = *hook.Repo.Owner.Login
|
|
||||||
repo.Name = *hook.Repo.Name
|
|
||||||
repo.FullName = *hook.Repo.FullName
|
|
||||||
repo.Link = *hook.Repo.HTMLURL
|
|
||||||
repo.Private = *hook.Repo.Private
|
|
||||||
repo.Clone = *hook.Repo.CloneURL
|
|
||||||
repo.Branch = "master"
|
|
||||||
if hook.Repo.DefaultBranch != nil {
|
|
||||||
repo.Branch = *hook.Repo.DefaultBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &common.Commit{}
|
|
||||||
c.Sha = *hook.PullRequest.Head.SHA
|
|
||||||
c.Ref = *hook.PullRequest.Head.Ref
|
|
||||||
c.Ref = fmt.Sprintf("refs/pull/%d/merge", *hook.PullRequest.Number)
|
|
||||||
c.Branch = *hook.PullRequest.Head.Ref
|
|
||||||
c.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
|
||||||
c.Remote = *hook.PullRequest.Head.Repo.CloneURL
|
|
||||||
c.Author = &common.Author{}
|
|
||||||
c.Author.Login = *hook.PullRequest.Head.User.Login
|
|
||||||
|
|
||||||
// Author.Email
|
|
||||||
// Message
|
|
||||||
|
|
||||||
pr := &common.PullRequest{}
|
|
||||||
pr.Number = *hook.PullRequest.Number
|
|
||||||
pr.Title = *hook.PullRequest.Title
|
|
||||||
pr.Base = &common.Commit{}
|
|
||||||
pr.Base.Sha = *hook.PullRequest.Base.SHA
|
|
||||||
pr.Base.Ref = *hook.PullRequest.Base.Ref
|
|
||||||
pr.Base.Remote = *hook.PullRequest.Base.Repo.CloneURL
|
|
||||||
pr.Link = *hook.PullRequest.HTMLURL
|
|
||||||
// Branch
|
|
||||||
// Message
|
|
||||||
// Timestamp
|
|
||||||
// Author.Login
|
|
||||||
// Author.Email
|
|
||||||
|
|
||||||
return &common.Hook{Event: "pull_request", Repo: repo, Commit: c, PullRequest: pr}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pushHook struct {
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
Deleted bool `json:"deleted"`
|
|
||||||
|
|
||||||
Head struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Timestamp string `json:"timestamp"`
|
|
||||||
|
|
||||||
Author struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
} `json:"author"`
|
|
||||||
|
|
||||||
Committer struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
} `json:"committer"`
|
|
||||||
} `json:"head_commit"`
|
|
||||||
|
|
||||||
Repo struct {
|
|
||||||
Owner struct {
|
|
||||||
Login string `json:"login"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"owner"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
CloneURL string `json:"clone_url"`
|
|
||||||
DefaultBranch string `json:"default_branch"`
|
|
||||||
} `json:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusPending = "pending"
|
|
||||||
StatusSuccess = "success"
|
|
||||||
StatusFailure = "failure"
|
|
||||||
StatusError = "error"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DescPending = "this build is pending"
|
|
||||||
DescSuccess = "the build was successful"
|
|
||||||
DescFailure = "the build failed"
|
|
||||||
DescError = "oops, something went wrong"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getStatus is a helper functin that converts a Drone
|
|
||||||
// status to a GitHub status.
|
|
||||||
func getStatus(status string) string {
|
|
||||||
switch status {
|
|
||||||
case common.StatePending, common.StateRunning:
|
|
||||||
return StatusPending
|
|
||||||
case common.StateSuccess:
|
|
||||||
return StatusSuccess
|
|
||||||
case common.StateFailure:
|
|
||||||
return StatusFailure
|
|
||||||
case common.StateError, common.StateKilled:
|
|
||||||
return StatusError
|
|
||||||
default:
|
|
||||||
return StatusError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDesc is a helper function that generates a description
|
|
||||||
// message for the build based on the status.
|
|
||||||
func getDesc(status string) string {
|
|
||||||
switch status {
|
|
||||||
case common.StatePending, common.StateRunning:
|
|
||||||
return DescPending
|
|
||||||
case common.StateSuccess:
|
|
||||||
return DescSuccess
|
|
||||||
case common.StateFailure:
|
|
||||||
return DescFailure
|
|
||||||
case common.StateError, common.StateKilled:
|
|
||||||
return DescError
|
|
||||||
default:
|
|
||||||
return DescError
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/google/go-github/github"
|
"github.com/drone/drone/shared/oauth2"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gorilla/securecookie"
|
"github.com/google/go-github/github"
|
||||||
"github.com/drone/drone/pkg/oauth2"
|
"github.com/gorilla/securecookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient is a helper function that returns a new GitHub
|
// NewClient is a helper function that returns a new GitHub
|
48
remote/github/types.go
Normal file
48
remote/github/types.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
type postHook struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type pushHook struct {
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
|
|
||||||
|
Head struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
|
||||||
|
Author struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"author"`
|
||||||
|
|
||||||
|
Committer struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"committer"`
|
||||||
|
} `json:"head_commit"`
|
||||||
|
|
||||||
|
Sender struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo struct {
|
||||||
|
Owner struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"owner"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Private bool `json:"private"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
|
CloneURL string `json:"clone_url"`
|
||||||
|
DefaultBranch string `json:"default_branch"`
|
||||||
|
} `json:"repository"`
|
||||||
|
}
|
|
@ -9,13 +9,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
|
"github.com/drone/drone/shared/envconfig"
|
||||||
"github.com/drone/drone/pkg/oauth2"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/pkg/remote"
|
"github.com/drone/drone/shared/oauth2"
|
||||||
"github.com/drone/drone/pkg/token"
|
"github.com/drone/drone/shared/token"
|
||||||
common "github.com/drone/drone/pkg/types"
|
|
||||||
"github.com/drone/drone/pkg/utils/httputil"
|
"github.com/Bugagazavr/go-gitlab-client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -32,18 +32,14 @@ type Gitlab struct {
|
||||||
PrivateMode bool
|
PrivateMode bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
Search bool
|
Search bool
|
||||||
|
|
||||||
cache *lru.Cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func Load(env envconfig.Env) *Gitlab {
|
||||||
remote.Register("gitlab", NewDriver)
|
config := env.String("REMOTE_CONFIG", "")
|
||||||
}
|
|
||||||
|
|
||||||
func NewDriver(config string) (remote.Remote, error) {
|
|
||||||
url_, err := url.Parse(config)
|
url_, err := url.Parse(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(err)
|
||||||
}
|
}
|
||||||
params := url_.Query()
|
params := url_.Query()
|
||||||
url_.RawQuery = ""
|
url_.RawQuery = ""
|
||||||
|
@ -66,40 +62,71 @@ func NewDriver(config string) (remote.Remote, error) {
|
||||||
// this is a temp workaround
|
// this is a temp workaround
|
||||||
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
|
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
|
||||||
|
|
||||||
// here we cache permissions to avoid too many api
|
return &gitlab
|
||||||
// calls. this should really be moved outise the
|
|
||||||
// remote plugin into the app
|
|
||||||
gitlab.cache, err = lru.New(1028)
|
|
||||||
return &gitlab, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gitlab) Login(token, secret string) (*common.User, error) {
|
// Login authenticates the session and returns the
|
||||||
client := NewClient(g.URL, token, g.SkipVerify)
|
// remote user details.
|
||||||
var login, err = client.CurrentUser()
|
func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
var config = &oauth2.Config{
|
||||||
|
ClientId: g.Client,
|
||||||
|
ClientSecret: g.Secret,
|
||||||
|
Scope: DefaultScope,
|
||||||
|
AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL),
|
||||||
|
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
|
||||||
|
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
|
||||||
}
|
}
|
||||||
user := common.User{}
|
|
||||||
|
trans_ := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the OAuth code
|
||||||
|
var code = req.FormValue("code")
|
||||||
|
if len(code) == 0 {
|
||||||
|
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var trans = &oauth2.Transport{Config: config, Transport: trans_}
|
||||||
|
var token_, err = trans.Exchange(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewClient(g.URL, token_.AccessToken, g.SkipVerify)
|
||||||
|
login, err := client.CurrentUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
user := &model.User{}
|
||||||
user.Login = login.Username
|
user.Login = login.Username
|
||||||
user.Email = login.Email
|
user.Email = login.Email
|
||||||
user.Token = token
|
user.Token = token_.AccessToken
|
||||||
user.Secret = secret
|
user.Secret = token_.RefreshToken
|
||||||
|
|
||||||
if strings.HasPrefix(login.AvatarUrl, "http") {
|
if strings.HasPrefix(login.AvatarUrl, "http") {
|
||||||
user.Avatar = login.AvatarUrl
|
user.Avatar = login.AvatarUrl
|
||||||
} else {
|
} else {
|
||||||
user.Avatar = g.URL + "/" + login.AvatarUrl
|
user.Avatar = g.URL + "/" + login.AvatarUrl
|
||||||
}
|
}
|
||||||
return &user, nil
|
|
||||||
|
return user, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orgs fetches the organizations for the given user.
|
func (g *Gitlab) Auth(token, secret string) (string, error) {
|
||||||
func (g *Gitlab) Orgs(u *common.User) ([]string, error) {
|
client := NewClient(g.URL, token, g.SkipVerify)
|
||||||
return nil, nil
|
login, err := client.CurrentUser()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return login.Username, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Repo fetches the named repository from the remote system.
|
||||||
func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error) {
|
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, owner, name)
|
id, err := GetProjectId(g, client, owner, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -110,7 +137,7 @@ func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repo := &common.Repo{}
|
repo := &model.Repo{}
|
||||||
repo.Owner = owner
|
repo.Owner = owner
|
||||||
repo.Name = name
|
repo.Name = name
|
||||||
repo.FullName = repo_.PathWithNamespace
|
repo.FullName = repo_.PathWithNamespace
|
||||||
|
@ -123,22 +150,44 @@ func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.PrivateMode {
|
if g.PrivateMode {
|
||||||
repo.Private = true
|
repo.IsPrivate = true
|
||||||
} else {
|
} else {
|
||||||
repo.Private = !repo_.Public
|
repo.IsPrivate = !repo_.Public
|
||||||
}
|
}
|
||||||
|
|
||||||
return repo, err
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perm fetches the named repository from the remote system.
|
// Repos fetches a list of repos from the remote system.
|
||||||
func (g *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error) {
|
func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
key := fmt.Sprintf("%s/%s/%s", u.Login, owner, name)
|
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||||
val, ok := g.cache.Get(key)
|
|
||||||
if ok {
|
var repos = []*model.RepoLite{}
|
||||||
return val.(*common.Perm), nil
|
|
||||||
|
all, err := client.AllProjects()
|
||||||
|
if err != nil {
|
||||||
|
return repos, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, repo := range all {
|
||||||
|
var parts = strings.Split(repo.PathWithNamespace, "/")
|
||||||
|
var owner = parts[0]
|
||||||
|
var name = parts[1]
|
||||||
|
|
||||||
|
repos = append(repos, &model.RepoLite{
|
||||||
|
Owner: owner,
|
||||||
|
Name: name,
|
||||||
|
FullName: repo.PathWithNamespace,
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: add repo.AvatarUrl
|
||||||
|
}
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm fetches the named repository from the remote system.
|
||||||
|
func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||||
|
|
||||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, owner, name)
|
id, err := GetProjectId(g, client, owner, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -149,43 +198,42 @@ func (g *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := &common.Perm{}
|
m := &model.Perm{}
|
||||||
m.Admin = IsAdmin(repo)
|
m.Admin = IsAdmin(repo)
|
||||||
m.Pull = IsRead(repo)
|
m.Pull = IsRead(repo)
|
||||||
m.Push = IsWrite(repo)
|
m.Push = IsWrite(repo)
|
||||||
g.cache.Add(key, m)
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
// GetScript fetches the build script (.drone.yml) from the remote
|
||||||
// repository and returns in string format.
|
// repository and returns in string format.
|
||||||
func (g *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, []byte, error) {
|
func (g *Gitlab) Script(user *model.User, repo *model.Repo, build *model.Build) ([]byte, []byte, error) {
|
||||||
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := client.RepoRawFile(id, build.Commit.Sha, ".drone.yml")
|
cfg, err := client.RepoRawFile(id, build.Commit, ".drone.yml")
|
||||||
enc, _ := client.RepoRawFile(id, build.Commit.Sha, ".drone.sec")
|
enc, _ := client.RepoRawFile(id, build.Commit, ".drone.sec")
|
||||||
return cfg, enc, err
|
return cfg, enc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE Currently gitlab doesn't support status for commits and events,
|
// NOTE Currently gitlab doesn't support status for commits and events,
|
||||||
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
|
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
|
||||||
// gitlab uses API to fetch build status on client side. But for now we skip this.
|
// gitlab uses API to fetch build status on client side. But for now we skip this.
|
||||||
func (g *Gitlab) Status(u *common.User, repo *common.Repo, b *common.Build) error {
|
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
// private repositories from a remote system.
|
// private repositories from a remote system.
|
||||||
func (g *Gitlab) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
|
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
url_, err := url.Parse(g.URL)
|
url_, err := url.Parse(g.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
netrc := &common.Netrc{}
|
netrc := &model.Netrc{}
|
||||||
netrc.Machine = url_.Host
|
netrc.Machine = url_.Host
|
||||||
|
|
||||||
switch g.CloneMode {
|
switch g.CloneMode {
|
||||||
|
@ -202,7 +250,7 @@ func (g *Gitlab) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
|
||||||
|
|
||||||
// Activate activates a repository by adding a Post-commit hook and
|
// Activate activates a repository by adding a Post-commit hook and
|
||||||
// a Public Deploy key, if applicable.
|
// a Public Deploy key, if applicable.
|
||||||
func (g *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypair, link string) error {
|
func (g *Gitlab) Activate(user *model.User, repo *model.Repo, k *model.Key, link string) error {
|
||||||
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -227,7 +275,7 @@ func (g *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypai
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// which are equal to link and removing the SSH deploy key.
|
||||||
func (g *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) error {
|
func (g *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) error {
|
||||||
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -239,12 +287,12 @@ func (g *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) e
|
||||||
|
|
||||||
// ParseHook parses the post-commit hook from the Request body
|
// ParseHook parses the post-commit hook from the Request body
|
||||||
// and returns the required data in a standard format.
|
// and returns the required data in a standard format.
|
||||||
func (g *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
|
func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
var payload, _ = ioutil.ReadAll(req.Body)
|
var payload, _ = ioutil.ReadAll(req.Body)
|
||||||
var parsed, err = gogitlab.ParseHook(payload)
|
var parsed, err = gogitlab.ParseHook(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch parsed.ObjectKind {
|
switch parsed.ObjectKind {
|
||||||
|
@ -253,92 +301,87 @@ func (g *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
|
||||||
case "tag_push", "push":
|
case "tag_push", "push":
|
||||||
return push(parsed, req)
|
return push(parsed, req)
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) {
|
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
var hook = new(common.Hook)
|
|
||||||
hook.Event = "pull_request"
|
|
||||||
hook.Repo = &common.Repo{}
|
|
||||||
hook.Repo.Owner = req.FormValue("owner")
|
|
||||||
hook.Repo.Name = req.FormValue("name")
|
|
||||||
hook.Repo.FullName = fmt.Sprintf("%s/%s", hook.Repo.Owner, hook.Repo.Name)
|
|
||||||
hook.Repo.Link = parsed.ObjectAttributes.Target.WebUrl
|
|
||||||
hook.Repo.Clone = parsed.ObjectAttributes.Target.HttpUrl
|
|
||||||
hook.Repo.Branch = "master"
|
|
||||||
|
|
||||||
hook.Commit = &common.Commit{}
|
repo := &model.Repo{}
|
||||||
hook.Commit.Message = parsed.ObjectAttributes.LastCommit.Message
|
repo.Owner = req.FormValue("owner")
|
||||||
hook.Commit.Sha = parsed.ObjectAttributes.LastCommit.Id
|
repo.Name = req.FormValue("name")
|
||||||
hook.Commit.Remote = parsed.ObjectAttributes.Source.HttpUrl
|
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||||
|
repo.Link = parsed.ObjectAttributes.Target.WebUrl
|
||||||
|
repo.Clone = parsed.ObjectAttributes.Target.HttpUrl
|
||||||
|
repo.Branch = "master"
|
||||||
|
|
||||||
|
build := &model.Build{}
|
||||||
|
build.Event = "pull_request"
|
||||||
|
build.Message = parsed.ObjectAttributes.LastCommit.Message
|
||||||
|
build.Commit = parsed.ObjectAttributes.LastCommit.Id
|
||||||
|
//build.Remote = parsed.ObjectAttributes.Source.HttpUrl
|
||||||
|
|
||||||
if parsed.ObjectAttributes.SourceProjectId == parsed.ObjectAttributes.TargetProjectId {
|
if parsed.ObjectAttributes.SourceProjectId == parsed.ObjectAttributes.TargetProjectId {
|
||||||
hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
|
build.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
|
||||||
} else {
|
} else {
|
||||||
hook.Commit.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
|
build.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch
|
build.Branch = parsed.ObjectAttributes.SourceBranch
|
||||||
hook.Commit.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp
|
// build.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp
|
||||||
|
|
||||||
hook.Commit.Author = &common.Author{}
|
build.Author = parsed.ObjectAttributes.LastCommit.Author.Name
|
||||||
hook.Commit.Author.Login = parsed.ObjectAttributes.LastCommit.Author.Name
|
build.Email = parsed.ObjectAttributes.LastCommit.Author.Email
|
||||||
hook.Commit.Author.Email = parsed.ObjectAttributes.LastCommit.Author.Email
|
build.Title = parsed.ObjectAttributes.Title
|
||||||
|
build.Link = parsed.ObjectAttributes.Url
|
||||||
|
|
||||||
hook.PullRequest = &common.PullRequest{}
|
return repo, build, nil
|
||||||
hook.PullRequest.Number = parsed.ObjectAttributes.IId
|
|
||||||
hook.PullRequest.Title = parsed.ObjectAttributes.Title
|
|
||||||
hook.PullRequest.Link = parsed.ObjectAttributes.Url
|
|
||||||
|
|
||||||
return hook, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func push(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) {
|
func push(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
var cloneUrl = parsed.Repository.GitHttpUrl
|
var cloneUrl = parsed.Repository.GitHttpUrl
|
||||||
|
|
||||||
var hook = new(common.Hook)
|
repo := &model.Repo{}
|
||||||
hook.Event = "push"
|
repo.Owner = req.FormValue("owner")
|
||||||
hook.Repo = &common.Repo{}
|
repo.Name = req.FormValue("name")
|
||||||
hook.Repo.Owner = req.FormValue("owner")
|
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||||
hook.Repo.Name = req.FormValue("name")
|
repo.Link = parsed.Repository.URL
|
||||||
hook.Repo.Link = parsed.Repository.URL
|
repo.Clone = cloneUrl
|
||||||
hook.Repo.Clone = cloneUrl
|
repo.Branch = "master"
|
||||||
hook.Repo.Branch = "master"
|
|
||||||
|
|
||||||
switch parsed.Repository.VisibilityLevel {
|
switch parsed.Repository.VisibilityLevel {
|
||||||
case 0:
|
case 0:
|
||||||
hook.Repo.Private = true
|
repo.IsPrivate = true
|
||||||
case 10:
|
case 10:
|
||||||
hook.Repo.Private = true
|
repo.IsPrivate = true
|
||||||
case 20:
|
case 20:
|
||||||
hook.Repo.Private = false
|
repo.IsPrivate = false
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.Repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
|
repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
|
||||||
|
|
||||||
hook.Commit = &common.Commit{}
|
build := &model.Build{}
|
||||||
hook.Commit.Sha = parsed.After
|
build.Event = model.EventPush
|
||||||
hook.Commit.Branch = parsed.Branch()
|
build.Commit = parsed.After
|
||||||
hook.Commit.Ref = parsed.Ref
|
build.Branch = parsed.Branch()
|
||||||
hook.Commit.Remote = cloneUrl
|
build.Ref = parsed.Ref
|
||||||
|
// hook.Commit.Remote = cloneUrl
|
||||||
|
|
||||||
var head = parsed.Head()
|
var head = parsed.Head()
|
||||||
hook.Commit.Message = head.Message
|
build.Message = head.Message
|
||||||
hook.Commit.Timestamp = head.Timestamp
|
// build.Timestamp = head.Timestamp
|
||||||
hook.Commit.Author = &common.Author{}
|
|
||||||
|
|
||||||
// extracts the commit author (ideally email)
|
// extracts the commit author (ideally email)
|
||||||
// from the post-commit hook
|
// from the post-commit hook
|
||||||
switch {
|
switch {
|
||||||
case head.Author != nil:
|
case head.Author != nil:
|
||||||
hook.Commit.Author.Email = head.Author.Email
|
build.Email = head.Author.Email
|
||||||
hook.Commit.Author.Login = parsed.UserName
|
build.Author = parsed.UserName
|
||||||
case head.Author == nil:
|
case head.Author == nil:
|
||||||
hook.Commit.Author.Login = parsed.UserName
|
build.Author = parsed.UserName
|
||||||
}
|
}
|
||||||
|
|
||||||
return hook, nil
|
return repo, build, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ¯\_(ツ)_/¯
|
// ¯\_(ツ)_/¯
|
|
@ -2,13 +2,12 @@ package gitlab
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/remote/builtin/gitlab/testdata"
|
"github.com/drone/drone/remote/gitlab/testdata"
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/franela/goblin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Gitlab(t *testing.T) {
|
func Test_Gitlab(t *testing.T) {
|
||||||
|
@ -16,17 +15,17 @@ func Test_Gitlab(t *testing.T) {
|
||||||
var server = testdata.NewServer()
|
var server = testdata.NewServer()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
var gitlab, err = NewDriver(server.URL + "?client_id=test&client_secret=test")
|
env := map[string]string{}
|
||||||
if err != nil {
|
env["REMOTE_CONFIG"] = server.URL + "?client_id=test&client_secret=test"
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = types.User{
|
gitlab := Load(env)
|
||||||
|
|
||||||
|
var user = model.User{
|
||||||
Login: "test_user",
|
Login: "test_user",
|
||||||
Token: "e3b0c44298fc1c149afbf4c8996fb",
|
Token: "e3b0c44298fc1c149afbf4c8996fb",
|
||||||
}
|
}
|
||||||
|
|
||||||
var repo = types.Repo{
|
var repo = model.Repo{
|
||||||
Name: "diaspora-client",
|
Name: "diaspora-client",
|
||||||
Owner: "diaspora",
|
Owner: "diaspora",
|
||||||
}
|
}
|
||||||
|
@ -56,7 +55,6 @@ func Test_Gitlab(t *testing.T) {
|
||||||
g.It("Should return repo permissions", func() {
|
g.It("Should return repo permissions", func() {
|
||||||
perm, err := gitlab.Perm(&user, "diaspora", "diaspora-client")
|
perm, err := gitlab.Perm(&user, "diaspora", "diaspora-client")
|
||||||
|
|
||||||
fmt.Println(gitlab.(*Gitlab), err)
|
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(perm.Admin).Equal(true)
|
g.Assert(perm.Admin).Equal(true)
|
||||||
g.Assert(perm.Pull).Equal(true)
|
g.Assert(perm.Pull).Equal(true)
|
||||||
|
@ -73,13 +71,13 @@ func Test_Gitlab(t *testing.T) {
|
||||||
// Test activate method
|
// Test activate method
|
||||||
g.Describe("Activate", func() {
|
g.Describe("Activate", func() {
|
||||||
g.It("Should be success", func() {
|
g.It("Should be success", func() {
|
||||||
err := gitlab.Activate(&user, &repo, &types.Keypair{}, "http://example.com/api/hook/test/test?access_token=token")
|
err := gitlab.Activate(&user, &repo, &model.Key{}, "http://example.com/api/hook/test/test?access_token=token")
|
||||||
|
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should be failed, when token not given", func() {
|
g.It("Should be failed, when token not given", func() {
|
||||||
err := gitlab.Activate(&user, &repo, &types.Keypair{}, "http://example.com/api/hook/test/test")
|
err := gitlab.Activate(&user, &repo, &model.Key{}, "http://example.com/api/hook/test/test")
|
||||||
|
|
||||||
g.Assert(err != nil).IsTrue()
|
g.Assert(err != nil).IsTrue()
|
||||||
})
|
})
|
||||||
|
@ -95,20 +93,20 @@ func Test_Gitlab(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test login method
|
// Test login method
|
||||||
g.Describe("Login", func() {
|
// g.Describe("Login", func() {
|
||||||
g.It("Should return user", func() {
|
// g.It("Should return user", func() {
|
||||||
user, err := gitlab.Login("valid_token", "")
|
// user, err := gitlab.Login("valid_token", "")
|
||||||
|
|
||||||
g.Assert(err == nil).IsTrue()
|
// g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(user == nil).IsFalse()
|
// g.Assert(user == nil).IsFalse()
|
||||||
})
|
// })
|
||||||
|
|
||||||
g.It("Should return error, when token is invalid", func() {
|
// g.It("Should return error, when token is invalid", func() {
|
||||||
_, err := gitlab.Login("invalid_token", "")
|
// _, err := gitlab.Login("invalid_token", "")
|
||||||
|
|
||||||
g.Assert(err != nil).IsTrue()
|
// g.Assert(err != nil).IsTrue()
|
||||||
})
|
// })
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Test hook method
|
// Test hook method
|
||||||
g.Describe("Hook", func() {
|
g.Describe("Hook", func() {
|
||||||
|
@ -119,14 +117,13 @@ func Test_Gitlab(t *testing.T) {
|
||||||
bytes.NewReader(testdata.PushHook),
|
bytes.NewReader(testdata.PushHook),
|
||||||
)
|
)
|
||||||
|
|
||||||
hook, err := gitlab.Hook(req)
|
repo, build, err := gitlab.Hook(req)
|
||||||
|
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
g.Assert(repo.Owner).Equal("diaspora")
|
||||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
g.Assert(repo.Name).Equal("diaspora-client")
|
||||||
g.Assert(hook.Commit.Ref).Equal("refs/heads/master")
|
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||||
|
|
||||||
g.Assert(hook.PullRequest == nil).IsTrue()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should parse tag push hook", func() {
|
g.It("Should parse tag push hook", func() {
|
||||||
|
@ -136,14 +133,13 @@ func Test_Gitlab(t *testing.T) {
|
||||||
bytes.NewReader(testdata.TagHook),
|
bytes.NewReader(testdata.TagHook),
|
||||||
)
|
)
|
||||||
|
|
||||||
hook, err := gitlab.Hook(req)
|
repo, build, err := gitlab.Hook(req)
|
||||||
|
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
g.Assert(repo.Owner).Equal("diaspora")
|
||||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
g.Assert(repo.Name).Equal("diaspora-client")
|
||||||
g.Assert(hook.Commit.Ref).Equal("refs/tags/v1.0.0")
|
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
|
||||||
|
|
||||||
g.Assert(hook.PullRequest == nil).IsTrue()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should parse merge request hook", func() {
|
g.It("Should parse merge request hook", func() {
|
||||||
|
@ -153,14 +149,13 @@ func Test_Gitlab(t *testing.T) {
|
||||||
bytes.NewReader(testdata.MergeRequestHook),
|
bytes.NewReader(testdata.MergeRequestHook),
|
||||||
)
|
)
|
||||||
|
|
||||||
hook, err := gitlab.Hook(req)
|
repo, build, err := gitlab.Hook(req)
|
||||||
|
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
g.Assert(repo.Owner).Equal("diaspora")
|
||||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
g.Assert(repo.Name).Equal("diaspora-client")
|
||||||
|
|
||||||
g.Assert(hook.PullRequest.Number).Equal(1)
|
g.Assert(build.Title).Equal("MS-Viewport")
|
||||||
g.Assert(hook.PullRequest.Title).Equal("MS-Viewport")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
|
"github.com/Bugagazavr/go-gitlab-client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient is a helper function that returns a new GitHub
|
// NewClient is a helper function that returns a new GitHub
|
|
@ -3,91 +3,70 @@ package remote
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/oauth2"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/pkg/types"
|
"github.com/drone/drone/remote/github"
|
||||||
|
"github.com/drone/drone/remote/gitlab"
|
||||||
|
"github.com/drone/drone/shared/envconfig"
|
||||||
|
|
||||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var drivers = make(map[string]DriverFunc)
|
func Load(env envconfig.Env) Remote {
|
||||||
|
driver := env.Get("REMOTE_DRIVER")
|
||||||
|
|
||||||
// Register makes a remote driver available by the provided name.
|
switch driver {
|
||||||
// If Register is called twice with the same name or if driver is nil,
|
case "github":
|
||||||
// it panics.
|
return github.Load(env)
|
||||||
func Register(name string, driver DriverFunc) {
|
case "gitlab":
|
||||||
if driver == nil {
|
return gitlab.Load(env)
|
||||||
panic("remote: Register driver is nil")
|
|
||||||
}
|
|
||||||
if _, dup := drivers[name]; dup {
|
|
||||||
panic("remote: Register called twice for driver " + name)
|
|
||||||
}
|
|
||||||
drivers[name] = driver
|
|
||||||
}
|
|
||||||
|
|
||||||
// DriverFunc returns a new connection to the remote.
|
default:
|
||||||
// Config is a struct, with base remote configuration.
|
log.Fatalf("unknown remote driver %s", driver)
|
||||||
type DriverFunc func(config string) (Remote, error)
|
|
||||||
|
|
||||||
// New creates a new remote connection.
|
|
||||||
func New(driver, config string) (Remote, error) {
|
|
||||||
fn, ok := drivers[driver]
|
|
||||||
if !ok {
|
|
||||||
log.Fatalf("remote: unknown driver %q", driver)
|
|
||||||
}
|
}
|
||||||
log.Infof("remote: loading driver %s", driver)
|
|
||||||
log.Infof("remote: loading config %s", config)
|
return nil
|
||||||
return fn(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Remote interface {
|
type Remote interface {
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates the session and returns the
|
||||||
// remote user details.
|
// remote user details.
|
||||||
Login(token, secret string) (*types.User, error)
|
Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error)
|
||||||
|
|
||||||
// Orgs fetches the organizations for the given user.
|
// Auth authenticates the session and returns the remote user
|
||||||
Orgs(u *types.User) ([]string, error)
|
// login for the given token and secret
|
||||||
|
Auth(token, secret string) (string, error)
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Repo fetches the named repository from the remote system.
|
||||||
Repo(u *types.User, owner, repo string) (*types.Repo, error)
|
Repo(u *model.User, owner, repo string) (*model.Repo, error)
|
||||||
|
|
||||||
|
// Repos fetches a list of repos from the remote system.
|
||||||
|
Repos(u *model.User) ([]*model.RepoLite, error)
|
||||||
|
|
||||||
// Perm fetches the named repository permissions from
|
// Perm fetches the named repository permissions from
|
||||||
// the remote system for the specified user.
|
// the remote system for the specified user.
|
||||||
Perm(u *types.User, owner, repo string) (*types.Perm, error)
|
Perm(u *model.User, owner, repo string) (*model.Perm, error)
|
||||||
|
|
||||||
// Script fetches the build script (.drone.yml) from the remote
|
// Script fetches the build script (.drone.yml) from the remote
|
||||||
// repository and returns in string format.
|
// repository and returns in string format.
|
||||||
Script(u *types.User, r *types.Repo, b *types.Build) ([]byte, []byte, error)
|
Script(u *model.User, r *model.Repo, b *model.Build) ([]byte, []byte, error)
|
||||||
|
|
||||||
// Status sends the commit status to the remote system.
|
// Status sends the commit status to the remote system.
|
||||||
// An example would be the GitHub pull request status.
|
// An example would be the GitHub pull request status.
|
||||||
Status(u *types.User, r *types.Repo, b *types.Build) error
|
Status(u *model.User, r *model.Repo, b *model.Build, link string) error
|
||||||
|
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
// private repositories from a remote system.
|
// private repositories from a remote system.
|
||||||
Netrc(u *types.User, r *types.Repo) (*types.Netrc, error)
|
Netrc(u *model.User, r *model.Repo) (*model.Netrc, error)
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
Activate(u *types.User, r *types.Repo, k *types.Keypair, link string) error
|
Activate(u *model.User, r *model.Repo, k *model.Key, link string) error
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// which are equal to link and removing the SSH deploy key.
|
||||||
Deactivate(u *types.User, r *types.Repo, link string) error
|
Deactivate(u *model.User, r *model.Repo, link string) error
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the post-commit hook from the Request body
|
||||||
// and returns the required data in a standard format.
|
// and returns the required data in a standard format.
|
||||||
Hook(r *http.Request) (*types.Hook, error)
|
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
||||||
|
|
||||||
// Oauth2Transport
|
|
||||||
Oauth2Transport(r *http.Request) *oauth2.Transport
|
|
||||||
|
|
||||||
// GetOrgs returns all allowed organizations for remote.
|
|
||||||
GetOrgs() []string
|
|
||||||
|
|
||||||
// GetOpen returns boolean field with enabled or disabled
|
|
||||||
// registration.
|
|
||||||
GetOpen() bool
|
|
||||||
|
|
||||||
// Default scope for remote
|
|
||||||
Scope() string
|
|
||||||
}
|
}
|
||||||
|
|
42
router/middleware/context/context.go
Normal file
42
router/middleware/context/context.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/drone/drone/engine"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDatabase(db *sql.DB) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Set("database", db)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Database(c *gin.Context) *sql.DB {
|
||||||
|
return c.MustGet("database").(*sql.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRemote(remote remote.Remote) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Set("remote", remote)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Remote(c *gin.Context) remote.Remote {
|
||||||
|
return c.MustGet("remote").(remote.Remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEngine(engine engine.Engine) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Set("engine", engine)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Engine(c *gin.Context) engine.Engine {
|
||||||
|
return c.MustGet("engine").(engine.Engine)
|
||||||
|
}
|
40
router/middleware/header/header.go
Normal file
40
router/middleware/header/header.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package header
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetHeaders() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Writer.Header().Add("X-Frame-Options", "DENY")
|
||||||
|
c.Writer.Header().Add("X-Content-Type-Options", "nosniff")
|
||||||
|
c.Writer.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||||
|
c.Writer.Header().Add("Cache-Control", "no-cache")
|
||||||
|
c.Writer.Header().Add("Cache-Control", "no-store")
|
||||||
|
c.Writer.Header().Add("Cache-Control", "max-age=0")
|
||||||
|
c.Writer.Header().Add("Cache-Control", "must-revalidate")
|
||||||
|
c.Writer.Header().Add("Cache-Control", "value")
|
||||||
|
c.Writer.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||||
|
c.Writer.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
||||||
|
//c.Writer.Header().Set("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
|
||||||
|
if c.Request.TLS != nil {
|
||||||
|
c.Writer.Header().Add("Strict-Transport-Security", "max-age=31536000")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization")
|
||||||
|
c.Writer.Header().Set("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
|
||||||
|
c.Writer.Header().Set("Content-Type", "application/json")
|
||||||
|
c.Writer.WriteHeader(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
236
router/middleware/session/repo.go
Normal file
236
router/middleware/session/repo.go
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cache *lru.Cache
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
cache, err = lru.New(1028)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Repo(c *gin.Context) *model.Repo {
|
||||||
|
v, ok := c.Get("repo")
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, ok := v.(*model.Repo)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRepo() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
owner = c.Param("owner")
|
||||||
|
name = c.Param("name")
|
||||||
|
)
|
||||||
|
|
||||||
|
db := context.Database(c)
|
||||||
|
user := User(c)
|
||||||
|
repo, err := model.GetRepoName(db, owner, name)
|
||||||
|
if err == nil {
|
||||||
|
c.Set("repo", repo)
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user is not nil, check the remote system
|
||||||
|
// to see if the repository actually exists. If yes,
|
||||||
|
// we can prompt the user to add.
|
||||||
|
if user != nil {
|
||||||
|
remote := context.Remote(c)
|
||||||
|
repo, _ = remote.Repo(user, owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found a repository, we should display a page
|
||||||
|
// to the user allowing them to activate.
|
||||||
|
if repo != nil && len(repo.FullName) != 0 {
|
||||||
|
c.HTML(http.StatusNotFound, "repo_activate.html", data)
|
||||||
|
} else {
|
||||||
|
c.HTML(http.StatusNotFound, "404.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Perm(c *gin.Context) *model.Perm {
|
||||||
|
v, ok := c.Get("perm")
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, ok := v.(*model.Perm)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetPerm() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
user := User(c)
|
||||||
|
repo := Repo(c)
|
||||||
|
remote := context.Remote(c)
|
||||||
|
perm := &model.Perm{}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
// attempt to get the permissions from a local cache
|
||||||
|
// just to avoid excess API calls to GitHub
|
||||||
|
key := fmt.Sprintf("%d.%d", user.ID, repo.ID)
|
||||||
|
val, ok := cache.Get(key)
|
||||||
|
if ok {
|
||||||
|
c.Set("perm", val.(*model.Perm))
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
log.Debugf("%s using cached %+v permission to %s",
|
||||||
|
user.Login, val, repo.FullName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// if the user is not authenticated, and the
|
||||||
|
// repository is private, the user has NO permission
|
||||||
|
// to view the repository.
|
||||||
|
case user == nil && repo.IsPrivate == true:
|
||||||
|
perm.Pull = false
|
||||||
|
perm.Push = false
|
||||||
|
perm.Admin = false
|
||||||
|
|
||||||
|
// if the user is not authenticated, but the repository
|
||||||
|
// is public, the user has pull-rights only.
|
||||||
|
case user == nil && repo.IsPrivate == false:
|
||||||
|
perm.Pull = true
|
||||||
|
perm.Push = false
|
||||||
|
perm.Admin = false
|
||||||
|
|
||||||
|
case user.Admin:
|
||||||
|
perm.Pull = true
|
||||||
|
perm.Push = true
|
||||||
|
perm.Admin = true
|
||||||
|
|
||||||
|
// otherwise if the user is authenticated we should
|
||||||
|
// check the remote system to get the users permissiosn.
|
||||||
|
default:
|
||||||
|
var err error
|
||||||
|
perm, err = remote.Perm(user, repo.Owner, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
perm.Pull = false
|
||||||
|
perm.Push = false
|
||||||
|
perm.Admin = false
|
||||||
|
|
||||||
|
// debug
|
||||||
|
log.Errorf("Error fetching permission for %s %s",
|
||||||
|
user.Login, repo.FullName)
|
||||||
|
}
|
||||||
|
// if we couldn't fetch permissions, but the repository
|
||||||
|
// is public, we should grant the user pull access.
|
||||||
|
if err != nil && repo.IsPrivate == false {
|
||||||
|
perm.Pull = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
|
||||||
|
// cache the updated repository permissions to
|
||||||
|
// prevent un-necessary GitHub API requests.
|
||||||
|
key := fmt.Sprintf("%d.%d", user.ID, repo.ID)
|
||||||
|
cache.Add(key, perm)
|
||||||
|
|
||||||
|
// debug
|
||||||
|
log.Debugf("%s granted %+v permission to %s",
|
||||||
|
user.Login, perm, repo.FullName)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Debugf("Guest granted %+v to %s", perm, repo.FullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("perm", perm)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustPull(c *gin.Context) {
|
||||||
|
user := User(c)
|
||||||
|
repo := Repo(c)
|
||||||
|
perm := Perm(c)
|
||||||
|
|
||||||
|
if perm.Pull {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user doesn't have pull permission to the
|
||||||
|
// repository we display a 404 error to avoid leaking
|
||||||
|
// repository information.
|
||||||
|
c.HTML(http.StatusNotFound, "404.html", gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
"Perm": perm,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustPush(c *gin.Context) {
|
||||||
|
user := User(c)
|
||||||
|
repo := Repo(c)
|
||||||
|
perm := Perm(c)
|
||||||
|
|
||||||
|
// if the user has push access, immediately proceed
|
||||||
|
// the middleware execution chain.
|
||||||
|
if perm.Push {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := gin.H{
|
||||||
|
"User": user,
|
||||||
|
"Repo": repo,
|
||||||
|
"Perm": perm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user has pull access we should tell them
|
||||||
|
// the operation is not authorized. Otherwise we should
|
||||||
|
// give a 404 to avoid leaking information.
|
||||||
|
if !perm.Pull {
|
||||||
|
c.HTML(http.StatusNotFound, "404.html", data)
|
||||||
|
} else {
|
||||||
|
c.HTML(http.StatusUnauthorized, "401.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugging
|
||||||
|
if user != nil {
|
||||||
|
log.Debugf("%s denied write access to %s",
|
||||||
|
user.Login, c.Request.URL.Path)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Debugf("Guest denied write access to %s %s",
|
||||||
|
c.Request.Method,
|
||||||
|
c.Request.URL.Path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Abort()
|
||||||
|
}
|
98
router/middleware/session/user.go
Normal file
98
router/middleware/session/user.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/shared/token"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func User(c *gin.Context) *model.User {
|
||||||
|
v, ok := c.Get("user")
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, ok := v.(*model.User)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func Token(c *gin.Context) *token.Token {
|
||||||
|
v, ok := c.Get("token")
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, ok := v.(*token.Token)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetUser() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var user *model.User
|
||||||
|
|
||||||
|
t, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||||
|
var db = context.Database(c)
|
||||||
|
var err error
|
||||||
|
user, err = model.GetUserLogin(db, t.Text)
|
||||||
|
return user.Hash, err
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
c.Set("user", user)
|
||||||
|
|
||||||
|
// if this is a session token (ie not the API token)
|
||||||
|
// this means the user is accessing with a web browser,
|
||||||
|
// so we should implement CSRF protection measures.
|
||||||
|
if t.Kind == token.SessToken {
|
||||||
|
err = token.CheckCsrf(c.Request, func(t *token.Token) (string, error) {
|
||||||
|
return user.Hash, nil
|
||||||
|
})
|
||||||
|
// if csrf token validation fails, exit immediately
|
||||||
|
// with a not authorized error.
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustAdmin() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
user := User(c)
|
||||||
|
switch {
|
||||||
|
case user == nil:
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
// c.HTML(http.StatusUnauthorized, "401.html", gin.H{})
|
||||||
|
case user.Admin == false:
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
// c.HTML(http.StatusForbidden, "401.html", gin.H{})
|
||||||
|
default:
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustUser() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
user := User(c)
|
||||||
|
switch {
|
||||||
|
case user == nil:
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
// c.HTML(http.StatusUnauthorized, "401.html", gin.H{})
|
||||||
|
default:
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
179
router/router.go
Normal file
179
router/router.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone/controller"
|
||||||
|
"github.com/drone/drone/router/middleware/header"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
"github.com/drone/drone/static"
|
||||||
|
"github.com/drone/drone/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(middleware ...gin.HandlerFunc) http.Handler {
|
||||||
|
e := gin.Default()
|
||||||
|
e.SetHTMLTemplate(template.Load())
|
||||||
|
e.StaticFS("/static", static.FileSystem())
|
||||||
|
|
||||||
|
e.Use(header.SetHeaders())
|
||||||
|
e.Use(middleware...)
|
||||||
|
e.Use(session.SetUser())
|
||||||
|
|
||||||
|
e.GET("/", controller.ShowIndex)
|
||||||
|
e.GET("/login", controller.ShowLogin)
|
||||||
|
e.GET("/logout", controller.GetLogout)
|
||||||
|
|
||||||
|
settings := e.Group("/settings")
|
||||||
|
{
|
||||||
|
settings.Use(session.MustUser())
|
||||||
|
settings.GET("/profile", controller.ShowUser)
|
||||||
|
settings.GET("/people", session.MustAdmin(), controller.ShowUsers)
|
||||||
|
settings.GET("/nodes", session.MustAdmin(), controller.ShowNodes)
|
||||||
|
}
|
||||||
|
repo := e.Group("/repos/:owner/:name")
|
||||||
|
{
|
||||||
|
repo.Use(session.SetRepo())
|
||||||
|
repo.Use(session.SetPerm())
|
||||||
|
repo.Use(session.MustPull)
|
||||||
|
|
||||||
|
repo.GET("", controller.ShowRepo)
|
||||||
|
repo.GET("/builds/:number", controller.ShowBuild)
|
||||||
|
repo.GET("/builds/:number/:job", controller.ShowBuild)
|
||||||
|
repo_settings := repo.Group("/settings")
|
||||||
|
{
|
||||||
|
repo_settings.Use(session.MustPush)
|
||||||
|
repo_settings.GET("", controller.ShowRepoConf)
|
||||||
|
repo_settings.GET("/:action", controller.ShowRepoConf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := e.Group("/api/user")
|
||||||
|
{
|
||||||
|
user.Use(session.MustUser())
|
||||||
|
user.GET("", controller.GetSelf)
|
||||||
|
user.GET("/feed", controller.GetFeed)
|
||||||
|
user.GET("/repos", controller.GetRepos)
|
||||||
|
user.POST("/token", controller.PostToken)
|
||||||
|
user.GET("/repos/remote", controller.GetRemoteRepos)
|
||||||
|
}
|
||||||
|
|
||||||
|
users := e.Group("/api/users")
|
||||||
|
{
|
||||||
|
users.Use(session.MustAdmin())
|
||||||
|
users.GET("", controller.GetUsers)
|
||||||
|
users.POST("", controller.PostUser)
|
||||||
|
users.GET("/:login", controller.GetUser)
|
||||||
|
users.PATCH("/:login", controller.PatchUser)
|
||||||
|
users.DELETE("/:login", controller.DeleteUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := e.Group("/api/nodes")
|
||||||
|
{
|
||||||
|
nodes.Use(session.MustAdmin())
|
||||||
|
nodes.GET("", controller.GetNodes)
|
||||||
|
nodes.POST("", controller.PostNode)
|
||||||
|
nodes.DELETE("/:node", controller.DeleteNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := e.Group("/api/repos/:owner/:name")
|
||||||
|
{
|
||||||
|
repos.POST("", controller.PostRepo)
|
||||||
|
|
||||||
|
repo := repos.Group("")
|
||||||
|
{
|
||||||
|
repo.Use(session.SetRepo())
|
||||||
|
repo.Use(session.SetPerm())
|
||||||
|
repo.Use(session.MustPull)
|
||||||
|
|
||||||
|
repo.GET("", controller.GetRepo)
|
||||||
|
repo.GET("/key", controller.GetRepoKey)
|
||||||
|
repo.GET("/builds", controller.GetBuilds)
|
||||||
|
repo.GET("/builds/:number", controller.GetBuild)
|
||||||
|
repo.GET("/logs/:number/:job", controller.GetBuildLogs)
|
||||||
|
|
||||||
|
// requires authenticated user
|
||||||
|
repo.POST("/starred", session.MustUser(), controller.PostStar)
|
||||||
|
repo.DELETE("/starred", session.MustUser(), controller.DeleteStar)
|
||||||
|
repo.POST("/encrypt", session.MustUser(), controller.PostSecure)
|
||||||
|
|
||||||
|
// requires push permissions
|
||||||
|
repo.PATCH("", session.MustPush, controller.PatchRepo)
|
||||||
|
repo.DELETE("", session.MustPush, controller.DeleteRepo)
|
||||||
|
|
||||||
|
repo.POST("/builds/:number", session.MustPush, controller.PostBuild)
|
||||||
|
// repo.DELETE("/builds/:number", MustPush(), controller.DeleteBuild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
badges := e.Group("/api/badges/:owner/:name")
|
||||||
|
{
|
||||||
|
badges.GET("/status.svg", controller.GetBadge)
|
||||||
|
badges.GET("/cc.xml", controller.GetCC)
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := e.Group("/hook")
|
||||||
|
{
|
||||||
|
hook.POST("", controller.PostHook)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := e.Group("/api/stream")
|
||||||
|
{
|
||||||
|
stream.Use(session.SetRepo())
|
||||||
|
stream.Use(session.SetPerm())
|
||||||
|
stream.Use(session.MustPull)
|
||||||
|
stream.GET("/:owner/:name", controller.GetRepoEvents)
|
||||||
|
stream.GET("/:owner/:name/:build/:number", controller.GetStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
auth := e.Group("/authorize")
|
||||||
|
{
|
||||||
|
auth.GET("", controller.GetLogin)
|
||||||
|
auth.POST("", controller.GetLogin)
|
||||||
|
auth.POST("/token", controller.GetLoginToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
gitlab := e.Group("/api/gitlab/:owner/:name")
|
||||||
|
{
|
||||||
|
gitlab.Use(session.SetRepo())
|
||||||
|
gitlab.GET("/commits/:sha", controller.GetCommit)
|
||||||
|
gitlab.GET("/pulls/:number", controller.GetPullRequest)
|
||||||
|
|
||||||
|
redirects := gitlab.Group("/redirect")
|
||||||
|
{
|
||||||
|
redirects.GET("/commits/:sha", controller.RedirectSha)
|
||||||
|
redirects.GET("/pulls/:number", controller.RedirectPullRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize is a helper function to work around the following
|
||||||
|
// issue with gin. https://github.com/gin-gonic/gin/issues/388
|
||||||
|
func normalize(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
parts := strings.Split(r.URL.Path, "/")[1:]
|
||||||
|
switch parts[0] {
|
||||||
|
case "settings", "api", "login", "logout", "", "authorize", "hook", "static":
|
||||||
|
// no-op
|
||||||
|
default:
|
||||||
|
|
||||||
|
if len(parts) > 2 && parts[2] != "settings" {
|
||||||
|
parts = append(parts[:2], append([]string{"builds"}, parts[2:]...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefix the URL with /repo so that it
|
||||||
|
// can be effectively routed.
|
||||||
|
parts = append([]string{"", "repos"}, parts...)
|
||||||
|
|
||||||
|
// reconstruct the path
|
||||||
|
r.URL.Path = strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
118
shared/crypto/crypto.go
Normal file
118
shared/crypto/crypto.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"code.google.com/p/go.crypto/ssh"
|
||||||
|
"github.com/square/go-jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RSA_BITS = 2048 // Default number of bits in an RSA key
|
||||||
|
RSA_BITS_MIN = 768 // Minimum number of bits in an RSA key
|
||||||
|
)
|
||||||
|
|
||||||
|
// standard characters allowed in token string.
|
||||||
|
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
||||||
|
|
||||||
|
// default token length
|
||||||
|
var length = 32
|
||||||
|
|
||||||
|
// Rand generates a 32-bit random string.
|
||||||
|
func Rand() string {
|
||||||
|
b := make([]byte, length)
|
||||||
|
r := make([]byte, length+(length/4)) // storage for random bytes.
|
||||||
|
clen := byte(len(chars))
|
||||||
|
maxrb := byte(256 - (256 % len(chars)))
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
io.ReadFull(rand.Reader, r)
|
||||||
|
for _, c := range r {
|
||||||
|
if c >= maxrb {
|
||||||
|
// Skip this number to avoid modulo bias.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b[i] = chars[c%clen]
|
||||||
|
i++
|
||||||
|
if i == length {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to generate an RSA Private Key.
|
||||||
|
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, RSA_BITS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function that marshalls an RSA Public Key to an SSH
|
||||||
|
// .authorized_keys format
|
||||||
|
func MarshalPublicKey(public *rsa.PublicKey) []byte {
|
||||||
|
private, err := ssh.NewPublicKey(public)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.MarshalAuthorizedKey(private)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function that marshalls an RSA Private Key to
|
||||||
|
// a PEM encoded file.
|
||||||
|
func MarshalPrivateKey(private *rsa.PrivateKey) []byte {
|
||||||
|
marshaled := x509.MarshalPKCS1PrivateKey(private)
|
||||||
|
encoded := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: marshaled})
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalPrivateKey is a helper function that unmarshals a PEM
|
||||||
|
// bytes to an RSA Private Key
|
||||||
|
func UnmarshalPrivateKey(private []byte) *rsa.PrivateKey {
|
||||||
|
decoded, _ := pem.Decode(private)
|
||||||
|
parsed, err := x509.ParsePKCS1PrivateKey(decoded.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts a secret string.
|
||||||
|
func Encrypt(in, privKey string) (string, error) {
|
||||||
|
rsaPrivKey, err := decodePrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypt(in, &rsaPrivKey.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePrivateKey is a helper function that unmarshals a PEM
|
||||||
|
// bytes to an RSA Private Key
|
||||||
|
func decodePrivateKey(privateKey string) (*rsa.PrivateKey, error) {
|
||||||
|
derBlock, _ := pem.Decode([]byte(privateKey))
|
||||||
|
return x509.ParsePKCS1PrivateKey(derBlock.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt encrypts a plaintext variable using JOSE with
|
||||||
|
// RSA_OAEP and A128GCM algorithms.
|
||||||
|
func encrypt(text string, pubKey *rsa.PublicKey) (string, error) {
|
||||||
|
var encrypted string
|
||||||
|
var plaintext = []byte(text)
|
||||||
|
|
||||||
|
// Creates a new encrypter using defaults
|
||||||
|
encrypter, err := jose.NewEncrypter(jose.RSA_OAEP, jose.A128GCM, pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, err
|
||||||
|
}
|
||||||
|
// Encrypts the plaintext value and serializes
|
||||||
|
// as a JOSE string.
|
||||||
|
object, err := encrypter.Encrypt(plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return encrypted, err
|
||||||
|
}
|
||||||
|
return object.CompactSerialize()
|
||||||
|
}
|
|
@ -1,13 +1,25 @@
|
||||||
package secure
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
"github.com/franela/goblin"
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/square/go-jose"
|
"github.com/square/go-jose"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Secure(t *testing.T) {
|
func TestKeys(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Generate Key", func() {
|
||||||
|
|
||||||
|
g.It("Generates a private key", func() {
|
||||||
|
_, err := GeneratePrivateKey()
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Encrypt(t *testing.T) {
|
||||||
|
|
||||||
g := goblin.Goblin(t)
|
g := goblin.Goblin(t)
|
||||||
g.Describe("Secure", func() {
|
g.Describe("Secure", func() {
|
|
@ -1,72 +0,0 @@
|
||||||
package sshutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
|
||||||
"hash"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/code.google.com/p/go.crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
RSA_BITS = 2048 // Default number of bits in an RSA key
|
|
||||||
RSA_BITS_MIN = 768 // Minimum number of bits in an RSA key
|
|
||||||
)
|
|
||||||
|
|
||||||
// helper function to generate an RSA Private Key.
|
|
||||||
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
|
||||||
return rsa.GenerateKey(rand.Reader, RSA_BITS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function that marshalls an RSA Public Key to an SSH
|
|
||||||
// .authorized_keys format
|
|
||||||
func MarshalPublicKey(pubkey *rsa.PublicKey) []byte {
|
|
||||||
pk, err := ssh.NewPublicKey(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ssh.MarshalAuthorizedKey(pk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function that marshalls an RSA Private Key to
|
|
||||||
// a PEM encoded file.
|
|
||||||
func MarshalPrivateKey(privkey *rsa.PrivateKey) []byte {
|
|
||||||
privateKeyMarshaled := x509.MarshalPKCS1PrivateKey(privkey)
|
|
||||||
privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: privateKeyMarshaled})
|
|
||||||
return privateKeyPEM
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnMarshalPrivateKey is a helper function that unmarshals a PEM
|
|
||||||
// bytes to an RSA Private Key
|
|
||||||
func UnMarshalPrivateKey(privateKeyPEM []byte) *rsa.PrivateKey {
|
|
||||||
derBlock, _ := pem.Decode(privateKeyPEM)
|
|
||||||
privateKey, err := x509.ParsePKCS1PrivateKey(derBlock.Bytes)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt is helper function to encrypt a plain-text string using
|
|
||||||
// an RSA public key.
|
|
||||||
func Encrypt(hash hash.Hash, pubkey *rsa.PublicKey, msg string) (string, error) {
|
|
||||||
src, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, []byte(msg), nil)
|
|
||||||
return base64.RawURLEncoding.EncodeToString(src), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt is helper function to encrypt a plain-text string using
|
|
||||||
// an RSA public key.
|
|
||||||
func Decrypt(hash hash.Hash, privkey *rsa.PrivateKey, secret string) (string, error) {
|
|
||||||
decoded, err := base64.RawURLEncoding.DecodeString(secret)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := rsa.DecryptOAEP(hash, rand.Reader, privkey, decoded, nil)
|
|
||||||
return string(out), err
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package sshutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSSHUtil(t *testing.T) {
|
|
||||||
|
|
||||||
g := goblin.Goblin(t)
|
|
||||||
g.Describe("sshutil", func() {
|
|
||||||
var encrypted, testMsg string
|
|
||||||
|
|
||||||
privkey, err := GeneratePrivateKey()
|
|
||||||
g.Assert(err == nil).IsTrue()
|
|
||||||
pubkey := privkey.PublicKey
|
|
||||||
sha256 := sha256.New()
|
|
||||||
testMsg = "foo=bar"
|
|
||||||
|
|
||||||
g.Before(func() {
|
|
||||||
encrypted, err = Encrypt(sha256, &pubkey, testMsg)
|
|
||||||
g.Assert(err == nil).IsTrue()
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Can decrypt encrypted msg", func() {
|
|
||||||
decrypted, err := Decrypt(sha256, privkey, encrypted)
|
|
||||||
g.Assert(err == nil).IsTrue()
|
|
||||||
g.Assert(decrypted == testMsg).IsTrue()
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Unmarshals private key from PEM block", func() {
|
|
||||||
privateKeyPEM := MarshalPrivateKey(privkey)
|
|
||||||
privateKey := UnMarshalPrivateKey(privateKeyPEM)
|
|
||||||
|
|
||||||
g.Assert(privateKey.PublicKey.E == pubkey.E).IsTrue()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
51
shared/database/database.go
Normal file
51
shared/database/database.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
//go:generate go-bindata -pkg database -o database_gen.go sqlite3/ mysql/ postgres/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/envconfig"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/rubenv/sql-migrate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(env envconfig.Env) *sql.DB {
|
||||||
|
var (
|
||||||
|
driver = env.String("DATABASE_DRIVER", "sqlite3")
|
||||||
|
config = env.String("DATABASE_CONFIG", "drone.sqlite")
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Infof("using database driver %s", driver)
|
||||||
|
log.Infof("using database config %s", config)
|
||||||
|
|
||||||
|
return Open(driver, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a database connection, runs the database migrations, and returns
|
||||||
|
// the database connection. Any errors connecting to the database or executing
|
||||||
|
// migrations will cause the application to exit.
|
||||||
|
func Open(driver, config string) *sql.DB {
|
||||||
|
var db, err = sql.Open(driver, config)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
log.Fatalln("database connection failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var migrations = &migrate.AssetMigrationSource{
|
||||||
|
Asset: Asset,
|
||||||
|
AssetDir: AssetDir,
|
||||||
|
Dir: driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = migrate.Exec(db, driver, migrations, migrate.Up)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
log.Fatalln("migration failed")
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
132
shared/database/mysql/1_init.sql
Normal file
132
shared/database/mysql/1_init.sql
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
user_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,user_login VARCHAR(500)
|
||||||
|
,user_token VARCHAR(500)
|
||||||
|
,user_secret VARCHAR(500)
|
||||||
|
,user_email VARCHAR(500)
|
||||||
|
,user_avatar VARCHAR(500)
|
||||||
|
,user_active BOOLEAN
|
||||||
|
,user_admin BOOLEAN
|
||||||
|
,user_hash VARCHAR(500)
|
||||||
|
|
||||||
|
,UNIQUE(user_login)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE repos (
|
||||||
|
repo_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,repo_user_id INTEGER
|
||||||
|
,repo_owner VARCHAR(500)
|
||||||
|
,repo_name VARCHAR(500)
|
||||||
|
,repo_full_name VARCHAR(1000)
|
||||||
|
,repo_avatar VARCHAR(500)
|
||||||
|
,repo_link VARCHAR(1000)
|
||||||
|
,repo_clone VARCHAR(1000)
|
||||||
|
,repo_branch VARCHAR(500)
|
||||||
|
,repo_timeout INTEGER
|
||||||
|
,repo_private BOOLEAN
|
||||||
|
,repo_trusted BOOLEAN
|
||||||
|
,repo_allow_pr BOOLEAN
|
||||||
|
,repo_allow_push BOOLEAN
|
||||||
|
,repo_allow_deploys BOOLEAN
|
||||||
|
,repo_allow_tags BOOLEAN
|
||||||
|
,repo_hash VARCHAR(500)
|
||||||
|
|
||||||
|
,UNIQUE(repo_owner, repo_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE stars (
|
||||||
|
star_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,star_repo_id INTEGER
|
||||||
|
,star_user_id INTEGER
|
||||||
|
|
||||||
|
,UNIQUE(star_repo_id, star_user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_star_user ON builds (star_user_id);
|
||||||
|
|
||||||
|
CREATE TABLE keys (
|
||||||
|
key_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,key_repo_id INTEGER
|
||||||
|
,key_public MEDIUMBLOB
|
||||||
|
,key_private MEDIUMBLOB
|
||||||
|
|
||||||
|
,UNIQUE(key_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE builds (
|
||||||
|
build_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,build_repo_id INTEGER
|
||||||
|
,build_number INTEGER
|
||||||
|
,build_event VARCHAR(500)
|
||||||
|
,build_status VARCHAR(500)
|
||||||
|
,build_created INTEGER
|
||||||
|
,build_started INTEGER
|
||||||
|
,build_finished INTEGER
|
||||||
|
,build_commit VARCHAR(500)
|
||||||
|
,build_branch VARCHAR(500)
|
||||||
|
,build_ref VARCHAR(500)
|
||||||
|
,build_refspec VARCHAR(1000)
|
||||||
|
,build_remote VARCHAR(500)
|
||||||
|
,build_title VARCHAR(1000)
|
||||||
|
,build_message VARCHAR(2000)
|
||||||
|
,build_timestamp INTEGER
|
||||||
|
,build_author VARCHAR(500)
|
||||||
|
,build_avatar VARCHAR(1000)
|
||||||
|
,build_email VARCHAR(500)
|
||||||
|
,build_link VARCHAR(1000)
|
||||||
|
|
||||||
|
,UNIQUE(build_number, build_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_build_repo ON builds (build_repo_id);
|
||||||
|
|
||||||
|
CREATE TABLE jobs (
|
||||||
|
job_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,job_node_id INTEGER
|
||||||
|
,job_build_id INTEGER
|
||||||
|
,job_number INTEGER
|
||||||
|
,job_status VARCHAR(500)
|
||||||
|
,job_exit_code INTEGER
|
||||||
|
,job_started INTEGER
|
||||||
|
,job_finished INTEGER
|
||||||
|
,job_environment VARCHAR(2000)
|
||||||
|
|
||||||
|
,UNIQUE(job_build_id, job_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_job_build ON jobs (job_build_id);
|
||||||
|
CREATE INDEX ix_job_node ON jobs (job_node_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
log_id INTEGER PRIMARY KEY AUTO_INCREMENT
|
||||||
|
,log_job_id INTEGER
|
||||||
|
,log_data MEDIUMBLOB
|
||||||
|
|
||||||
|
,UNIQUE(log_job_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS nodes (
|
||||||
|
node_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,node_addr VARCHAR(1024)
|
||||||
|
,node_arch VARCHAR(50)
|
||||||
|
,node_cert MEDIUMBLOB
|
||||||
|
,node_key MEDIUMBLOB
|
||||||
|
,node_ca MEDIUMBLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
|
||||||
|
DROP TABLE nodes;
|
||||||
|
DROP TABLE logs;
|
||||||
|
DROP TABLE jobs;
|
||||||
|
DROP TABLE builds;
|
||||||
|
DROP TABLE keys;
|
||||||
|
DROP TABLE stars;
|
||||||
|
DROP TABLE repos;
|
||||||
|
DROP TABLE users;
|
132
shared/database/postgres/1_init.sql
Normal file
132
shared/database/postgres/1_init.sql
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
user_id SERIAL PRIMARY KEY
|
||||||
|
,user_login VARCHAR(500)
|
||||||
|
,user_token VARCHAR(500)
|
||||||
|
,user_secret VARCHAR(500)
|
||||||
|
,user_email VARCHAR(500)
|
||||||
|
,user_avatar VARCHAR(500)
|
||||||
|
,user_active BOOLEAN
|
||||||
|
,user_admin BOOLEAN
|
||||||
|
,user_hash VARCHAR(500)
|
||||||
|
|
||||||
|
,UNIQUE(user_login)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE repos (
|
||||||
|
repo_id SERIAL PRIMARY KEY
|
||||||
|
,repo_user_id INTEGER
|
||||||
|
,repo_owner VARCHAR(500)
|
||||||
|
,repo_name VARCHAR(500)
|
||||||
|
,repo_full_name VARCHAR(1000)
|
||||||
|
,repo_avatar VARCHAR(500)
|
||||||
|
,repo_link VARCHAR(1000)
|
||||||
|
,repo_clone VARCHAR(1000)
|
||||||
|
,repo_branch VARCHAR(500)
|
||||||
|
,repo_timeout INTEGER
|
||||||
|
,repo_private BOOLEAN
|
||||||
|
,repo_trusted BOOLEAN
|
||||||
|
,repo_allow_pr BOOLEAN
|
||||||
|
,repo_allow_push BOOLEAN
|
||||||
|
,repo_allow_deploys BOOLEAN
|
||||||
|
,repo_allow_tags BOOLEAN
|
||||||
|
,repo_hash VARCHAR(500)
|
||||||
|
|
||||||
|
,UNIQUE(repo_owner, repo_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE stars (
|
||||||
|
star_id SERIAL PRIMARY KEY
|
||||||
|
,star_repo_id INTEGER
|
||||||
|
,star_user_id INTEGER
|
||||||
|
|
||||||
|
,UNIQUE(star_repo_id, star_user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_star_user ON builds (star_user_id);
|
||||||
|
|
||||||
|
CREATE TABLE keys (
|
||||||
|
key_id SERIAL PRIMARY KEY
|
||||||
|
,key_repo_id INTEGER
|
||||||
|
,key_public BYTEA
|
||||||
|
,key_private BYTEA
|
||||||
|
|
||||||
|
,UNIQUE(key_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE builds (
|
||||||
|
build_id SERIAL PRIMARY KEY
|
||||||
|
,build_repo_id INTEGER
|
||||||
|
,build_number INTEGER
|
||||||
|
,build_event VARCHAR(500)
|
||||||
|
,build_status VARCHAR(500)
|
||||||
|
,build_created INTEGER
|
||||||
|
,build_started INTEGER
|
||||||
|
,build_finished INTEGER
|
||||||
|
,build_commit VARCHAR(500)
|
||||||
|
,build_branch VARCHAR(500)
|
||||||
|
,build_ref VARCHAR(500)
|
||||||
|
,build_refspec VARCHAR(1000)
|
||||||
|
,build_remote VARCHAR(500)
|
||||||
|
,build_title VARCHAR(1000)
|
||||||
|
,build_message VARCHAR(2000)
|
||||||
|
,build_timestamp INTEGER
|
||||||
|
,build_author VARCHAR(500)
|
||||||
|
,build_avatar VARCHAR(1000)
|
||||||
|
,build_email VARCHAR(500)
|
||||||
|
,build_link VARCHAR(1000)
|
||||||
|
|
||||||
|
,UNIQUE(build_number, build_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_build_repo ON builds (build_repo_id);
|
||||||
|
|
||||||
|
CREATE TABLE jobs (
|
||||||
|
job_id SERIAL PRIMARY KEY
|
||||||
|
,job_node_id INTEGER
|
||||||
|
,job_build_id INTEGER
|
||||||
|
,job_number INTEGER
|
||||||
|
,job_status VARCHAR(500)
|
||||||
|
,job_exit_code INTEGER
|
||||||
|
,job_started INTEGER
|
||||||
|
,job_finished INTEGER
|
||||||
|
,job_environment VARCHAR(2000)
|
||||||
|
|
||||||
|
,UNIQUE(job_build_id, job_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_job_build ON jobs (job_build_id);
|
||||||
|
CREATE INDEX ix_job_node ON jobs (job_node_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
log_id SERIAL PRIMARY KEY
|
||||||
|
,log_job_id INTEGER
|
||||||
|
,log_data BYTEA
|
||||||
|
|
||||||
|
,UNIQUE(log_job_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS nodes (
|
||||||
|
node_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,node_addr VARCHAR(1024)
|
||||||
|
,node_arch VARCHAR(50)
|
||||||
|
,node_cert BYTEA
|
||||||
|
,node_key BYTEA
|
||||||
|
,node_ca BYTEA
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
|
||||||
|
DROP TABLE nodes;
|
||||||
|
DROP TABLE logs;
|
||||||
|
DROP TABLE jobs;
|
||||||
|
DROP TABLE builds;
|
||||||
|
DROP TABLE keys;
|
||||||
|
DROP TABLE stars;
|
||||||
|
DROP TABLE repos;
|
||||||
|
DROP TABLE users;
|
32
shared/database/rebind.go
Normal file
32
shared/database/rebind.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/russross/meddler"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rebind is a helper function that changes the sql
|
||||||
|
// bind type from ? to $ for postgres queries.
|
||||||
|
func Rebind(query string) string {
|
||||||
|
if meddler.Default != meddler.PostgreSQL {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
qb := []byte(query)
|
||||||
|
// Add space enough for 5 params before we have to allocate
|
||||||
|
rqb := make([]byte, 0, len(qb)+5)
|
||||||
|
j := 1
|
||||||
|
for _, b := range qb {
|
||||||
|
if b == '?' {
|
||||||
|
rqb = append(rqb, '$')
|
||||||
|
for _, b := range strconv.Itoa(j) {
|
||||||
|
rqb = append(rqb, byte(b))
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
rqb = append(rqb, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(rqb)
|
||||||
|
}
|
131
shared/database/sqlite3/1_init.sql
Normal file
131
shared/database/sqlite3/1_init.sql
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
user_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,user_login TEXT
|
||||||
|
,user_token TEXT
|
||||||
|
,user_secret TEXT
|
||||||
|
,user_email TEXT
|
||||||
|
,user_avatar TEXT
|
||||||
|
,user_active BOOLEAN
|
||||||
|
,user_admin BOOLEAN
|
||||||
|
,user_hash TEXT
|
||||||
|
|
||||||
|
,UNIQUE(user_login)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE repos (
|
||||||
|
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,repo_user_id INTEGER
|
||||||
|
,repo_owner TEXT
|
||||||
|
,repo_name TEXT
|
||||||
|
,repo_full_name TEXT
|
||||||
|
,repo_avatar TEXT
|
||||||
|
,repo_link TEXT
|
||||||
|
,repo_clone TEXT
|
||||||
|
,repo_branch TEXT
|
||||||
|
,repo_timeout INTEGER
|
||||||
|
,repo_private BOOLEAN
|
||||||
|
,repo_trusted BOOLEAN
|
||||||
|
,repo_allow_pr BOOLEAN
|
||||||
|
,repo_allow_push BOOLEAN
|
||||||
|
,repo_allow_deploys BOOLEAN
|
||||||
|
,repo_allow_tags BOOLEAN
|
||||||
|
,repo_hash TEXT
|
||||||
|
|
||||||
|
,UNIQUE(repo_owner, repo_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE stars (
|
||||||
|
star_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,star_repo_id INTEGER
|
||||||
|
,star_user_id INTEGER
|
||||||
|
|
||||||
|
,UNIQUE(star_repo_id, star_user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_star_user ON stars (star_user_id);
|
||||||
|
|
||||||
|
CREATE TABLE keys (
|
||||||
|
key_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,key_repo_id INTEGER
|
||||||
|
,key_public BLOB
|
||||||
|
,key_private BLOB
|
||||||
|
|
||||||
|
,UNIQUE(key_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE builds (
|
||||||
|
build_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,build_repo_id INTEGER
|
||||||
|
,build_number INTEGER
|
||||||
|
,build_event TEXT
|
||||||
|
,build_status TEXT
|
||||||
|
,build_created INTEGER
|
||||||
|
,build_started INTEGER
|
||||||
|
,build_finished INTEGER
|
||||||
|
,build_commit TEXT
|
||||||
|
,build_branch TEXT
|
||||||
|
,build_ref TEXT
|
||||||
|
,build_refspec TEXT
|
||||||
|
,build_remote TEXT
|
||||||
|
,build_title TEXT
|
||||||
|
,build_message TEXT
|
||||||
|
,build_timestamp INTEGER
|
||||||
|
,build_author TEXT
|
||||||
|
,build_avatar TEXT
|
||||||
|
,build_email TEXT
|
||||||
|
,build_link TEXT
|
||||||
|
|
||||||
|
,UNIQUE(build_number, build_repo_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_build_repo ON builds (build_repo_id);
|
||||||
|
|
||||||
|
CREATE TABLE jobs (
|
||||||
|
job_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,job_node_id INTEGER
|
||||||
|
,job_build_id INTEGER
|
||||||
|
,job_number INTEGER
|
||||||
|
,job_status TEXT
|
||||||
|
,job_exit_code INTEGER
|
||||||
|
,job_started INTEGER
|
||||||
|
,job_finished INTEGER
|
||||||
|
,job_environment TEXT
|
||||||
|
|
||||||
|
,UNIQUE(job_build_id, job_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_job_build ON jobs (job_build_id);
|
||||||
|
CREATE INDEX ix_job_node ON jobs (job_node_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
log_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,log_job_id INTEGER
|
||||||
|
,log_data BLOB
|
||||||
|
|
||||||
|
,UNIQUE(log_job_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS nodes (
|
||||||
|
node_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,node_addr TEXT
|
||||||
|
,node_arch TEXT
|
||||||
|
,node_cert BLOB
|
||||||
|
,node_key BLOB
|
||||||
|
,node_ca BLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
INSERT INTO nodes VALUES(null, 'unix:///var/run/docker.sock', 'linux_amd64', '', '', '');
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
|
||||||
|
DROP TABLE nodes;
|
||||||
|
DROP TABLE logs;
|
||||||
|
DROP TABLE jobs;
|
||||||
|
DROP TABLE builds;
|
||||||
|
DROP TABLE keys;
|
||||||
|
DROP TABLE stars;
|
||||||
|
DROP TABLE repos;
|
||||||
|
DROP TABLE users;
|
109
shared/docker/docker.go
Normal file
109
shared/docker/docker.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
LogOpts = &dockerclient.LogOptions{
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
LogOptsTail = &dockerclient.LogOptions{
|
||||||
|
Follow: true,
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run creates the docker container, pulling images if necessary, starts
|
||||||
|
// the container and blocks until the container exits, returning the exit
|
||||||
|
// information.
|
||||||
|
func Run(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
|
||||||
|
info, err := RunDaemon(client, conf, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wait(client, info.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDaemon creates the docker container, pulling images if necessary, starts
|
||||||
|
// the container and returns the container information. It does not wait for
|
||||||
|
// the container to exit.
|
||||||
|
func RunDaemon(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
|
||||||
|
|
||||||
|
// attempts to create the contianer
|
||||||
|
id, err := client.CreateContainer(conf, name)
|
||||||
|
if err != nil {
|
||||||
|
// and pull the image and re-create if that fails
|
||||||
|
err = client.PullImage(conf.Image, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id, err = client.CreateContainer(conf, name)
|
||||||
|
if err != nil {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches the container information
|
||||||
|
info, err := client.InspectContainer(id)
|
||||||
|
if err != nil {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// starts the container
|
||||||
|
err = client.StartContainer(id, &conf.HostConfig)
|
||||||
|
if err != nil {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks until the named container exits, returning the exit information.
|
||||||
|
func Wait(client dockerclient.Client, name string) (*dockerclient.ContainerInfo, error) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
client.StopContainer(name, 5)
|
||||||
|
client.KillContainer(name, "9")
|
||||||
|
}()
|
||||||
|
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
infoc := make(chan *dockerclient.ContainerInfo, 1)
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
// blocks and waits for the container to finish
|
||||||
|
// by streaming the logs (to /dev/null). Ideally
|
||||||
|
// we could use the `wait` function instead
|
||||||
|
rc, err := client.ContainerLogs(name, LogOptsTail)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, rc)
|
||||||
|
rc.Close()
|
||||||
|
|
||||||
|
info, err := client.InspectContainer(name)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
infoc <- info
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case info := <-infoc:
|
||||||
|
return info, nil
|
||||||
|
case err := <-errc:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue