You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	updated vendor files and paths
This commit is contained in:
		| @@ -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
		Reference in New Issue
	
	Block a user