mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-24 08:02:18 +02:00
updated vendor files and paths
This commit is contained in:
parent
155576fb03
commit
dfea14c7e5
@ -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
|
||||
|
||||
script:
|
||||
- go run make.go deps
|
||||
- go run make.go bindata
|
||||
- go run make.go vet
|
||||
- go run make.go fmt
|
||||
- go run make.go build
|
||||
- go run make.go test
|
||||
|
||||
- make dist
|
||||
- apt-get -y -qq update
|
||||
- apt-get -y -qq install libsqlite3-dev
|
||||
- make deps
|
||||
- make
|
||||
- make test
|
||||
- make deb
|
||||
|
||||
notify:
|
||||
email:
|
||||
@ -29,34 +27,26 @@ publish:
|
||||
bucket: downloads.drone.io
|
||||
access_key: $$AWS_KEY
|
||||
secret_key: $$AWS_SECRET
|
||||
source: dist/drone.deb
|
||||
source: contrib/debian/drone.deb
|
||||
target: $DRONE_BRANCH/
|
||||
when:
|
||||
owner: drone
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
clone:
|
||||
path: github.com/drone/drone
|
||||
|
||||
build:
|
||||
image: golang:1.5.0
|
||||
image: golang:1.5
|
||||
commands:
|
||||
- export GOPATH=/drone
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
- go run make.go deps
|
||||
- go run make.go bindata
|
||||
- go run make.go vet
|
||||
- go run make.go fmt
|
||||
- go run make.go build
|
||||
- go run make.go test
|
||||
|
||||
- make dist
|
||||
|
||||
compose:
|
||||
database:
|
||||
image: mysql:5.5
|
||||
environment:
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
- MYSQL_DATABASE=test
|
||||
- apt-get -y -qq update
|
||||
- apt-get -y -qq install libsqlite3-dev
|
||||
- make deps
|
||||
- make gen
|
||||
- make test
|
||||
- make build
|
||||
- make build_static
|
||||
- make deb
|
||||
|
41
.gitignore
vendored
41
.gitignore
vendored
@ -1,34 +1,13 @@
|
||||
drone.sublime-project
|
||||
drone.sublime-workspace
|
||||
.vagrant
|
||||
|
||||
*~
|
||||
~*
|
||||
drone
|
||||
drone_*
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.deb
|
||||
*.deb.*
|
||||
*.rpm
|
||||
*.out
|
||||
*.prof
|
||||
*.rice-box.go
|
||||
*.db
|
||||
*_gen.go
|
||||
*.html
|
||||
*.css
|
||||
*.txt
|
||||
*.min.css
|
||||
*.zip
|
||||
*.gz
|
||||
*.out
|
||||
*.min.js
|
||||
*_bindata.go
|
||||
*.toml
|
||||
|
||||
# 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
|
||||
*.deb
|
||||
temp/
|
||||
|
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
|
||||
WORKDIR $GOPATH/src/github.com/drone/drone
|
||||
FROM centurylink/ca-certs
|
||||
EXPOSE 8080
|
||||
|
||||
EXPOSE 80
|
||||
ADD drone_static /drone_static
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/drone"]
|
||||
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
|
||||
ENTRYPOINT ["/drone_static"]
|
@ -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)
|
||||
VERSION := 0.4.0-alpha
|
||||
PACKAGES = $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
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:
|
||||
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
|
||||
#
|
||||
# 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;'
|
||||
test:
|
||||
go test -cover $(PACKAGES)
|
||||
|
||||
run:
|
||||
bin/drone --debug
|
||||
deb:
|
||||
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
|
||||
install:
|
||||
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
|
||||
vendor:
|
||||
vexp
|
||||
|
@ -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 (
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/drone/drone/pkg/ccmenu"
|
||||
common "github.com/drone/drone/pkg/types"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/context"
|
||||
)
|
||||
|
||||
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>`)
|
||||
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>`)
|
||||
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>`)
|
||||
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>`)
|
||||
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>`)
|
||||
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 = `<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 = `<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 = `<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 = `<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) {
|
||||
var repo = ToRepo(c)
|
||||
var store = ToDatastore(c)
|
||||
var branch = c.Request.FormValue("branch")
|
||||
if len(branch) == 0 {
|
||||
branch = repo.Branch
|
||||
db := context.Database(c)
|
||||
repo, err := model.GetRepoName(db,
|
||||
c.Param("owner"),
|
||||
c.Param("name"),
|
||||
)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
// the 'none' badge, instead of throwing
|
||||
// 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 {
|
||||
c.Writer.Write(badgeNone)
|
||||
c.String(404, badgeNone)
|
||||
return
|
||||
}
|
||||
|
||||
switch build.Status {
|
||||
case common.StateSuccess:
|
||||
c.Writer.Write(badgeSuccess)
|
||||
case common.StateFailure:
|
||||
c.Writer.Write(badgeFailure)
|
||||
case common.StateError, common.StateKilled:
|
||||
c.Writer.Write(badgeError)
|
||||
case common.StatePending, common.StateRunning:
|
||||
c.Writer.Write(badgeStarted)
|
||||
case model.StatusSuccess:
|
||||
c.String(200, badgeSuccess)
|
||||
case model.StatusFailure:
|
||||
c.String(200, badgeFailure)
|
||||
case model.StatusError, model.StatusKilled:
|
||||
c.String(200, badgeError)
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
c.String(200, badgeStarted)
|
||||
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) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
list, err := store.BuildList(repo, 1, 0)
|
||||
if err != nil || len(list) == 0 {
|
||||
db := context.Database(c)
|
||||
repo, err := model.GetRepoName(db,
|
||||
c.Param("owner"),
|
||||
c.Param("name"),
|
||||
)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(404)
|
||||
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)
|
||||
}
|
||||
|
@ -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 (
|
||||
"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) {
|
||||
var branch string
|
||||
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
sha := c.Params.ByName("sha")
|
||||
db := context.Database(c)
|
||||
repo := session.Repo(c)
|
||||
|
||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||
return repo.Hash, nil
|
||||
})
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if parsed.Text != repo.FullName {
|
||||
c.AbortWithStatus(403)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
branch = c.Request.FormValue("branch")
|
||||
if branch == "" {
|
||||
commit := c.Param("sha")
|
||||
branch := c.Query("branch")
|
||||
if len(branch) == 0 {
|
||||
branch = repo.Branch
|
||||
}
|
||||
|
||||
build, err := store.BuildSha(repo, sha, branch)
|
||||
build, err := model.GetBuildCommit(db, repo, commit, branch)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
c.AbortWithError(http.StatusNotFound, err)
|
||||
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 {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, build)
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
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/pkg/queue"
|
||||
"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/yaml"
|
||||
"github.com/drone/drone/pkg/yaml/matrix"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/engine"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/context"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/yaml"
|
||||
"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) {
|
||||
remote := ToRemote(c)
|
||||
store := ToDatastore(c)
|
||||
queue_ := ToQueue(c)
|
||||
remote := context.Remote(c)
|
||||
db := context.Database(c)
|
||||
|
||||
hook, err := remote.Hook(c.Request)
|
||||
tmprepo, build, err := remote.Hook(c.Request)
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse hook. %s", err)
|
||||
c.Fail(400, err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if hook == nil {
|
||||
if build == nil {
|
||||
c.Writer.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
if hook.Repo == nil {
|
||||
if tmprepo == nil {
|
||||
log.Errorf("failure to ascertain repo from hook.")
|
||||
c.Writer.WriteHeader(400)
|
||||
return
|
||||
@ -42,16 +39,16 @@ func PostHook(c *gin.Context) {
|
||||
|
||||
// a build may be skipped if the text [CI SKIP]
|
||||
// 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")
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := store.RepoName(hook.Repo.Owner, hook.Repo.Name)
|
||||
repo, err := model.GetRepoName(db, tmprepo.Owner, tmprepo.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo %s/%s from hook. %s", hook.Repo.Owner, hook.Repo.Name, err)
|
||||
c.Fail(404, err)
|
||||
log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -61,7 +58,7 @@ func PostHook(c *gin.Context) {
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||
c.Fail(400, err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if parsed.Text != repo.FullName {
|
||||
@ -70,106 +67,134 @@ func PostHook(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case repo.UserID == 0:
|
||||
if repo.UserID == 0 {
|
||||
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
case !repo.Hooks.Push && hook.Commit != nil:
|
||||
log.Infof("ignoring hook. repo %s is disabled.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
case !repo.Hooks.PullRequest && hook.PullRequest != nil:
|
||||
log.Warnf("ignoring hook. repo %s is disabled for pull requests.", repo.FullName)
|
||||
}
|
||||
var skipped = true
|
||||
if (build.Event == model.EventPush && repo.AllowPush) ||
|
||||
(build.Event == model.EventPull && repo.AllowPull) ||
|
||||
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
||||
(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)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.User(repo.UserID)
|
||||
user, err := model.GetUser(db, repo.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
c.AbortWithError(500, err)
|
||||
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
|
||||
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.Fail(404, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
axes, err := matrix.Parse(string(raw))
|
||||
if err != nil {
|
||||
log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err)
|
||||
c.Fail(400, err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if len(axes) == 0 {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, _ := model.GetKey(db, repo)
|
||||
|
||||
// verify the branches can be built vs skipped
|
||||
when, _ := parser.ParseCondition(string(raw))
|
||||
if build.PullRequest != nil && when != nil && !when.MatchBranch(build.Commit.Branch) {
|
||||
log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, build.Commit.Branch)
|
||||
yconfig, _ := yaml.Parse(string(raw))
|
||||
var match = false
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
err = store.AddBuild(build)
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
log.Errorf("failure to begin database transaction", err)
|
||||
c.AbortWithError(500, err)
|
||||
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)
|
||||
|
||||
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 {
|
||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
}
|
||||
|
||||
// get the previous build so taht we can send
|
||||
// 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,
|
||||
Repo: repo,
|
||||
Build: build,
|
||||
BuildPrev: last,
|
||||
Keys: repo.Keys,
|
||||
Jobs: jobs,
|
||||
Keys: key,
|
||||
Netrc: netrc,
|
||||
Config: raw,
|
||||
Secret: sec,
|
||||
System: &common.System{
|
||||
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"), " "),
|
||||
},
|
||||
})
|
||||
|
||||
}
|
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 (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/pkg/token"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/model"
|
||||
"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) {
|
||||
remote := ToRemote(c)
|
||||
store := ToDatastore(c)
|
||||
db := context.Database(c)
|
||||
remote := context.Remote(c)
|
||||
|
||||
// when dealing with redirects we may need
|
||||
// to adjust the content type. I cannot, however,
|
||||
// rememver why, so need to revisit this line.
|
||||
c.Writer.Header().Del("Content-Type")
|
||||
|
||||
// TODO: move this back to the remote section
|
||||
getLoginOauth2(c)
|
||||
|
||||
// exit if authorization fails
|
||||
if c.Writer.Status() != 200 {
|
||||
tmpuser, open, err := remote.Login(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
log.Errorf("cannot authenticate user. %s", err)
|
||||
c.Redirect(303, "/login?error=oauth_error")
|
||||
return
|
||||
}
|
||||
// this will happen when the user is redirected by
|
||||
// the remote provide as part of the oauth dance.
|
||||
if tmpuser == nil {
|
||||
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
|
||||
u, err := store.UserLogin(login.Login)
|
||||
u, err := model.GetUserLogin(db, tmpuser.Login)
|
||||
if err != nil {
|
||||
count, err := store.UserCount()
|
||||
count, err := model.GetUserCount(db)
|
||||
if err != nil {
|
||||
log.Errorf("cannot register %s. %s", login.Login, err)
|
||||
c.Redirect(303, "/login#error=internal_error")
|
||||
log.Errorf("cannot register %s. %s", tmpuser.Login, err)
|
||||
c.Redirect(303, "/login?error=internal_error")
|
||||
return
|
||||
}
|
||||
|
||||
// if self-registration is disabled we should
|
||||
// return a notAuthorized error. the only exception
|
||||
// is if no users exist yet in the system we'll proceed.
|
||||
if !remote.GetOpen() && count != 0 {
|
||||
log.Errorf("cannot register %s. registration closed", login.Login)
|
||||
c.Redirect(303, "/login#error=access_denied")
|
||||
if !open && count != 0 {
|
||||
log.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
|
||||
// create the user account
|
||||
u = &types.User{}
|
||||
u.Login = login.Login
|
||||
u.Token = login.Token
|
||||
u.Secret = login.Secret
|
||||
u.Email = login.Email
|
||||
u.Avatar = login.Avatar
|
||||
u.Hash = types.GenerateToken()
|
||||
u = &model.User{}
|
||||
u.Login = tmpuser.Login
|
||||
u.Token = tmpuser.Token
|
||||
u.Secret = tmpuser.Secret
|
||||
u.Email = tmpuser.Email
|
||||
u.Avatar = tmpuser.Avatar
|
||||
u.Hash = crypto.Rand()
|
||||
|
||||
// insert the user into the database
|
||||
if err := store.AddUser(u); err != nil {
|
||||
log.Errorf("cannot insert %s. %s", login.Login, err)
|
||||
c.Redirect(303, "/login#error=internal_error")
|
||||
if err := model.CreateUser(db, u); err != nil {
|
||||
log.Errorf("cannot insert %s. %s", u.Login, err)
|
||||
c.Redirect(303, "/login?error=internal_error")
|
||||
return
|
||||
}
|
||||
|
||||
@ -89,20 +79,14 @@ func GetLogin(c *gin.Context) {
|
||||
|
||||
// update the user meta data and authorization
|
||||
// data and cache in the datastore.
|
||||
u.Token = login.Token
|
||||
u.Secret = login.Secret
|
||||
u.Email = login.Email
|
||||
u.Avatar = login.Avatar
|
||||
u.Token = tmpuser.Token
|
||||
u.Secret = tmpuser.Secret
|
||||
u.Email = tmpuser.Email
|
||||
u.Avatar = tmpuser.Avatar
|
||||
|
||||
// TODO: remove this once gitlab implements setting
|
||||
// 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 {
|
||||
if err := model.UpdateUser(db, u); err != nil {
|
||||
log.Errorf("cannot update %s. %s", u.Login, err)
|
||||
c.Redirect(303, "/login#error=internal_error")
|
||||
c.Redirect(303, "/login?error=internal_error")
|
||||
return
|
||||
}
|
||||
|
||||
@ -111,93 +95,65 @@ func GetLogin(c *gin.Context) {
|
||||
tokenstr, err := token.SignExpires(u.Hash, exp)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
// using the oauth2 protocol.
|
||||
func getLoginOauth2(c *gin.Context) {
|
||||
var remote = ToRemote(c)
|
||||
func GetLogout(c *gin.Context) {
|
||||
|
||||
// Bugagazavr: I think this must be moved to remote config
|
||||
//var scope = strings.Join(settings.Auth.Scope, ",")
|
||||
//if scope == "" {
|
||||
// scope = remote.Scope()
|
||||
//}
|
||||
var transport = remote.Oauth2Transport(c.Request)
|
||||
httputil.DelCookie(c.Writer, c.Request, "user_sess")
|
||||
httputil.DelCookie(c.Writer, c.Request, "user_last")
|
||||
c.Redirect(303, "/login")
|
||||
}
|
||||
|
||||
// get the OAuth code
|
||||
var code = c.Request.FormValue("code")
|
||||
//var state = c.Request.FormValue("state")
|
||||
if len(code) == 0 {
|
||||
// TODO this should be a random number, verified by a cookie
|
||||
c.Redirect(303, transport.AuthCodeURL("random"))
|
||||
return
|
||||
}
|
||||
func GetLoginToken(c *gin.Context) {
|
||||
db := context.Database(c)
|
||||
remote := context.Remote(c)
|
||||
|
||||
// exhange for a token
|
||||
var token, err = transport.Exchange(code)
|
||||
in := &tokenPayload{}
|
||||
err := c.Bind(in)
|
||||
if err != nil {
|
||||
log.Errorf("cannot get access_token. %s", err)
|
||||
c.Redirect(303, "/login#error=token_exchange")
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get user account
|
||||
user, err := remote.Login(token.AccessToken, token.RefreshToken)
|
||||
login, err := remote.Auth(in.Access, in.Refresh)
|
||||
if err != nil {
|
||||
log.Errorf("cannot get user with access_token. %s", err)
|
||||
c.Redirect(303, "/login#error=user_not_found")
|
||||
c.AbortWithError(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// add the user to the request
|
||||
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)
|
||||
user, err := model.GetUserLogin(db, login)
|
||||
if err != nil {
|
||||
log.Errorf("invalid username or password for %s. %s", username, err)
|
||||
c.Redirect(303, "/login#error=invalid_credentials")
|
||||
c.AbortWithError(http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
// add the user to the request
|
||||
c.Set("user", user)
|
||||
exp := time.Now().Add(time.Hour * 72).Unix()
|
||||
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
|
||||
// organization list to a whitelist of organizations that are
|
||||
// approved to use the system.
|
||||
func checkMembership(orgs, whitelist []string) bool {
|
||||
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
|
||||
type tokenPayload struct {
|
||||
Access string `json:"access_token,omitempty"`
|
||||
Refresh string `json:"refresh_token,omitempty"`
|
||||
Expires int64 `json:"expires_in,omitempty"`
|
||||
}
|
||||
|
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 (
|
||||
// "crypto/sha1"
|
||||
"net/http"
|
||||
|
||||
"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/github.com/ungerik/go-gravatar"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/drone/drone/pkg/token"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
"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"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// GetUserCurr accepts a request to retrieve the
|
||||
// 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))
|
||||
var cache *lru.Cache
|
||||
|
||||
// v := struct {
|
||||
// *types.User
|
||||
|
||||
// // token fingerprint
|
||||
// Token string `json:"token"`
|
||||
// }{u, f}
|
||||
|
||||
c.JSON(200, u)
|
||||
func init() {
|
||||
var err error
|
||||
cache, err = lru.New(1028)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// PutUserCurr accepts a request to update the currently
|
||||
// authenticated User profile.
|
||||
//
|
||||
// PUT /api/user
|
||||
//
|
||||
func PutUserCurr(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
user := ToUser(c)
|
||||
func GetSelf(c *gin.Context) {
|
||||
c.IndentedJSON(200, session.User(c))
|
||||
}
|
||||
|
||||
in := &types.User{}
|
||||
if !c.BindWith(in, binding.JSON) {
|
||||
func GetFeed(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
db := context.Database(c)
|
||||
feed, err := model.GetUserFeed(db, user, 25, 0)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// TODO: we are no longer auto-generating avatar
|
||||
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)
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, feed)
|
||||
}
|
||||
|
||||
// GetUserRepos accepts a request to get the currently
|
||||
// authenticated user's repository list from the datastore,
|
||||
// encoded and returned in JSON format.
|
||||
//
|
||||
// GET /api/user/repos
|
||||
//
|
||||
func GetUserRepos(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
user := ToUser(c)
|
||||
repos, err := store.RepoList(user)
|
||||
func GetRepos(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
db := context.Database(c)
|
||||
repos, err := model.GetRepoList(db, user)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(200, &repos)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(http.StatusOK, repos)
|
||||
}
|
||||
|
||||
// GetUserFeed accepts a request to get the currently
|
||||
// authenticated user's build feed from the datastore,
|
||||
// encoded and returned in JSON format.
|
||||
//
|
||||
// GET /api/user/feed
|
||||
//
|
||||
func GetUserFeed(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
user := ToUser(c)
|
||||
feed, err := store.UserFeed(user, 25, 0)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(200, &feed)
|
||||
func GetRemoteRepos(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
remote := context.Remote(c)
|
||||
|
||||
// attempt to get the repository list from the
|
||||
// cache since the operation is expensive
|
||||
v, ok := cache.Get(user.Login)
|
||||
if ok {
|
||||
c.IndentedJSON(http.StatusOK, v)
|
||||
return
|
||||
}
|
||||
|
||||
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 PostUserToken(c *gin.Context) {
|
||||
user := ToUser(c)
|
||||
func PostToken(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
|
||||
token := token.New(token.UserToken, user.Login)
|
||||
tokenstr, err := token.Sign(user.Hash)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
} 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 (
|
||||
"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/github.com/ungerik/go-gravatar"
|
||||
"net/http"
|
||||
|
||||
"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) {
|
||||
store := ToDatastore(c)
|
||||
users, err := store.UserList()
|
||||
db := context.Database(c)
|
||||
users, err := model.GetUserList(db)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(200, users)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
store := ToDatastore(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.UserLogin(name)
|
||||
db := context.Database(c)
|
||||
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, user)
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// PutUser accepts a request to update an existing user in
|
||||
// the system. The modified user account is returned in JSON
|
||||
// format if successful.
|
||||
//
|
||||
// PUT /api/users/:name
|
||||
//
|
||||
func PutUser(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
me := ToUser(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.UserLogin(name)
|
||||
func PatchUser(c *gin.Context) {
|
||||
me := session.User(c)
|
||||
db := context.Database(c)
|
||||
in := &model.User{}
|
||||
err := c.Bind(in)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
in := &types.User{}
|
||||
if !c.BindWith(in, binding.JSON) {
|
||||
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
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.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
|
||||
// downgrade her own account.
|
||||
if me.Login != user.Login {
|
||||
user.Admin = in.Admin
|
||||
}
|
||||
|
||||
err = store.SetUser(user)
|
||||
err = model.CreateUser(db, user)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(200, user)
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
store := ToDatastore(c)
|
||||
me := ToUser(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.UserLogin(name)
|
||||
me := session.User(c)
|
||||
db := context.Database(c)
|
||||
|
||||
user, err := model.GetUserLogin(db, c.Param("login"))
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// an administrator must not be able to
|
||||
// delete her own account.
|
||||
if user.Login == me.Login {
|
||||
c.Writer.WriteHeader(403)
|
||||
// cannot delete self
|
||||
if me.ID == user.ID {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.DelUser(user); err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.Writer.WriteHeader(204)
|
||||
err = model.DeleteUser(db, user)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
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 (
|
||||
"sync"
|
||||
|
||||
"github.com/drone/drone/pkg/bus"
|
||||
)
|
||||
|
||||
type Bus struct {
|
||||
type eventbus struct {
|
||||
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.
|
||||
func New() *Bus {
|
||||
return &Bus{
|
||||
subs: make(map[chan *bus.Event]bool),
|
||||
func newEventbus() *eventbus {
|
||||
return &eventbus{
|
||||
subs: make(map[chan *Event]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe adds the channel to the list of
|
||||
// subscribers. Each subscriber in the list will
|
||||
// receive broadcast events.
|
||||
func (b *Bus) Subscribe(c chan *bus.Event) {
|
||||
func (b *eventbus) subscribe(c chan *Event) {
|
||||
b.Lock()
|
||||
b.subs[c] = true
|
||||
b.Unlock()
|
||||
@ -30,19 +28,19 @@ func (b *Bus) Subscribe(c chan *bus.Event) {
|
||||
|
||||
// Unsubscribe removes the channel from the
|
||||
// list of subscribers.
|
||||
func (b *Bus) Unsubscribe(c chan *bus.Event) {
|
||||
func (b *eventbus) unsubscribe(c chan *Event) {
|
||||
b.Lock()
|
||||
delete(b.subs, c)
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
// Send dispatches a message to all subscribers.
|
||||
func (b *Bus) Send(event *bus.Event) {
|
||||
func (b *eventbus) send(event *Event) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
for s := range b.subs {
|
||||
go func(c chan *bus.Event) {
|
||||
go func(c chan *Event) {
|
||||
defer recover()
|
||||
c <- event
|
||||
}(s)
|
||||
|
@ -1,46 +1,45 @@
|
||||
package builtin
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
||||
"github.com/drone/drone/pkg/bus"
|
||||
. "github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
func TestBus(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Bus", func() {
|
||||
g.Describe("Event bus", func() {
|
||||
|
||||
g.It("Should unsubscribe", func() {
|
||||
c1 := make(chan *bus.Event)
|
||||
c2 := make(chan *bus.Event)
|
||||
b := New()
|
||||
b.Subscribe(c1)
|
||||
b.Subscribe(c2)
|
||||
c1 := make(chan *Event)
|
||||
c2 := make(chan *Event)
|
||||
b := newEventbus()
|
||||
b.subscribe(c1)
|
||||
b.subscribe(c2)
|
||||
g.Assert(len(b.subs)).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should subscribe", func() {
|
||||
c1 := make(chan *bus.Event)
|
||||
c2 := make(chan *bus.Event)
|
||||
b := New()
|
||||
b.Subscribe(c1)
|
||||
b.Subscribe(c2)
|
||||
c1 := make(chan *Event)
|
||||
c2 := make(chan *Event)
|
||||
b := newEventbus()
|
||||
b.subscribe(c1)
|
||||
b.subscribe(c2)
|
||||
g.Assert(len(b.subs)).Equal(2)
|
||||
b.Unsubscribe(c1)
|
||||
b.Unsubscribe(c2)
|
||||
b.unsubscribe(c1)
|
||||
b.unsubscribe(c2)
|
||||
g.Assert(len(b.subs)).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should send", func() {
|
||||
em := map[string]bool{"foo": true, "bar": true}
|
||||
e1 := &bus.Event{Name: "foo"}
|
||||
e2 := &bus.Event{Name: "bar"}
|
||||
c := make(chan *bus.Event)
|
||||
b := New()
|
||||
b.Subscribe(c)
|
||||
b.Send(e1)
|
||||
b.Send(e2)
|
||||
e1 := &Event{Name: "foo"}
|
||||
e2 := &Event{Name: "bar"}
|
||||
c := make(chan *Event)
|
||||
b := newEventbus()
|
||||
b.subscribe(c)
|
||||
b.send(e1)
|
||||
b.send(e2)
|
||||
r1 := <-c
|
||||
r2 := <-c
|
||||
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 (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone/pkg/bus"
|
||||
"github.com/drone/drone/pkg/remote"
|
||||
"github.com/drone/drone/pkg/store"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
)
|
||||
|
||||
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 {
|
||||
bus bus.Bus
|
||||
store store.Store
|
||||
bus *eventbus
|
||||
db *sql.DB
|
||||
remote remote.Remote
|
||||
}
|
||||
|
||||
func (u *updater) SetBuild(user *types.User, r *types.Repo, c *types.Build) error {
|
||||
err := u.store.SetBuild(c)
|
||||
func (u *updater) SetBuild(r *Task) error {
|
||||
err := model.UpdateBuild(u.db, r.Build)
|
||||
if err != nil {
|
||||
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 {
|
||||
// log err
|
||||
}
|
||||
|
||||
// we need this because builds coming from
|
||||
// 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)
|
||||
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.bus.Send(&bus.Event{
|
||||
Name: r.FullName,
|
||||
Kind: bus.EventRepo,
|
||||
u.bus.send(&Event{
|
||||
Name: r.Repo.FullName,
|
||||
Msg: msg,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) SetJob(r *types.Repo, c *types.Build, j *types.Job) error {
|
||||
err := u.store.SetJob(j)
|
||||
func (u *updater) SetJob(r *Task) error {
|
||||
err := model.UpdateJob(u.db, r.Job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we need this because builds coming from
|
||||
// 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)
|
||||
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.bus.Send(&bus.Event{
|
||||
Name: r.FullName,
|
||||
Kind: bus.EventRepo,
|
||||
u.bus.send(&Event{
|
||||
Name: r.Repo.FullName,
|
||||
Msg: msg,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) SetLogs(r *types.Repo, c *types.Build, j *types.Job, rc io.ReadCloser) error {
|
||||
path := fmt.Sprintf("/logs/%s/%v/%v", r.FullName, c.Number, j.Number)
|
||||
return u.store.SetBlobReader(path, rc)
|
||||
func (u *updater) SetLogs(r *Task, rc io.ReadCloser) error {
|
||||
return model.SetLog(u.db, r.Job, 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 (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/samalba/dockerclient"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
"github.com/drone/drone/shared/docker"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -35,26 +15,15 @@ var (
|
||||
DefaultEntrypoint = []string{"/bin/drone-exec"}
|
||||
|
||||
// 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
|
||||
DefaultPullRequestArgs = []string{"--pull", "--cache", "--clone", "--build"}
|
||||
DefaultPullRequestArgs = []string{"--cache", "--clone", "--build"}
|
||||
|
||||
// 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 {
|
||||
client dockerclient.Client
|
||||
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"},
|
||||
},
|
||||
Volumes: map[string]struct{}{
|
||||
"/drone": 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
|
||||
// since we'll be frequently updating the build image
|
||||
// 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 {
|
||||
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.
|
||||
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 = append(args, "--")
|
||||
args = append(args, string(stdin))
|
||||
@ -121,14 +80,11 @@ func (w *worker) Notify(stdin []byte) error {
|
||||
Image: DefaultAgent,
|
||||
Entrypoint: DefaultEntrypoint,
|
||||
Cmd: args,
|
||||
Env: environment,
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: volume,
|
||||
},
|
||||
HostConfig: dockerclient.HostConfig{},
|
||||
}
|
||||
|
||||
var err error
|
||||
w.notify, err = run(w.client, conf, "")
|
||||
w.notify, err = docker.Run(w.client, conf, "")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -136,7 +92,7 @@ func (w *worker) Notify(stdin []byte) error {
|
||||
// from the build and deploy agents.
|
||||
func (w *worker) Logs() (io.ReadCloser, error) {
|
||||
if w.build == nil {
|
||||
return nil, ErrLogging
|
||||
return nil, errLogging
|
||||
}
|
||||
return w.client.ContainerLogs(w.build.Id, logOpts)
|
||||
}
|
||||
@ -153,77 +109,3 @@ func (w *worker) Remove() {
|
||||
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 (
|
||||
StatePending = "pending"
|
||||
StateRunning = "running"
|
||||
StateSuccess = "success"
|
||||
StateFailure = "failure"
|
||||
StateKilled = "killed"
|
||||
StateError = "error"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/database"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type Build struct {
|
||||
ID int64 `json:"id"`
|
||||
RepoID int64 `json:"-" sql:"unique:ux_build_number,index:ix_build_repo_id"`
|
||||
Number int `json:"number" sql:"unique:ux_build_number"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
|
||||
Commit *Commit `json:"head_commit"`
|
||||
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||
|
||||
Jobs []*Job `json:"jobs,omitempty" sql:"-"`
|
||||
ID int64 `json:"id" meddler:"build_id,pk"`
|
||||
RepoID int64 `json:"-" meddler:"build_repo_id"`
|
||||
Number int `json:"number" meddler:"build_number"`
|
||||
Event string `json:"event" meddler:"build_event"`
|
||||
Status string `json:"status" meddler:"build_status"`
|
||||
Created int64 `json:"created_at" meddler:"build_created"`
|
||||
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"`
|
||||
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 {
|
||||
Number int `json:"number,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Link string `json:"link_url,omitempty"`
|
||||
Base *Commit `json:"base_commit,omitempty"`
|
||||
type BuildGroup struct {
|
||||
Date string
|
||||
Builds []*Build
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Sha string `json:"sha"`
|
||||
Ref string `json:"ref"`
|
||||
Link string `json:"link_url,omitempty"`
|
||||
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"`
|
||||
func GetBuild(db meddler.DB, id int64) (*Build, error) {
|
||||
var build = new(Build)
|
||||
var err = meddler.Load(db, buildTable, build, id)
|
||||
return build, err
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Login string `json:"login,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
func GetBuildNumber(db meddler.DB, repo *Repo, number int) (*Build, error) {
|
||||
var build = new(Build)
|
||||
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 (
|
||||
"encoding/xml"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/pkg/types"
|
||||
)
|
||||
|
||||
type CCProjects struct {
|
||||
@ -23,10 +21,10 @@ type CCProject struct {
|
||||
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{
|
||||
Name: r.Owner + "/" + r.Name,
|
||||
WebURL: r.Self,
|
||||
Name: r.FullName,
|
||||
WebURL: link,
|
||||
Activity: "Building",
|
||||
LastBuildStatus: "Unknown",
|
||||
LastBuildLabel: "Unknown",
|
||||
@ -34,24 +32,22 @@ func NewCC(r *types.Repo, b *types.Build) *CCProjects {
|
||||
|
||||
// if the build is not currently running then
|
||||
// we can return the latest build status.
|
||||
if b.Status != types.StatePending &&
|
||||
b.Status != types.StateRunning {
|
||||
if b.Status != StatusPending &&
|
||||
b.Status != StatusRunning {
|
||||
proj.Activity = "Sleeping"
|
||||
proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339)
|
||||
proj.LastBuildLabel = strconv.Itoa(b.Number)
|
||||
}
|
||||
|
||||
// ensure the last build state accepts a valid
|
||||
// ensure the last build Status accepts a valid
|
||||
// ccmenu enumeration
|
||||
switch b.Status {
|
||||
case types.StateError, types.StateKilled:
|
||||
case StatusError, StatusKilled:
|
||||
proj.LastBuildStatus = "Exception"
|
||||
case types.StateSuccess:
|
||||
case StatusSuccess:
|
||||
proj.LastBuildStatus = "Success"
|
||||
case types.StateFailure:
|
||||
case StatusFailure:
|
||||
proj.LastBuildStatus = "Failure"
|
||||
default:
|
||||
proj.LastBuildStatus = "Unknown"
|
||||
}
|
||||
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
BuildID int64 `json:"-" sql:"unique:ux_build_number,index:ix_job_build_id"`
|
||||
Number int `json:"number" sql:"unique:ux_build_number"`
|
||||
Status string `json:"status"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
ID int64 `json:"id" meddler:"job_id,pk"`
|
||||
BuildID int64 `json:"-" meddler:"job_build_id"`
|
||||
NodeID int64 `json:"-" meddler:"job_node_id"`
|
||||
Number int `json:"number" meddler:"job_number"`
|
||||
Status string `json:"status" meddler:"job_status"`
|
||||
ExitCode int `json:"exit_code" meddler:"job_exit_code"`
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"-" sql:"index:ix_repo_user_id"`
|
||||
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:"-"`
|
||||
}
|
||||
import (
|
||||
"github.com/drone/drone/shared/database"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type RepoLite struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"-"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Language string `json:"language"`
|
||||
Private bool `json:"private"`
|
||||
Created int64 `json:"created_at"`
|
||||
Updated int64 `json:"updated_at"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
type RepoCommit struct {
|
||||
ID int64 `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Number int `json:"number"`
|
||||
Status string `json:"status"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
type Repo struct {
|
||||
ID int64 `json:"id" meddler:"repo_id,pk"`
|
||||
UserID int64 `json:"-" meddler:"repo_user_id"`
|
||||
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"`
|
||||
Link string `json:"link_url" meddler:"repo_link"`
|
||||
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 {
|
||||
Pull bool `json:"pull" sql:"-"`
|
||||
Push bool `json:"push" sql:"-"`
|
||||
Admin bool `json:"admin" sql:"-"`
|
||||
func GetRepo(db meddler.DB, id int64) (*Repo, error) {
|
||||
var repo = new(Repo)
|
||||
var err = meddler.Load(db, repoTable, repo, id)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
type Hooks struct {
|
||||
PullRequest bool `json:"pull_request"`
|
||||
Push bool `json:"push"`
|
||||
Tags bool `json:"tags"`
|
||||
func GetRepoName(db meddler.DB, owner, name string) (*Repo, error) {
|
||||
var repo = new(Repo)
|
||||
var err = meddler.QueryRow(db, repo, database.Rebind(repoNameQuery), owner, name)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// Keypair represents an RSA public and private key
|
||||
// assigned to a repository. It may be used to clone
|
||||
// private repositories, or as a deployment key.
|
||||
type Keypair struct {
|
||||
Public string `json:"public,omitempty"`
|
||||
Private string `json:"private,omitempty"`
|
||||
func GetRepoList(db meddler.DB, user *User) ([]*Repo, error) {
|
||||
var repos = []*Repo{}
|
||||
var err = meddler.QueryAll(db, &repos, database.Rebind(repoListQuery), user.ID)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
Login string `json:"login,omitempty" sql:"unique:ux_user_login"`
|
||||
Token string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Avatar string `json:"avatar_url,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
Admin bool `json:"admin,omitempty"`
|
||||
|
||||
// randomly generated hash used to sign user
|
||||
// session and application tokens.
|
||||
Hash string `json:"-"`
|
||||
ID int64 `json:"id" meddler:"user_id,pk"`
|
||||
Login string `json:"login" meddler:"user_login"`
|
||||
Token string `json:"-" meddler:"user_token"`
|
||||
Secret string `json:"-" meddler:"user_secret"`
|
||||
Email string `json:"email" meddler:"user_email"`
|
||||
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
||||
Active bool `json:"active," meddler:"user_active"`
|
||||
Admin bool `json:"admin," meddler:"user_admin"`
|
||||
Hash string `json:"-" meddler:"user_hash"`
|
||||
}
|
||||
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/google/go-github/github"
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/gorilla/securecookie"
|
||||
"github.com/drone/drone/pkg/oauth2"
|
||||
"github.com/drone/drone/shared/oauth2"
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
// 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"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
|
||||
"github.com/drone/drone/pkg/oauth2"
|
||||
"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/model"
|
||||
"github.com/drone/drone/shared/envconfig"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/oauth2"
|
||||
"github.com/drone/drone/shared/token"
|
||||
|
||||
"github.com/Bugagazavr/go-gitlab-client"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -32,18 +32,14 @@ type Gitlab struct {
|
||||
PrivateMode bool
|
||||
SkipVerify bool
|
||||
Search bool
|
||||
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
func init() {
|
||||
remote.Register("gitlab", NewDriver)
|
||||
}
|
||||
func Load(env envconfig.Env) *Gitlab {
|
||||
config := env.String("REMOTE_CONFIG", "")
|
||||
|
||||
func NewDriver(config string) (remote.Remote, error) {
|
||||
url_, err := url.Parse(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
panic(err)
|
||||
}
|
||||
params := url_.Query()
|
||||
url_.RawQuery = ""
|
||||
@ -66,40 +62,71 @@ func NewDriver(config string) (remote.Remote, error) {
|
||||
// this is a temp workaround
|
||||
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
|
||||
|
||||
// here we cache permissions to avoid too many api
|
||||
// calls. this should really be moved outise the
|
||||
// remote plugin into the app
|
||||
gitlab.cache, err = lru.New(1028)
|
||||
return &gitlab, err
|
||||
return &gitlab
|
||||
}
|
||||
|
||||
func (g *Gitlab) Login(token, secret string) (*common.User, error) {
|
||||
client := NewClient(g.URL, token, g.SkipVerify)
|
||||
var login, err = client.CurrentUser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Login authenticates the session and returns the
|
||||
// remote user details.
|
||||
func (g *Gitlab) 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/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.Email = login.Email
|
||||
user.Token = token
|
||||
user.Secret = secret
|
||||
user.Token = token_.AccessToken
|
||||
user.Secret = token_.RefreshToken
|
||||
|
||||
if strings.HasPrefix(login.AvatarUrl, "http") {
|
||||
user.Avatar = login.AvatarUrl
|
||||
} else {
|
||||
user.Avatar = g.URL + "/" + login.AvatarUrl
|
||||
}
|
||||
return &user, nil
|
||||
|
||||
return user, true, nil
|
||||
}
|
||||
|
||||
// Orgs fetches the organizations for the given user.
|
||||
func (g *Gitlab) Orgs(u *common.User) ([]string, error) {
|
||||
return nil, nil
|
||||
func (g *Gitlab) Auth(token, secret string) (string, error) {
|
||||
client := NewClient(g.URL, token, g.SkipVerify)
|
||||
login, err := client.CurrentUser()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return login.Username, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
id, err := GetProjectId(g, client, owner, name)
|
||||
if err != nil {
|
||||
@ -110,7 +137,7 @@ func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &common.Repo{}
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = owner
|
||||
repo.Name = name
|
||||
repo.FullName = repo_.PathWithNamespace
|
||||
@ -123,22 +150,44 @@ func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
|
||||
}
|
||||
|
||||
if g.PrivateMode {
|
||||
repo.Private = true
|
||||
repo.IsPrivate = true
|
||||
} else {
|
||||
repo.Private = !repo_.Public
|
||||
repo.IsPrivate = !repo_.Public
|
||||
}
|
||||
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// Perm fetches the named repository from the remote system.
|
||||
func (g *Gitlab) 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
|
||||
// Repos fetches a list of repos from the remote system.
|
||||
func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||
|
||||
var repos = []*model.RepoLite{}
|
||||
|
||||
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)
|
||||
id, err := GetProjectId(g, client, owner, name)
|
||||
if err != nil {
|
||||
@ -149,43 +198,42 @@ func (g *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := &common.Perm{}
|
||||
m := &model.Perm{}
|
||||
m.Admin = IsAdmin(repo)
|
||||
m.Pull = IsRead(repo)
|
||||
m.Push = IsWrite(repo)
|
||||
g.cache.Add(key, m)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetScript fetches the build script (.drone.yml) from the remote
|
||||
// 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)
|
||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cfg, err := client.RepoRawFile(id, build.Commit.Sha, ".drone.yml")
|
||||
enc, _ := client.RepoRawFile(id, build.Commit.Sha, ".drone.sec")
|
||||
cfg, err := client.RepoRawFile(id, build.Commit, ".drone.yml")
|
||||
enc, _ := client.RepoRawFile(id, build.Commit, ".drone.sec")
|
||||
return cfg, enc, err
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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
|
||||
}
|
||||
|
||||
// Netrc returns a .netrc file that can be used to clone
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
netrc := &common.Netrc{}
|
||||
netrc := &model.Netrc{}
|
||||
netrc.Machine = url_.Host
|
||||
|
||||
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
|
||||
// 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)
|
||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||
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
|
||||
// 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)
|
||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||
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
|
||||
// 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()
|
||||
var payload, _ = ioutil.ReadAll(req.Body)
|
||||
var parsed, err = gogitlab.ParseHook(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch parsed.ObjectKind {
|
||||
@ -253,92 +301,87 @@ func (g *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
|
||||
case "tag_push", "push":
|
||||
return push(parsed, req)
|
||||
default:
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, 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"
|
||||
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
|
||||
|
||||
hook.Commit = &common.Commit{}
|
||||
hook.Commit.Message = parsed.ObjectAttributes.LastCommit.Message
|
||||
hook.Commit.Sha = parsed.ObjectAttributes.LastCommit.Id
|
||||
hook.Commit.Remote = parsed.ObjectAttributes.Source.HttpUrl
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = req.FormValue("owner")
|
||||
repo.Name = req.FormValue("name")
|
||||
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 {
|
||||
hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
|
||||
} 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
|
||||
hook.Commit.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp
|
||||
build.Branch = parsed.ObjectAttributes.SourceBranch
|
||||
// build.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp
|
||||
|
||||
hook.Commit.Author = &common.Author{}
|
||||
hook.Commit.Author.Login = parsed.ObjectAttributes.LastCommit.Author.Name
|
||||
hook.Commit.Author.Email = parsed.ObjectAttributes.LastCommit.Author.Email
|
||||
build.Author = parsed.ObjectAttributes.LastCommit.Author.Name
|
||||
build.Email = parsed.ObjectAttributes.LastCommit.Author.Email
|
||||
build.Title = parsed.ObjectAttributes.Title
|
||||
build.Link = parsed.ObjectAttributes.Url
|
||||
|
||||
hook.PullRequest = &common.PullRequest{}
|
||||
hook.PullRequest.Number = parsed.ObjectAttributes.IId
|
||||
hook.PullRequest.Title = parsed.ObjectAttributes.Title
|
||||
hook.PullRequest.Link = parsed.ObjectAttributes.Url
|
||||
|
||||
return hook, nil
|
||||
return repo, build, 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 hook = new(common.Hook)
|
||||
hook.Event = "push"
|
||||
hook.Repo = &common.Repo{}
|
||||
hook.Repo.Owner = req.FormValue("owner")
|
||||
hook.Repo.Name = req.FormValue("name")
|
||||
hook.Repo.Link = parsed.Repository.URL
|
||||
hook.Repo.Clone = cloneUrl
|
||||
hook.Repo.Branch = "master"
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = req.FormValue("owner")
|
||||
repo.Name = req.FormValue("name")
|
||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||
repo.Link = parsed.Repository.URL
|
||||
repo.Clone = cloneUrl
|
||||
repo.Branch = "master"
|
||||
|
||||
switch parsed.Repository.VisibilityLevel {
|
||||
case 0:
|
||||
hook.Repo.Private = true
|
||||
repo.IsPrivate = true
|
||||
case 10:
|
||||
hook.Repo.Private = true
|
||||
repo.IsPrivate = true
|
||||
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{}
|
||||
hook.Commit.Sha = parsed.After
|
||||
hook.Commit.Branch = parsed.Branch()
|
||||
hook.Commit.Ref = parsed.Ref
|
||||
hook.Commit.Remote = cloneUrl
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventPush
|
||||
build.Commit = parsed.After
|
||||
build.Branch = parsed.Branch()
|
||||
build.Ref = parsed.Ref
|
||||
// hook.Commit.Remote = cloneUrl
|
||||
|
||||
var head = parsed.Head()
|
||||
hook.Commit.Message = head.Message
|
||||
hook.Commit.Timestamp = head.Timestamp
|
||||
hook.Commit.Author = &common.Author{}
|
||||
build.Message = head.Message
|
||||
// build.Timestamp = head.Timestamp
|
||||
|
||||
// extracts the commit author (ideally email)
|
||||
// from the post-commit hook
|
||||
switch {
|
||||
case head.Author != nil:
|
||||
hook.Commit.Author.Email = head.Author.Email
|
||||
hook.Commit.Author.Login = parsed.UserName
|
||||
build.Email = head.Author.Email
|
||||
build.Author = parsed.UserName
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
||||
"github.com/drone/drone/pkg/remote/builtin/gitlab/testdata"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote/gitlab/testdata"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_Gitlab(t *testing.T) {
|
||||
@ -16,17 +15,17 @@ func Test_Gitlab(t *testing.T) {
|
||||
var server = testdata.NewServer()
|
||||
defer server.Close()
|
||||
|
||||
var gitlab, err = NewDriver(server.URL + "?client_id=test&client_secret=test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
env := map[string]string{}
|
||||
env["REMOTE_CONFIG"] = server.URL + "?client_id=test&client_secret=test"
|
||||
|
||||
var user = types.User{
|
||||
gitlab := Load(env)
|
||||
|
||||
var user = model.User{
|
||||
Login: "test_user",
|
||||
Token: "e3b0c44298fc1c149afbf4c8996fb",
|
||||
}
|
||||
|
||||
var repo = types.Repo{
|
||||
var repo = model.Repo{
|
||||
Name: "diaspora-client",
|
||||
Owner: "diaspora",
|
||||
}
|
||||
@ -56,7 +55,6 @@ func Test_Gitlab(t *testing.T) {
|
||||
g.It("Should return repo permissions", func() {
|
||||
perm, err := gitlab.Perm(&user, "diaspora", "diaspora-client")
|
||||
|
||||
fmt.Println(gitlab.(*Gitlab), err)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(perm.Admin).Equal(true)
|
||||
g.Assert(perm.Pull).Equal(true)
|
||||
@ -73,13 +71,13 @@ func Test_Gitlab(t *testing.T) {
|
||||
// Test activate method
|
||||
g.Describe("Activate", 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.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()
|
||||
})
|
||||
@ -95,20 +93,20 @@ func Test_Gitlab(t *testing.T) {
|
||||
})
|
||||
|
||||
// Test login method
|
||||
g.Describe("Login", func() {
|
||||
g.It("Should return user", func() {
|
||||
user, err := gitlab.Login("valid_token", "")
|
||||
// g.Describe("Login", func() {
|
||||
// g.It("Should return user", func() {
|
||||
// user, err := gitlab.Login("valid_token", "")
|
||||
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(user == nil).IsFalse()
|
||||
})
|
||||
// g.Assert(err == nil).IsTrue()
|
||||
// g.Assert(user == nil).IsFalse()
|
||||
// })
|
||||
|
||||
g.It("Should return error, when token is invalid", func() {
|
||||
_, err := gitlab.Login("invalid_token", "")
|
||||
// g.It("Should return error, when token is invalid", func() {
|
||||
// _, err := gitlab.Login("invalid_token", "")
|
||||
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
// g.Assert(err != nil).IsTrue()
|
||||
// })
|
||||
// })
|
||||
|
||||
// Test hook method
|
||||
g.Describe("Hook", func() {
|
||||
@ -119,14 +117,13 @@ func Test_Gitlab(t *testing.T) {
|
||||
bytes.NewReader(testdata.PushHook),
|
||||
)
|
||||
|
||||
hook, err := gitlab.Hook(req)
|
||||
repo, build, err := gitlab.Hook(req)
|
||||
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
||||
g.Assert(hook.Commit.Ref).Equal("refs/heads/master")
|
||||
g.Assert(repo.Owner).Equal("diaspora")
|
||||
g.Assert(repo.Name).Equal("diaspora-client")
|
||||
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||
|
||||
g.Assert(hook.PullRequest == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse tag push hook", func() {
|
||||
@ -136,14 +133,13 @@ func Test_Gitlab(t *testing.T) {
|
||||
bytes.NewReader(testdata.TagHook),
|
||||
)
|
||||
|
||||
hook, err := gitlab.Hook(req)
|
||||
repo, build, err := gitlab.Hook(req)
|
||||
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
||||
g.Assert(hook.Commit.Ref).Equal("refs/tags/v1.0.0")
|
||||
g.Assert(repo.Owner).Equal("diaspora")
|
||||
g.Assert(repo.Name).Equal("diaspora-client")
|
||||
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
|
||||
|
||||
g.Assert(hook.PullRequest == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse merge request hook", func() {
|
||||
@ -153,14 +149,13 @@ func Test_Gitlab(t *testing.T) {
|
||||
bytes.NewReader(testdata.MergeRequestHook),
|
||||
)
|
||||
|
||||
hook, err := gitlab.Hook(req)
|
||||
repo, build, err := gitlab.Hook(req)
|
||||
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(hook.Repo.Owner).Equal("diaspora")
|
||||
g.Assert(hook.Repo.Name).Equal("diaspora-client")
|
||||
g.Assert(repo.Owner).Equal("diaspora")
|
||||
g.Assert(repo.Name).Equal("diaspora-client")
|
||||
|
||||
g.Assert(hook.PullRequest.Number).Equal(1)
|
||||
g.Assert(hook.PullRequest.Title).Equal("MS-Viewport")
|
||||
g.Assert(build.Title).Equal("MS-Viewport")
|
||||
})
|
||||
})
|
||||
})
|
@ -5,7 +5,7 @@ import (
|
||||
"net/url"
|
||||
"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
|
@ -3,91 +3,70 @@ package remote
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/pkg/oauth2"
|
||||
"github.com/drone/drone/pkg/types"
|
||||
"github.com/drone/drone/model"
|
||||
"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.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, driver DriverFunc) {
|
||||
if driver == nil {
|
||||
panic("remote: Register driver is nil")
|
||||
}
|
||||
if _, dup := drivers[name]; dup {
|
||||
panic("remote: Register called twice for driver " + name)
|
||||
}
|
||||
drivers[name] = driver
|
||||
}
|
||||
switch driver {
|
||||
case "github":
|
||||
return github.Load(env)
|
||||
case "gitlab":
|
||||
return gitlab.Load(env)
|
||||
|
||||
// DriverFunc returns a new connection to the remote.
|
||||
// Config is a struct, with base remote configuration.
|
||||
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)
|
||||
default:
|
||||
log.Fatalf("unknown remote driver %s", driver)
|
||||
}
|
||||
log.Infof("remote: loading driver %s", driver)
|
||||
log.Infof("remote: loading config %s", config)
|
||||
return fn(config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Remote interface {
|
||||
// Login authenticates the session and returns the
|
||||
// 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.
|
||||
Orgs(u *types.User) ([]string, error)
|
||||
// Auth authenticates the session and returns the remote user
|
||||
// login for the given token and secret
|
||||
Auth(token, secret string) (string, error)
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// and returns the required data in a standard format.
|
||||
Hook(r *http.Request) (*types.Hook, 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
|
||||
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
||||
}
|
||||
|
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
|
||||
"github.com/drone/drone/Godeps/_workspace/src/github.com/square/go-jose"
|
||||
"github.com/franela/goblin"
|
||||
"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.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 New Issue
Block a user