diff --git a/.drone.yml b/.drone.yml index 182432e57..65b5070d1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,41 +5,11 @@ env: - GOROOT=/usr/local/go - PATH=$PATH:$GOROOT/bin:$GOPATH/bin script: - - sudo add-apt-repository ppa:git-core/ppa 1> /dev/null 2> /dev/null - - sudo apt-get update 1> /dev/null 2> /dev/null - - sudo apt-get update 1> /dev/null 2> /dev/null - - sudo apt-get -y install git zip libsqlite3-dev sqlite3 rpm 1> /dev/null 2> /dev/null - - gem install fpm - - rbenv rehash - - make docker - make deps + - make - make test - - make test_postgres - - make test_mysql - - make packages -services: - - postgres - - mysql + notify: email: recipients: - brad@drone.io - webhook: - urls: - - https://webhooks.gitter.im/e/$$GITTER_KEY - on_started: false - on_success: true - on_failure: true - -publish: - s3: - acl: public-read - region: us-east-1 - bucket: downloads.drone.io - access_key: $$AWS_KEY - secret_key: $$AWS_SECRET - source: packaging/output/ - target: $DRONE_BRANCH/ - recursive: true - when: - owner: drone diff --git a/.gitignore b/.gitignore index 6c837086d..8383f7998 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -NOTES.txt drone.sublime-project drone.sublime-workspace .vagrant @@ -11,8 +10,8 @@ drone.sublime-workspace *.rpm *.out *.rice-box.go +*.db +*.txt +*.toml -cli/cli -client/client -server/server -packaging/root/usr/local +drone \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0ec503c61..000000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# This is a Docker image for the Drone CI system. -# Use the following command to start the container: -# docker run -p 127.0.0.1:80:80 -t drone/drone - -FROM google/golang -ENV DRONE_SERVER_PORT :80 - -ADD . /gopath/src/github.com/drone/drone/ -WORKDIR /gopath/src/github.com/drone/drone - -RUN apt-get update -RUN apt-get -y install zip libsqlite3-dev sqlite3 1> /dev/null 2> /dev/null -RUN make docker deps build embed install - -EXPOSE 80 -VOLUME ["/var/lib/drone"] -ENTRYPOINT ["/usr/local/bin/droned"] diff --git a/Makefile b/Makefile index fe35faefe..7a9c3bd0b 100644 --- a/Makefile +++ b/Makefile @@ -1,101 +1,18 @@ SHA := $(shell git rev-parse --short HEAD) -VERSION := $(shell cat VERSION) -ITTERATION := $(shell date +%s) +VERSION := 0.4.0-alpha all: build deps: - go get github.com/GeertJohan/go.rice/rice go get -t -v ./... -docker: - mkdir -p $$GOPATH/src/github.com/docker/docker - git clone --depth=1 --branch=v1.5.0 git://github.com/docker/docker.git $$GOPATH/src/github.com/docker/docker - test: - @test -z "$(shell find . -name '*.go' | xargs gofmt -l)" || (echo "Need to run 'go fmt ./...'"; exit 1) go vet ./... go test -cover -short ./... -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/server/datastore/database - mysql -P 3306 --protocol=tcp -u root -e 'drop database test;' - -test_postgres: - TEST_DRIVER="postgres" TEST_DATASOURCE="host=127.0.0.1 user=postgres dbname=postgres sslmode=disable" go test -short github.com/drone/drone/server/datastore/database - build: - mkdir -p packaging/output - mkdir -p packaging/root/usr/local/bin - go build -o packaging/root/usr/local/bin/drone -ldflags "-X main.revision $(SHA) -X main.version $(VERSION)" github.com/drone/drone/cli - go build -o packaging/root/usr/local/bin/droned -ldflags "-X main.revision $(SHA) -X main.version $(VERSION)" github.com/drone/drone/server - -install: - install -t /usr/local/bin packaging/root/usr/local/bin/drone - install -t /usr/local/bin packaging/root/usr/local/bin/droned - -run: - @go run server/main.go --config=$$HOME/.drone/config.toml + go build -ldflags "-X main.revision $(SHA) -X main.version $(VERSION).$(SHA)" clean: find . -name "*.out" -delete - rm -rf packaging/output - rm -f packaging/root/usr/local/bin/drone - rm -f packaging/root/usr/local/bin/droned - -lessc: - lessc --clean-css server/app/styles/drone.less | autoprefixer > server/app/styles/drone.css - -packages: clean build embed deb rpm - -# embeds content in go source code so that it is compiled -# and packaged inside the go binary file. -embed: - rice --import-path="github.com/drone/drone/server" append --exec="packaging/root/usr/local/bin/droned" - -# creates a debian package for drone to install -# `sudo dpkg -i drone.deb` -deb: - fpm -s dir -t deb -n drone -v $(VERSION) -p packaging/output/drone.deb \ - --deb-priority optional --category admin \ - --force \ - --iteration $(ITTERATION) \ - --deb-compression bzip2 \ - --after-install packaging/scripts/postinst.deb \ - --before-remove packaging/scripts/prerm.deb \ - --after-remove packaging/scripts/postrm.deb \ - --url https://github.com/drone/drone \ - --description "Drone continuous integration server" \ - -m "Brad Rydzewski " \ - --license "Apache License 2.0" \ - --vendor "drone.io" -a amd64 \ - --config-files /etc/drone/drone.toml \ - packaging/root/=/ - cp packaging/output/drone.deb packaging/output/drone.deb.$(SHA) - -rpm: - fpm -s dir -t rpm -n drone -v $(VERSION) -p packaging/output/drone.rpm \ - --rpm-compression bzip2 --rpm-os linux \ - --force \ - --iteration $(ITTERATION) \ - --after-install packaging/scripts/postinst.rpm \ - --before-remove packaging/scripts/prerm.rpm \ - --after-remove packaging/scripts/postrm.rpm \ - --url https://github.com/drone/drone \ - --description "Drone continuous integration server" \ - -m "Brad Rydzewski " \ - --license "Apache License 2.0" \ - --vendor "drone.io" -a amd64 \ - --config-files /etc/drone/drone.toml \ - packaging/root/=/ - -# deploys drone to a staging server. this requires the following -# environment variables are set: -# -# DRONE_STAGING_HOST -- the hostname or ip -# DRONE_STAGING_USER -- the username used to login -# DRONE_STAGING_KEY -- the identity file path (~/.ssh/id_rsa) -deploy: - scp -i $$DRONE_STAGING_KEY packaging/output/drone.deb $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST:/tmp - ssh -i $$DRONE_STAGING_KEY $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST -- dpkg -i /tmp/drone.deb + rm -f drone diff --git a/VERSION b/VERSION deleted file mode 100644 index 0852d432c..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.3.0-alpha \ No newline at end of file diff --git a/cli/build.go b/cli/build.go deleted file mode 100644 index c14fb09f0..000000000 --- a/cli/build.go +++ /dev/null @@ -1,226 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/drone/drone/shared/build" - "github.com/drone/drone/shared/build/docker" - "github.com/drone/drone/shared/build/log" - "github.com/drone/drone/shared/build/repo" - "github.com/drone/drone/shared/build/script" - - "github.com/codegangsta/cli" -) - -const EXIT_STATUS = 1 - -// NewBuildCommand returns the CLI command for "build". -func NewBuildCommand() cli.Command { - return cli.Command{ - Name: "build", - Usage: "run a local build", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "i", - Value: "", - Usage: "identify file injected in the container", - }, - cli.BoolFlag{ - Name: "p", - Usage: "runs drone build in a privileged container", - }, - cli.BoolFlag{ - Name: "deploy", - Usage: "runs drone build with deployments enabled", - }, - cli.BoolFlag{ - Name: "publish", - Usage: "runs drone build with publishing enabled", - }, - cli.StringFlag{ - Name: "docker-host", - Value: getHost(), - Usage: "docker daemon address", - }, - cli.StringFlag{ - Name: "docker-cert", - Value: getCert(), - Usage: "docker daemon tls certificate", - }, - cli.StringFlag{ - Name: "docker-key", - Value: getKey(), - Usage: "docker daemon tls key", - }, - }, - Action: func(c *cli.Context) { - buildCommandFunc(c) - }, - } -} - -// buildCommandFunc executes the "build" command. -func buildCommandFunc(c *cli.Context) { - var privileged = c.Bool("p") - var identity = c.String("i") - var deploy = c.Bool("deploy") - var publish = c.Bool("publish") - var path string - - var dockerhost = c.String("docker-host") - var dockercert = c.String("docker-cert") - var dockerkey = c.String("docker-key") - - // the path is provided as an optional argument that - // will otherwise default to $PWD/.drone.yml - if len(c.Args()) > 0 { - path = c.Args()[0] - } - - switch len(path) { - case 0: - path, _ = os.Getwd() - path = filepath.Join(path, ".drone.yml") - default: - path = filepath.Clean(path) - path, _ = filepath.Abs(path) - path = filepath.Join(path, ".drone.yml") - } - - // this configures the default Docker logging levels, - // and suffix and prefix values. - log.SetPrefix("\033[2m[DRONE] ") - log.SetSuffix("\033[0m\n") - log.SetOutput(os.Stdout) - log.SetPriority(log.LOG_DEBUG) //LOG_NOTICE - docker.Logging = false - - var exit, _ = run(path, identity, dockerhost, dockercert, dockerkey, publish, deploy, privileged) - os.Exit(exit) -} - -// TODO this has gotten a bit out of hand. refactor input params -func run(path, identity, dockerhost, dockercert, dockerkey string, publish, deploy, privileged bool) (int, error) { - dockerClient, err := docker.NewHostCertFile(dockerhost, dockercert, dockerkey) - if err != nil { - log.Err(err.Error()) - return EXIT_STATUS, err - } - - // parse the private environment variables - envs := getParamMap("DRONE_ENV_") - - // parse the Drone yml file - s, err := script.ParseBuildFile(path, envs) - if err != nil { - log.Err(err.Error()) - return EXIT_STATUS, err - } - - // inject private environment variables into build script - for key, val := range envs { - s.Env = append(s.Env, key+"="+val) - } - - if deploy == false { - s.Deploy = nil - } - if publish == false { - s.Publish = nil - } - - // get the repository root directory - dir := filepath.Dir(path) - code := repo.Repo{ - Name: filepath.Base(dir), - Branch: "HEAD", // should we do this? - Path: dir, - } - - // does the local repository match the - // $GOPATH/src/{package} pattern? This is - // important so we know the target location - // where the code should be copied inside - // the container. - if gopath, ok := getRepoPath(dir); ok { - code.Dir = gopath - - } else if gopath, ok := getGoPath(dir); ok { - // in this case we found a GOPATH and - // reverse engineered the package path - code.Dir = gopath - - } else { - // otherwise just use directory name - code.Dir = filepath.Base(dir) - } - - // this is where the code gets uploaded to the container - // TODO move this code to the build package - code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir)) - - // ssh key to import into container - var key []byte - if len(identity) != 0 { - key, err = ioutil.ReadFile(identity) - if err != nil { - fmt.Printf("[Error] Could not find or read identity file %s\n", identity) - return EXIT_STATUS, err - } - } - - // loop through and create builders - builder := build.New(dockerClient) - builder.Build = s - builder.Repo = &code - builder.Key = key - builder.Stdout = os.Stdout - builder.Timeout = 300 * time.Minute - builder.Privileged = privileged - - // execute the build - if err := builder.Run(); err != nil { - log.Errf("Error executing build: %s", err.Error()) - return EXIT_STATUS, err - } - - fmt.Printf("\nDrone Build Results \033[90m(%s)\033[0m\n", dir) - - // loop through and print results - - build := builder.Build - res := builder.BuildState - duration := time.Duration(res.Finished - res.Started) - switch { - case builder.BuildState.ExitCode == 0: - fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) - case builder.BuildState.ExitCode != 0: - fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) - } - - return builder.BuildState.ExitCode, nil -} - -func getHost() string { - return os.Getenv("DOCKER_HOST") -} - -func getCert() string { - if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" { - return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "cert.pem") - } else { - return "" - } -} - -func getKey() string { - if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" { - return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "key.pem") - } else { - return "" - } -} diff --git a/cli/delete.go b/cli/delete.go deleted file mode 100644 index df3be638a..000000000 --- a/cli/delete.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewDeleteCommand returns the CLI command for "delete". -func NewDeleteCommand() cli.Command { - return cli.Command{ - Name: "delete", - Usage: "delete a repository", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, deleteCommandFunc) - }, - } -} - -// deleteCommandFunc executes the "delete" command. -func deleteCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, name string - var args = c.Args() - - if len(args) != 0 { - host, owner, name = parseRepo(args[0]) - } - - return client.Repos.Delete(host, owner, name) -} diff --git a/cli/disable.go b/cli/disable.go deleted file mode 100644 index 7f4467f18..000000000 --- a/cli/disable.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewDisableCommand returns the CLI command for "disable". -func NewDisableCommand() cli.Command { - return cli.Command{ - Name: "disable", - Usage: "disable a repository", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, disableCommandFunc) - }, - } -} - -// disableCommandFunc executes the "disable" command. -func disableCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, name string - var args = c.Args() - - if len(args) != 0 { - host, owner, name = parseRepo(args[0]) - } - - return client.Repos.Disable(host, owner, name) -} diff --git a/cli/enable.go b/cli/enable.go deleted file mode 100644 index c6b8ebadb..000000000 --- a/cli/enable.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewEnableCommand returns the CLI command for "enable". -func NewEnableCommand() cli.Command { - return cli.Command{ - Name: "enable", - Usage: "enable a repository", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, enableCommandFunc) - }, - } -} - -// enableCommandFunc executes the "enable" command. -func enableCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, name string - var args = c.Args() - - if len(args) != 0 { - host, owner, name = parseRepo(args[0]) - } - - return client.Repos.Enable(host, owner, name) -} diff --git a/cli/handle.go b/cli/handle.go deleted file mode 100644 index a7fc48f6b..000000000 --- a/cli/handle.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "os" - - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -type handlerFunc func(*cli.Context, *client.Client) error - -// handle wraps the command function handlers and -// sets up the environment. -func handle(c *cli.Context, fn handlerFunc) { - var token = c.GlobalString("token") - var server = c.GlobalString("server") - - // if no server url is provided we can default - // to the hosted Drone service. - if len(server) == 0 { - server = "http://test.drone.io" - } - - // create the drone client - client := client.New(token, server) - - // handle the function - if err := fn(c, client); err != nil { - println(err.Error()) - os.Exit(1) - } -} diff --git a/cli/keys.go b/cli/keys.go deleted file mode 100644 index 002908693..000000000 --- a/cli/keys.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewSetKeyCommand returns the CLI command for "set-key". -func NewSetKeyCommand() cli.Command { - return cli.Command{ - Name: "set-key", - Usage: "sets the SSH private key used to clone", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, setKeyCommandFunc) - }, - } -} - -// setKeyCommandFunc executes the "set-key" command. -func setKeyCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, name, path string - var args = c.Args() - - if len(args) != 0 { - host, owner, name = parseRepo(args[0]) - } - - if len(args) == 2 { - path = args[1] - } - - pub, err := ioutil.ReadFile(path) - if err != nil { - return fmt.Errorf("Could not find private RSA key %s. %s", path, err) - } - - path_pub := path + ".pub" - priv, err := ioutil.ReadFile(path_pub) - if err != nil { - return fmt.Errorf("Could not find public RSA key %s. %s", path_pub, err) - } - - return client.Repos.SetKey(host, owner, name, string(pub), string(priv)) -} diff --git a/cli/main.go b/cli/main.go deleted file mode 100644 index 78935e82c..000000000 --- a/cli/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "os" - - "github.com/codegangsta/cli" -) - -var ( - // commit sha for the current build. - version string - revision string -) - -func main() { - app := cli.NewApp() - app.Name = "drone" - app.Version = version - app.Usage = "command line utility" - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "t, token", - Value: "", - Usage: "server auth token", - EnvVar: "DRONE_TOKEN", - }, - cli.StringFlag{ - Name: "s, server", - Value: "", - Usage: "server location", - EnvVar: "DRONE_SERVER", - }, - } - - app.Commands = []cli.Command{ - NewBuildCommand(), - NewReposCommand(), - NewStatusCommand(), - NewEnableCommand(), - NewDisableCommand(), - NewRestartCommand(), - NewWhoamiCommand(), - NewSetKeyCommand(), - NewDeleteCommand(), - } - - app.Run(os.Args) -} diff --git a/cli/repos.go b/cli/repos.go deleted file mode 100644 index 8c5761dbd..000000000 --- a/cli/repos.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewReposCommand returns the CLI command for "repos". -func NewReposCommand() cli.Command { - return cli.Command{ - Name: "repos", - Usage: "lists active remote repositories", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "a, all", - Usage: "list all repositories", - }, - }, - Action: func(c *cli.Context) { - handle(c, reposCommandFunc) - }, - } -} - -// reposCommandFunc executes the "repos" command. -func reposCommandFunc(c *cli.Context, client *client.Client) error { - repos, err := client.Repos.List() - if err != nil { - return err - } - - var all = c.Bool("a") - for _, repo := range repos { - if !all && !repo.Active { - continue - } - - fmt.Printf("%s/%s/%s\n", repo.Host, repo.Owner, repo.Name) - } - return nil -} diff --git a/cli/restart.go b/cli/restart.go deleted file mode 100644 index cf6b51ecb..000000000 --- a/cli/restart.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewRestartCommand returns the CLI command for "restart". -func NewRestartCommand() cli.Command { - return cli.Command{ - Name: "restart", - Usage: "restarts a build on the server", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, restartCommandFunc) - }, - } -} - -// restartCommandFunc executes the "restart" command. -func restartCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, repo, branch, sha string - var args = c.Args() - - if len(args) != 0 { - host, owner, repo = parseRepo(args[0]) - } - - switch len(args) { - case 2: - branch = "master" - sha = args[1] - case 3, 4, 5: - branch = args[1] - sha = args[2] - } - - return client.Commits.Rebuild(host, owner, repo, branch, sha) -} diff --git a/cli/status.go b/cli/status.go deleted file mode 100644 index 7f3412f99..000000000 --- a/cli/status.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewStatusCommand returns the CLI command for "status". -func NewStatusCommand() cli.Command { - return cli.Command{ - Name: "status", - Usage: "display a repository build status", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "b, branch", - Usage: "branch to display", - }, - }, - Action: func(c *cli.Context) { - handle(c, statusCommandFunc) - }, - } -} - -// statusCommandFunc executes the "status" command. -func statusCommandFunc(c *cli.Context, client *client.Client) error { - var host, owner, repo, branch string - var args = c.Args() - - if len(args) != 0 { - host, owner, repo = parseRepo(args[0]) - } - - if c.IsSet("branch") { - branch = c.String("branch") - } else { - branch = "master" - } - - builds, err := client.Commits.ListBranch(host, owner, repo, branch) - if err != nil { - return err - } else if len(builds) == 0 { - return nil - } - - var build = builds[len(builds)-1] - fmt.Printf("%s\t%s\t%s\t%s\t%v", build.Status, build.ShaShort(), build.Timestamp, build.Author, build.Message) - return nil -} diff --git a/cli/util.go b/cli/util.go deleted file mode 100644 index ec4f2de7d..000000000 --- a/cli/util.go +++ /dev/null @@ -1,112 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - "time" -) - -func parseRepo(str string) (host, owner, repo string) { - var parts = strings.Split(str, "/") - if len(parts) != 3 { - return - } - host = parts[0] - owner = parts[1] - repo = parts[2] - return -} - -// getGoPath checks the source codes absolute path -// in reference to the host operating system's GOPATH -// to correctly determine the code's package path. This -// is Go-specific, since Go code must exist in -// $GOPATH/src/github.com/{owner}/{name} -func getGoPath(dir string) (string, bool) { - path := os.Getenv("GOPATH") - if len(path) == 0 { - return "", false - } - // append src to the GOPATH, since - // the code will be stored in the src dir - path = filepath.Join(path, "src") - if !filepath.HasPrefix(dir, path) { - return "", false - } - - // remove the prefix from the directory - // this should leave us with the go package name - return dir[len(path):], true -} - -var gopathExp = regexp.MustCompile("./src/(github.com/[^/]+/[^/]+|bitbucket.org/[^/]+/[^/]+|code.google.com/[^/]+/[^/]+)") - -// getRepoPath checks the source codes absolute path -// on the host operating system in an attempt -// to correctly determine the code's package path. This -// is Go-specific, since Go code must exist in -// $GOPATH/src/github.com/{owner}/{name} -func getRepoPath(dir string) (path string, ok bool) { - // let's get the package directory based - // on the path in the host OS - indexes := gopathExp.FindStringIndex(dir) - if len(indexes) == 0 { - return - } - - index := indexes[len(indexes)-1] - - // if the dir is /home/ubuntu/go/src/github.com/foo/bar - // the index will start at /src/github.com/foo/bar. - // We'll need to strip "/src/" which is where the - // magic number 5 comes from. - index = strings.LastIndex(dir, "/src/") - return dir[index+5:], true -} - -// GetRepoMap returns a map of enivronment variables that -// should be injected into the .drone.yml -func getParamMap(prefix string) map[string]string { - envs := map[string]string{} - - for _, item := range os.Environ() { - env := strings.SplitN(item, "=", 2) - if len(env) != 2 { - continue - } - - key := env[0] - val := env[1] - if strings.HasPrefix(key, prefix) { - envs[strings.TrimPrefix(key, prefix)] = val - } - } - return envs -} - -// prints the time as a human readable string -func humanizeDuration(d time.Duration) string { - if seconds := int(d.Seconds()); seconds < 1 { - return "Less than a second" - } else if seconds < 60 { - return fmt.Sprintf("%d seconds", seconds) - } else if minutes := int(d.Minutes()); minutes == 1 { - return "About a minute" - } else if minutes < 60 { - return fmt.Sprintf("%d minutes", minutes) - } else if hours := int(d.Hours()); hours == 1 { - return "About an hour" - } else if hours < 48 { - return fmt.Sprintf("%d hours", hours) - } else if hours < 24*7*2 { - return fmt.Sprintf("%d days", hours/24) - } else if hours < 24*30*3 { - return fmt.Sprintf("%d weeks", hours/24/7) - } else if hours < 24*365*2 { - return fmt.Sprintf("%d months", hours/24/30) - } - return fmt.Sprintf("%f years", d.Hours()/24/365) -} diff --git a/cli/whoami.go b/cli/whoami.go deleted file mode 100644 index 7bff13da3..000000000 --- a/cli/whoami.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/codegangsta/cli" - "github.com/drone/drone/client" -) - -// NewWhoamiCommand returns the CLI command for "whoami". -func NewWhoamiCommand() cli.Command { - return cli.Command{ - Name: "whoami", - Usage: "outputs the current user", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - handle(c, whoamiCommandFunc) - }, - } -} - -// whoamiCommandFunc communicates with the server and echoes -// the currently authenticated user. -func whoamiCommandFunc(c *cli.Context, client *client.Client) error { - user, err := client.Users.GetCurrent() - if err != nil { - return err - } - - fmt.Println(user.Login) - return nil -} diff --git a/client/client.go b/client/client.go deleted file mode 100644 index d40d85f38..000000000 --- a/client/client.go +++ /dev/null @@ -1,149 +0,0 @@ -package client - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "net/url" - "strconv" -) - -type Client struct { - token string - url string - - Commits *CommitService - Repos *RepoService - Users *UserService -} - -func New(token, url string) *Client { - c := Client{ - token: token, - url: url, - } - - c.Commits = &CommitService{&c} - c.Repos = &RepoService{&c} - c.Users = &UserService{&c} - return &c -} - -var ( - ErrNotFound = errors.New("Not Found") - ErrForbidden = errors.New("Forbidden") - ErrBadRequest = errors.New("Bad Request") - ErrNotAuthorized = errors.New("Unauthorized") - ErrInternalServer = errors.New("Internal Server Error") -) - -// runs an http.Request and parses the JSON-encoded http.Response, -// storing the result in the value pointed to by v. -func (c *Client) run(method, path string, in, out interface{}) error { - - // create the URI - uri, err := url.Parse(c.url + path) - if err != nil { - return err - } - - if len(uri.Scheme) == 0 { - uri.Scheme = "http" - } - - if len(c.token) > 0 { - params := uri.Query() - params.Add("access_token", c.token) - uri.RawQuery = params.Encode() - } - - // create the request - req := &http.Request{ - URL: uri, - Method: method, - ProtoMajor: 1, - ProtoMinor: 1, - Close: true, - ContentLength: 0, - } - - // if data input is provided, serialize to JSON - if in != nil { - inJson, err := json.Marshal(in) - if err != nil { - return err - } - - buf := bytes.NewBuffer(inJson) - req.Body = ioutil.NopCloser(buf) - - req.ContentLength = int64(len(inJson)) - req.Header.Set("Content-Length", strconv.Itoa(len(inJson))) - req.Header.Set("Content-Type", "application/json") - } - - // make the request using the default http client - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - - // make sure we defer close the body - defer resp.Body.Close() - - // Check for an http error status (ie not 200 StatusOK) - switch resp.StatusCode { - case 404: - return ErrNotFound - case 403: - return ErrForbidden - case 401: - return ErrNotAuthorized - case 400: - return ErrBadRequest - case 500: - return ErrInternalServer - } - - // Decode the JSON response - if out != nil { - return json.NewDecoder(resp.Body).Decode(out) - } - - return nil -} - -// do makes an http.Request and returns the response -func (c *Client) do(method, path string) (*http.Response, error) { - - // create the URI - uri, err := url.Parse(c.url + path) - if err != nil { - return nil, err - } - - if len(uri.Scheme) == 0 { - uri.Scheme = "http" - } - - if len(c.token) > 0 { - params := uri.Query() - params.Add("access_token", c.token) - uri.RawQuery = params.Encode() - } - - // create the request - req := &http.Request{ - URL: uri, - Method: method, - ProtoMajor: 1, - ProtoMinor: 1, - Close: true, - ContentLength: 0, - } - - // make the request using the default http client - return http.DefaultClient.Do(req) -} diff --git a/client/commits.go b/client/commits.go deleted file mode 100644 index 0d9cc8f15..000000000 --- a/client/commits.go +++ /dev/null @@ -1,52 +0,0 @@ -package client - -import ( - "fmt" - "io" - - "github.com/drone/drone/shared/model" -) - -type CommitService struct { - *Client -} - -// GET /api/repos/{host}/{owner}/{name}/branch/{branch}/commit/{commit} -func (s *CommitService) Get(host, owner, name, branch, sha string) (*model.Commit, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s", host, owner, name, branch, sha) - var commit = model.Commit{} - var err = s.run("GET", path, nil, &commit) - return &commit, err -} - -// GET /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console -func (s *CommitService) GetOutput(host, owner, name, branch, sha string) (io.ReadCloser, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s/console", host, owner, name, branch, sha) - resp, err := s.do("GET", path) - if err != nil { - return nil, nil - } - return resp.Body, nil -} - -// POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}?action=rebuild -func (s *CommitService) Rebuild(host, owner, name, branch, sha string) error { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s?action=rebuild", host, owner, name, branch, sha) - return s.run("POST", path, nil, nil) -} - -// GET /api/repos/{host}/{owner}/{name}/feed -func (s *CommitService) List(host, owner, name string) ([]*model.Commit, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/feed", host, owner, name) - var list []*model.Commit - var err = s.run("GET", path, nil, &list) - return list, err -} - -// GET /api/repos/{host}/{owner}/{name}/branch/{branch} -func (s *CommitService) ListBranch(host, owner, name, branch string) ([]*model.Commit, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name) - var list []*model.Commit - var err = s.run("GET", path, nil, &list) - return list, err -} diff --git a/client/repos.go b/client/repos.go deleted file mode 100644 index 29c0d38f4..000000000 --- a/client/repos.go +++ /dev/null @@ -1,62 +0,0 @@ -package client - -import ( - "fmt" - - "github.com/drone/drone/shared/model" -) - -type RepoService struct { - *Client -} - -// GET /api/repos/{host}/{owner}/{name} -func (s *RepoService) Get(host, owner, name string) (*model.Repo, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) - var repo = model.Repo{} - var err = s.run("GET", path, nil, &repo) - return &repo, err -} - -// PUT /api/repos/{host}/{owner}/{name} -func (s *RepoService) Update(repo *model.Repo) (*model.Repo, error) { - var path = fmt.Sprintf("/api/repos/%s/%s/%s", repo.Host, repo.Owner, repo.Name) - var result = model.Repo{} - var err = s.run("PUT", path, &repo, &result) - return &result, err -} - -// POST /api/repos/{host}/{owner}/{name} -func (s *RepoService) Enable(host, owner, name string) error { - var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) - return s.run("POST", path, nil, nil) -} - -// POST /api/repos/{host}/{owner}/{name}/deactivate -func (s *RepoService) Disable(host, owner, name string) error { - var path = fmt.Sprintf("/api/repos/%s/%s/%s/deactivate", host, owner, name) - return s.run("POST", path, nil, nil) -} - -// DELETE /api/repos/{host}/{owner}/{name}?remove=true -func (s *RepoService) Delete(host, owner, name string) error { - var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) - return s.run("DELETE", path, nil, nil) -} - -// PUT /api/repos/{host}/{owner}/{name} -func (s *RepoService) SetKey(host, owner, name, pub, priv string) error { - var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) - var in = struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` - }{pub, priv} - return s.run("PUT", path, &in, nil) -} - -// GET /api/user/repos -func (s *RepoService) List() ([]*model.Repo, error) { - var repos []*model.Repo - var err = s.run("GET", "/api/user/repos", nil, &repos) - return repos, err -} diff --git a/client/users.go b/client/users.go deleted file mode 100644 index 0fdce70b6..000000000 --- a/client/users.go +++ /dev/null @@ -1,47 +0,0 @@ -package client - -import ( - "fmt" - - "github.com/drone/drone/shared/model" -) - -type UserService struct { - *Client -} - -// GET /api/users/{host}/{login} -func (s *UserService) Get(remote, login string) (*model.User, error) { - var path = fmt.Sprintf("/api/users/%s/%s", remote, login) - var user = model.User{} - var err = s.run("GET", path, nil, &user) - return &user, err -} - -// GET /api/user -func (s *UserService) GetCurrent() (*model.User, error) { - var user = model.User{} - var err = s.run("GET", "/api/user", nil, &user) - return &user, err -} - -// POST /api/users/{host}/{login} -func (s *UserService) Create(remote, login string) (*model.User, error) { - var path = fmt.Sprintf("/api/users/%s/%s", remote, login) - var user = model.User{} - var err = s.run("POST", path, nil, &user) - return &user, err -} - -// DELETE /api/users/{host}/{login} -func (s *UserService) Delete(remote, login string) error { - var path = fmt.Sprintf("/api/users/%s/%s", remote, login) - return s.run("DELETE", path, nil, nil) -} - -// GET /api/users -func (s *UserService) List() ([]*model.User, error) { - var users []*model.User - var err = s.run("GET", "/api/users", nil, &users) - return users, err -} diff --git a/common/build.go b/common/build.go new file mode 100644 index 000000000..305542fcd --- /dev/null +++ b/common/build.go @@ -0,0 +1,67 @@ +package common + +const ( + StatePending = "pending" + StateRunning = "running" + StateSuccess = "success" + StateFailure = "failure" + StateKilled = "killed" + StateError = "error" +) + +type Build struct { + Number int `json:"number"` + State string `json:"state"` + Tasks int `json:"task_count"` + Duration int64 `json:"duration"` + Started int64 `json:"started_at"` + Finished int64 `json:"finished_at"` + Created int64 `json:"created_at"` + Updated int64 `json:"updated_at"` + + // Commit represents the commit data send in the + // post-commit hook. This will not be populated when + // a pull requests. + Commit *Commit `json:"head_commit,omitempty"` + + // PullRequest represents the pull request data sent + // in the post-commit hook. This will only be populated + // when a pull request. + PullRequest *PullRequest `json:"pull_request,omitempty"` +} + +type Status struct { + State string `json:"state"` + Link string `json:"target_url"` + Desc string `json:"description"` + Context string `json:"context"` +} + +type Commit struct { + Sha string `json:"sha,omitempty"` + Ref string `json:"ref,omitempty"` + Message string `json:"message,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Author *Author `json:"author,omitempty"` + Remote *Remote `json:"repo,omitempty"` +} + +type PullRequest struct { + Number int `json:"number,omitempty"` + Title string `json:"title,omitempty"` + Source *Commit `json:"source,omitempty"` + Target *Commit `json:"target,omitempty"` +} + +type Author struct { + Name string `json:"name,omitempty"` + Login string `json:"login,omitempty"` + Email string `json:"email,omitempty"` + Gravatar string `json:"gravatar_id,omitempty"` +} + +type Remote struct { + Name string `json:"name,omitempty"` + FullName string `json:"full_name,omitempty"` + Clone string `json:"clone_url,omitempty"` +} diff --git a/shared/model/cctray.go b/common/ccmenu/ccmenu.go similarity index 56% rename from shared/model/cctray.go rename to common/ccmenu/ccmenu.go index ab0f3f35c..55eaf3ad8 100644 --- a/shared/model/cctray.go +++ b/common/ccmenu/ccmenu.go @@ -1,8 +1,11 @@ -package model +package ccmenu import ( "encoding/xml" + "strconv" "time" + + "github.com/drone/drone/common" ) type CCProjects struct { @@ -20,7 +23,7 @@ type CCProject struct { WebURL string `xml:"webUrl,attr"` } -func NewCC(r *Repo, c *Commit, url string) *CCProjects { +func NewCC(r *common.Repo, b *common.Build, url string) *CCProjects { proj := &CCProject{ Name: r.Owner + "/" + r.Name, WebURL: url, @@ -31,21 +34,24 @@ func NewCC(r *Repo, c *Commit, url string) *CCProjects { // if the build is not currently running then // we can return the latest build status. - if c.Status != StatusStarted && - c.Status != StatusEnqueue { + if b.State != common.StatePending && + b.State != common.StateRunning { proj.Activity = "Sleeping" - proj.LastBuildStatus = c.Status - proj.LastBuildTime = time.Unix(c.Started, 0).Format(time.RFC3339) - proj.LastBuildLabel = c.ShaShort() + proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339) + proj.LastBuildLabel = strconv.Itoa(b.Number) } - // If the build is not running, and not successful, - // then set to Failure. Not sure CCTray will support - // our custom failure types (ie Killed) - if c.Status != StatusStarted && - c.Status != StatusEnqueue && - c.Status != StatusSuccess { - proj.LastBuildStatus = StatusFailure + // ensure the last build state accepts a valid + // ccmenu enumeration + switch b.State { + case common.StateError, common.StateKilled: + proj.LastBuildStatus = "Exception" + case common.StateSuccess: + proj.LastBuildStatus = "Success" + case common.StateFailure: + proj.LastBuildStatus = "Failure" + default: + proj.LastBuildStatus = "Unknown" } return &CCProjects{Project: proj} diff --git a/common/gravatar/gravatar.go b/common/gravatar/gravatar.go new file mode 100644 index 000000000..e13666dad --- /dev/null +++ b/common/gravatar/gravatar.go @@ -0,0 +1,16 @@ +package gravatar + +import ( + "crypto/md5" + "fmt" + "strings" +) + +// helper function to create a Gravatar Hash +// for the given Email address. +func Generate(email string) string { + email = strings.ToLower(strings.TrimSpace(email)) + hash := md5.New() + hash.Write([]byte(email)) + return fmt.Sprintf("%x", hash.Sum(nil)) +} diff --git a/common/hook.go b/common/hook.go new file mode 100644 index 000000000..a554aa568 --- /dev/null +++ b/common/hook.go @@ -0,0 +1,7 @@ +package common + +type Hook struct { + Repo *Repo + Commit *Commit + PullRequest *PullRequest +} diff --git a/shared/httputil/httputil.go b/common/httputil/httputil.go similarity index 100% rename from shared/httputil/httputil.go rename to common/httputil/httputil.go diff --git a/plugin/remote/github/oauth/oauth.go b/common/oauth2/oauth2.go similarity index 99% rename from plugin/remote/github/oauth/oauth.go rename to common/oauth2/oauth2.go index fafdffcfc..97059c499 100644 --- a/plugin/remote/github/oauth/oauth.go +++ b/common/oauth2/oauth2.go @@ -34,7 +34,7 @@ // // btw, r.FormValue("state") == "foo" // } // -package oauth +package oauth2 import ( "encoding/json" diff --git a/common/repo.go b/common/repo.go new file mode 100644 index 000000000..235599dd7 --- /dev/null +++ b/common/repo.go @@ -0,0 +1,59 @@ +package common + +type Repo struct { + ID int64 `json:"id"` + Owner string `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Language string `json:"language"` + Private bool `json:"private"` + Link string `json:"link_url"` + Clone string `json:"clone_url"` + Branch string `json:"default_branch"` + + Timeout int64 `json:"timeout"` + Trusted bool `json:"trusted"` + Disabled bool `json:"disabled"` + DisablePR bool `json:"disable_prs"` + DisableTag bool `json:"disable_tags"` + + Created int64 `json:"created_at"` + Updated int64 `json:"updated_at"` + + User *User `json:"user,omitempty"` + Last *Build `json:"last_build,omitempty"` +} + +// 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"` + Private string `json:"-"` +} + +// Subscriber represents a user's subscription +// to a repository. This determines if the repository +// is displayed on the user dashboard and in the user +// event feed. +type Subscriber struct { + Login string `json:"login,omitempty"` + + // Determines if notifications should be + // received from this repository. + Subscribed bool `json:"subscribed"` + + // Determines if all notifications should be + // blocked from this repository. + Ignored bool `json:"ignored"` +} + +// Perm represents a user's permissiont to access +// a repository. Pull indicates read-only access. Push +// indiates write access. Admin indicates god access. +type Perm struct { + Login string `json:"login,omitempty"` + Pull bool `json:"pull"` + Push bool `json:"push"` + Admin bool `json:"admin"` +} diff --git a/shared/sshutil/sshutil.go b/common/sshutil/sshutil.go similarity index 100% rename from shared/sshutil/sshutil.go rename to common/sshutil/sshutil.go diff --git a/common/task.go b/common/task.go new file mode 100644 index 000000000..d266ca5d5 --- /dev/null +++ b/common/task.go @@ -0,0 +1,14 @@ +package common + +type Task struct { + Number int `json:"number"` + State string `json:"state"` + ExitCode int `json:"exit_code"` + Duration int64 `json:"duration"` + Started int64 `json:"started_at"` + Finished int64 `json:"finished_at"` + + // Environment represents the build environment + // combination from the matrix. + Environment map[string]string `json:"environment,omitempty"` +} diff --git a/common/token.go b/common/token.go new file mode 100644 index 000000000..7370598b0 --- /dev/null +++ b/common/token.go @@ -0,0 +1,9 @@ +package common + +type Token struct { + Sha string `json:"-"` + Login string `json:"-"` + Repos []string `json:"repos,omitempty"` + Scopes []string `json:"scopes,omitempty"` + Expiry int64 `json:"expiry,omitempty"` +} diff --git a/common/user.go b/common/user.go new file mode 100644 index 000000000..22801e544 --- /dev/null +++ b/common/user.go @@ -0,0 +1,13 @@ +package common + +type User struct { + Login string `json:"login,omitempty"` + Token string `json:"-"` + Secret string `json:"-"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Gravatar string `json:"gravatar_id,omitempty"` + Admin bool `json:"admin,omitempty"` + Created int64 `json:"created_at,omitempty"` + Updated int64 `json:"updated_at,omitempty"` +} diff --git a/datastore/bolt/bolt.go b/datastore/bolt/bolt.go new file mode 100644 index 000000000..a128bb9bc --- /dev/null +++ b/datastore/bolt/bolt.go @@ -0,0 +1,66 @@ +package bolt + +import ( + "errors" + "github.com/boltdb/bolt" +) + +var ( + ErrKeyNotFound = errors.New("Key not found") + ErrKeyExists = errors.New("Key exists") +) + +var ( + bucketUser = []byte("user") + bucketUserRepos = []byte("user_repos") + bucketUserTokens = []byte("user_tokens") + bucketTokens = []byte("token") + bucketRepo = []byte("repo") + bucketRepoKeys = []byte("repo_keys") + bucketRepoParams = []byte("repo_params") + bucketRepoUsers = []byte("repo_users") + bucketBuild = []byte("build") + bucketBuildStatus = []byte("build_status") + bucketBuildTasks = []byte("build_tasks") + bucketBuildLogs = []byte("build_logs") + bucketBuildSeq = []byte("build_seq") +) + +type DB struct { + *bolt.DB +} + +func New(path string) (*DB, error) { + db, err := bolt.Open(path, 0600, nil) + if err != nil { + return nil, err + } + + // Initialize all the required buckets. + db.Update(func(tx *bolt.Tx) error { + tx.CreateBucketIfNotExists(bucketUser) + tx.CreateBucketIfNotExists(bucketUserRepos) + tx.CreateBucketIfNotExists(bucketUserTokens) + tx.CreateBucketIfNotExists(bucketTokens) + tx.CreateBucketIfNotExists(bucketRepo) + tx.CreateBucketIfNotExists(bucketRepoKeys) + tx.CreateBucketIfNotExists(bucketRepoParams) + tx.CreateBucketIfNotExists(bucketRepoUsers) + tx.CreateBucketIfNotExists(bucketBuild) + tx.CreateBucketIfNotExists(bucketBuildStatus) + tx.CreateBucketIfNotExists(bucketBuildTasks) + tx.CreateBucketIfNotExists(bucketBuildLogs) + tx.CreateBucketIfNotExists(bucketBuildSeq) + return nil + }) + + return &DB{db}, nil +} + +func Must(path string) *DB { + db, err := New(path) + if err != nil { + panic(err) + } + return db +} diff --git a/datastore/bolt/build.go b/datastore/bolt/build.go new file mode 100644 index 000000000..5187b4a2c --- /dev/null +++ b/datastore/bolt/build.go @@ -0,0 +1,91 @@ +package bolt + +import ( + "bytes" + "strconv" + "time" + + "github.com/boltdb/bolt" + "github.com/drone/drone/common" +) + +// GetBuild gets the specified build number for the +// named repository and build number +func (db *DB) GetBuild(repo string, build int) (*common.Build, error) { + build_ := &common.Build{} + key := []byte(repo + "/" + strconv.Itoa(build)) + err := get(db, bucketBuild, key, build_) + return build_, err +} + +// GetBuildList gets a list of recent builds for the +// named repository. +func (db *DB) GetBuildList(repo string) ([]*common.Build, error) { + // get the last build sequence number (stored in key in `bucketBuildSeq`) + // get all builds where build number > sequent-20 + // github.com/foo/bar/{number} + return nil, nil +} + +// GetBuildLast gets the last executed build for the +// named repository. +func (db *DB) GetBuildLast(repo string) (*common.Build, error) { + // get the last build sequence number (stored in key in `bucketBuildSeq`) + // return that build + return nil, nil +} + +// GetBuildStatus gets the named build status for the +// named repository and build number. +func (db *DB) GetBuildStatus(repo string, build int, status string) (*common.Status, error) { + status_ := &common.Status{} + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + status) + err := update(db, bucketBuildStatus, key, status) + return status_, err +} + +// GetBuildStatusList gets a list of all build statues for +// the named repository and build number. +func (db *DB) GetBuildStatusList(repo string, build int) ([]*common.Status, error) { + // TODO (bradrydzewski) explore efficiency of cursor vs index + + statuses := []*common.Status{} + err := db.View(func(tx *bolt.Tx) error { + c := tx.Bucket(bucketBuildStatus).Cursor() + prefix := []byte(repo + "/" + strconv.Itoa(build) + "/") + for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { + status := &common.Status{} + if err := decode(v, status); err != nil { + return err + } + statuses = append(statuses, status) + } + return nil + }) + return statuses, err +} + +// InsertBuild inserts a new build for the named repository +func (db *DB) InsertBuild(repo string, build *common.Build) error { + // TODO(bradrydzewski) use the `bucketBuildSeq` to increment the + // sequence for the build and set the build number. + key := []byte(repo + "/" + strconv.Itoa(build.Number)) + return update(db, bucketBuild, key, build) +} + +// InsertBuildStatus inserts a new build status for the +// named repository and build number. If the status already +// exists an error is returned. +func (db *DB) InsertBuildStatus(repo string, build int, status *common.Status) error { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + status.Context) + return update(db, bucketBuildStatus, key, status) +} + +// UpdateBuild updates an existing build for the named +// repository. If the build already exists and error is +// returned. +func (db *DB) UpdateBuild(repo string, build *common.Build) error { + key := []byte(repo + "/" + strconv.Itoa(build.Number)) + build.Updated = time.Now().UTC().Unix() + return update(db, bucketBuild, key, build) +} diff --git a/datastore/bolt/build_test.go b/datastore/bolt/build_test.go new file mode 100644 index 000000000..a3ce8fd08 --- /dev/null +++ b/datastore/bolt/build_test.go @@ -0,0 +1 @@ +package bolt diff --git a/datastore/bolt/repo.go b/datastore/bolt/repo.go new file mode 100644 index 000000000..771750f5b --- /dev/null +++ b/datastore/bolt/repo.go @@ -0,0 +1,85 @@ +package bolt + +import ( + "time" + + "github.com/drone/drone/common" +) + +// GetRepo gets the repository by name. +func (db *DB) GetRepo(repo string) (*common.Repo, error) { + repo_ := &common.Repo{} + key := []byte(repo) + err := get(db, bucketRepo, key, repo_) + return repo_, err +} + +// GetRepoParams gets the private environment parameters +// for the given repository. +func (db *DB) GetRepoParams(repo string) (map[string]string, error) { + params := map[string]string{} + key := []byte(repo) + err := get(db, bucketRepoParams, key, ¶ms) + return params, err +} + +// GetRepoParams gets the private and public rsa keys +// for the given repository. +func (db *DB) GetRepoKeys(repo string) (*common.Keypair, error) { + keypair := &common.Keypair{} + key := []byte(repo) + err := get(db, bucketRepoKeys, key, keypair) + return keypair, err +} + +// UpdateRepos updates a repository. If the repository +// does not exist an error is returned. +func (db *DB) UpdateRepo(repo *common.Repo) error { + key := []byte(repo.FullName) + repo.Updated = time.Now().UTC().Unix() + return update(db, bucketRepo, key, repo) +} + +// InsertRepo inserts a repository in the datastore and +// subscribes the user to that repository. +func (db *DB) InsertRepo(user *common.User, repo *common.Repo) error { + key := []byte(repo.FullName) + repo.Created = time.Now().UTC().Unix() + repo.Updated = time.Now().UTC().Unix() + // TODO(bradrydzewski) add repo to user index + // TODO(bradrydzewski) add user to repo index + return insert(db, bucketRepo, key, repo) +} + +// UpsertRepoParams inserts or updates the private +// environment parameters for the named repository. +func (db *DB) UpsertRepoParams(repo string, params map[string]string) error { + key := []byte(repo) + return update(db, bucketRepoParams, key, params) +} + +// UpsertRepoKeys inserts or updates the private and +// public keypair for the named repository. +func (db *DB) UpsertRepoKeys(repo string, keypair *common.Keypair) error { + key := []byte(repo) + return update(db, bucketRepoKeys, key, keypair) +} + +// DeleteRepo deletes the repository. +func (db *DB) DeleteRepo(repo *common.Repo) error { + t, err := db.Begin(true) + if err != nil { + return err + } + key := []byte(repo.FullName) + err = t.Bucket(bucketRepo).Delete(key) + if err != nil { + t.Rollback() + return err + } + t.Bucket(bucketRepoKeys).Delete(key) + t.Bucket(bucketRepoParams).Delete(key) + // TODO(bradrydzewski) delete all builds + // TODO(bradrydzewski) delete all tasks + return t.Commit() +} diff --git a/datastore/bolt/repo_test.go b/datastore/bolt/repo_test.go new file mode 100644 index 000000000..6338e07e9 --- /dev/null +++ b/datastore/bolt/repo_test.go @@ -0,0 +1,24 @@ +package bolt + +import ( + "testing" + + . "github.com/franela/goblin" +) + +func TestRepo(t *testing.T) { + g := Goblin(t) + g.Describe("Repos", func() { + + g.It("Should find by name") + g.It("Should find params") + g.It("Should find keys") + g.It("Should delete") + g.It("Should insert") + g.It("Should not insert if exists") + g.It("Should insert params") + g.It("Should update params") + g.It("Should insert keys") + g.It("Should update keys") + }) +} diff --git a/datastore/bolt/task.go b/datastore/bolt/task.go new file mode 100644 index 000000000..6d5b5e332 --- /dev/null +++ b/datastore/bolt/task.go @@ -0,0 +1,82 @@ +package bolt + +import ( + "strconv" + + "github.com/drone/drone/common" +) + +// GetTask gets the task at index N for the named +// repository and build number. +func (db *DB) GetTask(repo string, build int, task int) (*common.Task, error) { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task)) + task_ := &common.Task{} + err := get(db, bucketBuildTasks, key, task_) + return task_, err +} + +// GetTaskLogs gets the task logs at index N for +// the named repository and build number. +func (db *DB) GetTaskLogs(repo string, build int, task int) ([]byte, error) { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task)) + log, err := raw(db, bucketBuildLogs, key) + return log, err +} + +// GetTaskList gets all tasks for the named repository +// and build number. +func (db *DB) GetTaskList(repo string, build int) ([]*common.Task, error) { + // fetch the build so that we can get the + // number of child tasks. + build_, err := db.GetBuild(repo, build) + if err != nil { + return nil, err + } + + t, err := db.Begin(false) + if err != nil { + return nil, err + } + defer t.Rollback() + + // based on the number of child tasks, incrment + // and loop to get each task from the bucket. + tasks := []*common.Task{} + for i := 1; i <= build_.Number; i++ { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(i)) + raw := t.Bucket(bucketBuildTasks).Get(key) + if raw == nil { + return nil, ErrKeyNotFound + } + task := &common.Task{} + err := decode(raw, task) + if err != nil { + return nil, err + } + tasks = append(tasks, task) + } + return tasks, nil +} + +// UpsertTask inserts or updates a task for the named +// repository and build number. +func (db *DB) UpsertTask(repo string, build int, task *common.Task) error { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task.Number)) + return update(db, bucketBuildTasks, key, task) +} + +// UpsertTaskLogs inserts or updates a task logs for the +// named repository and build number. +func (db *DB) UpsertTaskLogs(repo string, build int, task int, log []byte) error { + key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task)) + t, err := db.Begin(true) + if err != nil { + return err + } + err = t.Bucket(bucketBuildLogs).Put(key, log) + if err != nil { + t.Rollback() + return err + } + return t.Commit() +} diff --git a/datastore/bolt/task_test.go b/datastore/bolt/task_test.go new file mode 100644 index 000000000..a3ce8fd08 --- /dev/null +++ b/datastore/bolt/task_test.go @@ -0,0 +1 @@ +package bolt diff --git a/datastore/bolt/token.go b/datastore/bolt/token.go new file mode 100644 index 000000000..4e48054e1 --- /dev/null +++ b/datastore/bolt/token.go @@ -0,0 +1,27 @@ +package bolt + +import ( + "github.com/drone/drone/common" +) + +// GetToken gets a token by sha value. +func (db *DB) GetToken(sha string) (*common.Token, error) { + token := &common.Token{} + key := []byte(sha) + err := get(db, bucketTokens, key, token) + return token, err +} + +// InsertToken inserts a new user token in the datastore. +// If the token already exists and error is returned. +func (db *DB) InsertToken(token *common.Token) error { + key := []byte(token.Sha) + return insert(db, bucketTokens, key, token) + // TODO(bradrydzewski) add token to users_token index +} + +// DeleteUser deletes the token. +func (db *DB) DeleteToken(token *common.Token) error { + key := []byte(token.Sha) + return delete(db, bucketUser, key) +} diff --git a/datastore/bolt/token_test.go b/datastore/bolt/token_test.go new file mode 100644 index 000000000..8fd4c8be2 --- /dev/null +++ b/datastore/bolt/token_test.go @@ -0,0 +1,19 @@ +package bolt + +import ( + "testing" + + . "github.com/franela/goblin" +) + +func TestToken(t *testing.T) { + g := Goblin(t) + g.Describe("Tokens", func() { + + g.It("Should find by sha") + g.It("Should list for user") + g.It("Should delete") + g.It("Should insert") + g.It("Should not insert if exists") + }) +} diff --git a/datastore/bolt/user.go b/datastore/bolt/user.go new file mode 100644 index 000000000..edbe9f095 --- /dev/null +++ b/datastore/bolt/user.go @@ -0,0 +1,136 @@ +package bolt + +import ( + "time" + + "github.com/drone/drone/common" +) + +// GetUser gets a user by user login. +func (db *DB) GetUser(login string) (*common.User, error) { + user := &common.User{} + key := []byte(login) + err := get(db, bucketUser, key, user) + return user, err +} + +// GetUserTokens gets a list of all tokens for +// the given user login. +func (db *DB) GetUserTokens(login string) ([]*common.Token, error) { + t, err := db.Begin(false) + if err != nil { + return nil, err + } + defer t.Rollback() + tokens := []*common.Token{} + + // get the index of user tokens and unmarshal + // to a string array. + key := []byte(login) + raw := t.Bucket(bucketUserTokens).Get(key) + keys := [][]byte{} + err = decode(raw, &keys) + if err != nil { + return tokens, err + } + + // for each item in the index, get the repository + // and append to the array + for _, key := range keys { + token := &common.Token{} + raw = t.Bucket(bucketTokens).Get(key) + err = decode(raw, token) + if err != nil { + break + } + tokens = append(tokens, token) + } + return tokens, err +} + +// GetUserRepos gets a list of repositories for the +// given user account. +func (db *DB) GetUserRepos(login string) ([]*common.Repo, error) { + t, err := db.Begin(false) + if err != nil { + return nil, err + } + defer t.Rollback() + repos := []*common.Repo{} + + // get the index of user repos and unmarshal + // to a string array. + key := []byte(login) + raw := t.Bucket(bucketUserRepos).Get(key) + keys := [][]byte{} + err = decode(raw, &keys) + if err != nil { + return repos, err + } + + // for each item in the index, get the repository + // and append to the array + for _, key := range keys { + repo := &common.Repo{} + raw = t.Bucket(bucketRepo).Get(key) + err = decode(raw, repo) + if err != nil { + break + } + repos = append(repos, repo) + } + return repos, err +} + +// GetUserCount gets a count of all registered users +// in the system. +func (db *DB) GetUserCount() (int, error) { + t, err := db.Begin(false) + if err != nil { + return 0, err + } + defer t.Rollback() + return t.Bucket(bucketUser).Stats().KeyN, nil +} + +// GetUserList gets a list of all registered users. +func (db *DB) GetUserList() ([]*common.User, error) { + t, err := db.Begin(false) + if err != nil { + return nil, err + } + defer t.Rollback() + users := []*common.User{} + err = t.Bucket(bucketUser).ForEach(func(key, raw []byte) error { + user := &common.User{} + err := decode(raw, user) + users = append(users, user) + return err + }) + return users, err +} + +// UpdateUser updates an existing user. If the user +// does not exists an error is returned. +func (db *DB) UpdateUser(user *common.User) error { + key := []byte(user.Login) + user.Updated = time.Now().UTC().Unix() + return update(db, bucketUser, key, user) +} + +// InsertUser inserts a new user into the datastore. If +// the user login already exists an error is returned. +func (db *DB) InsertUser(user *common.User) error { + key := []byte(user.Login) + user.Created = time.Now().UTC().Unix() + user.Updated = time.Now().UTC().Unix() + return insert(db, bucketUser, key, user) +} + +// DeleteUser deletes the user. +func (db *DB) DeleteUser(user *common.User) error { + key := []byte(user.Login) + // TODO(bradrydzewski) delete user subscriptions + // TODO(bradrydzewski) delete user tokens + return delete(db, bucketUser, key) +} diff --git a/datastore/bolt/user_test.go b/datastore/bolt/user_test.go new file mode 100644 index 000000000..6064a09a9 --- /dev/null +++ b/datastore/bolt/user_test.go @@ -0,0 +1,86 @@ +package bolt + +import ( + "os" + "testing" + + "github.com/drone/drone/common" + . "github.com/franela/goblin" +) + +func TestUser(t *testing.T) { + g := Goblin(t) + g.Describe("Users", func() { + var db *DB // temporary database + + // create a new database before each unit + // test and destroy afterwards. + g.BeforeEach(func() { + db = Must("/tmp/drone.test.db") + }) + g.AfterEach(func() { + os.Remove(db.Path()) + }) + + g.It("Should find", func() { + db.InsertUser(&common.User{Login: "octocat"}) + user, err := db.GetUser("octocat") + g.Assert(err).Equal(nil) + g.Assert(user.Login).Equal("octocat") + }) + + g.It("Should insert", func() { + err := db.InsertUser(&common.User{Login: "octocat"}) + g.Assert(err).Equal(nil) + + user, err := db.GetUser("octocat") + g.Assert(err).Equal(nil) + g.Assert(user.Login).Equal("octocat") + g.Assert(user.Created != 0).IsTrue() + g.Assert(user.Updated != 0).IsTrue() + }) + + g.It("Should not insert if exists", func() { + db.InsertUser(&common.User{Login: "octocat"}) + err := db.InsertUser(&common.User{Login: "octocat"}) + g.Assert(err).Equal(ErrKeyExists) + }) + + g.It("Should update", func() { + db.InsertUser(&common.User{Login: "octocat"}) + user, err := db.GetUser("octocat") + g.Assert(err).Equal(nil) + + user.Email = "octocat@github.com" + err = db.UpdateUser(user) + g.Assert(err).Equal(nil) + + user_, err := db.GetUser("octocat") + g.Assert(err).Equal(nil) + g.Assert(user_.Login).Equal(user.Login) + g.Assert(user_.Email).Equal(user.Email) + }) + + g.It("Should delete", func() { + db.InsertUser(&common.User{Login: "octocat"}) + user, err := db.GetUser("octocat") + g.Assert(err).Equal(nil) + + err = db.DeleteUser(user) + g.Assert(err).Equal(nil) + + _, err = db.GetUser("octocat") + g.Assert(err).Equal(ErrKeyNotFound) + }) + + g.It("Should list") + + g.It("Should count", func() { + db.InsertUser(&common.User{Login: "bert"}) + db.InsertUser(&common.User{Login: "ernie"}) + count, err := db.GetUserCount() + g.Assert(err).Equal(nil) + g.Assert(count).Equal(2) + }) + }) +} diff --git a/datastore/bolt/util.go b/datastore/bolt/util.go new file mode 100644 index 000000000..baa73e610 --- /dev/null +++ b/datastore/bolt/util.go @@ -0,0 +1,92 @@ +package bolt + +import "github.com/youtube/vitess/go/bson" + +func encode(v interface{}) ([]byte, error) { + return bson.Marshal(v) +} + +func decode(raw []byte, v interface{}) error { + return bson.Unmarshal(raw, v) +} + +func get(db *DB, bucket, key []byte, v interface{}) error { + t, err := db.Begin(false) + if err != nil { + return err + } + defer t.Rollback() + raw := t.Bucket(bucket).Get(key) + if raw == nil { + return ErrKeyNotFound + } + return bson.Unmarshal(raw, v) +} + +func raw(db *DB, bucket, key []byte) ([]byte, error) { + t, err := db.Begin(false) + if err != nil { + return nil, err + } + defer t.Rollback() + raw := t.Bucket(bucket).Get(key) + if raw == nil { + return nil, ErrKeyNotFound + } + return raw, nil +} + +func update(db *DB, bucket, key []byte, v interface{}) error { + t, err := db.Begin(true) + if err != nil { + return err + } + raw, err := encode(v) + if err != nil { + t.Rollback() + return err + } + err = t.Bucket(bucket).Put(key, raw) + if err != nil { + t.Rollback() + return err + } + return t.Commit() +} + +func insert(db *DB, bucket, key []byte, v interface{}) error { + t, err := db.Begin(true) + if err != nil { + return err + } + raw, err := encode(v) + if err != nil { + t.Rollback() + return err + } + // verify the key does not already exists + // in the bucket. If exists, fail + if t.Bucket(bucket).Get(key) != nil { + t.Rollback() + return ErrKeyExists + } + err = t.Bucket(bucket).Put(key, raw) + if err != nil { + t.Rollback() + return err + } + return t.Commit() +} + +func delete(db *DB, bucket, key []byte) error { + t, err := db.Begin(true) + if err != nil { + return err + } + err = t.Bucket(bucket).Delete(key) + if err != nil { + t.Rollback() + return err + } + return t.Commit() +} diff --git a/datastore/datastore.go b/datastore/datastore.go new file mode 100644 index 000000000..863c8ebf6 --- /dev/null +++ b/datastore/datastore.go @@ -0,0 +1,131 @@ +package datastore + +import "github.com/drone/drone/common" + +type Datastore interface { + // GetUser gets a user by user login. + GetUser(string) (*common.User, error) + + // GetUserTokens gets a list of all tokens for + // the given user login. + GetUserTokens(string) ([]*common.Token, error) + + // GetUserRepos gets a list of repositories for the + // given user account. + GetUserRepos(string) ([]*common.Repo, error) + + // GetUserCount gets a count of all registered users + // in the system. + GetUserCount() (int, error) + + // GetUserList gets a list of all registered users. + GetUserList() ([]*common.User, error) + + // UpdateUser updates an existing user. If the user + // does not exists an error is returned. + UpdateUser(*common.User) error + + // InsertUser inserts a new user into the datastore. If + // the user login already exists an error is returned. + InsertUser(*common.User) error + + // DeleteUser deletes the user. + DeleteUser(*common.User) error + + // GetToken gets a token by sha value. + GetToken(string) (*common.Token, error) + + // InsertToken inserts a new user token in the datastore. + // If the token already exists and error is returned. + InsertToken(*common.Token) error + + // DeleteUser deletes the token. + DeleteToken(*common.Token) error + + // GetRepo gets the repository by name. + GetRepo(string) (*common.Repo, error) + + // GetRepoParams gets the private environment parameters + // for the given repository. + GetRepoParams(string) (map[string]string, error) + + // GetRepoParams gets the private and public rsa keys + // for the given repository. + GetRepoKeys(string) (*common.Keypair, error) + + // UpdateRepos updates a repository. If the repository + // does not exist an error is returned. + UpdateRepo(*common.Repo) error + + // InsertRepo inserts a repository in the datastore and + // subscribes the user to that repository. + InsertRepo(*common.User, *common.Repo) error + + // UpsertRepoParams inserts or updates the private + // environment parameters for the named repository. + UpsertRepoParams(string, map[string]string) error + + // UpsertRepoKeys inserts or updates the private and + // public keypair for the named repository. + UpsertRepoKeys(string, *common.Keypair) error + + // DeleteRepo deletes the repository. + DeleteRepo(*common.Repo) error + + // GetBuild gets the specified build number for the + // named repository and build number + GetBuild(string, int) (*common.Build, error) + + // GetBuildList gets a list of recent builds for the + // named repository. + GetBuildList(string) ([]*common.Build, error) + + // GetBuildLast gets the last executed build for the + // named repository. + GetBuildLast(string) (*common.Build, error) + + // GetBuildStatus gets the named build status for the + // named repository and build number. + GetBuildStatus(string, int, string) (*common.Status, error) + + // GetBuildStatusList gets a list of all build statues for + // the named repository and build number. + GetBuildStatusList(string, int) ([]*common.Status, error) + + // InsertBuild inserts a new build for the named repository + InsertBuild(string, *common.Build) error + + // InsertBuildStatus inserts a new build status for the + // named repository and build number. If the status already + // exists an error is returned. + InsertBuildStatus(string, int, *common.Status) error + + // UpdateBuild updates an existing build for the named + // repository. If the build already exists and error is + // returned. + UpdateBuild(string, *common.Build) error + + // GetTask gets the task at index N for the named + // repository and build number. + GetTask(string, int, int) (*common.Task, error) + + // GetTaskLogs gets the task logs at index N for + // the named repository and build number. + GetTaskLogs(string, int, int) ([]byte, error) + + // GetTaskList gets all tasks for the named repository + // and build number. + GetTaskList(string, int) ([]*common.Task, error) + + // UpsertTask inserts or updates a task for the named + // repository and build number. + UpsertTask(string, int, *common.Task) error + + // UpsertTaskLogs inserts or updates a task logs for the + // named repository and build number. + UpsertTaskLogs(string, int, int, []byte) error +} + +// GetSubscriber(string, string) (*common.Subscriber, error) +// InsertSubscriber(string, *common.Subscriber) error +// DeleteSubscriber(string, string) error diff --git a/drone.go b/drone.go new file mode 100644 index 000000000..586c1429b --- /dev/null +++ b/drone.go @@ -0,0 +1,20 @@ +package main + +import ( + _ "github.com/drone/drone/common" + "github.com/drone/drone/datastore" + "github.com/drone/drone/datastore/bolt" +) + +var ( + revision string + version string +) + +var ds datastore.Datastore + +func main() { + ds, _ = bolt.New("drone.toml") + println(revision) + println(version) +} diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml deleted file mode 100644 index a28f117a0..000000000 --- a/packaging/root/etc/drone/drone.toml +++ /dev/null @@ -1,82 +0,0 @@ - -[server] -port=":80" - -##################################################################### -# SSL configuration -# -# [server.ssl] -# key="" -# cert="" - -##################################################################### -# Assets configuration -# -# [server.assets] -# folder="" - -# [session] -# secret="" -# expires="" - -##################################################################### -# Database configuration, by default using SQLite3. -# You can also use postgres and mysql. See the documentation -# for more details. - -[database] -driver="sqlite3" -datasource="/var/lib/drone/drone.sqlite" - -# [github] -# client="" -# secret="" -# orgs=[] -# open=false - -# [github_enterprise] -# client="" -# secret="" -# api="" -# url="" -# orgs=[] -# private_mode=false -# open=false - -# [bitbucket] -# client="" -# secret="" -# open=false - -# [gitlab] -# url="" -# client="" -# secret="" -# skip_verify=false -# open=false - -# [gogs] -# url="" -# secret="" -# open=false - -##################################################################### -# SMTP configuration for Drone. This is required if you plan -# to send email notifications for build statuses. -# -# [smtp] -# host="" -# port="" -# from="" -# user="" -# pass="" - -# [docker] -# cert="" -# key="" - -# [worker] -# nodes=[ -# "unix:///var/run/docker.sock", -# "unix:///var/run/docker.sock" -# ] diff --git a/packaging/root/usr/share/drone/init.d/drone b/packaging/root/usr/share/drone/init.d/drone deleted file mode 100644 index bd2e94c2c..000000000 --- a/packaging/root/usr/share/drone/init.d/drone +++ /dev/null @@ -1,75 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: drone -# Required-Start: $local_fs $remote_fs $network $syslog -# Required-Stop: $local_fs $remote_fs $network $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Drone continuous integration server -### END INIT INFO - -DAEMON_OPTS="--config=/etc/drone/drone.toml" - -pid() { - if [ -f /usr/local/bin/droned ]; then - pidof /usr/local/bin/droned - fi -} - -stop() { - if pidof /usr/local/bin/droned >/dev/null; then - kill -9 "$(pid)" - else - echo "Drone not runned" - exit 1 - fi -} - -start() { - if pidof /usr/local/bin/droned >/dev/null; then - echo "Drone already runned" - exit 1 - else - nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 & - fi -} - -restart() { - if pidof /usr/local/bin/droned >/dev/null; then - kill -9 "$(pid)" - nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 & - exit 0 - else - nohup droned $DAEMON_OPTS > /var/log/drone.log 2>&1 & - exit 0 - fi -} - -status() { - if pidof /usr/local/bin/droned >/dev/null; then - echo "Drone with pid $(pid) is running" - else - echo "Drone is not running" - fi - exit 0 -} - -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - restart - ;; - status) - status - ;; - *) - echo "Usage: service drone {start|stop|restart|status}" - exit 1 - ;; -esac \ No newline at end of file diff --git a/packaging/root/usr/share/drone/init/drone.conf b/packaging/root/usr/share/drone/init/drone.conf deleted file mode 100644 index 5efeb02c9..000000000 --- a/packaging/root/usr/share/drone/init/drone.conf +++ /dev/null @@ -1,8 +0,0 @@ -start on (filesystem and net-device-up) - -chdir /var/lib/drone -console log - -script - /usr/local/bin/droned --config=/etc/drone/drone.toml -end script diff --git a/packaging/root/usr/share/drone/systemd/drone.service b/packaging/root/usr/share/drone/systemd/drone.service deleted file mode 100644 index dd590a18a..000000000 --- a/packaging/root/usr/share/drone/systemd/drone.service +++ /dev/null @@ -1,26 +0,0 @@ -# -# systemd unit file for CentOS 7, Ubuntu bleeding edge -# -[Unit] -Description=Drone -# start us only once the network and logging subsystems are available -After=syslog.target network.target - -# See these pages for lots of options: -# http://0pointer.de/public/systemd-man/systemd.service.html -# http://0pointer.de/public/systemd-man/systemd.exec.html -[Service] -Type=simple -ExecStart=/usr/local/bin/droned --config=/etc/drone/drone.toml - -# if we crash, restart -RestartSec=1 -Restart=on-failure - -# use syslog for logging -StandardOutput=syslog -StandardError=syslog -SyslogIdentifier=droned - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/root/var/lib/drone/.keep b/packaging/root/var/lib/drone/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packaging/scripts/postinst.deb b/packaging/scripts/postinst.deb deleted file mode 100644 index e6c79c533..000000000 --- a/packaging/scripts/postinst.deb +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/sh - -set -e - -case "$1" in - abort-upgrade|abort-remove|abort-deconfigure|configure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -if [ -f /etc/drone/drone.toml ]; then - chmod 600 /etc/drone/drone.toml -fi - -dist() { - lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//' -} - -version() { - lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }' -} - -upstart() { - if [ -d /etc/init ]; then - echo "Your system $(dist) $(version): using upstart to control Drone" - if [ -f /usr/local/bin/droned ]; then - if pidof /usr/local/bin/droned >/dev/null; then - initctl stop drone || : - fi - fi - - cp -r /usr/share/drone/init/drone.conf /etc/init/drone.conf - initctl start drone || : - else - echo "Couldn't find upstart to control Drone, cannot proceed." - echo "Open an issue and tell us about your system." - exit 1 - fi -} - -sysv() { - if [ -d /etc/init.d ]; then - echo "Your system $(dist) $(version): using SysV to control Drone" - if [ -f /usr/local/bin/droned ] && [ -f /etc/init.d/drone ]; then - if pidof /usr/local/bin/droned >/dev/null; then - /etc/init.d/drone stop - fi - fi - - cp -r /usr/share/drone/init.d/drone /etc/init.d/drone - chmod 0755 /etc/init.d/drone - update-rc.d drone defaults - exec /etc/init.d/drone start || : - else - echo "Couldn't find SysV to control Drone, cannot proceed." - echo "Open an issue and tell us about your system." - exit 1 - fi -} - -systemd() { - if which systemctl > /dev/null; then - cp /usr/share/drone/systemd/drone.service /lib/systemd/system/drone.service - - systemctl daemon-reload || : - if [ "$1" = "configure" ] ; then - echo "Your system $(dist) $(version): using systemd to control Drone" - systemctl enable drone || : - systemctl restart drone || : - fi - else - echo "Couldn't find systemd to control Drone, cannot proceed." - echo "Open an issue and tell us about your system." - exit 1 - fi -} - -case "$(dist)" in - debian) - if [ "$(version)" -lt "8" ]; then - sysv - else - systemd $1 - fi - ;; - ubuntu) - if [ "$(version)" -lt "15" ]; then - upstart - else - systemd $1 - fi - ;; - *) - echo "\033[33m Your system $(dist) $(version) \033[0m" - echo "\033[33m This system is not supported, you can install service manually \033[0m" - ;; -esac - -exit 0 \ No newline at end of file diff --git a/packaging/scripts/postinst.rpm b/packaging/scripts/postinst.rpm deleted file mode 100644 index 34d175950..000000000 --- a/packaging/scripts/postinst.rpm +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -set -e - -if [ -f /etc/drone/drone.toml ]; then - chmod 600 /etc/drone/drone.toml -fi - -if which systemctl > /dev/null; then - echo "Using systemd to control Drone" - cp /usr/share/drone/systemd/drone.service /lib/systemd/system/drone.service - - systemctl daemon-reload || : - if [ "$1" = 1 ] ; then - # first time install - systemctl enable drone || : - systemctl start drone || : - else - echo "Upgrading drone" - fi -else - echo "Couldn't find systemd to control Drone, cannot proceed." - echo "Open an issue and tell us about your system." - exit 1 -fi - -exit 0 \ No newline at end of file diff --git a/packaging/scripts/postrm.deb b/packaging/scripts/postrm.deb deleted file mode 100644 index f560a9e26..000000000 --- a/packaging/scripts/postrm.deb +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh - -set -e - -dist() { - lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//' -} - -version() { - lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }' -} - -upstart() { - rm -f /etc/init/drone.conf -} - -systemd() { - rm -f /lib/systemd/system/drone.service -} - -sysv() { - update-rc.d -f drone remove - rm -f /etc/init.d/drone -} - -validate_ver() { - echo "$(version) < $1" | bc -} - -case "$(dist)" in - debian) - if [ "$(version)" -lt "8" ]; then - sysv - else - systemd - fi - ;; - ubuntu) - if [ "$(version)" -lt "15" ]; then - upstart - else - systemd - fi - ;; - *) - echo "\033[33m Please remove service manually \033[0m" - ;; -esac - -# https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html - -if [ "$1" = "purge" ] ; then - echo "Purging drone configuration" - rm -rf /etc/drone -fi \ No newline at end of file diff --git a/packaging/scripts/postrm.rpm b/packaging/scripts/postrm.rpm deleted file mode 100644 index 2d49887f3..000000000 --- a/packaging/scripts/postrm.rpm +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -# https://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch09s04s05.html - -systemctl daemon-reload || : -if [ "$1" -ge 1 ] ; then - # Package upgrade, not uninstall - systemctl try-restart drone || : -fi \ No newline at end of file diff --git a/packaging/scripts/prerm.deb b/packaging/scripts/prerm.deb deleted file mode 100644 index 9a685d38f..000000000 --- a/packaging/scripts/prerm.deb +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh -set -e - -dist() { - lsb_release -i | awk '{print tolower($3)}' | sed -e 's/^ *//' -e 's/ *$//' -} - -version() { - lsb_release -r | awk '{print $2}' | sed -e 's/^ *//' -e 's/ *$//' | awk -F. '{ print $1 }' -} - -echo Stopping drone - -upstart() { - initctl stop drone || : -} - -systemd() { - if [ $1 = "remove" ] ; then - systemctl --no-reload disable drone || : - systemctl stop drone || : - fi -} - -sysv() { - if [ -f /etc/init.d/drone ] ; then - if pidof /usr/local/bin/droned >/dev/null; then - exec /etc/init.d/drone stop || : - fi - fi -} - -validate_ver() { - echo "$(version) < $1" | bc -} - -case "$(dist)" in - debian) - if [ "$(version)" -lt "8" ]; then - sysv - else - systemd $1 - fi - ;; - ubuntu) - if [ "$(version)" -lt "15" ]; then - upstart - else - systemd $1 - fi - ;; - *) - if [ -f /usr/local/bin/droned ]; then - if pidof /usr/local/bin/droned >/dev/null; then - kill -9 `pidof /usr/local/bin/droned` - fi - fi - ;; -esac \ No newline at end of file diff --git a/packaging/scripts/prerm.rpm b/packaging/scripts/prerm.rpm deleted file mode 100644 index d6b3b7c9b..000000000 --- a/packaging/scripts/prerm.rpm +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e - -if [ "$1" -eq 0 ] ; then - echo Stopping Drone - systemctl --no-reload disable drone || : - systemctl stop drone || : -fi \ No newline at end of file diff --git a/plugin/condition/condition.go b/plugin/condition/condition.go deleted file mode 100644 index c7266b485..000000000 --- a/plugin/condition/condition.go +++ /dev/null @@ -1,67 +0,0 @@ -package condition - -import ( - "path/filepath" - "strings" -) - -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 - Condition string // Indicates the step should run if bash condition evals to true - PullRequest *bool `yaml:"pull_requests"` // Indicates the step should run for all pull requests - AllBranches *bool `yaml:"all_branches"` // Indicates the step should run for all branches - - // Indicates the step should only run when the following - // matrix values are present for the sub-build. - Matrix map[string]string -} - -// MatchPullRequest is a helper function that returns false -// if Pull Requests are disbled, but the pull request string -// is not empty. -func (c *Condition) MatchPullRequest(pr string) bool { - if len(pr) == 0 { - return true - } - if c.PullRequest == nil { - return false - } - return *c.PullRequest -} - -// 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 c.AllBranches != nil && *c.AllBranches == true { - return true - } - 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 - } -} diff --git a/plugin/condition/condition_test.go b/plugin/condition/condition_test.go deleted file mode 100644 index e9a8c1caa..000000000 --- a/plugin/condition/condition_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package condition - -import ( - "testing" -) - -type Bool bool - -func Test_MatchPullRequest(t *testing.T) { - - var c = Condition{} - var got, want = c.MatchPullRequest(""), true - if got != want { - t.Errorf("Non-pull requests are always enabled, expected %v, got %v", want, got) - } - - got, want = c.MatchPullRequest("65"), false - if got != want { - t.Errorf("Pull requests should be disabled by default, expected %v, got %v", want, got) - } - - c.PullRequest = new(bool) - *c.PullRequest = false - got, want = c.MatchPullRequest("65"), false - if got != want { - t.Errorf("Pull requests can be explicity disabled, expected %v, got %v", want, got) - } - - c.PullRequest = new(bool) - *c.PullRequest = true - got, want = c.MatchPullRequest("65"), true - if got != want { - t.Errorf("Pull requests can be explicitly enabled, expected %v, got %v", want, got) - } -} - -func Test_MatchBranch(t *testing.T) { - - var c = Condition{} - var got, want = c.MatchBranch("master"), true - if got != want { - t.Errorf("All branches should be enabled by default, expected %v, got %v", want, got) - } - - c.Branch = "" - got, want = c.MatchBranch("master"), true - if got != want { - t.Errorf("Empty branch should match, expected %v, got %v", want, got) - } - - c.Branch = "master" - got, want = c.MatchBranch("master"), true - if got != want { - t.Errorf("Branch should match, expected %v, got %v", want, got) - } - - c.Branch = "master" - got, want = c.MatchBranch("dev"), false - if got != want { - t.Errorf("Branch should not match, expected %v, got %v", want, got) - } - - c.Branch = "release/*" - got, want = c.MatchBranch("release/1.0.0"), true - if got != want { - t.Errorf("Branch should match wildcard, expected %v, got %v", want, got) - } -} - -func Test_MatchOwner(t *testing.T) { - - var c = Condition{} - var got, want = c.MatchOwner("drone"), true - if got != want { - t.Errorf("All owners should be enabled by default, expected %v, got %v", want, got) - } - - c.Owner = "" - got, want = c.MatchOwner("drone"), true - if got != want { - t.Errorf("Empty owner should match, expected %v, got %v", want, got) - } - - c.Owner = "drone" - got, want = c.MatchOwner("drone"), true - if got != want { - t.Errorf("Owner should match, expected %v, got %v", want, got) - } - - c.Owner = "drone" - got, want = c.MatchOwner("drone/config"), true - if got != want { - t.Errorf("Owner/Repo should match, expected %v, got %v", want, got) - } - - c.Owner = "drone" - got, want = c.MatchOwner("github.com/drone/config"), true - if got != want { - t.Errorf("Host/Owner/Repo should match, expected %v, got %v", want, got) - } - - c.Owner = "bradrydzewski" - got, want = c.MatchOwner("drone"), false - if got != want { - t.Errorf("Owner should not match, expected %v, got %v", want, got) - } - - c.Owner = "drone" - got, want = c.MatchOwner("bradrydzewski/drone"), false - if got != want { - t.Errorf("Owner/Repo should not match, expected %v, got %v", want, got) - } - - c.Owner = "drone" - got, want = c.MatchOwner("github.com/bradrydzewski/drone"), false - if got != want { - t.Errorf("Host/Owner/Repo should not match, expected %v, got %v", want, got) - } -} diff --git a/plugin/deploy/bash.go b/plugin/deploy/bash.go deleted file mode 100644 index 983cba663..000000000 --- a/plugin/deploy/bash.go +++ /dev/null @@ -1,25 +0,0 @@ -package deploy - -import ( - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Bash struct { - Script []string `yaml:"script,omitempty"` - Command string `yaml:"command,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (g *Bash) Write(f *buildfile.Buildfile) { - g.Script = append(g.Script, g.Command) - - for _, cmd := range g.Script { - f.WriteCmd(cmd) - } -} - -func (g *Bash) GetCondition() *condition.Condition { - return g.Condition -} diff --git a/plugin/deploy/bash_test.go b/plugin/deploy/bash_test.go deleted file mode 100644 index ba5027019..000000000 --- a/plugin/deploy/bash_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package deploy - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - - "gopkg.in/yaml.v1" -) - -// emulate Build struct -type buildWithBash struct { - Deploy *Deploy `yaml:"deploy,omitempty"` -} - -var sampleYmlWithBash = ` -deploy: - bash: - command: 'echo bash_deployed' -` - -var sampleYmlWithScript = ` -deploy: - bash: - script: - - ./bin/deploy.sh - - ./bin/check.sh -` - -var sampleYmlWithBashAndScript = ` -deploy: - bash: - command: ./bin/some_cmd.sh - script: - - ./bin/deploy.sh - - ./bin/check.sh -` - -func setUpWithBash(input string) (string, error) { - var buildStruct buildWithBash - err := yaml.Unmarshal([]byte(input), &buildStruct) - if err != nil { - return "", err - } - bf := buildfile.New() - buildStruct.Deploy.Write(bf, nil) - return bf.String(), err -} - -func TestBashDeployment(t *testing.T) { - bscr, err := setUpWithBash(sampleYmlWithBash) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "echo bash_deployed") { - t.Error("Expect script to contains bash command") - } -} - -func TestBashDeploymentWithScript(t *testing.T) { - bscr, err := setUpWithBash(sampleYmlWithScript) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "./bin/deploy.sh") { - t.Error("Expect script to contains bash script") - } - - if !strings.Contains(bscr, "./bin/check.sh") { - t.Error("Expect script to contains bash script") - } -} - -func TestBashDeploymentWithBashAndScript(t *testing.T) { - bscr, err := setUpWithBash(sampleYmlWithBashAndScript) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "./bin/deploy.sh") { - t.Error("Expect script to contains bash script") - } - - if !strings.Contains(bscr, "./bin/check.sh") { - t.Error("Expect script to contains bash script") - } - - if !strings.Contains(bscr, "./bin/some_cmd.sh") { - t.Error("Expect script to contains bash script") - } -} diff --git a/plugin/deploy/cloudfoundry.go b/plugin/deploy/cloudfoundry.go deleted file mode 100644 index 872b69b49..000000000 --- a/plugin/deploy/cloudfoundry.go +++ /dev/null @@ -1,51 +0,0 @@ -package deploy - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type CloudFoundry struct { - Target string `yaml:"target,omitempty"` - Username string `yaml:"username,omitempty"` - Password string `yaml:"password,omitempty"` - Org string `yaml:"org,omitempty"` - Space string `yaml:"space,omitempty"` - - App string `yaml:"app,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (cf *CloudFoundry) Write(f *buildfile.Buildfile) { - downloadCmd := "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb" - installCmd := "dpkg -i cf-cli_amd64.deb 1> /dev/null 2> /dev/null" - - // download and install the cf tool - f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", downloadCmd, downloadCmd)) - f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", installCmd, installCmd)) - - // login - loginCmd := "cf login -a %s -u %s -p %s" - - organization := cf.Org - if organization != "" { - loginCmd += fmt.Sprintf(" -o %s", organization) - } - - space := cf.Space - if space != "" { - loginCmd += fmt.Sprintf(" -s %s", space) - } - - f.WriteCmdSilent(fmt.Sprintf(loginCmd, cf.Target, cf.Username, cf.Password)) - - // push app - pushCmd := "cf push %s" - f.WriteCmd(fmt.Sprintf(pushCmd, cf.App)) -} - -func (cf *CloudFoundry) GetCondition() *condition.Condition { - return cf.Condition -} diff --git a/plugin/deploy/cloudfoundry/cloudfoundry.go b/plugin/deploy/cloudfoundry/cloudfoundry.go deleted file mode 100644 index 73e5e0405..000000000 --- a/plugin/deploy/cloudfoundry/cloudfoundry.go +++ /dev/null @@ -1,51 +0,0 @@ -package cloudfoundry - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type CloudFoundry struct { - Target string `yaml:"target,omitempty"` - Username string `yaml:"username,omitempty"` - Password string `yaml:"password,omitempty"` - Org string `yaml:"org,omitempty"` - Space string `yaml:"space,omitempty"` - - App string `yaml:"app,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (cf *CloudFoundry) Write(f *buildfile.Buildfile) { - downloadCmd := "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb" - installCmd := "dpkg -i cf-cli_amd64.deb 1> /dev/null 2> /dev/null" - - // download and install the cf tool - f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", downloadCmd, downloadCmd)) - f.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && sudo %s || %s", installCmd, installCmd)) - - // login - loginCmd := "cf login -a %s -u %s -p %s" - - organization := cf.Org - if organization != "" { - loginCmd += fmt.Sprintf(" -o %s", organization) - } - - space := cf.Space - if space != "" { - loginCmd += fmt.Sprintf(" -s %s", space) - } - - f.WriteCmdSilent(fmt.Sprintf(loginCmd, cf.Target, cf.Username, cf.Password)) - - // push app - pushCmd := "cf push %s" - f.WriteCmd(fmt.Sprintf(pushCmd, cf.App)) -} - -func (cf *CloudFoundry) GetCondition() *condition.Condition { - return cf.Condition -} diff --git a/plugin/deploy/cloudfoundry_test.go b/plugin/deploy/cloudfoundry_test.go deleted file mode 100644 index f8ba39880..000000000 --- a/plugin/deploy/cloudfoundry_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package deploy - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - - "gopkg.in/yaml.v1" -) - -// emulate Build struct -type DeployToCF struct { - Deploy *Deploy `yaml:"deploy,omitempty"` -} - -var sampleYmlBasic = ` -deploy: - cloudfoundry: - target: https://api.example.com - username: foo - password: bar -` - -var sampleYmlWithOrg = ` -deploy: - cloudfoundry: - target: https://api.example.com - username: foo - password: bar - org: custom-org -` - -var sampleYmlWithSpace = ` -deploy: - cloudfoundry: - target: https://api.example.com - username: foo - password: bar - org: custom-org - space: dev -` - -var sampleYmlWithAppName = ` -deploy: - cloudfoundry: - target: https://api.example.com - username: foo - password: bar - app: test-app -` - -func setUpWithCF(input string) (string, error) { - var buildStruct DeployToCF - err := yaml.Unmarshal([]byte(input), &buildStruct) - if err != nil { - return "", err - } - bf := buildfile.New() - buildStruct.Deploy.Write(bf, nil) - return bf.String(), err -} - -func TestCloudFoundryToolInstall(t *testing.T) { - bscr, err := setUpWithCF(sampleYmlBasic) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "curl -sLO http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb") { - t.Error("Expect script to contain download command") - } - - if !strings.Contains(bscr, "dpkg -i cf-cli_amd64.deb") { - t.Error("Expect script to contain install command") - } -} - -func TestCloudFoundryDeployment(t *testing.T) { - bscr, err := setUpWithCF(sampleYmlBasic) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar") { - t.Error("Expect login script to contain username and password") - } - - if !strings.Contains(bscr, "cf push") { - t.Error("Expect script to contain push") - } -} - -func TestCloudFoundryDeploymentWithOrg(t *testing.T) { - bscr, err := setUpWithCF(sampleYmlWithOrg) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar -o custom-org") { - t.Error("Expect login script to contain organization") - } -} - -func TestCloudFoundryDeploymentWithSpace(t *testing.T) { - bscr, err := setUpWithCF(sampleYmlWithSpace) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "cf login -a https://api.example.com -u foo -p bar -o custom-org -s dev") { - t.Error("Expect login script to contain space") - } -} - -func TestCloudFoundryDeploymentWithApp(t *testing.T) { - bscr, err := setUpWithCF(sampleYmlWithAppName) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, "cf push test-app") { - t.Error("Expect login script to contain app name") - } -} diff --git a/plugin/deploy/deis/deis.go b/plugin/deploy/deis/deis.go deleted file mode 100644 index b98721ec8..000000000 --- a/plugin/deploy/deis/deis.go +++ /dev/null @@ -1,56 +0,0 @@ -package deis - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -const ( - // Gommand to the current commit hash - CmdRevParse = "COMMIT=$(git rev-parse HEAD)" - - // Command to set the git user and email based on the - // individual that made the commit. - CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" - CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" -) - -// deploy: -// deis: -// app: safe-island-6261 -// deisurl: deis.myurl.tdl:2222/ - -type Deis struct { - App string `yaml:"app,omitempty"` - Force bool `yaml:"force,omitempty"` - Deisurl string `yaml:"deisurl,omitempty"` - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (h *Deis) Write(f *buildfile.Buildfile) { - f.WriteCmdSilent(CmdRevParse) - f.WriteCmdSilent(CmdGlobalUser) - f.WriteCmdSilent(CmdGlobalEmail) - - // git@deis.yourdomain.com:2222/drone.git - - f.WriteCmd(fmt.Sprintf("git remote add deis ssh://git@%s%s.git", h.Deisurl, h.App)) - - switch h.Force { - case true: - // this is useful when the there are artifacts generated - // by the build script, such as less files converted to css, - // that need to be deployed to Deis. - f.WriteCmd(fmt.Sprintf("git add -A")) - f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'")) - f.WriteCmd(fmt.Sprintf("git push deis HEAD:master --force")) - case false: - // otherwise we just do a standard git push - f.WriteCmd(fmt.Sprintf("git push deis $COMMIT:master")) - } -} - -func (h *Deis) GetCondition() *condition.Condition { - return h.Condition -} diff --git a/plugin/deploy/deis/deis_test.go b/plugin/deploy/deis/deis_test.go deleted file mode 100644 index b9621aa9e..000000000 --- a/plugin/deploy/deis/deis_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package deis - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Deis(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Deis Deploy", func() { - - g.It("Should set git.config", func() { - b := new(buildfile.Buildfile) - h := Deis{ - App: "drone", - Deisurl: "deis.yourdomain.com:2222", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, CmdRevParse)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) - }) - - g.It("Should add remote", func() { - b := new(buildfile.Buildfile) - h := Deis{ - App: "drone", - Deisurl: "deis.yourdomain.com:2222/", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit remote add deis ssh://git@deis.yourdomain.com:2222/drone.git\n")).Equal(true) - }) - - g.It("Should push to remote", func() { - b := new(buildfile.Buildfile) - d := Deis{ - App: "drone", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit push deis $COMMIT:master\n")).Equal(true) - }) - - g.It("Should force push to remote", func() { - b := new(buildfile.Buildfile) - h := Deis{ - Force: true, - App: "drone", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit push deis HEAD:master --force\n")).Equal(true) - }) - - }) -} diff --git a/plugin/deploy/deployment.go b/plugin/deploy/deployment.go deleted file mode 100644 index 198c71a27..000000000 --- a/plugin/deploy/deployment.go +++ /dev/null @@ -1,74 +0,0 @@ -package deploy - -import ( - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/repo" - - "github.com/drone/drone/plugin/deploy/deis" - "github.com/drone/drone/plugin/deploy/git" - "github.com/drone/drone/plugin/deploy/heroku" - "github.com/drone/drone/plugin/deploy/modulus" - "github.com/drone/drone/plugin/deploy/nodejitsu" - "github.com/drone/drone/plugin/deploy/tsuru" -) - -// Deploy stores the configuration details -// for deploying build artifacts when -// a Build has succeeded -type Deploy struct { - CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"` - Git *git.Git `yaml:"git,omitempty"` - Heroku *heroku.Heroku `yaml:"heroku,omitempty"` - Deis *deis.Deis `yaml:"deis,omitempty"` - Modulus *modulus.Modulus `yaml:"modulus,omitempty"` - Nodejitsu *nodejitsu.Nodejitsu `yaml:"nodejitsu,omitempty"` - SSH *SSH `yaml:"ssh,omitempty"` - Tsuru *tsuru.Tsuru `yaml:"tsuru,omitempty"` - Bash *Bash `yaml:"bash,omitempty"` -} - -func (d *Deploy) Write(f *buildfile.Buildfile, r *repo.Repo) { - - if d.CloudFoundry != nil && match(d.CloudFoundry.GetCondition(), r) { - d.CloudFoundry.Write(f) - } - if d.Git != nil && match(d.Git.GetCondition(), r) { - d.Git.Write(f) - } - if d.Heroku != nil && match(d.Heroku.GetCondition(), r) { - d.Heroku.Write(f) - } - if d.Deis != nil && match(d.Deis.GetCondition(), r) { - d.Deis.Write(f) - } - if d.Modulus != nil && match(d.Modulus.GetCondition(), r) { - d.Modulus.Write(f) - } - if d.Nodejitsu != nil && match(d.Nodejitsu.GetCondition(), r) { - d.Nodejitsu.Write(f) - } - if d.SSH != nil && match(d.SSH.GetCondition(), r) { - d.SSH.Write(f) - } - if d.Tsuru != nil && match(d.Tsuru.GetCondition(), r) { - d.Tsuru.Write(f) - } - if d.Bash != nil && match(d.Bash.GetCondition(), r) { - d.Bash.Write(f) - } -} - -func match(c *condition.Condition, r *repo.Repo) bool { - switch { - case c == nil: - return true - case !c.MatchBranch(r.Branch): - return false - case !c.MatchOwner(r.Name): - return false - case !c.MatchPullRequest(r.PR): - return false - } - return true -} diff --git a/plugin/deploy/git/git.go b/plugin/deploy/git/git.go deleted file mode 100644 index 298c502ae..000000000 --- a/plugin/deploy/git/git.go +++ /dev/null @@ -1,56 +0,0 @@ -package git - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -const ( - // Gommand to the current commit hash - CmdRevParse = "COMMIT=$(git rev-parse HEAD)" - - // Command to set the git user and email based on the - // individual that made the commit. - CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" - CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" -) - -type Git struct { - Target string `yaml:"target,omitempty"` - Force bool `yaml:"force,omitempty"` - Branch string `yaml:"branch,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (g *Git) Write(f *buildfile.Buildfile) { - f.WriteCmdSilent(CmdRevParse) - f.WriteCmdSilent(CmdGlobalUser) - f.WriteCmdSilent(CmdGlobalEmail) - - // add target as a git remote - f.WriteCmd(fmt.Sprintf("git remote add deploy %s", g.Target)) - - dest := g.Branch - if len(dest) == 0 { - dest = "master" - } - - switch g.Force { - case true: - // this is useful when the there are artifacts generated - // by the build script, such as less files converted to css, - // that need to be deployed to git remote. - f.WriteCmd(fmt.Sprintf("git add -A")) - f.WriteCmd(fmt.Sprintf("git commit -m 'add build artifacts'")) - f.WriteCmd(fmt.Sprintf("git push deploy HEAD:%s --force", dest)) - case false: - // otherwise we just do a standard git push - f.WriteCmd(fmt.Sprintf("git push deploy $COMMIT:%s", dest)) - } -} - -func (g *Git) GetCondition() *condition.Condition { - return g.Condition -} diff --git a/plugin/deploy/git/git_test.go b/plugin/deploy/git/git_test.go deleted file mode 100644 index 5502647ad..000000000 --- a/plugin/deploy/git/git_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package git - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Git(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Git Deploy", func() { - - g.It("Should set git.config", func() { - b := new(buildfile.Buildfile) - d := Git{ - Target: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, CmdRevParse)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) - }) - - g.It("Should add remote", func() { - b := new(buildfile.Buildfile) - d := Git{ - Target: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit remote add deploy git://foo.com/bar/baz.git\n")).Equal(true) - }) - - g.It("Should push to remote", func() { - b := new(buildfile.Buildfile) - d := Git{ - Target: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit push deploy $COMMIT:master\n")).Equal(true) - }) - - g.It("Should push to alternate branch", func() { - b := new(buildfile.Buildfile) - d := Git{ - Branch: "foo", - Target: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit push deploy $COMMIT:foo\n")).Equal(true) - }) - - g.It("Should force push to remote", func() { - b := new(buildfile.Buildfile) - d := Git{ - Force: true, - Target: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit commit -m 'add build artifacts'\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit push deploy HEAD:master --force\n")).Equal(true) - }) - - }) -} diff --git a/plugin/deploy/heroku/heroku.go b/plugin/deploy/heroku/heroku.go deleted file mode 100644 index 75135f640..000000000 --- a/plugin/deploy/heroku/heroku.go +++ /dev/null @@ -1,56 +0,0 @@ -package heroku - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -const ( - // Gommand to the current commit hash - CmdRevParse = "COMMIT=$(git rev-parse HEAD)" - - // Command to set the git user and email based on the - // individual that made the commit. - CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" - CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" - - // Command to write the API token to ~/.netrc - // use "_" since heroku git authentication ignores username - CmdLogin = "echo 'machine git.heroku.com login _ password %s' >> ~/.netrc" -) - -type Heroku struct { - App string `yaml:"app,omitempty"` - Force bool `yaml:"force,omitempty"` - Token string `yaml:"token,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (h *Heroku) Write(f *buildfile.Buildfile) { - f.WriteCmdSilent(CmdRevParse) - f.WriteCmdSilent(CmdGlobalUser) - f.WriteCmdSilent(CmdGlobalEmail) - f.WriteCmdSilent(fmt.Sprintf(CmdLogin, h.Token)) - - // add heroku as a git remote - f.WriteCmd(fmt.Sprintf("git remote add heroku https://git.heroku.com/%s.git", h.App)) - - switch h.Force { - case true: - // this is useful when the there are artifacts generated - // by the build script, such as less files converted to css, - // that need to be deployed to Heroku. - f.WriteCmd(fmt.Sprintf("git add -A")) - f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'")) - f.WriteCmd(fmt.Sprintf("git push heroku HEAD:refs/heads/master --force")) - case false: - // otherwise we just do a standard git push - f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:refs/heads/master")) - } -} - -func (h *Heroku) GetCondition() *condition.Condition { - return h.Condition -} diff --git a/plugin/deploy/heroku/heroku_test.go b/plugin/deploy/heroku/heroku_test.go deleted file mode 100644 index 71e3d160c..000000000 --- a/plugin/deploy/heroku/heroku_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package heroku - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Heroku(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Heroku Deploy", func() { - - g.It("Should set git.config", func() { - b := new(buildfile.Buildfile) - h := Heroku{ - App: "drone", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, CmdRevParse)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) - }) - - g.It("Should write token", func() { - b := new(buildfile.Buildfile) - h := Heroku{ - App: "drone", - Token: "mock-token", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\necho 'machine git.heroku.com login _ password mock-token' >> ~/.netrc\n")).Equal(true) - }) - - g.It("Should add remote", func() { - b := new(buildfile.Buildfile) - h := Heroku{ - App: "drone", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit remote add heroku https://git.heroku.com/drone.git\n")).Equal(true) - }) - - g.It("Should push to remote", func() { - b := new(buildfile.Buildfile) - d := Heroku{ - App: "drone", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit push heroku $COMMIT:refs/heads/master\n")).Equal(true) - }) - - g.It("Should force push to remote", func() { - b := new(buildfile.Buildfile) - h := Heroku{ - Force: true, - App: "drone", - } - - h.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit push heroku HEAD:refs/heads/master --force\n")).Equal(true) - }) - - }) -} diff --git a/plugin/deploy/modulus/modulus.go b/plugin/deploy/modulus/modulus.go deleted file mode 100644 index fa06236ee..000000000 --- a/plugin/deploy/modulus/modulus.go +++ /dev/null @@ -1,36 +0,0 @@ -package modulus - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Modulus struct { - Project string `yaml:"project,omitempty"` - Token string `yaml:"token,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (m *Modulus) Write(f *buildfile.Buildfile) { - if len(m.Token) == 0 || len(m.Project) == 0 { - return - } - f.WriteEnv("MODULUS_TOKEN", m.Token) - - // Verify npm exists, otherwise we cannot install the - // modulus command line utility. - f.WriteCmdSilent("[ -f /usr/bin/npm ] || echo ERROR: npm is required for modulus.io deployments") - f.WriteCmdSilent("[ -f /usr/bin/npm ] || exit 1") - - // Install the Modulus command line interface then deploy the configured - // project. - f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g modulus") - f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g modulus") - f.WriteCmd(fmt.Sprintf("modulus deploy -p %q", m.Project)) -} - -func (m *Modulus) GetCondition() *condition.Condition { - return m.Condition -} diff --git a/plugin/deploy/modulus/modulus_test.go b/plugin/deploy/modulus/modulus_test.go deleted file mode 100644 index b8f56ad3e..000000000 --- a/plugin/deploy/modulus/modulus_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package modulus - -import ( - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Modulus(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Modulus Deploy", func() { - - g.It("Requires a Project name", func() { - b := new(buildfile.Buildfile) - m := Modulus{ - Project: "foo", - } - - m.Write(b) - g.Assert(b.String()).Equal("") - }) - - g.It("Requires a Token", func() { - b := new(buildfile.Buildfile) - m := Modulus{ - Token: "bar", - } - - m.Write(b) - g.Assert(b.String()).Equal("") - }) - - g.It("Should execute deploy commands", func() { - b := new(buildfile.Buildfile) - m := Modulus{ - Project: "foo", - Token: "bar", - } - - m.Write(b) - g.Assert(b.String()).Equal(`export MODULUS_TOKEN="bar" -[ -f /usr/bin/npm ] || echo ERROR: npm is required for modulus.io deployments -[ -f /usr/bin/npm ] || exit 1 -[ -f /usr/bin/sudo ] || npm install -g modulus -[ -f /usr/bin/sudo ] && sudo npm install -g modulus -echo '#DRONE:6d6f64756c7573206465706c6f79202d702022666f6f22' -modulus deploy -p "foo" -`) - }) - }) -} diff --git a/plugin/deploy/nodejitsu/nodejitsu.go b/plugin/deploy/nodejitsu/nodejitsu.go deleted file mode 100644 index 11eb215c7..000000000 --- a/plugin/deploy/nodejitsu/nodejitsu.go +++ /dev/null @@ -1,32 +0,0 @@ -package nodejitsu - -import ( - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Nodejitsu struct { - User string `yaml:"user,omitempty"` - Token string `yaml:"token,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (n *Nodejitsu) Write(f *buildfile.Buildfile) { - if len(n.Token) == 0 || len(n.User) == 0 { - return - } - - f.WriteEnv("username", n.User) - f.WriteEnv("apiToken", n.Token) - - // Install the jitsu command line interface then - // deploy the configured app. - f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g jitsu") - f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g jitsu") - f.WriteCmd("jitsu deploy") -} - -func (n *Nodejitsu) GetCondition() *condition.Condition { - return n.Condition -} diff --git a/plugin/deploy/nodejitsu/nodejitsu_test.go b/plugin/deploy/nodejitsu/nodejitsu_test.go deleted file mode 100644 index 9acbf8de1..000000000 --- a/plugin/deploy/nodejitsu/nodejitsu_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package nodejitsu - -import ( - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Modulus(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Nodejitsu Deploy", func() { - - g.It("Requires a User", func() { - b := new(buildfile.Buildfile) - n := Nodejitsu{ - User: "foo", - } - - n.Write(b) - g.Assert(b.String()).Equal("") - }) - - g.It("Requires a Token", func() { - b := new(buildfile.Buildfile) - n := Nodejitsu{ - Token: "bar", - } - - n.Write(b) - g.Assert(b.String()).Equal("") - }) - - g.It("Should execute deploy commands", func() { - b := new(buildfile.Buildfile) - n := Nodejitsu{ - User: "foo", - Token: "bar", - } - - n.Write(b) - g.Assert(b.String()).Equal(`export username="foo" -export apiToken="bar" -[ -f /usr/bin/sudo ] || npm install -g jitsu -[ -f /usr/bin/sudo ] && sudo npm install -g jitsu -echo '#DRONE:6a69747375206465706c6f79' -jitsu deploy -`) - }) - }) -} diff --git a/plugin/deploy/ssh.go b/plugin/deploy/ssh.go deleted file mode 100644 index 1b54960a5..000000000 --- a/plugin/deploy/ssh.go +++ /dev/null @@ -1,105 +0,0 @@ -package deploy - -import ( - "fmt" - "strconv" - "strings" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -// SSH struct holds configuration data for deployment -// via ssh, deployment done by scp-ing file(s) listed -// in artifacts to the target host, and then run cmd -// remotely. -// It is assumed that the target host already -// add this repo public key in the host's `authorized_hosts` -// file. And the private key is already copied to `.ssh/id_rsa` -// inside the build container. No further check will be done. -type SSH struct { - - // Target is the deployment host in this format - // user@hostname:/full/path - // - // PORT may be omitted if its default to port 22. - Target string `yaml:"target,omitempty"` - - // Artifacts is a list of files/dirs to be deployed - // to the target host. If artifacts list more than one file - // it will be compressed into a single tar.gz file. - // if artifacts contain: - // - GITARCHIVE - // - // other file listed in artifacts will be ignored, instead, we will - // create git archive from the current revision and deploy that file - // alone. - // If you need to deploy the git archive along with some other files, - // please use build script to create the git archive, and then list - // the archive name here with the other files. - Artifacts []string `yaml:"artifacts,omitempty"` - - // Cmd is a single command executed at target host after the artifacts - // is deployed. - Cmd string `yaml:"cmd,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -// Write down the buildfile -func (s *SSH) Write(f *buildfile.Buildfile) { - host := strings.SplitN(s.Target, " ", 2) - if len(host) == 1 { - host = append(host, "22") - } - if _, err := strconv.Atoi(host[1]); err != nil { - host[1] = "22" - } - - // Is artifact created? - artifact := false - - for _, a := range s.Artifacts { - if a == "GITARCHIVE" { - artifact = createGitArchive(f) - break - } - } - - if !artifact { - if len(s.Artifacts) > 1 { - artifact = compress(f, s.Artifacts) - } else if len(s.Artifacts) == 1 { - f.WriteEnv("ARTIFACT", s.Artifacts[0]) - artifact = true - } - } - - if artifact { - scpCmd := "scp -o StrictHostKeyChecking=no -P %s -r ${ARTIFACT} %s" - f.WriteCmd(fmt.Sprintf(scpCmd, host[1], host[0])) - } - - if len(s.Cmd) > 0 { - sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s %q" - f.WriteCmd(fmt.Sprintf(sshCmd, host[1], strings.SplitN(host[0], ":", 2)[0], s.Cmd)) - } -} - -func createGitArchive(f *buildfile.Buildfile) bool { - f.WriteEnv("COMMIT", "$(git rev-parse HEAD)") - f.WriteEnv("ARTIFACT", "${PWD##*/}-${COMMIT}.tar.gz") - f.WriteCmdSilent("git archive --format=tar.gz --prefix=${PWD##*/}/ ${COMMIT} > ${ARTIFACT}") - return true -} - -func compress(f *buildfile.Buildfile, files []string) bool { - cmd := "tar -cf ${ARTIFACT} %s" - f.WriteEnv("ARTIFACT", "${PWD##*/}.tar.gz") - f.WriteCmdSilent(fmt.Sprintf(cmd, strings.Join(files, " "))) - return true -} - -func (s *SSH) GetCondition() *condition.Condition { - return s.Condition -} diff --git a/plugin/deploy/ssh_test.go b/plugin/deploy/ssh_test.go deleted file mode 100644 index 54aa1a1cf..000000000 --- a/plugin/deploy/ssh_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package deploy - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - - "gopkg.in/yaml.v1" -) - -// emulate Build struct -type build struct { - Deploy *Deploy `yaml:"deploy,omitempty"` -} - -var sampleYml = ` -deploy: - ssh: - target: user@test.example.com - cmd: /opt/bin/redeploy.sh -` - -var sampleYml1 = ` -deploy: - ssh: - target: user@test.example.com:/srv/app/location 2212 - artifacts: - - build.result - cmd: /opt/bin/redeploy.sh -` - -var sampleYml2 = ` -deploy: - ssh: - target: user@test.example.com:/srv/app/location 2212 - artifacts: - - build.result - - config/file - cmd: /opt/bin/redeploy.sh -` - -var sampleYml3 = ` -deploy: - ssh: - target: user@test.example.com:/srv/app/location 2212 - artifacts: - - GITARCHIVE - cmd: /opt/bin/redeploy.sh -` - -func setUp(input string) (string, error) { - var buildStruct build - err := yaml.Unmarshal([]byte(input), &buildStruct) - if err != nil { - return "", err - } - bf := buildfile.New() - buildStruct.Deploy.Write(bf, nil) - return bf.String(), err -} - -func TestSSHNoArtifact(t *testing.T) { - bscr, err := setUp(sampleYml) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if strings.Contains(bscr, `scp`) { - t.Error("Expect script not to contains scp command") - } - - if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com \"/opt/bin/redeploy.sh\"") { - t.Error("Expect script to contains ssh command") - } -} - -func TestSSHOneArtifact(t *testing.T) { - bscr, err := setUp(sampleYml1) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, `ARTIFACT="build.result"`) { - t.Error("Expect script to contains artifact") - } - - if !strings.Contains(bscr, "scp -o StrictHostKeyChecking=no -P 2212 -r ${ARTIFACT} user@test.example.com:/srv/app/location") { - t.Errorf("Expect script to contains scp command, got:\n%s", bscr) - } -} - -func TestSSHMultiArtifact(t *testing.T) { - bscr, err := setUp(sampleYml2) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, `ARTIFACT="${PWD##*/}.tar.gz"`) { - t.Errorf("Expect script to contains artifact") - } - - if !strings.Contains(bscr, "tar -cf ${ARTIFACT} build.result config/file") { - t.Errorf("Expect script to contains tar command. got: %s\n", bscr) - } -} - -func TestSSHGitArchive(t *testing.T) { - bscr, err := setUp(sampleYml3) - if err != nil { - t.Fatalf("Can't unmarshal deploy script: %s", err) - } - - if !strings.Contains(bscr, `COMMIT="$(git rev-parse HEAD)"`) { - t.Errorf("Expect script to contains commit ref") - } - - if !strings.Contains(bscr, `ARTIFACT="${PWD##*/}-${COMMIT}.tar.gz"`) { - t.Errorf("Expect script to contains artifact") - } - - if strings.Contains(bscr, "=GITARCHIVE") { - t.Errorf("Doesn't expect script to contains GITARCHIVE literals") - } - - if !strings.Contains(bscr, "git archive --format=tar.gz --prefix=${PWD##*/}/ ${COMMIT} > ${ARTIFACT}") { - t.Errorf("Expect script to run git archive") - } -} diff --git a/plugin/deploy/tsuru/tsuru.go b/plugin/deploy/tsuru/tsuru.go deleted file mode 100644 index 808f8867b..000000000 --- a/plugin/deploy/tsuru/tsuru.go +++ /dev/null @@ -1,50 +0,0 @@ -package tsuru - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -const ( - // Gommand to the current commit hash - CmdRevParse = "COMMIT=$(git rev-parse HEAD)" - - // Command to set the git user and email based on the - // individual that made the commit. - CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" - CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" -) - -type Tsuru struct { - Force bool `yaml:"force,omitempty"` - Remote string `yaml:"remote,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (t *Tsuru) Write(f *buildfile.Buildfile) { - f.WriteCmdSilent(CmdRevParse) - f.WriteCmdSilent(CmdGlobalUser) - f.WriteCmdSilent(CmdGlobalEmail) - - // add tsuru as a git remote - f.WriteCmd(fmt.Sprintf("git remote add tsuru %s", t.Remote)) - - switch t.Force { - case true: - // this is useful when the there are artifacts generated - // by the build script, such as less files converted to css, - // that need to be deployed to Tsuru. - f.WriteCmd(fmt.Sprintf("git add -A")) - f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'")) - f.WriteCmd(fmt.Sprintf("git push tsuru HEAD:master --force")) - case false: - // otherwise we just do a standard git push - f.WriteCmd(fmt.Sprintf("git push tsuru $COMMIT:master")) - } -} - -func (t *Tsuru) GetCondition() *condition.Condition { - return t.Condition -} diff --git a/plugin/deploy/tsuru/tsuru_test.go b/plugin/deploy/tsuru/tsuru_test.go deleted file mode 100644 index 4473e69e3..000000000 --- a/plugin/deploy/tsuru/tsuru_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package tsuru - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_Tsuru(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Tsuru Deploy", func() { - - g.It("Should set git.config", func() { - b := new(buildfile.Buildfile) - d := Tsuru{ - Remote: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, CmdRevParse)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true) - g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) - }) - - g.It("Should add remote", func() { - b := new(buildfile.Buildfile) - d := Tsuru{ - Remote: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit remote add tsuru git://foo.com/bar/baz.git\n")).Equal(true) - }) - - g.It("Should push to remote", func() { - b := new(buildfile.Buildfile) - d := Tsuru{ - Remote: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit push tsuru $COMMIT:master\n")).Equal(true) - }) - - g.It("Should force push to remote", func() { - b := new(buildfile.Buildfile) - d := Tsuru{ - Force: true, - Remote: "git://foo.com/bar/baz.git", - } - - d.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit push tsuru HEAD:master --force\n")).Equal(true) - }) - - }) -} diff --git a/plugin/notify/email/email.go b/plugin/notify/email/email.go deleted file mode 100644 index 666c6d251..000000000 --- a/plugin/notify/email/email.go +++ /dev/null @@ -1,175 +0,0 @@ -package email - -import ( - "bytes" - "fmt" - "net" - "net/smtp" - "strings" - - "github.com/drone/config" - "github.com/drone/drone/shared/model" -) - -const ( - NotifyAlways = "always" // always send email notification - NotifyNever = "never" // never send email notifications - NotifyAuthor = "author" // only send email notifications to the author - NotifyAfterChange = "change" // only if the previous commit has a different status - - NotifyTrue = "true" // alias for NotifyTrue - NotifyFalse = "false" // alias for NotifyFalse - NotifyOn = "on" // alias for NotifyTrue - NotifyOff = "off" // alias for NotifyFalse - NotifyBlame = "blame" // alias for NotifyAuthor -) - -const ( - Subject = "[%s] %s/%s (%s - %s)" -) - -var ( - DefaultHost = config.String("smtp-host", "") - DefaultPort = config.String("smtp-port", "") - DefaultFrom = config.String("smtp-from", "") - DefaultUser = config.String("smtp-user", "") - DefaultPass = config.String("smtp-pass", "") -) - -type Email struct { - Recipients []string `yaml:"recipients"` - Success string `yaml:"on_success"` - Failure string `yaml:"on_failure"` - - Host string `yaml:"host"` - Port string `yaml:"port"` - From string `yaml:"from"` - Username string `yaml:"username"` - Password string `yaml:"password"` -} - -// Send will send an email, either success or failure, -// based on the Commit Status. -func (e *Email) Send(context *model.Request) error { - var status = context.Commit.Status - - switch status { - // no builds are triggered for pending builds - case model.StatusEnqueue, model.StatusStarted: - return nil - case model.StatusSuccess: - return e.sendSuccess(context) - default: - return e.sendFailure(context) - } -} - -// sendFailure sends email notifications to the list of -// recipients indicating the build failed. -func (e *Email) sendFailure(context *model.Request) error { - - switch e.Failure { - case NotifyFalse, NotifyNever, NotifyOff: - return nil - // if the last commit in this branch was a different status, notify - case NotifyAfterChange: - if context.Prior.Status == context.Commit.Status { - return nil - } - // if configured to email the author, replace - // the recipiends with the commit author email. - case NotifyBlame, NotifyAuthor: - e.Recipients = []string{context.Commit.Author} - } - - // generate the email failure template - var buf bytes.Buffer - err := failureTemplate.ExecuteTemplate(&buf, "_", context) - if err != nil { - return err - } - - // generate the email subject - var subject = fmt.Sprintf( - Subject, - context.Commit.Status, - context.Repo.Owner, - context.Repo.Name, - context.Commit.Branch, - context.Commit.ShaShort(), - ) - - return e.send(subject, buf.String(), e.Recipients) -} - -// sendSuccess sends email notifications to the list of -// recipients indicating the build was a success. -func (e *Email) sendSuccess(context *model.Request) error { - - switch e.Success { - case NotifyFalse, NotifyNever, NotifyOff: - return nil - // if the last commit in this branch was a different status, notify - case NotifyAfterChange: - if context.Prior.Status == context.Commit.Status { - return nil - } - // if configured to email the author, replace - // the recipiends with the commit author email. - case NotifyBlame, NotifyAuthor: - e.Recipients = []string{context.Commit.Author} - } - - // generate the email success template - var buf bytes.Buffer - err := successTemplate.ExecuteTemplate(&buf, "_", context) - if err != nil { - return err - } - - // generate the email subject - var subject = fmt.Sprintf( - Subject, - context.Commit.Status, - context.Repo.Owner, - context.Repo.Name, - context.Commit.Branch, - context.Commit.ShaShort(), - ) - - return e.send(subject, buf.String(), e.Recipients) -} - -func (e *Email) send(subject, body string, recipients []string) error { - - if len(recipients) == 0 { - return nil - } - - // the user can provide their own smtp server - // configuration. If None provided, attempt to - // use the global configuration set in the environet - // variables. - if len(*DefaultHost) != 0 { - e.Host = *DefaultHost - e.Port = *DefaultPort - e.From = *DefaultFrom - e.Username = *DefaultUser - e.Password = *DefaultPass - } - - var auth smtp.Auth - var addr = net.JoinHostPort(e.Host, e.Port) - - // setup the authentication to the smtp server - // if the username and password are provided. - if len(e.Username) > 0 { - auth = smtp.PlainAuth("", e.Username, e.Password, e.Host) - } - - // genereate the raw email message - var to = strings.Join(e.Recipients, ",") - var raw = fmt.Sprintf(rawMessage, e.From, to, subject, body) - - return smtp.SendMail(addr, auth, e.From, e.Recipients, []byte(raw)) -} diff --git a/plugin/notify/email/template.go b/plugin/notify/email/template.go deleted file mode 100644 index 378fac8ba..000000000 --- a/plugin/notify/email/template.go +++ /dev/null @@ -1,42 +0,0 @@ -package email - -import ( - "html/template" -) - -// raw email message template -var rawMessage = `From: %s -To: %s -Subject: %s -MIME-version: 1.0 -Content-Type: text/html; charset="UTF-8" - -%s` - -// default success email template -var successTemplate = template.Must(template.New("_").Parse(` -

- Build was Successful - (see results) -

-

Repository : {{.Repo.Owner}}/{{.Repo.Name}}

-

Commit : {{.Commit.ShaShort}}

-

Author : {{.Commit.Author}}

-

Branch : {{.Commit.Branch}}

-

Message:

-

{{ .Commit.Message }}

-`)) - -// default failure email template -var failureTemplate = template.Must(template.New("_").Parse(` -

- Build Failed - (see results) -

-

Repository : {{.Repo.Owner}}/{{.Repo.Name}}

-

Commit : {{.Commit.ShaShort}}

-

Author : {{.Commit.Author}}

-

Branch : {{.Commit.Branch}}

-

Message:

-

{{ .Commit.Message }}

-`)) diff --git a/plugin/notify/flowdock/client.go b/plugin/notify/flowdock/client.go deleted file mode 100644 index ba0c7835c..000000000 --- a/plugin/notify/flowdock/client.go +++ /dev/null @@ -1,105 +0,0 @@ -package flowdock - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -const ( - ENDPOINT = "https://api.flowdock.com/v1/messages/team_inbox/" -) - -var ( - // Required default client settings - Token = "" - Source = "" - FromAddress = "" - - // Optional default client settings - FromName = "" - ReplyTo = "" - Project = "" - Link = "" - Tags = []string{} -) - -type Client struct { - // Required - Token string - Source string - FromAddress string - Subject string - Content string - - // Optional - FromName string - ReplyTo string - Project string - Link string - Tags []string -} - -func (c *Client) Inbox(subject, content string) error { - return send(c.Token, c.Source, c.FromAddress, subject, content, c.FromName, c.ReplyTo, c.Project, c.Link, c.Tags) -} - -func Inbox(subject, content string) error { - return send(Token, Source, FromAddress, subject, content, FromName, ReplyTo, Project, Link, Tags) -} - -func send(token, source, fromAddress, subject, content, fromName, replyTo, project, link string, tags []string) error { - // Required validation - if len(token) == 0 { - return fmt.Errorf(`"Token" is required`) - } - if len(source) == 0 { - return fmt.Errorf(`"Source" is required`) - } - if len(fromAddress) == 0 { - return fmt.Errorf(`"FromAddress" is required`) - } - if len(subject) == 0 { - return fmt.Errorf(`"Subject" is required`) - } - - // Build payload - payload := map[string]interface{}{ - "source": source, - "from_address": fromAddress, - "subject": subject, - "content": content, - } - if len(fromName) > 0 { - payload["from_name"] = fromName - } - if len(replyTo) > 0 { - payload["reply_to"] = replyTo - } - if len(project) > 0 { - payload["project"] = project - } - if len(link) > 0 { - payload["link"] = link - } - if len(tags) > 0 { - payload["tags"] = tags - } - jsonPayload, err := json.Marshal(payload) - if err != nil { - return err - } - - // Send to Flowdock - resp, err := http.Post(ENDPOINT+token, "application/json", bytes.NewReader(jsonPayload)) - defer resp.Body.Close() - - if resp.StatusCode == 200 { - return nil - } else { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("Unexpected response from Flowdock: %s %s", resp.Status, string(bodyBytes)) - } -} diff --git a/plugin/notify/flowdock/flowdock.go b/plugin/notify/flowdock/flowdock.go deleted file mode 100644 index e3b1d06f9..000000000 --- a/plugin/notify/flowdock/flowdock.go +++ /dev/null @@ -1,83 +0,0 @@ -package flowdock - -import ( - "fmt" - "strings" - - "github.com/drone/drone/shared/model" -) - -const ( - flowdockStartedSubject = "Building %s (%s)" - flowdockSuccessSubject = "Build: %s (%s) is SUCCESS" - flowdockFailureSubject = "Build: %s (%s) is FAILED" - flowdockMessage = "

%s

\nBuild: %s
\nResult: %s
\nAuthor: %s
Commit: %s
\nRepository Url: %s" - flowdockBuildOkEmail = "build+ok@flowdock.com" - flowdockBuildFailEmail = "build+fail@flowdock.com" -) - -type Flowdock struct { - Token string `yaml:"token,omitempty"` - Source string `yaml:"source,omitempty"` - Tags string `yaml:"tags,omitempty"` - Started bool `yaml:"on_started,omitempty"` - Success bool `yaml:"on_success,omitempty"` - Failure bool `yaml:"on_failure,omitempty"` -} - -func (f *Flowdock) Send(context *model.Request) error { - switch { - case context.Commit.Status == "Started" && f.Started: - return f.sendStarted(context) - case context.Commit.Status == "Success" && f.Success: - return f.sendSuccess(context) - case context.Commit.Status == "Failure" && f.Failure: - return f.sendFailure(context) - } - - return nil -} - -func (f *Flowdock) getBuildUrl(context *model.Request) string { - return fmt.Sprintf("%s/%s/%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name, context.Commit.Branch, context.Commit.Sha) -} - -func (f *Flowdock) getRepoUrl(context *model.Request) string { - return fmt.Sprintf("%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name) -} - -func (f *Flowdock) getMessage(context *model.Request) string { - buildUrl := fmt.Sprintf("%s", f.getBuildUrl(context), context.Commit.ShaShort()) - return fmt.Sprintf(flowdockMessage, context.Repo.Name, buildUrl, context.Commit.Status, context.Commit.Author, context.Commit.Message, f.getRepoUrl(context)) -} - -func (f *Flowdock) sendStarted(context *model.Request) error { - fromAddress := context.Commit.Author - subject := fmt.Sprintf(flowdockStartedSubject, context.Repo.Name, context.Commit.Branch) - msg := f.getMessage(context) - tags := strings.Split(f.Tags, ",") - return f.send(fromAddress, subject, msg, tags) -} - -func (f *Flowdock) sendFailure(context *model.Request) error { - fromAddress := flowdockBuildFailEmail - tags := strings.Split(f.Tags, ",") - subject := fmt.Sprintf(flowdockFailureSubject, context.Repo.Name, context.Commit.Branch) - msg := f.getMessage(context) - return f.send(fromAddress, subject, msg, tags) -} - -func (f *Flowdock) sendSuccess(context *model.Request) error { - fromAddress := flowdockBuildOkEmail - tags := strings.Split(f.Tags, ",") - subject := fmt.Sprintf(flowdockSuccessSubject, context.Repo.Name, context.Commit.Branch) - msg := f.getMessage(context) - return f.send(fromAddress, subject, msg, tags) -} - -// helper function to send Flowdock requests -func (f *Flowdock) send(fromAddress, subject, message string, tags []string) error { - c := Client{Token: f.Token, Source: f.Source, FromName: "drone.io", FromAddress: fromAddress, Tags: tags} - go c.Inbox(subject, message) - return nil -} diff --git a/plugin/notify/github/github.go b/plugin/notify/github/github.go deleted file mode 100644 index b1e5db3c2..000000000 --- a/plugin/notify/github/github.go +++ /dev/null @@ -1,158 +0,0 @@ -package github - -import ( - "fmt" - "net/url" - - "code.google.com/p/goauth2/oauth" - "github.com/drone/drone/shared/model" - "github.com/google/go-github/github" -) - -const ( - NotifyDisabled = "disabled" - NotifyFalse = "false" - NotifyOff = "off" -) - -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" -) - -const ( - BaseURL = "https://api.github.com/" -) - -type GitHub string - -// Send uses the github status API to update the build -// status in github or github enterprise. -func (g GitHub) Send(context *model.Request) error { - - // a user can toggle the status api on / off - // in the .drone.yml - switch g { - case NotifyDisabled, NotifyOff, NotifyFalse: - return nil - } - - // this should only be executed for GitHub and - // GitHub enterprise requests. - switch context.Repo.Remote { - case model.RemoteGithub, model.RemoteGithubEnterprise: - break - default: - return nil - } - - var target = getTarget( - context.Host, - context.Repo.Host, - context.Repo.Owner, - context.Repo.Name, - context.Commit.Branch, - context.Commit.Sha, - ) - - return send( - context.Repo.URL, - context.Repo.Host, - context.Repo.Owner, - context.Repo.Name, - getStatus(context.Commit.Status), - getDesc(context.Commit.Status), - target, - context.Commit.Sha, - context.User.Access, - ) -} - -func send(rawurl, host, owner, repo, status, desc, target, ref, token string) error { - transport := &oauth.Transport{ - Token: &oauth.Token{AccessToken: token}, - } - - data := github.RepoStatus{ - Context: github.String("Drone"), - State: github.String(status), - Description: github.String(desc), - TargetURL: github.String(target), - } - - client := github.NewClient(transport.Client()) - - // if this is for github enterprise we need to set - // the base url. Per the documentation, we need to - // ensure there is a trailing slash. - if host != model.RemoteGithub { - client.BaseURL, _ = getEndpoint(rawurl) - } - - _, _, err := client.Repositories.CreateStatus(owner, repo, ref, &data) - return err -} - -// getStatus is a helper functin that converts a Drone -// status to a GitHub status. -func getStatus(status string) string { - switch status { - case model.StatusEnqueue, model.StatusStarted: - 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.StatusEnqueue, model.StatusStarted: - return DescPending - case model.StatusSuccess: - return DescSuccess - case model.StatusFailure: - return DescFailure - case model.StatusError, model.StatusKilled: - return DescError - default: - return DescError - } -} - -// getTarget is a helper function that generates a URL -// for the user to click and jump to the build results. -// -// for example: -// https://drone.io/github.com/drone/drone-test-go/master/c22aec9c53 -func getTarget(url, host, owner, repo, branch, commit string) string { - return fmt.Sprintf("%s/%s/%s/%s/%s/%s", url, host, owner, repo, branch, commit) -} - -// getEndpoint is a helper funcation that parsed the -// repository HTML URL to determine the API URL. It is -// intended for use with GitHub enterprise. -func getEndpoint(rawurl string) (*url.URL, error) { - uri, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - uri.Path = "/api/v3/" - return uri, nil -} diff --git a/plugin/notify/github/github_test.go b/plugin/notify/github/github_test.go deleted file mode 100644 index 7f905b208..000000000 --- a/plugin/notify/github/github_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package github - -import ( - "testing" - - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func Test_Client(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Github Status", func() { - - g.It("Should get a status", func() { - g.Assert(getStatus(model.StatusEnqueue)).Equal(StatusPending) - g.Assert(getStatus(model.StatusStarted)).Equal(StatusPending) - g.Assert(getStatus(model.StatusSuccess)).Equal(StatusSuccess) - g.Assert(getStatus(model.StatusFailure)).Equal(StatusFailure) - g.Assert(getStatus(model.StatusError)).Equal(StatusError) - g.Assert(getStatus(model.StatusKilled)).Equal(StatusError) - g.Assert(getStatus(model.StatusNone)).Equal(StatusError) - }) - - g.It("Should get a description", func() { - g.Assert(getDesc(model.StatusEnqueue)).Equal(DescPending) - g.Assert(getDesc(model.StatusStarted)).Equal(DescPending) - g.Assert(getDesc(model.StatusSuccess)).Equal(DescSuccess) - g.Assert(getDesc(model.StatusFailure)).Equal(DescFailure) - g.Assert(getDesc(model.StatusError)).Equal(DescError) - g.Assert(getDesc(model.StatusKilled)).Equal(DescError) - g.Assert(getDesc(model.StatusNone)).Equal(DescError) - }) - - g.It("Should get a target url", func() { - var ( - url = "https://drone.io" - host = "github.com" - owner = "drone" - repo = "go-bitbucket" - branch = "master" - commit = "0c0cf4ece975efdfcf6daa78b03d4e84dd257da7" - ) - - var got = getTarget(url, host, owner, repo, branch, commit) - var want = "https://drone.io/github.com/drone/go-bitbucket/master/0c0cf4ece975efdfcf6daa78b03d4e84dd257da7" - g.Assert(got).Equal(want) - }) - }) -} diff --git a/plugin/notify/gitter.go b/plugin/notify/gitter.go deleted file mode 100644 index 15b7fb861..000000000 --- a/plugin/notify/gitter.go +++ /dev/null @@ -1,77 +0,0 @@ -package notify - -import ( - "encoding/json" - "fmt" - - "github.com/drone/drone/shared/model" -) - -const ( - gitterEndpoint = "https://api.gitter.im/v1/rooms/%s/chatMessages" - gitterStartedMessage = "*Building* %s, commit [%s](%s), author %s" - gitterSuccessMessage = "*Success* %s, commit [%s](%s), author %s" - gitterFailureMessage = "*Failed* %s, commit [%s](%s), author %s" -) - -type Gitter struct { - RoomID string `yaml:"room_id,omitempty"` - Token string `yaml:"token,omitempty"` - Started bool `yaml:"on_started,omitempty"` - Success bool `yaml:"on_success,omitempty"` - Failure bool `yaml:"on_failure,omitempty"` -} - -func (g *Gitter) Send(context *model.Request) error { - switch { - case context.Commit.Status == model.StatusStarted && g.Started: - return g.sendStarted(context) - case context.Commit.Status == model.StatusSuccess && g.Success: - return g.sendSuccess(context) - case context.Commit.Status == model.StatusFailure && g.Failure: - return g.sendFailure(context) - } - - return nil -} - -func (g *Gitter) getMessage(context *model.Request, message string) string { - url := getBuildUrl(context) - return fmt.Sprintf(message, context.Repo.Name, context.Commit.ShaShort(), url, context.Commit.Author) -} - -func (g *Gitter) sendStarted(context *model.Request) error { - return g.send(g.getMessage(context, gitterStartedMessage)) -} - -func (g *Gitter) sendSuccess(context *model.Request) error { - return g.send(g.getMessage(context, gitterSuccessMessage)) -} - -func (g *Gitter) sendFailure(context *model.Request) error { - return g.send(g.getMessage(context, gitterFailureMessage)) -} - -// helper function to send HTTP requests -func (g *Gitter) send(msg string) error { - // data will get posted in this format - data := struct { - Text string `json:"text"` - }{msg} - - // data json encoded - payload, err := json.Marshal(data) - if err != nil { - return err - } - - // send payload - url := fmt.Sprintf(gitterEndpoint, g.RoomID) - - // create headers - headers := make(map[string]string) - headers["Accept"] = "application/json" - headers["Authorization"] = fmt.Sprintf("Bearer %s", g.Token) - - return sendJson(url, payload, headers) -} diff --git a/plugin/notify/hipchat.go b/plugin/notify/hipchat.go deleted file mode 100644 index d1d7f1406..000000000 --- a/plugin/notify/hipchat.go +++ /dev/null @@ -1,79 +0,0 @@ -package notify - -import ( - "fmt" - - "github.com/andybons/hipchat" - "github.com/drone/drone/shared/model" -) - -const ( - startedMessage = "Building %s (%s) by %s
- %s" - successMessage = "Success %s (%s) by %s" - failureMessage = "Failed %s (%s) by %s" -) - -type Hipchat struct { - Room string `yaml:"room,omitempty"` - Token string `yaml:"token,omitempty"` - Started bool `yaml:"on_started,omitempty"` - Success bool `yaml:"on_success,omitempty"` - Failure bool `yaml:"on_failure,omitempty"` -} - -type HipchatClient interface { - PostMessage(req hipchat.MessageRequest) error -} - -func (h *Hipchat) Send(context *model.Request) error { - client := &hipchat.Client{AuthToken: h.Token} - return h.SendWithClient(client, context) -} - -func (h *Hipchat) SendWithClient(client HipchatClient, context *model.Request) error { - switch { - case context.Commit.Status == "Started" && h.Started: - return h.sendStarted(client, context) - case context.Commit.Status == "Success" && h.Success: - return h.sendSuccess(client, context) - case context.Commit.Status == "Failure" && h.Failure: - return h.sendFailure(client, context) - } - - return nil -} - -func (h *Hipchat) buildLink(context *model.Request) string { - repoName := context.Repo.Owner + "/" + context.Repo.Name - url := context.Host + "/" + context.Repo.Host + "/" + repoName + "/" + context.Commit.Branch + "/" + context.Commit.Sha - return fmt.Sprintf("%s#%s", url, repoName, context.Commit.ShaShort()) -} - -func (h *Hipchat) sendStarted(client HipchatClient, context *model.Request) error { - msg := fmt.Sprintf(startedMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author, context.Commit.Message) - return h.send(client, hipchat.ColorYellow, hipchat.FormatHTML, msg, false) -} - -func (h *Hipchat) sendFailure(client HipchatClient, context *model.Request) error { - msg := fmt.Sprintf(failureMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author) - return h.send(client, hipchat.ColorRed, hipchat.FormatHTML, msg, true) -} - -func (h *Hipchat) sendSuccess(client HipchatClient, context *model.Request) error { - msg := fmt.Sprintf(successMessage, h.buildLink(context), context.Commit.Branch, context.Commit.Author) - return h.send(client, hipchat.ColorGreen, hipchat.FormatHTML, msg, false) -} - -// helper function to send Hipchat requests -func (h *Hipchat) send(client HipchatClient, color, format, message string, notify bool) error { - req := hipchat.MessageRequest{ - RoomId: h.Room, - From: "Drone", - Message: message, - Color: color, - MessageFormat: format, - Notify: notify, - } - - return client.PostMessage(req) -} diff --git a/plugin/notify/hipchat_test.go b/plugin/notify/hipchat_test.go deleted file mode 100644 index 5f3e809d4..000000000 --- a/plugin/notify/hipchat_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package notify - -import ( - "testing" - - "github.com/andybons/hipchat" - "github.com/drone/drone/shared/model" -) - -type MockHipchatClient struct { - Request hipchat.MessageRequest -} - -func (c *MockHipchatClient) PostMessage(req hipchat.MessageRequest) error { - c.Request = req - return nil -} - -var client = &MockHipchatClient{} - -var subject = &Hipchat{ - Room: "SampleRoom", - Token: "foo", - Started: true, - Success: true, - Failure: true, -} - -var request = &model.Request{ - Host: "http://examplehost.com", - Repo: &model.Repo{ - Host: "examplegit.com", - Owner: "owner", - Name: "repo", - }, - Commit: &model.Commit{ - Sha: "abc", - Branch: "example", - Status: "Started", - Message: "Test Commit", - Author: "Test User", - }, - User: &model.User{ - Login: "TestUser", - }, -} - -func Test_SendStarted(t *testing.T) { - request.Commit.Status = "Started" - - subject.SendWithClient(client, request) - expected := hipchat.MessageRequest{ - RoomId: "SampleRoom", - From: "Drone", - Message: "Building owner/repo#abc (example) by Test User
- Test Commit", - Color: hipchat.ColorYellow, - MessageFormat: hipchat.FormatHTML, - Notify: false, - } - - if client.Request != expected { - t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request) - } -} - -func Test_SendSuccess(t *testing.T) { - request.Commit.Status = "Success" - - subject.SendWithClient(client, request) - expected := hipchat.MessageRequest{ - RoomId: "SampleRoom", - From: "Drone", - Message: "Success owner/repo#abc (example) by Test User", - Color: hipchat.ColorGreen, - MessageFormat: hipchat.FormatHTML, - Notify: false, - } - - if client.Request != expected { - t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request) - } -} - -func Test_SendFailure(t *testing.T) { - request.Commit.Status = "Failure" - - subject.SendWithClient(client, request) - expected := hipchat.MessageRequest{ - RoomId: "SampleRoom", - From: "Drone", - Message: "Failed owner/repo#abc (example) by Test User", - Color: hipchat.ColorRed, - MessageFormat: hipchat.FormatHTML, - Notify: true, - } - - if client.Request != expected { - t.Errorf("Invalid hipchat payload. Expected: %v, got %v", expected, client.Request) - } -} diff --git a/plugin/notify/irc/irc.go b/plugin/notify/irc/irc.go deleted file mode 100644 index 33fa02662..000000000 --- a/plugin/notify/irc/irc.go +++ /dev/null @@ -1,75 +0,0 @@ -package irc - -import ( - "fmt" - - "github.com/drone/drone/shared/model" - "github.com/thoj/go-ircevent" -) - -const ( - MessageStarted = "Building: %s, commit %s, author %s" - MessageSuccess = "Success: %s, commit %s, author %s" - MessageFailure = "Failed: %s, commit %s, author %s" -) - -type IRC struct { - Channel string - Nick string - Server string - Started *bool `yaml:"on_started,omitempty"` - Success *bool `yaml:"on_success,omitempty"` - Failure *bool `yaml:"on_failure,omitempty"` -} - -func (i *IRC) Send(req *model.Request) error { - switch { - case req.Commit.Status == model.StatusStarted && i.Started != nil && *i.Started == true: - return i.sendStarted(req) - case req.Commit.Status == model.StatusSuccess && i.Success != nil && *i.Success == true: - return i.sendSuccess(req) - case req.Commit.Status == model.StatusFailure && i.Failure != nil && *i.Failure == true: - return i.sendFailure(req) - } - return nil -} - -func (i *IRC) sendStarted(req *model.Request) error { - msg := fmt.Sprintf(MessageStarted, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author) - return i.send(i.Channel, msg) -} - -func (i *IRC) sendFailure(req *model.Request) error { - msg := fmt.Sprintf(MessageFailure, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author) - return i.send(i.Channel, msg) -} - -func (i *IRC) sendSuccess(req *model.Request) error { - msg := fmt.Sprintf(MessageSuccess, req.Repo.Name, req.Commit.ShaShort(), req.Commit.Author) - return i.send(i.Channel, msg) -} - -// send is a helper function that will send notice messages -// to the connected IRC client -func (i *IRC) send(channel string, message string) error { - client := irc.IRC(i.Nick, i.Nick) - - if client == nil { - return fmt.Errorf("Error creating IRC client") - } - - err := client.Connect(i.Server) - - if err != nil { - return fmt.Errorf("Error connecting to IRC server: %v", err) - } - - client.AddCallback("001", func(_ *irc.Event) { - client.Notice(channel, message) - client.Quit() - }) - - go client.Loop() - - return nil -} diff --git a/plugin/notify/katoim/katoim.go b/plugin/notify/katoim/katoim.go deleted file mode 100644 index 29dd14e25..000000000 --- a/plugin/notify/katoim/katoim.go +++ /dev/null @@ -1,139 +0,0 @@ -package katoim - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - - "github.com/drone/drone/shared/model" -) - -const ( - katoimEndpoint = "https://api.kato.im/rooms/%s/simple" - katoimStartedMessage = "*Building* %s, commit [%s](%s), author %s" - katoimSuccessMessage = "*Success* %s, commit [%s](%s), author %s" - katoimFailureMessage = "*Failed* %s, commit [%s](%s), author %s" - - NotifyTrue = "true" - NotifyFalse = "false" - NotifyOn = "on" - NotifyOff = "off" - NotifyNever = "never" - NotifyAlways = "always" -) - -type KatoIM struct { - RoomID string `yaml:"room_id,omitempty"` - Started string `yaml:"on_started,omitempty"` - Success string `yaml:"on_success,omitempty"` - Failure string `yaml:"on_failure,omitempty"` -} - -func (k *KatoIM) Send(context *model.Request) error { - switch { - case context.Commit.Status == model.StatusStarted: - return k.sendStarted(context) - case context.Commit.Status == model.StatusSuccess: - return k.sendSuccess(context) - case context.Commit.Status == model.StatusFailure: - return k.sendFailure(context) - } - - return nil -} - -func (k *KatoIM) getMessage(context *model.Request, message string) string { - url := getBuildUrl(context) - return fmt.Sprintf(message, context.Repo.Name, context.Commit.ShaShort(), url, context.Commit.Author) -} - -// sendStarted disabled by default -func (k *KatoIM) sendStarted(context *model.Request) error { - switch k.Started { - case NotifyTrue, NotifyAlways, NotifyOn: - return k.send(k.getMessage(context, katoimStartedMessage), "yellow") - default: - return nil - } -} - -// sendSuccess enabled by default -func (k *KatoIM) sendSuccess(context *model.Request) error { - switch k.Success { - case NotifyFalse, NotifyNever, NotifyOff: - return nil - case NotifyTrue, NotifyAlways, NotifyOn, "": - return k.send(k.getMessage(context, katoimSuccessMessage), "green") - default: - return nil - } -} - -// sendFailure enabled by default -func (k *KatoIM) sendFailure(context *model.Request) error { - switch k.Failure { - case NotifyFalse, NotifyNever, NotifyOff: - return nil - case NotifyTrue, NotifyAlways, NotifyOn, "": - return k.send(k.getMessage(context, katoimFailureMessage), "red") - default: - return nil - } -} - -// helper function to send HTTP requests -func (k *KatoIM) send(msg, color string) error { - // data will get posted in this format - data := struct { - Text string `json:"text"` - Color string `json:"color"` - Renderer string `json:"renderer"` - From string `json:"from"` - }{msg, color, "markdown", "Drone"} - - // data json encoded - payload, err := json.Marshal(data) - if err != nil { - return err - } - - // send payload - url := fmt.Sprintf(katoimEndpoint, k.RoomID) - - // create headers - headers := make(map[string]string) - headers["Accept"] = "application/json" - - return sendJson(url, payload, headers) -} - -func getBuildUrl(context *model.Request) string { - return fmt.Sprintf("%s/%s/%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name, context.Commit.Branch, context.Commit.Sha) -} - -// helper fuction to sent HTTP Post requests -// with JSON data as the payload. -func sendJson(url string, payload []byte, headers map[string]string) error { - client := &http.Client{} - buf := bytes.NewBuffer(payload) - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - if headers != nil { - for k, v := range headers { - req.Header.Add(k, v) - } - } - - resp, err := client.Do(req) - if err != nil { - return err - } - resp.Body.Close() - return nil -} diff --git a/plugin/notify/notification.go b/plugin/notify/notification.go deleted file mode 100644 index da6701ace..000000000 --- a/plugin/notify/notification.go +++ /dev/null @@ -1,141 +0,0 @@ -package notify - -import ( - "bytes" - "fmt" - "log" - "net/http" - - "github.com/drone/drone/plugin/notify/email" - "github.com/drone/drone/plugin/notify/flowdock" - "github.com/drone/drone/plugin/notify/github" - "github.com/drone/drone/plugin/notify/irc" - "github.com/drone/drone/plugin/notify/katoim" - "github.com/drone/drone/plugin/notify/webhook" - "github.com/drone/drone/shared/model" -) - -type Sender interface { - Send(context *model.Request) error -} - -// Notification stores the configuration details -// for notifying a user, or group of users, -// when their Build has completed. -type Notification struct { - Email *email.Email `yaml:"email,omitempty"` - Webhook *webhook.Webhook `yaml:"webhook,omitempty"` - Hipchat *Hipchat `yaml:"hipchat,omitempty"` - Irc *irc.IRC `yaml:"irc,omitempty"` - Slack *Slack `yaml:"slack,omitempty"` - Gitter *Gitter `yaml:"gitter,omitempty"` - Flowdock *flowdock.Flowdock `yaml:"flowdock,omitempty"` - KatoIM *katoim.KatoIM `yaml:"katoim,omitempty"` - - GitHub github.GitHub `yaml:"--"` -} - -func (n *Notification) Send(context *model.Request) error { - // send email notifications - if n.Email != nil { - err := n.Email.Send(context) - if err != nil { - log.Println(err) - } - } - - // send webhook notifications - if n.Webhook != nil { - err := n.Webhook.Send(context) - if err != nil { - log.Println(err) - } - } - - // send hipchat notifications - if n.Hipchat != nil { - err := n.Hipchat.Send(context) - if err != nil { - log.Println(err) - } - } - - // send irc notifications - if n.Irc != nil { - err := n.Irc.Send(context) - if err != nil { - log.Println(err) - } - } - - // send slack notifications - if n.Slack != nil { - err := n.Slack.Send(context) - if err != nil { - log.Println(err) - } - } - - // send gitter notifications - if n.Gitter != nil { - err := n.Gitter.Send(context) - if err != nil { - log.Println(err) - } - } - - // send gitter notifications - if n.Flowdock != nil { - err := n.Flowdock.Send(context) - if err != nil { - log.Println(err) - } - } - - // send kato-im notifications - if n.KatoIM != nil { - err := n.KatoIM.Send(context) - if err != nil { - log.Println(err) - } - } - - // send email notifications - // TODO (bradrydzewski) need to improve this code - githubStatus := new(github.GitHub) - if err := githubStatus.Send(context); err != nil { - log.Println(err) - } - - return nil -} - -func getBuildUrl(context *model.Request) string { - return fmt.Sprintf("%s/%s/%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name, context.Commit.Branch, context.Commit.Sha) -} - -// helper fuction to sent HTTP Post requests -// with JSON data as the payload. -func sendJson(url string, payload []byte, headers map[string]string) error { - client := &http.Client{} - buf := bytes.NewBuffer(payload) - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - if headers != nil { - for k, v := range headers { - req.Header.Add(k, v) - } - } - - resp, err := client.Do(req) - if err != nil { - return err - } - resp.Body.Close() - return nil -} diff --git a/plugin/notify/notify_test.go b/plugin/notify/notify_test.go deleted file mode 100644 index 8faca4084..000000000 --- a/plugin/notify/notify_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package notify - -import ( - "testing" - - "github.com/drone/drone/shared/model" -) - -func Test_getBuildUrl(t *testing.T) { - c := &model.Request{ - Host: "http://examplehost.com", - Repo: &model.Repo{ - Host: "examplegit.com", - Owner: "owner", - Name: "repo", - }, - Commit: &model.Commit{ - Sha: "abc", - Branch: "example", - }, - } - expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc" - output := getBuildUrl(c) - - if output != expected { - t.Errorf("Failed to build url. Expected: %s, got %s", expected, output) - } -} diff --git a/plugin/notify/slack.go b/plugin/notify/slack.go deleted file mode 100644 index 2aa175be4..000000000 --- a/plugin/notify/slack.go +++ /dev/null @@ -1,104 +0,0 @@ -package notify - -import ( - "encoding/json" - "fmt" - - "github.com/drone/drone/shared/model" -) - -const ( - slackStartedMessage = "*Building* <%s|%s> (%s) by %s" - slackStartedFallbackMessage = "Building %s (%s) by %s" - slackSuccessMessage = "*Success* <%s|%s> (%s) by %s" - slackSuccessFallbackMessage = "Success %s (%s) by %s" - slackFailureMessage = "*Failed* <%s|%s> (%s) by %s" - slackFailureFallbackMessage = "Failed %s (%s) by %s" - drone_icon = "https://avatars.githubusercontent.com/drone" -) - -type Slack struct { - WebhookUrl string `yaml:"webhook_url,omitempty"` - Channel string `yaml:"channel,omitempty"` - Username string `yaml:"username,omitempty"` - Started bool `yaml:"on_started,omitempty"` - Success bool `yaml:"on_success,omitempty"` - Failure bool `yaml:"on_failure,omitempty"` -} - -func (s *Slack) Send(context *model.Request) error { - switch { - case context.Commit.Status == "Started" && s.Started: - return s.sendStarted(context) - case context.Commit.Status == "Success" && s.Success: - return s.sendSuccess(context) - case context.Commit.Status == "Failure" && s.Failure: - return s.sendFailure(context) - } - - return nil -} - -func (s *Slack) getMessage(context *model.Request, message string) string { - url := getBuildUrl(context) - // drone/drone#3333333 - linktext := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort() - - return fmt.Sprintf(message, url, linktext, context.Commit.Branch, context.Commit.Author) -} - -func (s *Slack) getFallbackMessage(context *model.Request, message string) string { - // drone/drone#3333333 - text := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort() - - return fmt.Sprintf(message, text, context.Commit.Branch, context.Commit.Author) -} - -func (s *Slack) sendStarted(context *model.Request) error { - return s.send(s.getMessage(context, slackStartedMessage)+"\n - "+context.Commit.Message, - s.getFallbackMessage(context, slackStartedFallbackMessage), "warning") -} - -func (s *Slack) sendSuccess(context *model.Request) error { - return s.send(s.getMessage(context, slackSuccessMessage), - s.getFallbackMessage(context, slackSuccessFallbackMessage), "good") -} - -func (s *Slack) sendFailure(context *model.Request) error { - return s.send(s.getMessage(context, slackFailureMessage), - s.getFallbackMessage(context, slackFailureFallbackMessage), "danger") -} - -// helper function to send HTTP requests -func (s *Slack) send(msg string, fallback string, color string) error { - type Attachment struct { - Fallback string `json:"fallback"` - Text string `json:"text"` - Color string `json:"color"` - MrkdwnIn []string `json:"mrkdwn_in"` - } - - attachments := []Attachment{ - Attachment{ - fallback, - msg, - color, - []string{"fallback", "text"}, - }, - } - // data will get posted in this format - data := struct { - Channel string `json:"channel"` - Username string `json:"username"` - Icon string `json:"icon_url"` - Attachments []Attachment `json:"attachments"` - }{s.Channel, s.Username, drone_icon, attachments} - - // data json encoded - payload, err := json.Marshal(data) - if err != nil { - return err - } - - return sendJson(s.WebhookUrl, payload, nil) -} diff --git a/plugin/notify/slack_test.go b/plugin/notify/slack_test.go deleted file mode 100644 index 78ecb6eed..000000000 --- a/plugin/notify/slack_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package notify - -import "testing" - -/* -var request = &model.Request{ - Host: "http://examplehost.com", - Repo: &model.Repo{ - Host: "examplegit.com", - Owner: "owner", - Name: "repo", - }, - Commit: &model.Commit{ - Sha: "abc", - Branch: "example", - Status: "Started", - Message: "Test Commit", - Author: "Test User", - }, - User: &model.User{ - Login: "TestUser", - }, -} -*/ - -var ( - slackExpectedLink = "" - slackExpectedFallbackText = "owner/repo#abc (example) by Test User" - slackExpectedBase = slackExpectedLink + " (example) by Test User" -) - -func Test_slackStartedMessage(t *testing.T) { - actual := (&Slack{}).getMessage(request, slackStartedMessage) - - expected := "*Building* " + slackExpectedBase - - if actual != expected { - t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) - } -} - -func Test_slackStartedFallbackMessage(t *testing.T) { - actual := (&Slack{}).getFallbackMessage(request, slackStartedFallbackMessage) - - expected := "Building " + slackExpectedFallbackText - - if actual != expected { - t.Errorf("Invalid fallback started message for Slack. Expected %v, got %v", expected, actual) - } -} - -func Test_slackSuccessMessage(t *testing.T) { - actual := (&Slack{}).getMessage(request, slackSuccessMessage) - - expected := "*Success* " + slackExpectedBase - - if actual != expected { - t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) - } -} - -func Test_slackSuccessFallbackMessage(t *testing.T) { - actual := (&Slack{}).getFallbackMessage(request, slackSuccessFallbackMessage) - - expected := "Success " + slackExpectedFallbackText - - if actual != expected { - t.Errorf("Invalid success fallback message for Slack. Expected %v, got %v", expected, actual) - } -} - -func Test_slackFailureMessage(t *testing.T) { - actual := (&Slack{}).getMessage(request, slackFailureMessage) - - expected := "*Failed* " + slackExpectedBase - - if actual != expected { - t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) - } -} - -func Test_slackFailureFallbackMessage(t *testing.T) { - actual := (&Slack{}).getFallbackMessage(request, slackFailureFallbackMessage) - - expected := "Failed " + slackExpectedFallbackText - - if actual != expected { - t.Errorf("Invalid failure fallback message for Slack. Expected %v, got %v", expected, actual) - } -} diff --git a/plugin/notify/webhook/webhook.go b/plugin/notify/webhook/webhook.go deleted file mode 100644 index 61a3423b0..000000000 --- a/plugin/notify/webhook/webhook.go +++ /dev/null @@ -1,59 +0,0 @@ -package webhook - -import ( - "bytes" - "encoding/json" - "net/http" - - "github.com/drone/drone/shared/model" -) - -type Webhook struct { - URL []string `yaml:"urls,omitempty"` - Success *bool `yaml:"on_success,omitempty"` - Failure *bool `yaml:"on_failure,omitempty"` -} - -func (w *Webhook) Send(context *model.Request) error { - switch { - case context.Commit.Status == model.StatusSuccess && w.Success != nil && *w.Success == true: - return w.send(context) - case context.Commit.Status == model.StatusFailure && w.Failure != nil && *w.Failure == true: - return w.send(context) - } - - return nil -} - -// helper function to send HTTP requests -func (w *Webhook) send(context *model.Request) error { - // data will get posted in this format - data := struct { - From string `json:"from_url"` - Owner *model.User `json:"owner"` - Repo *model.Repo `json:"repository"` - Commit *model.Commit `json:"commit"` - }{context.Host, context.User, context.Repo, context.Commit} - - // data json encoded - payload, err := json.Marshal(data) - if err != nil { - return err - } - - for _, url := range w.URL { - go sendJson(url, payload) - } - return nil -} - -// helper fuction to sent HTTP Post requests -// with JSON data as the payload. -func sendJson(url string, payload []byte) { - buf := bytes.NewBuffer(payload) - resp, err := http.Post(url, "application/json", buf) - if err != nil { - return - } - resp.Body.Close() -} diff --git a/plugin/notify/zapier.go b/plugin/notify/zapier.go deleted file mode 100644 index a3131f139..000000000 --- a/plugin/notify/zapier.go +++ /dev/null @@ -1 +0,0 @@ -package notify diff --git a/plugin/pipeline/pipeline.go b/plugin/pipeline/pipeline.go deleted file mode 100644 index fb2071c7f..000000000 --- a/plugin/pipeline/pipeline.go +++ /dev/null @@ -1 +0,0 @@ -package pipeline diff --git a/plugin/publish/azure.go b/plugin/publish/azure.go deleted file mode 100644 index c504fefe5..000000000 --- a/plugin/publish/azure.go +++ /dev/null @@ -1,48 +0,0 @@ -package publish - -import ( - "fmt" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Azure struct { - StorageAccount string `yaml:"storage_account,omitempty"` - StorageAccessKey string `yaml:"storage_access_key,omitempty"` - StorageContainer string `yaml:"storage_container,omitempty"` - - // Uploads file indicated by Source to file - // indicated by Target. Only individual file names - // are supported by Source and Target - Source string `yaml:"source,omitempty"` - Target string `yaml:"target"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (a *Azure) Write(f *buildfile.Buildfile) { - if len(a.StorageAccount) == 0 || len(a.StorageAccessKey) == 0 || len(a.StorageContainer) == 0 || len(a.Source) == 0 { - return - } - - f.WriteCmdSilent("echo 'publishing to Azure Storage ...'") - - // install Azure xplat CLI - f.WriteCmdSilent("[ -f /usr/bin/sudo ] || npm install -g azure-cli 1> /dev/null 2> /dev/null") - f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo npm install -g azure-cli 1> /dev/null 2> /dev/null") - - f.WriteEnv("AZURE_STORAGE_ACCOUNT", a.StorageAccount) - f.WriteEnv("AZURE_STORAGE_ACCESS_KEY", a.StorageAccessKey) - - // if target isn't specified, set to source - if len(a.Target) == 0 { - a.Target = a.Source - } - - f.WriteCmd(fmt.Sprintf(`azure storage blob upload --container %s %s %s`, a.StorageContainer, a.Source, a.Target)) -} - -func (a *Azure) GetCondition() *condition.Condition { - return a.Condition -} diff --git a/plugin/publish/bintray/bintray.go b/plugin/publish/bintray/bintray.go deleted file mode 100644 index 9a0383e2a..000000000 --- a/plugin/publish/bintray/bintray.go +++ /dev/null @@ -1,50 +0,0 @@ -package bintray - -import ( - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Bintray struct { - Username string `yaml:"username"` - ApiKey string `yaml:"api_key"` - Packages []Package `yaml:"packages"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (b *Bintray) Write(f *buildfile.Buildfile) { - var cmd string - - // Validate Username, ApiKey, Packages - if len(b.Username) == 0 || len(b.ApiKey) == 0 || len(b.Packages) == 0 { - f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`) - - if len(b.Username) == 0 { - f.WriteCmdSilent(`echo -e "\tusername not defined in yaml config"`) - } - - if len(b.ApiKey) == 0 { - f.WriteCmdSilent(`echo -e "\tapi_key not defined in yaml config"`) - } - - if len(b.Packages) == 0 { - f.WriteCmdSilent(`echo -e "\tpackages not defined in yaml config"`) - } - - f.WriteCmdSilent("exit 1") - - return - } - - for _, pkg := range b.Packages { - pkg.Write(b.Username, b.ApiKey, f) - } - - f.WriteCmd(cmd) - -} - -func (b *Bintray) GetCondition() *condition.Condition { - return b.Condition -} diff --git a/plugin/publish/bintray/package.go b/plugin/publish/bintray/package.go deleted file mode 100644 index 31f35e1d6..000000000 --- a/plugin/publish/bintray/package.go +++ /dev/null @@ -1,116 +0,0 @@ -package bintray - -import ( - "fmt" - "strings" - - "github.com/drone/drone/shared/build/buildfile" -) - -const bintray_endpoint = "https://api.bintray.com/content/%s/%s/%s/%s/%s" - -type Package struct { - File string `yaml:"file"` - Type string `yaml:"type"` - Owner string `yaml:"owner"` - Repository string `yaml:"repository"` - Package string `yaml:"package"` - Version string `yaml:"version"` - Target string `yaml:"target"` - Distr string `yaml:"distr,omitempty"` - Component string `yaml:"component,omitempty"` - Arch []string `yaml:"arch,omitempty"` - Publish bool `yaml:"publish,omitempty"` - Override bool `yaml:"override,omitempty"` -} - -func (p *Package) Write(username, api_key string, f *buildfile.Buildfile) { - if len(p.File) == 0 || len(p.Owner) == 0 || len(p.Repository) == 0 || len(p.Package) == 0 || len(p.Version) == 0 || len(p.Target) == 0 { - f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`) - - if len(p.Package) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage not defined in yaml config"`)) - return - } - - if len(p.File) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: file not defined in yaml config"`, p.Package)) - } - - if len(p.Owner) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: owner not defined in yaml config"`, p.Package)) - } - - if len(p.Repository) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: repository not defined in yaml config"`, p.Package)) - } - - if len(p.Version) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: version not defined in yaml config"`, p.Package)) - } - - if len(p.Target) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: target not defined in yaml config"`, p.Package)) - } - - f.WriteCmdSilent("exit 1") - - return - } - - switch p.Type { - case "deb": - p.debUpload(username, api_key, f) - case "rpm": - p.upload(username, api_key, f) - case "maven": - p.upload(username, api_key, f) - default: - p.upload(username, api_key, f) - } -} - -func (p *Package) debUpload(username, api_key string, f *buildfile.Buildfile) { - if len(p.Distr) == 0 || len(p.Component) == 0 || len(p.Arch) == 0 { - f.WriteCmdSilent(`echo -e "Bintray Plugin: Missing argument(s)\n\n"`) - - if len(p.Distr) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: distr not defined in yaml config"`, p.Package)) - } - - if len(p.Component) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: component not defined in yaml config"`, p.Package)) - } - - if len(p.Arch) == 0 { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\tpackage %s: arch not defined in yaml config"`, p.Package)) - } - - f.WriteCmdSilent("exit 1") - - return - } - - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\nUpload %s to %s/%s/%s"`, p.File, p.Owner, p.Repository, p.Package)) - f.WriteCmdSilent(fmt.Sprintf("curl -s -T %s -u%s:%s %s\\;deb_distribution\\=%s\\;deb_component\\=%s\\;deb_architecture=\\%s\\;publish\\=%d\\;override\\=%d", - p.File, username, api_key, p.getEndpoint(), p.Distr, p.Component, strings.Join(p.Arch, ","), boolToInt(p.Publish), boolToInt(p.Override))) - -} - -func (p *Package) upload(username, api_key string, f *buildfile.Buildfile) { - f.WriteCmdSilent(fmt.Sprintf(`echo -e "\nUpload %s to %s/%s/%s"`, p.File, p.Owner, p.Repository, p.Package)) - f.WriteCmdSilent(fmt.Sprintf("curl -s -T %s -u%s:%s %s\\;publish\\=%d\\;override\\=%d", - p.File, username, api_key, p.getEndpoint(), boolToInt(p.Publish), boolToInt(p.Override))) -} - -func (p *Package) getEndpoint() string { - return fmt.Sprintf(bintray_endpoint, p.Owner, p.Repository, p.Package, p.Version, p.Target) -} - -func boolToInt(val bool) int { - if val { - return 1 - } else { - return 0 - } -} diff --git a/plugin/publish/docker.go b/plugin/publish/docker.go deleted file mode 100644 index 3ab7bdb88..000000000 --- a/plugin/publish/docker.go +++ /dev/null @@ -1,130 +0,0 @@ -package publish - -import ( - "fmt" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Docker struct { - // The path to the dockerfile to create the image from. If the path is empty or no - // path is specified then the docker file will be built from the base directory. - Dockerfile string `yaml:"docker_file"` - - // Connection information for the docker server that will build the image - // Same format than DOCKER_HOST envvar, i.e.: tcp://172.16.1.1:2375 - DockerHost string `yaml:"docker_host"` - // The Docker client version to download. Will default to latest if not set - DockerVersion string `yaml:"docker_version"` - - // Optional Arguments to allow finer-grained control of registry - // endpoints - RegistryLoginUrl string `yaml:"registry_login_url"` - ImageName string `yaml:"image_name"` - RegistryLogin bool `yaml:"registry_login"` - - // Authentication credentials for index.docker.io - Username string `yaml:"username"` - Password string `yaml:"password"` - Email string `yaml:"email"` - - // Keep the build on the Docker host after pushing? - KeepBuild bool `yaml:"keep_build"` - Tag string `yaml:"tag"` - Tags []string `yaml:"tags"` - ForceTags bool `yaml:"force_tags"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -// Write adds commands to the buildfile to do the following: -// 1. Install the docker client in the Drone container if required. -// 2. Build a docker image based on the dockerfile defined in the config. -// 3. Push that docker image to index.docker.io. -// 4. Delete the docker image on the server it was build on so we conserve disk space. -func (d *Docker) Write(f *buildfile.Buildfile) { - if len(d.DockerHost) == 0 || len(d.ImageName) == 0 { - f.WriteCmdSilent(`echo -e "Docker Plugin: Missing argument(s)\n\n"`) - if len(d.DockerHost) == 0 { - f.WriteCmdSilent(`echo -e "\tdocker_host not defined in yaml"`) - } - if len(d.ImageName) == 0 { - f.WriteCmdSilent(`echo -e "\timage_name not defined in yaml"`) - } - return - } - - // If docker version is unspecified, download and install the latest client - if len(d.DockerVersion) == 0 { - d.DockerVersion = "latest" - } - - if len(d.DockerVersion) > 0 { - // Download docker binary and install it as /usr/local/bin/docker if it does not exist - f.WriteCmd("type -p docker || wget -qO- https://get.docker.io/builds/Linux/x86_64/docker-" + - d.DockerVersion + ".tgz |sudo tar zxf - -C /") - } - - dockerPath := "." - if len(d.Dockerfile) != 0 { - dockerPath = fmt.Sprintf("- < %s", d.Dockerfile) - } - - // Run the command commands to build and deploy the image. - - // Add the single tag if one exists - if len(d.Tag) > 0 { - d.Tags = append(d.Tags, d.Tag) - } - - // If no tags are specified, use the commit hash - if len(d.Tags) == 0 { - d.Tags = append(d.Tags, "$(git rev-parse --short HEAD)") - } - - // There is always at least 1 tag - buildImageTag := d.Tags[0] - - // Export docker host once - f.WriteCmd("export DOCKER_HOST=" + d.DockerHost) - - // Build the image - f.WriteCmd(fmt.Sprintf("docker build --pull -t %s:%s %s", d.ImageName, buildImageTag, dockerPath)) - - // Login? - if d.RegistryLogin == true { - // If email is unspecified, pass in -e ' ' to avoid having - // registry URL interpreted as email, which will fail cryptically. - emailOpt := "' '" - if d.Email != "" { - emailOpt = d.Email - } - f.WriteCmdSilent(fmt.Sprintf("docker login -u %s -p %s -e %s %s", - d.Username, d.Password, emailOpt, d.RegistryLoginUrl)) - } - - // Tag and push all tags - for _, tag := range d.Tags { - if tag != buildImageTag { - var options string - if d.ForceTags { - options = "-f" - } - f.WriteCmd(fmt.Sprintf("docker tag %s %s:%s %s:%s", options, d.ImageName, buildImageTag, d.ImageName, tag)) - } - - f.WriteCmd(fmt.Sprintf("docker push %s:%s", d.ImageName, tag)) - } - - // Remove tags after pushing unless keepBuild is set - if !d.KeepBuild { - for _, tag := range d.Tags { - f.WriteCmd(fmt.Sprintf("docker rmi %s:%s", d.ImageName, tag)) - } - } -} - -func (d *Docker) GetCondition() *condition.Condition { - return d.Condition -} diff --git a/plugin/publish/docker_test.go b/plugin/publish/docker_test.go deleted file mode 100644 index 915d28f83..000000000 --- a/plugin/publish/docker_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package publish - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/repo" - "gopkg.in/yaml.v1" -) - -type PublishToDrone struct { - Publish *Publish `yaml:"publish,omitempty"` -} - -func setUpWithDrone(input string) (string, error) { - var buildStruct PublishToDrone - err := yaml.Unmarshal([]byte(input), &buildStruct) - if err != nil { - return "", err - } - bf := buildfile.New() - buildStruct.Publish.Write(bf, &repo.Repo{Name: "name"}) - return bf.String(), err -} - -// DockerHost and version test (no auth) -var dockerHostYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.3.0 - image_name: registry/image -` - -func TestDockerHost(t *testing.T) { - response, err := setUpWithDrone(dockerHostYaml) - t.Log(dockerHostYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - expected := "export DOCKER_HOST=tcp://server:1000" - if !strings.Contains(response, expected) { - t.Fatalf("Response: " + response + " doesn't export correct " + - "DOCKER_HOST envvar: expected " + expected + "\n\n") - } - expected = "https://get.docker.io/builds/Linux/x86_64/docker-1.3.0.tgz" - if !strings.Contains(response, expected) { - t.Fatalf("Response: " + response + " doesn't download from:" + expected + "\n\n") - } -} - -var dockerHostNoVersionYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - image_name: registry/image -` - -func TestDockerHostNoVersion(t *testing.T) { - response, err := setUpWithDrone(dockerHostNoVersionYaml) - t.Log(dockerHostNoVersionYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - expected := "export DOCKER_HOST=tcp://server:1000" - if !strings.Contains(response, expected) { - t.Fatalf("Response: " + response + " doesn't export correct " + - "DOCKER_HOST envvar: expected " + expected + "\n\n") - } - download := "https://get.docker.io/builds/Linux/x86_64/docker-latest.tgz" - if !strings.Contains(response, download) { - t.Fatalf("Response: " + response + " doesn't download from:" + download + "\n\n") - } -} - -// Private Registry Test (no auth) -var privateRegistryNoAuthYaml = ` -publish: - docker: - dockerfile: file_path - docker_host: tcp://server:1000 - docker_version: 1.0 - registry_login: false - image_name: registry/image -` - -func TestPrivateRegistryNoAuth(t *testing.T) { - response, err := setUpWithDrone(privateRegistryNoAuthYaml) - t.Log(privateRegistryNoAuthYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - if !strings.Contains(response, "docker build --pull -t registry/image:$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " doesn't contain registry in image-names: expected registry/image\n\n") - } -} - -// Private Registry Test (with auth) -var privateRegistryAuthYaml = ` -publish: - docker: - dockerfile: file_path - docker_host: tcp://server:1000 - docker_version: 1.0 - registry_login_url: https://registry:8000/v1/ - registry_login: true - username: username - password: password - email: email@example.com - image_name: registry/image -` - -func TestPrivateRegistryAuth(t *testing.T) { - response, err := setUpWithDrone(privateRegistryAuthYaml) - t.Log(privateRegistryAuthYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - if !strings.Contains(response, "docker login -u username -p password -e email@example.com https://registry:8000/v1/") { - t.Log("\n\n\n\ndocker login -u username -p xxxxxxxx -e email@example.com https://registry:8000/v1/\n\n\n\n") - t.Fatalf("Response: " + response + " doesn't contain private registry login\n\n") - } - if !strings.Contains(response, "docker build --pull -t registry/image:$(git rev-parse --short HEAD) .") { - t.Log("docker build --pull -t registry/image:$(git rev-parse --short HEAD) .") - t.Fatalf("Response: " + response + " doesn't contain registry in image-names\n\n") - } -} - -// Keep builds Test -var keepBuildsYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.0 - keep_build: true - username: username - password: password - email: email@example.com - image_name: image -` - -func TestKeepBuilds(t *testing.T) { - response, err := setUpWithDrone(keepBuildsYaml) - t.Log(keepBuildsYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - if strings.Contains(response, "docker rmi") { - t.Fatalf("Response: " + response + " incorrectly instructs the docker server to remove the builds when it shouldn't\n\n") - } -} - -// Custom Tag test -var customTagYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.0 - tag: release-0.1 - username: username - password: password - email: email@example.com - image_name: username/image -` - -func TestSingleTag(t *testing.T) { - response, err := setUpWithDrone(customTagYaml) - t.Log(customTagYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n", err.Error()) - } - if strings.Contains(response, "$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " is tagging images from git-refs when it should use a custom tag\n\n") - } - if !strings.Contains(response, "docker build --pull -t username/image:release-0.1") { - t.Fatalf("Response: " + response + " isn't tagging images using our custom tag\n\n") - } - if !strings.Contains(response, "docker push username/image:release-0.1") { - t.Fatalf("Response: " + response + " doesn't push the custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-0.1") { - t.Fatalf("Response: " + response + " doesn't remove custom tagged image\n\n") - } -} - -var missingFieldsYaml = ` -publish: - docker: - dockerfile: file -` - -var multipleTagsYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.0 - tags: [release-0.2, release-latest] - username: username - password: password - email: email@example.com - image_name: username/image -` - -func TestTagsNoSingle(t *testing.T) { - response, err := setUpWithDrone(multipleTagsYaml) - t.Log(multipleTagsYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n", err.Error()) - } - if strings.Contains(response, "$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " is tagging images from git-refs when it should using custom tag\n\n") - } - if !strings.Contains(response, "docker build --pull -t username/image:release-0.2") { - t.Fatalf("Response: " + response + " isn't tagging images using our first custom tag\n\n") - } - if !strings.Contains(response, "docker tag username/image:release-0.2 username/image:release-latest") { - t.Fatalf("Response: " + response + " isn't tagging images using our second custom tag\n\n") - } - if !strings.Contains(response, "docker push username/image:release-0.2") { - t.Fatalf("Response: " + response + " doesn't push the custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-0.2") { - t.Fatalf("Response: " + response + " doesn't remove custom tagged image\n\n") - } - if !strings.Contains(response, "docker push username/image:release-latest") { - t.Fatalf("Response: " + response + " doesn't push the second custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-latest") { - t.Fatalf("Response: " + response + " doesn't remove second custom tagged image\n\n") - } -} - -var bothTagsYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.0 - tag: release-0.2 - tags: [release-0.3, release-latest] - username: username - password: password - email: email@example.com - image_name: username/image -` - -func TestTagsWithSingle(t *testing.T) { - response, err := setUpWithDrone(bothTagsYaml) - t.Log(bothTagsYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n", err.Error()) - } - if strings.Contains(response, "$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " is tagging images from git-refs when it should using custom tag\n\n") - } - if !strings.Contains(response, "docker build --pull -t username/image:release-0.3") { - t.Fatalf("Response: " + response + " isn't tagging images using our first custom tag\n\n") - } - if !strings.Contains(response, "docker tag username/image:release-0.3 username/image:release-0.2") { - t.Fatalf("Response: " + response + " isn't tagging images using our second custom tag\n\n") - } - if !strings.Contains(response, "docker tag username/image:release-0.3 username/image:release-latest") { - t.Fatalf("Response: " + response + " isn't tagging images using our third custom tag\n\n") - } - if !strings.Contains(response, "docker push username/image:release-0.2") { - t.Fatalf("Response: " + response + " doesn't push the custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-0.2") { - t.Fatalf("Response: " + response + " doesn't remove custom tagged image\n\n") - } - if !strings.Contains(response, "docker push username/image:release-0.3") { - t.Fatalf("Response: " + response + " doesn't push the custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-0.3") { - t.Fatalf("Response: " + response + " doesn't remove custom tagged image\n\n") - } - if !strings.Contains(response, "docker push username/image:release-latest") { - t.Fatalf("Response: " + response + " doesn't push the second custom tagged image\n\n") - } - if !strings.Contains(response, "docker rmi username/image:release-latest") { - t.Fatalf("Response: " + response + " doesn't remove second custom tagged image\n\n") - } -} - -func TestMissingFields(t *testing.T) { - response, err := setUpWithDrone(missingFieldsYaml) - t.Log(missingFieldsYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - if !strings.Contains(response, "Missing argument(s)") { - t.Fatalf("Response: " + response + " didn't contain missing arguments warning\n\n") - } -} - -var validYaml = ` -publish: - docker: - docker_file: file_path - docker_host: tcp://server:1000 - docker_version: 1.0 - username: user - password: password - email: email - image_name: user/image - registry_login: true -` - -func TestValidYaml(t *testing.T) { - response, err := setUpWithDrone(validYaml) - t.Log(validYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - - if !strings.Contains(response, "docker build --pull -t user/image:$(git rev-parse --short HEAD) - <") { - t.Fatalf("Response: " + response + "doesn't contain build command for commit hash\n\n") - } - if !strings.Contains(response, "docker login -u user -p password -e email") { - t.Fatalf("Response: " + response + " doesn't contain login command\n\n") - } - if !strings.Contains(response, "docker push user/image:$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " doesn't contain push command\n\n") - } - if !strings.Contains(response, "docker rmi user/image:"+ - "$(git rev-parse --short HEAD)") { - t.Fatalf("Response: " + response + " doesn't contain remove image command\n\n") - } -} - -var withoutDockerFileYaml = ` -publish: - docker: - docker_host: tcp://server:1000 - docker_version: 1.0 - image_name: user/image - username: user - password: password - email: email -` - -func TestWithoutDockerFile(t *testing.T) { - response, err := setUpWithDrone(withoutDockerFileYaml) - t.Log(withoutDockerFileYaml) - if err != nil { - t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) - } - - if !strings.Contains(response, "docker build --pull -t user/image:$(git rev-parse --short HEAD) .") { - t.Fatalf("Response: " + response + " doesn't contain build command\n\n") - } -} diff --git a/plugin/publish/dropbox.go b/plugin/publish/dropbox.go deleted file mode 100644 index 77dba0619..000000000 --- a/plugin/publish/dropbox.go +++ /dev/null @@ -1,37 +0,0 @@ -package publish - -import ( - "fmt" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" - "strings" -) - -type Dropbox struct { - AccessToken string `yaml:"access_token,omitempty"` - - Source string `yaml:"source,omitempty"` - Target string `yaml:"target,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (d *Dropbox) Write(f *buildfile.Buildfile) { - - if len(d.AccessToken) == 0 || len(d.Source) == 0 || len(d.Target) == 0 { - return - } - if strings.HasPrefix(d.Target, "/") { - d.Target = d.Target[1:] - } - - f.WriteCmdSilent("echo 'publishing to Dropbox ...'") - - cmd := "curl --upload-file %s -H \"Authorization: Bearer %s\" \"https://api-content.dropbox.com/1/files_put/auto/%s?overwrite=true\"" - f.WriteCmd(fmt.Sprintf(cmd, d.Source, d.AccessToken, d.Target)) - -} - -func (d *Dropbox) GetCondition() *condition.Condition { - return d.Condition -} diff --git a/plugin/publish/gems.go b/plugin/publish/gems.go deleted file mode 100644 index 30b1a5b2a..000000000 --- a/plugin/publish/gems.go +++ /dev/null @@ -1 +0,0 @@ -package publish diff --git a/plugin/publish/github.go b/plugin/publish/github.go deleted file mode 100644 index 9e98ea177..000000000 --- a/plugin/publish/github.go +++ /dev/null @@ -1,131 +0,0 @@ -package publish - -import ( - "fmt" - "strings" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -import () - -type Github struct { - // Script is an optional list of commands to run to prepare for a release. - Script []string `yaml:"script"` - - // Artifacts is a list of files or directories to release. - Artifacts []string `yaml:"artifacts"` - - // Tag is the name of the tag to create for this release. - Tag string `yaml:"tag"` - - // Name is the name of the release. Defaults to tag. - Name string `yaml:"name"` - - // Description describes the release. Defaults to empty string. - Description string `yaml:"description"` - - // Draft is an identifier on a Github release. - Draft bool `yaml:"draft"` - - // Prerelease is an identifier on a Github release. - Prerelease bool `yaml:"prerelease"` - - // Token is the Github token to use when publishing the release. - Token string `yaml:"token"` - - // User is the Github user for the repository you'd like to publish to. - User string `yaml:"user"` - - // Repo is the name of the Github repostiory you like to publish to. - Repo string `yaml:"repo"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -// Write adds commands to run that will publish a Github release. -func (g *Github) Write(f *buildfile.Buildfile) { - if len(g.Artifacts) == 0 || g.Tag == "" || g.Token == "" || g.User == "" || g.Repo == "" { - f.WriteCmdSilent(`echo -e "Github Plugin: Missing argument(s)"\n\n`) - if len(g.Artifacts) == 0 { - f.WriteCmdSilent(`echo -e "\tartifacts not defined in yaml config" && false`) - } - if g.Tag == "" { - f.WriteCmdSilent(`echo -e "\ttag not defined in yaml config" && false`) - } - if g.Token == "" { - f.WriteCmdSilent(`echo -e "\ttoken not defined in yaml config" && false`) - } - if g.User == "" { - f.WriteCmdSilent(`echo -e "\tuser not defined in yaml config" && false`) - } - if g.Repo == "" { - f.WriteCmdSilent(`echo -e "\trepo not defined in yaml config" && false`) - } - return - } - - // Default name is tag - if g.Name == "" { - g.Name = g.Tag - } - - for _, cmd := range g.Script { - f.WriteCmd(cmd) - } - - f.WriteEnv("GITHUB_TOKEN", g.Token) - - // Install github-release - f.WriteCmd("curl -L -o /tmp/github-release.tar.bz2 https://github.com/aktau/github-release/releases/download/v0.5.2/linux-amd64-github-release.tar.bz2") - f.WriteCmd("tar jxf /tmp/github-release.tar.bz2 -C /tmp/ && sudo mv /tmp/bin/linux/amd64/github-release /usr/local/bin/github-release") - - // Create the release. Ignore 422 errors, which indicate the tag has already been created. - // Doing otherwise would create the expectation that every commit should be tagged and released, - // which is not the norm. - draftStr := "" - if g.Draft { - draftStr = "--draft" - } - prereleaseStr := "" - if g.Prerelease { - prereleaseStr = "--pre-release" - } - f.WriteCmd(fmt.Sprintf(` -result=$(github-release release -u %s -r %s -t %s -n "%s" -d "%s" %s %s || true) -if [[ $result == *422* ]]; then - echo -e "Release already exists for this tag."; - exit 0 -elif [[ $result == "" ]]; then - echo -e "Release created."; -else - echo -e "Error creating release: $result" - exit 1 -fi -`, g.User, g.Repo, g.Tag, g.Name, g.Description, draftStr, prereleaseStr)) - - // Upload files - artifactStr := strings.Join(g.Artifacts, " ") - f.WriteCmd(fmt.Sprintf(` -for f in %s; do - # treat directories and files differently - if [ -d $f ]; then - for ff in $(ls $f); do - echo -e "uploading $ff" - github-release upload -u %s -r %s -t %s -n $ff -f $f/$ff - done - elif [ -f $f ]; then - echo -e "uploading $f" - github-release upload -u %s -r %s -t %s -n $f -f $f - else - echo -e "$f is not a file or directory" - exit 1 - fi -done -`, artifactStr, g.User, g.Repo, g.Tag, g.User, g.Repo, g.Tag)) -} - -func (g *Github) GetCondition() *condition.Condition { - return g.Condition -} diff --git a/plugin/publish/github_test.go b/plugin/publish/github_test.go deleted file mode 100644 index 7fcdfaaa9..000000000 --- a/plugin/publish/github_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package publish - -import ( - "fmt" - "strings" - "testing" - - "gopkg.in/yaml.v1" -) - -var validcfg = map[string]interface{}{ - "artifacts": []string{"release/"}, - "tag": "v1.0", - "token": "github-token", - "user": "drone", - "repo": "drone", -} - -func buildfileForConfig(config map[string]interface{}) (string, error) { - yml, err := yaml.Marshal(map[string]interface{}{ - "publish": config, - }) - if err != nil { - return "", err - } - return setUpWithDrone(string(yml)) -} - -func TestRequiredConfig(t *testing.T) { - for _, required := range []string{"artifacts", "tag", "token", "user", "repo"} { - invalidcfg := make(map[string]interface{}) - for k, v := range validcfg { - if k != required { - invalidcfg[k] = v - } - } - buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": invalidcfg}) - if err != nil { - t.Fatal(err) - } - contains := fmt.Sprintf("%s not defined", required) - if !strings.Contains(buildfilestr, contains) { - t.Fatalf("Expected buildfile to contain error '%s': %s", contains, buildfilestr) - } - } -} - -func TestScript(t *testing.T) { - cmd := "echo run me!" - scriptcfg := make(map[string]interface{}) - scriptcfg["script"] = []string{cmd} - for k, v := range validcfg { - scriptcfg[k] = v - } - buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": scriptcfg}) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(buildfilestr, cmd) { - t.Fatalf("Expected buildfile to contain command '%s': %s", cmd, buildfilestr) - } -} - -func TestDefaultBehavior(t *testing.T) { - buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": validcfg}) - if err != nil { - t.Fatal(err) - } - defaultname := fmt.Sprintf(`-n "%s"`, validcfg["tag"].(string)) - if !strings.Contains(buildfilestr, defaultname) { - t.Fatalf("Expected buildfile to contain name default to tag '%s': %s", defaultname, buildfilestr) - } - if strings.Contains(buildfilestr, "--draft") { - t.Fatalf("Should not create a draft release by default: %s", buildfilestr) - } - if strings.Contains(buildfilestr, "--pre-release") { - t.Fatalf("Should not create a pre-release release by default: %s", buildfilestr) - } - if !strings.Contains(buildfilestr, "github-release release") { - t.Fatalf("Should create a release: %s", buildfilestr) - } - if !strings.Contains(buildfilestr, "github-release upload") { - t.Fatalf("Should upload a file: %s", buildfilestr) - } -} - -func TestOpts(t *testing.T) { - optscfg := make(map[string]interface{}) - optscfg["draft"] = true - optscfg["prerelease"] = true - for k, v := range validcfg { - optscfg[k] = v - } - buildfilestr, err := buildfileForConfig(map[string]interface{}{"github": optscfg}) - if err != nil { - t.Fatal(err) - } - for _, flag := range []string{"--draft", "--pre-release"} { - if !strings.Contains(buildfilestr, flag) { - t.Fatalf("Expected buildfile to contain flag '%s': %s", flag, buildfilestr) - } - } -} diff --git a/plugin/publish/maven.go b/plugin/publish/maven.go deleted file mode 100644 index 30b1a5b2a..000000000 --- a/plugin/publish/maven.go +++ /dev/null @@ -1 +0,0 @@ -package publish diff --git a/plugin/publish/npm/npm.go b/plugin/publish/npm/npm.go deleted file mode 100644 index f300ec7cf..000000000 --- a/plugin/publish/npm/npm.go +++ /dev/null @@ -1,116 +0,0 @@ -package npm - -import ( - "fmt" - - "github.com/drone/config" - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -// command to create the .npmrc file that stores -// the login credentials, as opposed to npm login -// which requires stdin. -const CmdLogin = ` -cat < ~/.npmrc -_auth = $(echo "%s:%s" | tr -d "\r\n" | base64) -email = %s -EOF -` - -// command to publish npm package if not published -const CmdPublish = ` -_NPM_PACKAGE_NAME=$(cd %s && npm list | head -n 1 | cut -d ' ' -f1) -_NPM_PACKAGE_TAG="%s" -if [ -z "$(npm info ${_NPM_PACKAGE_NAME} 2> /dev/null)" ] -then - npm publish %s - [ -n ${_NPM_PACKAGE_TAG} ] && npm tag ${_NPM_PACKAGE_NAME} ${_NPM_PACKAGE_TAG} -else - echo "skipping publish, package ${_NPM_PACKAGE_NAME} already published" -fi -unset _NPM_PACKAGE_NAME -unset _NPM_PACKAGE_TAG -` - -const ( - CmdAlwaysAuth = "npm set always-auth true" - CmdSetRegistry = "npm config set registry %s" -) - -var ( - DefaultUser = config.String("npm-user", "") - DefaultPass = config.String("npm-pass", "") - DefaultEmail = config.String("npm-email", "") -) - -type NPM struct { - // The Email address used by NPM to connect - // and publish to a repository - Email string `yaml:"email,omitempty"` - - // The Username used by NPM to connect - // and publish to a repository - Username string `yaml:"username,omitempty"` - - // The Password used by NPM to connect - // and publish to a repository - Password string `yaml:"password,omitempty"` - - // The registry URL of custom npm repository - Registry string `yaml:"registry,omitempty"` - - // A folder containing the package.json file - Folder string `yaml:"folder,omitempty"` - - // Registers the published package with the given tag - Tag string `yaml:"tag,omitempty"` - - // Force npm to always require authentication when accessing the registry. - AlwaysAuth bool `yaml:"always_auth"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (n *NPM) Write(f *buildfile.Buildfile) { - // If the yaml doesn't provide a username or password - // we should attempt to use the global defaults. - if len(n.Email) == 0 || - len(n.Username) == 0 || - len(n.Password) == 0 { - n.Username = *DefaultUser - n.Password = *DefaultPass - n.Email = *DefaultEmail - } - - // If the yaml doesn't provide a username or password, - // and there was not global configuration defined, EXIT. - if len(n.Email) == 0 || - len(n.Username) == 0 || - len(n.Password) == 0 { - return - } - - // Setup the npm credentials - f.WriteCmdSilent(fmt.Sprintf(CmdLogin, n.Username, n.Password, n.Email)) - - // Setup custom npm registry - if len(n.Registry) != 0 { - f.WriteCmd(fmt.Sprintf(CmdSetRegistry, n.Registry)) - } - - // Set npm to always authenticate - if n.AlwaysAuth { - f.WriteCmd(CmdAlwaysAuth) - } - - if len(n.Folder) == 0 { - n.Folder = "." - } - - f.WriteString(fmt.Sprintf(CmdPublish, n.Folder, n.Tag, n.Folder)) -} - -func (n *NPM) GetCondition() *condition.Condition { - return n.Condition -} diff --git a/plugin/publish/npm/npm_test.go b/plugin/publish/npm/npm_test.go deleted file mode 100644 index 0ea8c4fec..000000000 --- a/plugin/publish/npm/npm_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package npm - -import ( - "strings" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/franela/goblin" -) - -func Test_NPM(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("NPM Publish", func() { - - g.BeforeEach(func() { - var user, pass, email = "", "", "" - DefaultEmail = &user - DefaultUser = &pass - DefaultPass = &email - }) - - g.It("Should run publish", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - Folder: "/path/to/repo", - } - - n.Write(b) - out := b.String() - g.Assert(strings.Contains(out, "npm publish /path/to/repo\n")).Equal(true) - g.Assert(strings.Contains(out, "\nnpm set")).Equal(false) - g.Assert(strings.Contains(out, "\nnpm config set")).Equal(false) - }) - - g.It("Should use current directory if folder is empty", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - } - - n.Write(b) - g.Assert(strings.Contains(b.String(), "npm publish .\n")).Equal(true) - }) - - g.It("Should set tag", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - Folder: "/path/to/repo", - Tag: "1.0.0", - } - - n.Write(b) - g.Assert(strings.Contains(b.String(), "\n_NPM_PACKAGE_TAG=\"1.0.0\"\n")).Equal(true) - g.Assert(strings.Contains(b.String(), "npm tag ${_NPM_PACKAGE_NAME} ${_NPM_PACKAGE_TAG}\n")).Equal(true) - }) - - g.It("Should set registry", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - Folder: "/path/to/repo", - Registry: "https://npmjs.com", - } - - n.Write(b) - g.Assert(strings.Contains(b.String(), "\nnpm config set registry https://npmjs.com\n")).Equal(true) - }) - - g.It("Should set always-auth", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - Folder: "/path/to/repo", - AlwaysAuth: true, - } - - n.Write(b) - g.Assert(strings.Contains(b.String(), CmdAlwaysAuth)).Equal(true) - }) - - g.It("Should skip when no username or password", func() { - b := new(buildfile.Buildfile) - n := new(NPM) - - n.Write(b) - g.Assert(b.String()).Equal("") - }) - - g.It("Should use default username or password", func() { - b := new(buildfile.Buildfile) - n := new(NPM) - - expected := `cat < ~/.npmrc -_auth = $(echo "foo:bar" | tr -d "\r\n" | base64) -email = foo@bar.com -EOF` - - var user, pass, email string = "foo", "bar", "foo@bar.com" - DefaultUser = &user - DefaultPass = &pass - DefaultEmail = &email - - n.Write(b) - g.Assert(strings.Contains(b.String(), expected)).Equal(true) - }) - - g.It("Should create npmrc", func() { - b := new(buildfile.Buildfile) - n := NPM{ - Email: "foo@bar.com", - Username: "foo", - Password: "bar", - Folder: "/path/to/repo", - AlwaysAuth: true, - } - - expected := `cat < ~/.npmrc -_auth = $(echo "foo:bar" | tr -d "\r\n" | base64) -email = foo@bar.com -EOF` - - n.Write(b) - g.Assert(strings.Contains(b.String(), expected)).Equal(true) - }) - }) -} diff --git a/plugin/publish/pub.go b/plugin/publish/pub.go deleted file mode 100644 index 30b1a5b2a..000000000 --- a/plugin/publish/pub.go +++ /dev/null @@ -1 +0,0 @@ -package publish diff --git a/plugin/publish/publish.go b/plugin/publish/publish.go deleted file mode 100644 index 936eedaae..000000000 --- a/plugin/publish/publish.go +++ /dev/null @@ -1,85 +0,0 @@ -package publish - -import ( - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/plugin/publish/bintray" - "github.com/drone/drone/plugin/publish/npm" - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/repo" -) - -// Publish stores the configuration details -// for publishing build artifacts when -// a Build has succeeded -type Publish struct { - Azure *Azure `yaml:"azure,omitempty"` - S3 *S3 `yaml:"s3,omitempty"` - Swift *Swift `yaml:"swift,omitempty"` - PyPI *PyPI `yaml:"pypi,omitempty"` - NPM *npm.NPM `yaml:"npm,omitempty"` - Docker *Docker `yaml:"docker,omitempty"` - Github *Github `yaml:"github,omitempty"` - Dropbox *Dropbox `yaml:"dropbox,omitempty"` - Bintray *bintray.Bintray `yaml:"bintray,omitempty"` -} - -func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { - // Azure - if p.Azure != nil && match(p.Azure.GetCondition(), r) { - p.Azure.Write(f) - } - - // S3 - if p.S3 != nil && match(p.S3.GetCondition(), r) { - p.S3.Write(f) - } - - // Swift - if p.Swift != nil && match(p.Swift.GetCondition(), r) { - p.Swift.Write(f) - } - - // PyPI - if p.PyPI != nil && match(p.PyPI.GetCondition(), r) { - p.PyPI.Write(f) - } - - // NPM - if p.NPM != nil && match(p.NPM.GetCondition(), r) { - p.NPM.Write(f) - } - - // Github - if p.Github != nil && match(p.Github.GetCondition(), r) { - p.Github.Write(f) - } - - // Docker - if p.Docker != nil && match(p.Docker.GetCondition(), r) { - p.Docker.Write(f) - } - - // Dropbox - if p.Dropbox != nil && match(p.Dropbox.GetCondition(), r) { - p.Dropbox.Write(f) - } - - // Bintray - if p.Bintray != nil && match(p.Bintray.GetCondition(), r) { - p.Bintray.Write(f) - } -} - -func match(c *condition.Condition, r *repo.Repo) bool { - switch { - case c == nil: - return true - case !c.MatchBranch(r.Branch): - return false - case !c.MatchOwner(r.Name): - return false - case !c.MatchPullRequest(r.PR): - return false - } - return true -} diff --git a/plugin/publish/pypi.go b/plugin/publish/pypi.go deleted file mode 100644 index 60f1e295e..000000000 --- a/plugin/publish/pypi.go +++ /dev/null @@ -1,91 +0,0 @@ -package publish - -import ( - "fmt" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -// set up the .pypirc file -var pypirc = ` -cat < $HOME/.pypirc -[distutils] -index-servers = - %s - -[%s] -username:%s -password:%s -%s -EOF` - -var deployCmd = ` -if [ -n "$_PYPI_SETUP_PY" ] -then - python $_PYPI_SETUP_PY sdist %s upload -r %s - if [ $? -ne 0 ] - then - echo "Deploy to PyPI failed - perhaps due to the version number not being incremented. Continuing..." - fi -else - echo "Failed to find setup.py file" -fi -` - -type PyPI struct { - Username string `yaml:"username,omitempty"` - Password string `yaml:"password,omitempty"` - Formats []string `yaml:"formats,omitempty"` - Repository string `yaml:"repository,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (p *PyPI) Write(f *buildfile.Buildfile) { - var indexServer string - var repository string - - if len(p.Username) == 0 || len(p.Password) == 0 { - // nothing to do if the config is fundamentally flawed - return - } - - // Handle the setting a custom pypi server/repository - if len(p.Repository) == 0 { - indexServer = "pypi" - repository = "" - } else { - indexServer = "custom" - repository = fmt.Sprintf("repository:%s", p.Repository) - } - - f.WriteCmdSilent("echo 'publishing to PyPI...'") - - // find the setup.py file - f.WriteCmdSilent("_PYPI_SETUP_PY=$(find . -name 'setup.py')") - - // build the .pypirc file that pypi expects - f.WriteCmdSilent(fmt.Sprintf(pypirc, indexServer, indexServer, p.Username, p.Password, repository)) - formatStr := p.BuildFormatStr() - - // if we found the setup.py file use it to deploy - f.WriteCmdSilent(fmt.Sprintf(deployCmd, formatStr, indexServer)) -} - -func (p *PyPI) BuildFormatStr() string { - if len(p.Formats) == 0 { - // the format parameter is optional - if it's not here, - // omit the format string completely. - return "" - } - fmtStr := "--formats " - for i := range p.Formats { - fmtStr += p.Formats[i] + "," - } - return fmtStr[:len(fmtStr)-1] -} - -func (p *PyPI) GetCondition() *condition.Condition { - return p.Condition -} diff --git a/plugin/publish/s3.go b/plugin/publish/s3.go deleted file mode 100644 index 634d44e0f..000000000 --- a/plugin/publish/s3.go +++ /dev/null @@ -1,101 +0,0 @@ -package publish - -import ( - "fmt" - "strings" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type S3 struct { - Key string `yaml:"access_key,omitempty"` - Secret string `yaml:"secret_key,omitempty"` - Bucket string `yaml:"bucket,omitempty"` - - // us-east-1 - // us-west-1 - // us-west-2 - // eu-west-1 - // ap-southeast-1 - // ap-southeast-2 - // ap-northeast-1 - // sa-east-1 - Region string `yaml:"region,omitempty"` - - // Indicates the files ACL, which should be one - // of the following: - // private - // public-read - // public-read-write - // authenticated-read - // bucket-owner-read - // bucket-owner-full-control - Access string `yaml:"acl,omitempty"` - - // Copies the files from the specified directory. - // Regexp matching will apply to match multiple - // files - // - // Examples: - // /path/to/file - // /path/to/*.txt - // /path/to/*/*.txt - // /path/to/** - Source string `yaml:"source,omitempty"` - Target string `yaml:"target,omitempty"` - - // Recursive uploads - Recursive bool `yaml:"recursive"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (s *S3) Write(f *buildfile.Buildfile) { - - // skip if AWS key or SECRET are empty. A good example for this would - // be forks building a project. S3 might be configured in the source - // repo, but not in the fork - if len(s.Key) == 0 || len(s.Secret) == 0 { - return - } - - // debugging purposes so we can see if / where something is failing - f.WriteCmdSilent("echo 'publishing to Amazon S3 ...'") - - // install the AWS cli using PIP - f.WriteCmdSilent("[ -f /usr/bin/sudo ] || pip install awscli 1> /dev/null 2> /dev/null") - f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo pip install awscli 1> /dev/null 2> /dev/null") - - f.WriteEnv("AWS_ACCESS_KEY_ID", s.Key) - f.WriteEnv("AWS_SECRET_ACCESS_KEY", s.Secret) - - // make sure a default region is set - if len(s.Region) == 0 { - s.Region = "us-east-1" - } - - // make sure a default access is set - // let's be conservative and assume private - if len(s.Access) == 0 { - s.Access = "private" - } - - // if the target starts with a "/" we need - // to remove it, otherwise we might adding - // a 3rd slash to s3:// - if strings.HasPrefix(s.Target, "/") { - s.Target = s.Target[1:] - } - - switch s.Recursive { - case true: - f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --recursive --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region)) - case false: - f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region)) - } -} - -func (s *S3) GetCondition() *condition.Condition { - return s.Condition -} diff --git a/plugin/publish/swift.go b/plugin/publish/swift.go deleted file mode 100644 index d56b3e739..000000000 --- a/plugin/publish/swift.go +++ /dev/null @@ -1,72 +0,0 @@ -package publish - -import ( - "fmt" - "strings" - - "github.com/drone/drone/plugin/condition" - "github.com/drone/drone/shared/build/buildfile" -) - -type Swift struct { - // Username for authentication - Username string `yaml:"username,omitempty"` - - // Password for authentication - // With Rackspace this is usually an API Key - Password string `yaml:"password,omitempty"` - - // Container to upload files to - Container string `yaml:"container,omitempty"` - - // Base API version URL to authenticate against - // Rackspace: https://identity.api.rackspacecloud.com/v2.0 - AuthURL string `yaml:"auth_url,omitempty"` - - // Region to communicate with, in a generic OpenStack install - // this may be RegionOne - Region string `yaml:"region,omitempty"` - - // Source file or directory to upload, if source is a directory, - // upload the contents of the directory - Source string `yaml:"source,omitempty"` - - // Destination to write the file(s) to. Should contain the full - // object name if source is a file - Target string `yaml:"target,omitempty"` - - Condition *condition.Condition `yaml:"when,omitempty"` -} - -func (s *Swift) Write(f *buildfile.Buildfile) { - var target string - // All options are required, so ensure they are present - if len(s.Username) == 0 || len(s.Password) == 0 || len(s.AuthURL) == 0 || len(s.Region) == 0 || len(s.Source) == 0 || len(s.Container) == 0 { - f.WriteCmdSilent(`echo "Swift: Missing argument(s)"`) - return - } - - // If a target was provided, prefix it with a / - if len(s.Target) > 0 { - target = fmt.Sprintf("/%s", strings.TrimPrefix(s.Target, "/")) - } - - // debugging purposes so we can see if / where something is failing - f.WriteCmdSilent(`echo "Swift: Publishing..."`) - - // install swiftly using PIP - f.WriteCmdSilent("[ -f /usr/bin/sudo ] || pip install swiftly 1> /dev/null 2> /dev/null") - f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo pip install swiftly 1> /dev/null 2> /dev/null") - - // Write out environment variables - f.WriteEnv("SWIFTLY_AUTH_URL", s.AuthURL) - f.WriteEnv("SWIFTLY_AUTH_USER", s.Username) - f.WriteEnv("SWIFTLY_AUTH_KEY", s.Password) - f.WriteEnv("SWIFTLY_REGION", s.Region) - - f.WriteCmd(fmt.Sprintf(`swiftly put -i %s %s%s`, s.Source, s.Container, target)) -} - -func (s *Swift) GetCondition() *condition.Condition { - return s.Condition -} diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go deleted file mode 100644 index e29f0d413..000000000 --- a/plugin/remote/bitbucket/bitbucket.go +++ /dev/null @@ -1,310 +0,0 @@ -package bitbucket - -import ( - "fmt" - "net/http" - "net/url" - "regexp" - "time" - - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/drone/go-bitbucket/bitbucket" - "github.com/drone/go-bitbucket/oauth1" -) - -const ( - DefaultAPI = "https://api.bitbucket.org/1.0" - DefaultURL = "https://bitbucket.org" -) - -// parses an email address from string format -// `John Doe ` -var emailRegexp = regexp.MustCompile("<(.*)>") - -type Bitbucket struct { - URL string - API string - Client string - Secret string - Open bool -} - -func New(url, api, client, secret string, open bool) *Bitbucket { - return &Bitbucket{ - URL: url, - API: api, - Client: client, - Secret: secret, - Open: open, - } -} - -func NewDefault(client, secret string, open bool) *Bitbucket { - return New(DefaultURL, DefaultAPI, client, secret, open) -} - -// Authorize handles Bitbucket API Authorization -func (r *Bitbucket) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { - consumer := oauth1.Consumer{ - RequestTokenURL: "https://bitbucket.org/api/1.0/oauth/request_token/", - AuthorizationURL: "https://bitbucket.org/!api/1.0/oauth/authenticate", - AccessTokenURL: "https://bitbucket.org/api/1.0/oauth/access_token/", - CallbackURL: httputil.GetScheme(req) + "://" + httputil.GetHost(req) + "/api/auth/bitbucket.org", - ConsumerKey: r.Client, - ConsumerSecret: r.Secret, - } - - // get the oauth verifier - verifier := req.FormValue("oauth_verifier") - if len(verifier) == 0 { - // Generate a Request Token - requestToken, err := consumer.RequestToken() - if err != nil { - return nil, err - } - - // add the request token as a signed cookie - httputil.SetCookie(res, req, "bitbucket_token", requestToken.Encode()) - - url, _ := consumer.AuthorizeRedirect(requestToken) - http.Redirect(res, req, url, http.StatusSeeOther) - return nil, nil - } - - // remove bitbucket token data once before redirecting - // back to the application. - defer httputil.DelCookie(res, req, "bitbucket_token") - - // get the tokens from the request - requestTokenStr := httputil.GetCookie(req, "bitbucket_token") - requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr) - if err != nil { - return nil, err - } - - // exchange for an access token - accessToken, err := consumer.AuthorizeToken(requestToken, verifier) - if err != nil { - return nil, err - } - - // create the Bitbucket client - client := bitbucket.New( - r.Client, - r.Secret, - accessToken.Token(), - accessToken.Secret(), - ) - - // get the currently authenticated Bitbucket User - user, err := client.Users.Current() - if err != nil { - return nil, err - } - - // put the user data in the common format - login := model.Login{ - Login: user.User.Username, - Access: accessToken.Token(), - Secret: accessToken.Secret(), - Name: user.User.DisplayName, - } - - email, _ := client.Emails.FindPrimary(user.User.Username) - if email != nil { - login.Email = email.Email - } - - return &login, nil -} - -// GetKind returns the internal identifier of this remote Bitbucket instane. -func (r *Bitbucket) GetKind() string { - return model.RemoteBitbucket -} - -// GetHost returns the hostname of this remote Bitbucket instance. -func (r *Bitbucket) GetHost() string { - uri, _ := url.Parse(r.URL) - return uri.Host -} - -// GetRepos fetches all repositories that the specified -// user has access to in the remote system. -func (r *Bitbucket) GetRepos(user *model.User) ([]*model.Repo, error) { - var repos []*model.Repo - var client = bitbucket.New( - r.Client, - r.Secret, - user.Access, - user.Secret, - ) - var list, err = client.Repos.List() - if err != nil { - return nil, err - } - - var remote = r.GetKind() - var hostname = r.GetHost() - - for _, item := range list { - // for now we only support git repos - if item.Scm != "git" { - continue - } - - // these are the urls required to clone the repository - // TODO use the bitbucketurl.Host and bitbucketurl.Scheme instead of hardcoding - // so that we can support Stash. - var html = fmt.Sprintf("https://bitbucket.org/%s/%s", item.Owner, item.Slug) - var clone = fmt.Sprintf("https://bitbucket.org/%s/%s.git", item.Owner, item.Slug) - var ssh = fmt.Sprintf("git@bitbucket.org:%s/%s.git", item.Owner, item.Slug) - - var repo = model.Repo{ - UserID: user.ID, - Remote: remote, - Host: hostname, - Owner: item.Owner, - Name: item.Slug, - Private: item.Private, - URL: html, - CloneURL: clone, - GitURL: clone, - SSHURL: ssh, - Role: &model.Perm{ - Admin: true, - Write: true, - Read: true, - }, - } - - if repo.Private { - repo.CloneURL = repo.SSHURL - } - - repos = append(repos, &repo) - } - - return repos, err -} - -// GetScript fetches the build script (.drone.yml) from the remote -// repository and returns in string format. -func (r *Bitbucket) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { - var client = bitbucket.New( - r.Client, - r.Secret, - user.Access, - user.Secret, - ) - - // get the yaml from the database - var raw, err = client.Sources.Find(repo.Owner, repo.Name, hook.Sha, ".drone.yml") - if err != nil { - return nil, err - } - - return []byte(raw.Data), nil -} - -// Activate activates a repository by adding a Post-commit hook and -// a Public Deploy key, if applicable. -func (r *Bitbucket) Activate(user *model.User, repo *model.Repo, link string) error { - var client = bitbucket.New( - r.Client, - r.Secret, - user.Access, - user.Secret, - ) - - // parse the hostname from the hook, and use this - // to name the ssh key - var hookurl, err = url.Parse(link) - if err != nil { - return err - } - - // if the repository is private we'll need - // to upload a github key to the repository - if repo.Private { - // name the key - var keyname = "drone@" + hookurl.Host - var _, err = client.RepoKeys.CreateUpdate(repo.Owner, repo.Name, repo.PublicKey, keyname) - if err != nil { - return err - } - } - - // add the hook - _, err = client.Brokers.CreateUpdate(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost) - 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 (r *Bitbucket) Deactivate(user *model.User, repo *model.Repo, link string) error { - var client = bitbucket.New( - r.Client, - r.Secret, - user.Access, - user.Secret, - ) - title, err := GetKeyTitle(link) - if err != nil { - return err - } - if err := client.RepoKeys.DeleteName(repo.Owner, repo.Name, title); err != nil { - return err - } - return client.Brokers.DeleteUrl(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost) -} - -// ParseHook parses the post-commit hook from the Request body -// and returns the required data in a standard format. -func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) { - var payload = req.FormValue("payload") - var hook, err = bitbucket.ParseHook([]byte(payload)) - if err != nil { - return nil, err - } - - // verify the payload has the minimum amount of required data. - if hook.Repo == nil || hook.Commits == nil || len(hook.Commits) == 0 { - return nil, fmt.Errorf("Invalid Bitbucket post-commit Hook. Missing Repo or Commit data.") - } - - var author = hook.Commits[len(hook.Commits)-1].RawAuthor - var matches = emailRegexp.FindStringSubmatch(author) - if len(matches) == 2 { - author = matches[1] - } - - return &model.Hook{ - Owner: hook.Repo.Owner, - Repo: hook.Repo.Slug, - Sha: hook.Commits[len(hook.Commits)-1].Hash, - Branch: hook.Commits[len(hook.Commits)-1].Branch, - Author: author, - Timestamp: time.Now().UTC().String(), - Message: hook.Commits[len(hook.Commits)-1].Message, - }, nil -} - -func (r *Bitbucket) OpenRegistration() bool { - return r.Open -} - -func (r *Bitbucket) GetToken(user *model.User) (*model.Token, error) { - return nil, nil -} - -// GetKeyTitle is a helper function that generates a title for the -// RSA public key based on the username and domain name. -func GetKeyTitle(rawurl string) (string, error) { - var uri, err = url.Parse(rawurl) - if err != nil { - return "", err - } - return fmt.Sprintf("drone@%s", uri.Host), nil -} diff --git a/plugin/remote/bitbucket/register.go b/plugin/remote/bitbucket/register.go deleted file mode 100644 index 108c68d90..000000000 --- a/plugin/remote/bitbucket/register.go +++ /dev/null @@ -1,25 +0,0 @@ -package bitbucket - -import ( - "github.com/drone/config" - "github.com/drone/drone/plugin/remote" -) - -var ( - // Bitbucket cloud configuration details - bitbucketClient = config.String("bitbucket-client", "") - bitbucketSecret = config.String("bitbucket-secret", "") - bitbucketOpen = config.Bool("bitbucket-open", false) -) - -// Registers the Bitbucket plugin using the default -// settings from the config file or environment -// variables. -func Register() { - if len(*bitbucketClient) == 0 || len(*bitbucketSecret) == 0 { - return - } - remote.Register( - NewDefault(*bitbucketClient, *bitbucketSecret, *bitbucketOpen), - ) -} diff --git a/plugin/remote/github/github.go b/plugin/remote/github/github.go deleted file mode 100644 index 4edc71b99..000000000 --- a/plugin/remote/github/github.go +++ /dev/null @@ -1,335 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/drone/drone/plugin/remote/github/oauth" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/drone/go-github/github" -) - -const ( - DefaultAPI = "https://api.github.com/" - DefaultURL = "https://github.com" - DefaultScope = "repo,repo:status,user:email" -) - -type GitHub struct { - URL string - API string - Client string - Secret string - Private bool - SkipVerify bool - Orgs []string - Open bool -} - -func New(url, api, client, secret string, private, skipVerify bool, orgs []string, open bool) *GitHub { - var github = GitHub{ - URL: url, - API: api, - Client: client, - Secret: secret, - Private: private, - SkipVerify: skipVerify, - Orgs: orgs, - Open: open, - } - // the API must have a trailing slash - if !strings.HasSuffix(github.API, "/") { - github.API += "/" - } - // the URL must NOT have a trailing slash - if strings.HasSuffix(github.URL, "/") { - github.URL = github.URL[:len(github.URL)-1] - } - return &github -} - -func NewDefault(client, secret string, orgs []string, open bool) *GitHub { - return New(DefaultURL, DefaultAPI, client, secret, false, false, orgs, open) -} - -// Authorize handles GitHub API Authorization. -func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { - var config = &oauth.Config{ - ClientId: r.Client, - ClientSecret: r.Secret, - Scope: DefaultScope, - AuthURL: fmt.Sprintf("%s/login/oauth/authorize", r.URL), - TokenURL: fmt.Sprintf("%s/login/oauth/access_token", r.URL), - RedirectURL: fmt.Sprintf("%s/api/auth/%s", httputil.GetURL(req), r.GetKind()), - } - - // get the OAuth code - var code = req.FormValue("code") - var state = req.FormValue("state") - if len(code) == 0 { - var random = GetRandom() - httputil.SetCookie(res, req, "github_state", random) - http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) - return nil, nil - } - - cookieState := httputil.GetCookie(req, "github_state") - httputil.DelCookie(res, req, "github_state") - if cookieState != state { - return nil, fmt.Errorf("Error matching state in OAuth2 redirect") - } - - var trans = &oauth.Transport{Config: config} - var token, err = trans.Exchange(code) - if err != nil { - return nil, fmt.Errorf("Error exchanging token. %s", err) - } - - var client = NewClient(r.API, token.AccessToken, r.SkipVerify) - var useremail, errr = GetUserEmail(client) - if errr != nil { - return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr) - } - - if len(r.Orgs) > 0 { - allowedOrg, err := UserBelongsToOrg(client, r.Orgs) - if err != nil { - return nil, fmt.Errorf("Could not check org membership. %s", err) - } - if !allowedOrg { - return nil, fmt.Errorf("User does not belong to correct org. Must belong to %v", r.Orgs) - } - } - - var login = new(model.Login) - login.ID = int64(*useremail.ID) - login.Access = token.AccessToken - login.Login = *useremail.Login - login.Email = *useremail.Email - if useremail.Name != nil { - login.Name = *useremail.Name - } - - return login, nil -} - -// GetKind returns the internal identifier of this remote GitHub instane. -func (r *GitHub) GetKind() string { - if r.IsEnterprise() { - return model.RemoteGithubEnterprise - } else { - return model.RemoteGithub - } -} - -// GetHost returns the hostname of this remote GitHub instance. -func (r *GitHub) GetHost() string { - uri, _ := url.Parse(r.URL) - return uri.Host -} - -// IsEnterprise returns true if the remote system is an -// instance of GitHub Enterprise Edition. -func (r *GitHub) IsEnterprise() bool { - return r.URL != DefaultURL -} - -// GetRepos fetches all repositories that the specified -// user has access to in the remote system. -func (r *GitHub) GetRepos(user *model.User) ([]*model.Repo, error) { - var repos []*model.Repo - var client = NewClient(r.API, user.Access, r.SkipVerify) - var list, err = GetAllRepos(client) - if err != nil { - return nil, err - } - - var remote = r.GetKind() - var hostname = r.GetHost() - - for _, item := range list { - var repo = model.Repo{ - UserID: user.ID, - Remote: remote, - Host: hostname, - Owner: *item.Owner.Login, - Name: *item.Name, - Private: *item.Private, - URL: *item.HTMLURL, - CloneURL: *item.GitURL, - GitURL: *item.GitURL, - SSHURL: *item.SSHURL, - Role: &model.Perm{}, - } - - if r.Private || repo.Private { - repo.CloneURL = *item.SSHURL - repo.Private = true - } - - // if no permissions we should skip the repository - // entirely, since this should never happen - if item.Permissions == nil { - continue - } - - repo.Role.Admin = (*item.Permissions)["admin"] - repo.Role.Write = (*item.Permissions)["push"] - repo.Role.Read = (*item.Permissions)["pull"] - repos = append(repos, &repo) - } - - return repos, err -} - -// GetScript fetches the build script (.drone.yml) from the remote -// repository and returns in string format. -func (r *GitHub) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { - var client = NewClient(r.API, user.Access, r.SkipVerify) - return GetFile(client, repo.Owner, repo.Name, ".drone.yml", hook.Sha) -} - -// Deactivate removes a repository by removing all the post-commit hooks -// which are equal to link and removing the SSH deploy key. -func (r *GitHub) Deactivate(user *model.User, repo *model.Repo, link string) error { - var client = NewClient(r.API, user.Access, r.SkipVerify) - var title, err = GetKeyTitle(link) - if err != nil { - return err - } - - // remove the deploy-key if it is installed remote. - if err := DeleteKey(client, repo.Owner, repo.Name, title, repo.PublicKey); err != nil { - return err - } - - return DeleteHook(client, repo.Owner, repo.Name, link) -} - -// Activate activates a repository by adding a Post-commit hook and -// a Public Deploy key, if applicable. -func (r *GitHub) Activate(user *model.User, repo *model.Repo, link string) error { - var client = NewClient(r.API, user.Access, r.SkipVerify) - var 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 repo.SSHURL == repo.CloneURL { - _, err = CreateUpdateKey(client, repo.Owner, repo.Name, title, repo.PublicKey) - if err != nil { - return err - } - } - - _, err = CreateUpdateHook(client, repo.Owner, repo.Name, link) - return err -} - -// ParseHook parses the post-commit hook from the Request body -// and returns the required data in a standard format. -func (r *GitHub) ParseHook(req *http.Request) (*model.Hook, error) { - // handle github ping - if req.Header.Get("X-Github-Event") == "ping" { - return nil, nil - } - - // handle github pull request hook differently - if req.Header.Get("X-Github-Event") == "pull_request" { - return r.ParsePullRequestHook(req) - } - - // parse the github Hook payload - var payload = GetPayload(req) - var data, err = github.ParseHook(payload) - if err != nil { - return nil, nil - } - - // make sure this is being triggered because of a commit - // and not something like a tag deletion or whatever - if data.IsTag() || - data.IsGithubPages() || - data.IsHead() == false || - data.IsDeleted() { - return nil, nil - } - - var hook = new(model.Hook) - hook.Repo = data.Repo.Name - hook.Owner = data.Repo.Owner.Login - hook.Sha = data.Head.Id - hook.Branch = data.Branch() - - if len(hook.Owner) == 0 { - hook.Owner = data.Repo.Owner.Name - } - - // extract the author and message from the commit - // this is kind of experimental, since I don't know - // what I'm doing here. - if data.Head != nil && data.Head.Author != nil { - hook.Message = data.Head.Message - hook.Timestamp = data.Head.Timestamp - hook.Author = data.Head.Author.Email - } else if data.Commits != nil && len(data.Commits) > 0 && data.Commits[0].Author != nil { - hook.Message = data.Commits[0].Message - hook.Timestamp = data.Commits[0].Timestamp - hook.Author = data.Commits[0].Author.Email - } - - return hook, nil -} - -// ParsePullRequestHook parses the pull request hook from the Request body -// and returns the required data in a standard format. -func (r *GitHub) ParsePullRequestHook(req *http.Request) (*model.Hook, error) { - - // parse the payload to retrieve the pull-request - // hook meta-data. - var payload = GetPayload(req) - var data, err = github.ParsePullRequestHook(payload) - if err != nil { - return nil, err - } - - // ignore these - if data.Action != "opened" && data.Action != "synchronize" { - return nil, nil - } - - // TODO we should also store the pull request branch (ie from x to y) - // we can find it here: data.PullRequest.Head.Ref - var hook = model.Hook{ - Owner: data.Repo.Owner.Login, - Repo: data.Repo.Name, - Sha: data.PullRequest.Head.Sha, - Branch: data.PullRequest.Head.Ref, - Author: data.PullRequest.User.Login, - Gravatar: data.PullRequest.User.GravatarId, - Timestamp: time.Now().UTC().String(), - Message: data.PullRequest.Title, - PullRequest: strconv.Itoa(data.Number), - } - - if len(hook.Owner) == 0 { - hook.Owner = data.Repo.Owner.Name - } - - return &hook, nil -} - -func (r *GitHub) OpenRegistration() bool { - return r.Open -} - -func (r *GitHub) GetToken(user *model.User) (*model.Token, error) { - return nil, nil -} diff --git a/plugin/remote/github/github_test.go b/plugin/remote/github/github_test.go deleted file mode 100644 index c46aede92..000000000 --- a/plugin/remote/github/github_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package github - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/drone/drone/plugin/remote/github/testdata" - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func Test_Github(t *testing.T) { - // setup a dummy github server - var server = testdata.NewServer() - defer server.Close() - - var github = GitHub{ - URL: server.URL, - API: server.URL, - } - var user = model.User{ - Access: "e3b0c44298fc1c149afbf4c8996fb", - } - var repo = model.Repo{ - Owner: "octocat", - Name: "Hello-World", - } - var hook = model.Hook{ - Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e", - } - - g := goblin.Goblin(t) - g.Describe("GitHub Plugin", func() { - - g.It("Should identify github vs github enterprise", func() { - var ghc = &GitHub{URL: "https://github.com"} - var ghe = &GitHub{URL: "https://github.drone.io"} - g.Assert(ghc.IsEnterprise()).IsFalse() - g.Assert(ghe.IsEnterprise()).IsTrue() - g.Assert(ghc.GetKind()).Equal(model.RemoteGithub) - g.Assert(ghe.GetKind()).Equal(model.RemoteGithubEnterprise) - }) - - g.It("Should parse the hostname", func() { - var ghc = &GitHub{URL: "https://github.com"} - var ghe = &GitHub{URL: "https://github.drone.io:80"} - g.Assert(ghc.GetHost()).Equal("github.com") - g.Assert(ghe.GetHost()).Equal("github.drone.io:80") - }) - - g.It("Should get the repo list", func() { - var repos, err = github.GetRepos(&user) - g.Assert(err == nil).IsTrue() - g.Assert(len(repos)).Equal(4) - g.Assert(repos[0].Name).Equal("Hello-World") - g.Assert(repos[0].Owner).Equal("octocat") - g.Assert(repos[0].Host).Equal(github.GetHost()) - g.Assert(repos[0].Remote).Equal(github.GetKind()) - g.Assert(repos[0].Private).Equal(true) - g.Assert(repos[0].CloneURL).Equal("git@github.com:octocat/Hello-World.git") - g.Assert(repos[0].SSHURL).Equal("git@github.com:octocat/Hello-World.git") - g.Assert(repos[0].GitURL).Equal("git://github.com/octocat/Hello-World.git") - g.Assert(repos[0].Role.Admin).Equal(true) - g.Assert(repos[0].Role.Read).Equal(true) - g.Assert(repos[0].Role.Write).Equal(true) - }) - - g.It("Should get the build script", func() { - var script, err = github.GetScript(&user, &repo, &hook) - g.Assert(err == nil).IsTrue() - g.Assert(string(script)).Equal("image: go") - }) - - g.It("Should activate a public repo", func() { - repo.Private = false - repo.CloneURL = "git://github.com/octocat/Hello-World.git" - repo.SSHURL = "git@github.com:octocat/Hello-World.git" - var err = github.Activate(&user, &repo, "http://example.com") - g.Assert(err == nil).IsTrue() - }) - - g.It("Should activate a private repo", func() { - repo.Name = "Hola-Mundo" - repo.Private = true - repo.CloneURL = "git@github.com:octocat/Hola-Mundo.git" - repo.SSHURL = "git@github.com:octocat/Hola-Mundo.git" - var err = github.Activate(&user, &repo, "http://example.com") - g.Assert(err == nil).IsTrue() - }) - - g.It("Should parse a commit hook") - - g.It("Should parse a pull request hook") - - g.Describe("Authorize", func() { - g.AfterEach(func() { - github.Orgs = []string{} - }) - - var resp = httptest.NewRecorder() - var state = "validstate" - var req, _ = http.NewRequest( - "GET", - fmt.Sprintf("%s/?code=sekret&state=%s", server.URL, state), - nil, - ) - req.AddCookie(&http.Cookie{Name: "github_state", Value: state}) - - g.It("Should authorize a valid user with no org restrictions", func() { - var login, err = github.Authorize(resp, req) - g.Assert(err == nil).IsTrue() - g.Assert(login == nil).IsFalse() - }) - - g.It("Should authorize a valid user in the correct org", func() { - github.Orgs = []string{"octocats-inc"} - var login, err = github.Authorize(resp, req) - g.Assert(err == nil).IsTrue() - g.Assert(login == nil).IsFalse() - }) - - g.It("Should not authorize a valid user in the wrong org", func() { - github.Orgs = []string{"acme"} - var login, err = github.Authorize(resp, req) - g.Assert(err != nil).IsTrue() - g.Assert(login == nil).IsTrue() - }) - }) - }) -} diff --git a/plugin/remote/github/helper.go b/plugin/remote/github/helper.go deleted file mode 100644 index 2f05ea0cc..000000000 --- a/plugin/remote/github/helper.go +++ /dev/null @@ -1,328 +0,0 @@ -package github - -import ( - "crypto/tls" - "encoding/base32" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - - "github.com/drone/drone/plugin/remote/github/oauth" - "github.com/google/go-github/github" - "github.com/gorilla/securecookie" -) - -// NewClient is a helper function that returns a new GitHub -// client using the provided OAuth token. -func NewClient(uri, token string, skipVerify bool) *github.Client { - t := &oauth.Transport{ - Token: &oauth.Token{AccessToken: token}, - } - - // this is for GitHub enterprise users that are using - // self-signed certificates. - if skipVerify { - t.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - - c := github.NewClient(t.Client()) - c.BaseURL, _ = url.Parse(uri) - return c -} - -// GetUserEmail is a heper function that retrieves the currently -// authenticated user from GitHub + Email address. -func GetUserEmail(client *github.Client) (*github.User, error) { - user, _, err := client.Users.Get("") - if err != nil { - return nil, err - } - - emails, _, err := client.Users.ListEmails(nil) - if err != nil { - return nil, err - } - - for _, email := range emails { - if *email.Primary && *email.Verified { - user.Email = email.Email - return user, nil - } - } - - // WARNING, HACK - // for out-of-date github enterprise editions the primary - // and verified fields won't exist. - if !strings.HasPrefix(*user.HTMLURL, DefaultURL) && len(emails) != 0 { - user.Email = emails[0].Email - return user, nil - } - - return nil, fmt.Errorf("No verified Email address for GitHub account") -} - -// GetAllRepos is a helper function that returns an aggregated list -// of all user and organization repositories. -func GetAllRepos(client *github.Client) ([]github.Repository, error) { - orgs, err := GetOrgs(client) - if err != nil { - return nil, err - } - - repos, err := GetUserRepos(client) - if err != nil { - return nil, err - } - - for _, org := range orgs { - list, err := GetOrgRepos(client, *org.Login) - if err != nil { - return nil, err - } - repos = append(repos, list...) - } - - return repos, nil -} - -// GetUserRepos is a helper function that returns a list of -// all user repositories. Paginated results are aggregated into -// a single list. -func GetUserRepos(client *github.Client) ([]github.Repository, error) { - var repos []github.Repository - var opts = github.RepositoryListOptions{} - opts.PerPage = 100 - opts.Page = 1 - - // loop through user repository list - for opts.Page > 0 { - list, resp, err := client.Repositories.List("", &opts) - if err != nil { - return nil, err - } - repos = append(repos, list...) - - // increment the next page to retrieve - opts.Page = resp.NextPage - } - - return repos, nil -} - -// GetOrgRepos is a helper function that returns a list of -// all org repositories. Paginated results are aggregated into -// a single list. -func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error) { - var repos []github.Repository - var opts = github.RepositoryListByOrgOptions{} - opts.PerPage = 100 - opts.Page = 1 - - // loop through user repository list - for opts.Page > 0 { - list, resp, err := client.Repositories.ListByOrg(org, &opts) - if err != nil { - return nil, err - } - repos = append(repos, list...) - - // increment the next page to retrieve - opts.Page = resp.NextPage - } - - return repos, nil -} - -// GetOrgs is a helper function that returns a list of -// all orgs that a user belongs to. -func GetOrgs(client *github.Client) ([]github.Organization, error) { - var orgs []github.Organization - var opts = github.ListOptions{} - opts.Page = 1 - - for opts.Page > 0 { - list, resp, err := client.Organizations.List("", &opts) - if err != nil { - return nil, err - } - orgs = append(orgs, list...) - - // increment the next page to retrieve - opts.Page = resp.NextPage - } - return orgs, nil -} - -// GetHook is a heper function that retrieves a hook by -// hostname. To do this, it will retrieve a list of all hooks -// and iterate through the list. -func GetHook(client *github.Client, owner, name, url string) (*github.Hook, error) { - hooks, _, err := client.Repositories.ListHooks(owner, name, nil) - if err != nil { - return nil, err - } - for _, hook := range hooks { - if hook.Config["url"] == url { - return &hook, nil - } - } - return nil, nil -} - -func DeleteHook(client *github.Client, owner, name, url string) error { - hook, err := GetHook(client, owner, name, url) - if err != nil { - return err - } - - _, err = client.Repositories.DeleteHook(owner, name, *hook.ID) - return err -} - -// CreateHook is a heper function that creates a post-commit hook -// for the specified repository. -func CreateHook(client *github.Client, owner, name, url string) (*github.Hook, error) { - var hook = new(github.Hook) - hook.Name = github.String("web") - hook.Events = []string{"push", "pull_request"} - hook.Config = map[string]interface{}{} - hook.Config["url"] = url - hook.Config["content_type"] = "form" - created, _, err := client.Repositories.CreateHook(owner, name, hook) - return created, err -} - -// CreateUpdateHook is a heper function that creates a post-commit hook -// for the specified repository if it does not already exist, otherwise -// it updates the existing hook -func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.Hook, error) { - var hook, _ = GetHook(client, owner, name, url) - if hook != nil { - hook.Name = github.String("web") - hook.Events = []string{"push", "pull_request"} - hook.Config = map[string]interface{}{} - hook.Config["url"] = url - hook.Config["content_type"] = "form" - var updated, _, err = client.Repositories.EditHook(owner, name, *hook.ID, hook) - return updated, err - } - - return CreateHook(client, owner, name, url) -} - -// GetKey is a heper function that retrieves a public Key by -// title. To do this, it will retrieve a list of all keys -// and iterate through the list. -func GetKey(client *github.Client, owner, name, title string) (*github.Key, error) { - keys, _, err := client.Repositories.ListKeys(owner, name, nil) - if err != nil { - return nil, err - } - for _, key := range keys { - if *key.Title == title { - return &key, nil - } - } - return nil, nil -} - -// GetKeyTitle is a helper function that generates a title for the -// RSA public key based on the username and domain name. -func GetKeyTitle(rawurl string) (string, error) { - var uri, err = url.Parse(rawurl) - if err != nil { - return "", err - } - return fmt.Sprintf("drone@%s", uri.Host), nil -} - -// DeleteKey is a helper function that deletes a deploy key -// for the specified repository. -func DeleteKey(client *github.Client, owner, name, title, key string) error { - var k, err = GetKey(client, owner, name, title) - if err != nil { - return err - } - _, err = client.Repositories.DeleteKey(owner, name, *k.ID) - return err -} - -// CreateKey is a helper function that creates a deploy key -// for the specified repository. -func CreateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) { - var k = new(github.Key) - k.Title = github.String(title) - k.Key = github.String(key) - created, _, err := client.Repositories.CreateKey(owner, name, k) - return created, err -} - -// CreateUpdateKey is a helper function that creates a deployment key -// for the specified repository if it does not already exist, otherwise -// it updates the existing key -func CreateUpdateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) { - var k, _ = GetKey(client, owner, name, title) - if k != nil { - k.Title = github.String(title) - k.Key = github.String(key) - client.Repositories.DeleteKey(owner, name, *k.ID) - } - - return CreateKey(client, owner, name, title, key) -} - -// GetFile is a heper function that retrieves a file from -// GitHub and returns its contents in byte array format. -func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) { - var opts = new(github.RepositoryContentGetOptions) - opts.Ref = ref - content, _, _, err := client.Repositories.GetContents(owner, name, path, opts) - if err != nil { - return nil, err - } - return content.Decode() -} - -// GetRandom is a helper function that generates a 32-bit random -// key, base32 encoded as a string value. -func GetRandom() string { - return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) -} - -// GetPayload is a helper function that will parse the JSON payload. It will -// first check for a `payload` parameter in a POST, but can fallback to a -// raw JSON body as well. -func GetPayload(req *http.Request) []byte { - var payload = req.FormValue("payload") - if len(payload) == 0 { - raw, _ := ioutil.ReadAll(req.Body) - return raw - } - return []byte(payload) -} - -// UserBelongsToOrg returns true if the currently authenticated user is a -// member of any of the organizations provided. -func UserBelongsToOrg(client *github.Client, permittedOrgs []string) (bool, error) { - userOrgs, err := GetOrgs(client) - if err != nil { - return false, err - } - - userOrgSet := make(map[string]struct{}, len(userOrgs)) - for _, org := range userOrgs { - userOrgSet[*org.Login] = struct{}{} - } - - for _, org := range permittedOrgs { - if _, ok := userOrgSet[org]; ok { - return true, nil - } - } - - return false, nil -} diff --git a/plugin/remote/github/helper_test.go b/plugin/remote/github/helper_test.go deleted file mode 100644 index 3155e61e6..000000000 --- a/plugin/remote/github/helper_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package github - -import ( - "testing" - - "github.com/drone/drone/plugin/remote/github/testdata" - "github.com/franela/goblin" -) - -func Test_Helper(t *testing.T) { - // setup a dummy github server - var server = testdata.NewServer() - defer server.Close() - - var client = NewClient(server.URL, "sekret", false) - - g := goblin.Goblin(t) - g.Describe("GitHub Helper Functions", func() { - - g.It("Should Get a User") - g.It("Should Get a User Primary Email") - g.It("Should Get a User + Primary Email") - - g.It("Should Get a list of Orgs", func() { - var orgs, err = GetOrgs(client) - g.Assert(err == nil).IsTrue() - g.Assert(len(orgs)).Equal(1) - g.Assert(*orgs[0].Login).Equal("octocats-inc") - }) - - g.It("Should Get a list of User Repos") - g.It("Should Get a list of Org Repos") - g.It("Should Get a list of All Repos") - g.It("Should Get a Repo Key") - g.It("Should Get a Repo Hook") - g.It("Should Create a Repo Key") - g.It("Should Create a Repo Hook") - g.It("Should Create or Update a Repo Key") - g.It("Should Create or Update a Repo Hook") - g.It("Should Get a Repo File") - - g.Describe("UserBelongsToOrg", func() { - g.It("Should confirm user does belong to 'octocats-inc' org", func() { - var requiredOrgs = []string{"one", "octocats-inc", "two"} - var member, err = UserBelongsToOrg(client, requiredOrgs) - g.Assert(err == nil).IsTrue() - g.Assert(member).IsTrue() - }) - - g.It("Should confirm user not does belong to 'octocats-inc' org", func() { - var requiredOrgs = []string{"one", "two"} - var member, err = UserBelongsToOrg(client, requiredOrgs) - g.Assert(err == nil).IsTrue() - g.Assert(member).IsFalse() - }) - }) - }) -} diff --git a/plugin/remote/github/register.go b/plugin/remote/github/register.go deleted file mode 100644 index 21d714e05..000000000 --- a/plugin/remote/github/register.go +++ /dev/null @@ -1,64 +0,0 @@ -package github - -import ( - "github.com/drone/config" - "github.com/drone/drone/plugin/remote" -) - -var ( - // GitHub cloud configuration details - githubClient = config.String("github-client", "") - githubSecret = config.String("github-secret", "") - githubOrgs = config.Strings("github-orgs") - githubOpen = config.Bool("github-open", false) - - // GitHub Enterprise configuration details - githubEnterpriseURL = config.String("github-enterprise-url", "") - githubEnterpriseAPI = config.String("github-enterprise-api", "") - githubEnterpriseClient = config.String("github-enterprise-client", "") - githubEnterpriseSecret = config.String("github-enterprise-secret", "") - githubEnterprisePrivate = config.Bool("github-enterprise-private-mode", true) - githubEnterpriseSkipVerify = config.Bool("github-enterprise-skip-verify", false) - githubEnterpriseOrgs = config.Strings("github-enterprise-orgs") - githubEnterpriseOpen = config.Bool("github-enterprise-open", false) -) - -// Registers the GitHub plugins using the default -// settings from the config file or environment -// variables. -func Register() { - registerGitHub() - registerGitHubEnterprise() -} - -// registers the GitHub (github.com) plugin -func registerGitHub() { - if len(*githubClient) == 0 || len(*githubSecret) == 0 { - return - } - remote.Register( - NewDefault(*githubClient, *githubSecret, *githubOrgs, *githubOpen), - ) -} - -// registers the GitHub Enterprise plugin -func registerGitHubEnterprise() { - if len(*githubEnterpriseURL) == 0 || - len(*githubEnterpriseAPI) == 0 || - len(*githubEnterpriseClient) == 0 || - len(*githubEnterpriseSecret) == 0 { - return - } - remote.Register( - New( - *githubEnterpriseURL, - *githubEnterpriseAPI, - *githubEnterpriseClient, - *githubEnterpriseSecret, - *githubEnterprisePrivate, - *githubEnterpriseSkipVerify, - *githubEnterpriseOrgs, - *githubEnterpriseOpen, - ), - ) -} diff --git a/plugin/remote/github/testdata/testdata.go b/plugin/remote/github/testdata/testdata.go deleted file mode 100644 index d84262c5b..000000000 --- a/plugin/remote/github/testdata/testdata.go +++ /dev/null @@ -1,215 +0,0 @@ -package testdata - -import ( - "net/http" - "net/http/httptest" -) - -// setup a mock server for testing purposes. -func NewServer() *httptest.Server { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - - // handle requests and serve mock data - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - - // evaluate the path to serve a dummy data file - switch r.URL.Path { - case "/login/oauth/access_token": - w.Write(accessTokenPayload) - return - case "/user": - w.Write(userPayload) - return - case "/user/emails": - w.Write(userEmailsPayload) - return - case "/user/repos": - w.Write(userReposPayload) - return - case "/user/orgs": - w.Write(userOrgsPayload) - return - case "/orgs/octocats-inc/repos": - w.Write(userReposPayload) - return - case "/repos/octocat/Hello-World/contents/.drone.yml": - w.Write(droneYamlPayload) - return - case "/repos/octocat/Hello-World/hooks": - switch r.Method { - case "POST": - w.Write(createHookPayload) - return - } - case "/repos/octocat/Hola-Mundo/hooks": - switch r.Method { - case "POST": - w.Write(createHookPayload) - return - } - case "/repos/octocat/Hola-Mundo/keys": - switch r.Method { - case "POST": - w.Write(createKeyPayload) - return - } - } - - // else return a 404 - http.NotFound(w, r) - }) - - // return the server to the client which - // will need to know the base URL path - return server -} - -var accessTokenPayload = []byte(`access_token=sekret&scope=repo%2Cuser%3Aemail&token_type=bearer`) - -var userPayload = []byte(` -{ - "login": "octocat", - "id": 1, - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false, - "name": "monalisa octocat", - "company": "GitHub", - "blog": "https://github.com/blog", - "location": "San Francisco", - "email": "octocat@github.com", - "hireable": false, - "bio": "There once was...", - "public_repos": 2, - "public_gists": 1, - "followers": 20, - "following": 0, - "created_at": "2008-01-14T04:33:35Z", - "updated_at": "2008-01-14T04:33:35Z", - "total_private_repos": 100, - "owned_private_repos": 100, - "private_gists": 81, - "disk_usage": 10000, - "collaborators": 8, - "plan": { - "name": "Medium", - "space": 400, - "private_repos": 20, - "collaborators": 0 - } -} -`) - -var userEmailsPayload = []byte(` -[ - { - "email": "octocat@github.com", - "verified": true, - "primary": true - } -] -`) - -// sample repository list -var userReposPayload = []byte(` -[ - { - "owner": { - "login": "octocat", - "id": 1 - }, - "id": 1296269, - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "private": true, - "url": "https://api.github.com/repos/octocat/Hello-World", - "html_url": "https://github.com/octocat/Hello-World", - "clone_url": "https://github.com/octocat/Hello-World.git", - "git_url": "git://github.com/octocat/Hello-World.git", - "ssh_url": "git@github.com:octocat/Hello-World.git", - "permissions": { - "admin": true, - "push": true, - "pull": true - } - }, - { - "owner": { - "login": "octocat", - "id": 1 - }, - "id": 9626921, - "name": "Hola-Mundo", - "full_name": "octocat/Hola-Mundo", - "private": false, - "url": "https://api.github.com/repos/octocat/Hola-Mundo", - "html_url": "https://github.com/octocat/Hola-Mundo", - "clone_url": "https://github.com/octocat/Hola-Mundo.git", - "git_url": "git://github.com/octocat/Hola-Mundo.git", - "ssh_url": "git@github.com:octocat/Hola-Mundo.git", - "permissions": { - "admin": false, - "push": false, - "pull": true - } - } -] -`) - -var emptySetPayload = []byte(`[]`) -var emptyObjPayload = []byte(`{}`) - -// sample org list response -var userOrgsPayload = []byte(` -[ - { "login": "octocats-inc", "id": 1 } -] -`) - -// sample content response for .drone.yml request -var droneYamlPayload = []byte(` -{ - "type": "file", - "encoding": "base64", - "name": ".drone.yml", - "path": ".drone.yml", - "content": "aW1hZ2U6IGdv" -} -`) - -// sample create hook response -var createHookPayload = []byte(` -{ - "id": 1, - "name": "web", - "events": [ "push", "pull_request" ], - "active": true, - "config": { - "url": "http://example.com", - "content_type": "json" - } -} -`) - -// sample create hook response -var createKeyPayload = []byte(` -{ - "id": 1, - "key": "ssh-rsa AAA...", - "url": "https://api.github.com/user/keys/1", - "title": "octocat@octomac" -} -`) diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go deleted file mode 100644 index 984eed6b9..000000000 --- a/plugin/remote/gitlab/gitlab.go +++ /dev/null @@ -1,297 +0,0 @@ -package gitlab - -import ( - "crypto/tls" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "code.google.com/p/goauth2/oauth" - "github.com/Bugagazavr/go-gitlab-client" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" -) - -type Gitlab struct { - url string - SkipVerify bool - Open bool - Client string - Secret string -} - -func New(url string, skipVerify, open bool, client, secret string) *Gitlab { - return &Gitlab{ - url: url, - SkipVerify: skipVerify, - Open: open, - Client: client, - Secret: secret, - } -} - -// Authorize handles authentication with thrid party remote systems, -// such as github or bitbucket, and returns user data. -func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { - host := httputil.GetURL(req) - config := NewOauthConfig(r, host) - - var code = req.FormValue("code") - var state = req.FormValue("state") - - if len(code) == 0 { - var random = GetRandom() - httputil.SetCookie(res, req, "gitlab_state", random) - http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) - return nil, nil - } - - cookieState := httputil.GetCookie(req, "gitlab_state") - httputil.DelCookie(res, req, "gitlab_state") - if cookieState != state { - return nil, fmt.Errorf("Error matching state in OAuth2 redirect") - } - - var trans = &oauth.Transport{Config: config} - var token, err = trans.Exchange(code) - if err != nil { - return nil, fmt.Errorf("Error exchanging token. %s", err) - } - - var client = NewClient(r.url, token.AccessToken, r.SkipVerify) - - var user, errr = client.CurrentUser() - if errr != nil { - return nil, fmt.Errorf("Error retrieving current user. %s", errr) - } - - var login = new(model.Login) - login.ID = int64(user.Id) - login.Access = token.AccessToken - login.Secret = token.RefreshToken - login.Login = user.Username - login.Email = user.Email - return login, nil -} - -// GetKind returns the identifier of this remote GitHub instane. -func (r *Gitlab) GetKind() string { - return model.RemoteGitlab -} - -// GetHost returns the hostname of this remote GitHub instance. -func (r *Gitlab) GetHost() string { - uri, _ := url.Parse(r.url) - return uri.Host -} - -// GetRepos fetches all repositories that the specified -// user has access to in the remote system. -func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) { - - var repos []*model.Repo - var client = NewClient(r.url, user.Access, r.SkipVerify) - var list, err = client.AllProjects() - if err != nil { - return nil, err - } - - var remote = r.GetKind() - var hostname = r.GetHost() - - for _, item := range list { - var repo = model.Repo{ - UserID: user.ID, - Remote: remote, - Host: hostname, - Owner: item.Namespace.Path, - Name: item.Path, - Private: !item.Public, - CloneURL: item.HttpRepoUrl, - GitURL: item.HttpRepoUrl, - SSHURL: item.SshRepoUrl, - URL: item.Url, - Role: &model.Perm{}, - } - - if repo.Private { - repo.CloneURL = repo.SSHURL - } - - // if the user is the owner we can assume full access, - // otherwise check for the permission items. - if repo.Owner == user.Login { - repo.Role = new(model.Perm) - repo.Role.Admin = true - repo.Role.Write = true - repo.Role.Read = true - } else { - // Fetch current project - project, err := client.Project(strconv.Itoa(item.Id)) - if err != nil || project.Permissions == nil { - continue - } - repo.Role.Admin = IsAdmin(project) - repo.Role.Write = IsWrite(project) - repo.Role.Read = IsRead(project) - } - - repos = append(repos, &repo) - } - - return repos, err -} - -// GetScript fetches the build script (.drone.yml) from the remote -// repository and returns in string format. -func (r *Gitlab) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { - var client = NewClient(r.url, user.Access, r.SkipVerify) - var path = ns(repo.Owner, repo.Name) - return client.RepoRawFile(path, hook.Sha, ".drone.yml") -} - -// Activate activates a repository by adding a Post-commit hook and -// a Public Deploy key, if applicable. -func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error { - var client = NewClient(r.url, user.Access, r.SkipVerify) - var path = ns(repo.Owner, repo.Name) - var title, err = GetKeyTitle(link) - if err != nil { - return err - } - - // if the repository is private we'll need - // to upload a github key to the repository - if repo.Private { - var err = client.AddProjectDeployKey(path, title, repo.PublicKey) - if err != nil { - return err - } - } - - // append the repo owner / name to the hook url since gitlab - // doesn't send this detail in the post-commit hook - link += "?owner=" + repo.Owner + "&name=" + repo.Name - - // add the hook - return client.AddProjectHook(path, link, true, false, true) -} - -// Deactivate removes a repository by removing all the post-commit hooks -// which are equal to link and removing the SSH deploy key. -func (r *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) error { - var client = NewClient(r.url, user.Access, r.SkipVerify) - var path = ns(repo.Owner, repo.Name) - - keys, err := client.ProjectDeployKeys(path) - if err != nil { - return err - } - var pubkey = strings.TrimSpace(repo.PublicKey) - for _, k := range keys { - if pubkey == strings.TrimSpace(k.Key) { - if err := client.RemoveProjectDeployKey(path, strconv.Itoa(k.Id)); err != nil { - return err - } - break - } - } - hooks, err := client.ProjectHooks(path) - if err != nil { - return err - } - link += "?owner=" + repo.Owner + "&name=" + repo.Name - for _, h := range hooks { - if link == h.Url { - if err := client.RemoveProjectHook(path, strconv.Itoa(h.Id)); err != nil { - return err - } - break - } - } - return nil -} - -// ParseHook parses the post-commit hook from the Request body -// and returns the required data in a standard format. -func (r *Gitlab) ParseHook(req *http.Request) (*model.Hook, error) { - - defer req.Body.Close() - var payload, _ = ioutil.ReadAll(req.Body) - var parsed, err = gogitlab.ParseHook(payload) - if err != nil { - return nil, err - } - - if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { - return nil, nil - } - - if parsed.ObjectKind == "merge_request" { - // TODO (bradrydzewski) figure out how to handle merge requests - return nil, nil - } - - if len(parsed.After) == 0 { - return nil, nil - } - - var hook = new(model.Hook) - hook.Owner = req.FormValue("owner") - hook.Repo = req.FormValue("name") - hook.Sha = parsed.After - hook.Branch = parsed.Branch() - - var head = parsed.Head() - hook.Message = head.Message - hook.Timestamp = head.Timestamp - - // extracts the commit author (ideally email) - // from the post-commit hook - switch { - case head.Author != nil: - hook.Author = head.Author.Email - case head.Author == nil: - hook.Author = parsed.UserName - } - - return hook, nil -} - -func (r *Gitlab) OpenRegistration() bool { - return r.Open -} - -func (r *Gitlab) GetToken(user *model.User) (*model.Token, error) { - expiry := time.Unix(user.TokenExpiry, 0) - if expiry.Sub(time.Now()) > (60 * time.Second) { - return nil, nil - } - - t := &oauth.Transport{ - Config: NewOauthConfig(r, ""), - Token: &oauth.Token{ - AccessToken: user.Access, - RefreshToken: user.Secret, - Expiry: expiry, - }, - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{InsecureSkipVerify: r.SkipVerify}, - }, - } - - if err := t.Refresh(); err != nil { - return nil, err - } - - var token = new(model.Token) - token.AccessToken = t.Token.AccessToken - token.RefreshToken = t.Token.RefreshToken - token.Expiry = t.Token.Expiry.Unix() - return token, nil -} diff --git a/plugin/remote/gitlab/gitlab_test.go b/plugin/remote/gitlab/gitlab_test.go deleted file mode 100644 index 37333a866..000000000 --- a/plugin/remote/gitlab/gitlab_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package gitlab - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/drone/drone/plugin/remote/gitlab/testdata" - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func Test_Github(t *testing.T) { - // setup a dummy github server - var server = testdata.NewServer() - defer server.Close() - - var gitlab = New(server.URL, false, false, "", "") - var user = model.User{ - Access: "e3b0c44298fc1c149afbf4c8996fb", - } - /* - var repo = model.Repo{ - Owner: "gitlab", - Name: "Hello-World", - } - var commit = model.Commit{ - Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e", - } - */ - - g := goblin.Goblin(t) - g.Describe("Gitlab Plugin", func() { - - g.It("Should get the repo list", func() { - var repos, err = gitlab.GetRepos(&user) - g.Assert(err == nil).IsTrue() - g.Assert(len(repos)).Equal(2) - g.Assert(repos[0].Name).Equal("diaspora-client") - g.Assert(repos[0].Owner).Equal("diaspora") - g.Assert(repos[0].Host).Equal(gitlab.GetHost()) - g.Assert(repos[0].Remote).Equal(gitlab.GetKind()) - g.Assert(repos[0].Private).Equal(true) - g.Assert(repos[0].Role.Admin).Equal(true) - g.Assert(repos[0].Role.Read).Equal(true) - g.Assert(repos[0].Role.Write).Equal(true) - }) - - g.Describe("Authorize", func() { - var resp = httptest.NewRecorder() - var state = "validstate" - var req, _ = http.NewRequest( - "GET", - fmt.Sprintf("%s/?code=sekret&state=%s", server.URL, state), - nil, - ) - req.AddCookie(&http.Cookie{Name: "gitlab_state", Value: state}) - - g.It("Should authorize a valid user", func() { - var login, err = gitlab.Authorize(resp, req) - g.Assert(err == nil).IsTrue() - g.Assert(login == nil).IsFalse() - }) - }) - /* - g.It("Should get the build script", func() { - var script, err = github.GetScript(&user, &repo, &commit) - g.Assert(err == nil).IsTrue() - g.Assert(string(script)).Equal("image: go") - }) - - g.It("Should activate a public repo", func() { - repo.Private = false - repo.CloneURL = "git://github.com/octocat/Hello-World.git" - repo.SSHURL = "git@github.com:octocat/Hello-World.git" - var err = github.Activate(&user, &repo, "http://example.com") - g.Assert(err == nil).IsTrue() - }) - - g.It("Should activate a private repo", func() { - repo.Name = "Hola-Mundo" - repo.Private = true - repo.CloneURL = "git@github.com:octocat/Hola-Mundo.git" - repo.SSHURL = "git@github.com:octocat/Hola-Mundo.git" - var err = github.Activate(&user, &repo, "http://example.com") - g.Assert(err == nil).IsTrue() - }) - */ - g.It("Should parse a commit hook") - - g.It("Should ignore a pull request hook") - }) -} diff --git a/plugin/remote/gitlab/helper.go b/plugin/remote/gitlab/helper.go deleted file mode 100644 index 8e67a5d9d..000000000 --- a/plugin/remote/gitlab/helper.go +++ /dev/null @@ -1,100 +0,0 @@ -package gitlab - -import ( - "encoding/base32" - "fmt" - "net/url" - - "code.google.com/p/goauth2/oauth" - "github.com/Bugagazavr/go-gitlab-client" - "github.com/gorilla/securecookie" -) - -func NewOauthConfig(g *Gitlab, host string) *oauth.Config { - return &oauth.Config{ - ClientId: g.Client, - ClientSecret: g.Secret, - Scope: "api", - AuthURL: fmt.Sprintf("%s/oauth/authorize", g.url), - TokenURL: fmt.Sprintf("%s/oauth/token", g.url), - RedirectURL: fmt.Sprintf("%s/api/auth/%s", host, g.GetKind()), - } -} - -// NewClient is a helper function that returns a new GitHub -// client using the provided OAuth token. -func NewClient(url, accessToken string, skipVerify bool) *gogitlab.Gitlab { - client := gogitlab.NewGitlabCert(url, "/api/v3", accessToken, skipVerify) - client.Bearer = true - return client -} - -// IsRead is a helper function that returns true if the -// user has Read-only access to the repository. -func IsRead(proj *gogitlab.Project) bool { - var user = proj.Permissions.ProjectAccess - var group = proj.Permissions.GroupAccess - - switch { - case proj.Public: - return true - case user != nil && user.AccessLevel >= 20: - return true - case group != nil && group.AccessLevel >= 20: - return true - default: - return false - } -} - -// IsWrite is a helper function that returns true if the -// user has Read-Write access to the repository. -func IsWrite(proj *gogitlab.Project) bool { - var user = proj.Permissions.ProjectAccess - var group = proj.Permissions.GroupAccess - - switch { - case user != nil && user.AccessLevel >= 30: - return true - case group != nil && group.AccessLevel >= 30: - return true - default: - return false - } -} - -// IsAdmin is a helper function that returns true if the -// user has Admin access to the repository. -func IsAdmin(proj *gogitlab.Project) bool { - var user = proj.Permissions.ProjectAccess - var group = proj.Permissions.GroupAccess - - switch { - case user != nil && user.AccessLevel >= 40: - return true - case group != nil && group.AccessLevel >= 40: - return true - default: - return false - } -} - -// GetKeyTitle is a helper function that generates a title for the -// RSA public key based on the username and domain name. -func GetKeyTitle(rawurl string) (string, error) { - var uri, err = url.Parse(rawurl) - if err != nil { - return "", err - } - return fmt.Sprintf("drone@%s", uri.Host), nil -} - -func ns(owner, name string) string { - return fmt.Sprintf("%s%%2F%s", owner, name) -} - -// GetRandom is a helper function that generates a 32-bit random -// key, base32 encoded as a string value. -func GetRandom() string { - return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) -} diff --git a/plugin/remote/gitlab/helper_test.go b/plugin/remote/gitlab/helper_test.go deleted file mode 100644 index 4d4b35afe..000000000 --- a/plugin/remote/gitlab/helper_test.go +++ /dev/null @@ -1 +0,0 @@ -package gitlab diff --git a/plugin/remote/gitlab/register.go b/plugin/remote/gitlab/register.go deleted file mode 100644 index 4b6aeceed..000000000 --- a/plugin/remote/gitlab/register.go +++ /dev/null @@ -1,33 +0,0 @@ -package gitlab - -import ( - "github.com/drone/config" - "github.com/drone/drone/plugin/remote" -) - -var ( - gitlabURL = config.String("gitlab-url", "") - gitlabSkipVerify = config.Bool("gitlab-skip-verify", false) - gitlabOpen = config.Bool("gitlab-open", false) - - gitlabClient = config.String("gitlab-client", "") - gitlabSecret = config.String("gitlab-secret", "") -) - -// Registers the Gitlab plugin using the default -// settings from the config file or environment -// variables. -func Register() { - if len(*gitlabURL) == 0 { - return - } - remote.Register( - New( - *gitlabURL, - *gitlabSkipVerify, - *gitlabOpen, - *gitlabClient, - *gitlabSecret, - ), - ) -} diff --git a/plugin/remote/gitlab/testdata/testdata.go b/plugin/remote/gitlab/testdata/testdata.go deleted file mode 100644 index cb727a878..000000000 --- a/plugin/remote/gitlab/testdata/testdata.go +++ /dev/null @@ -1,264 +0,0 @@ -package testdata - -import ( - "net/http" - "net/http/httptest" -) - -// setup a mock server for testing purposes. -func NewServer() *httptest.Server { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - - // handle requests and serve mock data - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - //println(r.URL.Path + " " + r.Method) - // evaluate the path to serve a dummy data file - switch r.URL.Path { - case "/api/v3/projects": - w.Write(projectsPayload) - return - case "/api/v3/projects/4": - w.Write(project4Paylod) - return - case "/api/v3/projects/6": - w.Write(project6Paylod) - return - case "/api/v3/session": - w.Write(sessionPayload) - return - case "/oauth/token": - w.Write(accessTokenPayload) - return - case "/api/v3/user": - w.Write(currentUserPayload) - return - } - - // else return a 404 - http.NotFound(w, r) - }) - - // return the server to the client which - // will need to know the base URL path - return server -} - -// sample repository list -var projectsPayload = []byte(` -[ - { - "id": 4, - "description": null, - "default_branch": "master", - "public": false, - "visibility_level": 0, - "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", - "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", - "web_url": "http://example.com/diaspora/diaspora-client", - "owner": { - "id": 3, - "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" - }, - "name": "Diaspora Client", - "name_with_namespace": "Diaspora / Diaspora Client", - "path": "diaspora-client", - "path_with_namespace": "diaspora/diaspora-client", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", - "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", - "description": "", - "id": 3, - "name": "Diaspora", - "owner_id": 1, - "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" - }, - "archived": false - }, - { - "id": 6, - "description": null, - "default_branch": "master", - "public": false, - "visibility_level": 0, - "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", - "http_url_to_repo": "http://example.com/brightbox/puppet.git", - "web_url": "http://example.com/brightbox/puppet", - "owner": { - "id": 4, - "name": "Brightbox", - "created_at": "2013-09-30T13:46:02Z" - }, - "name": "Puppet", - "name_with_namespace": "Brightbox / Puppet", - "path": "puppet", - "path_with_namespace": "brightbox/puppet", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "snippets_enabled": false, - "created_at": "2013-09-30T13:46:02Z", - "last_activity_at": "2013-09-30T13:46:02Z", - "namespace": { - "created_at": "2013-09-30T13:46:02Z", - "description": "", - "id": 4, - "name": "Brightbox", - "owner_id": 1, - "path": "brightbox", - "updated_at": "2013-09-30T13:46:02Z" - }, - "archived": false - } -] -`) - -var project4Paylod = []byte(` -{ - "id": 4, - "description": null, - "default_branch": "master", - "public": false, - "visibility_level": 0, - "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", - "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", - "web_url": "http://example.com/diaspora/diaspora-client", - "owner": { - "id": 3, - "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" - }, - "name": "Diaspora Client", - "name_with_namespace": "Diaspora / Diaspora Client", - "path": "diaspora-client", - "path_with_namespace": "diaspora/diaspora-client", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", - "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", - "description": "", - "id": 3, - "name": "Diaspora", - "owner_id": 1, - "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" - }, - "archived": false, - "permissions": { - "project_access": { - "access_level": 10, - "notification_level": 3 - }, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -} -`) - -var project6Paylod = []byte(` -{ - "id": 6, - "description": null, - "default_branch": "master", - "public": false, - "visibility_level": 0, - "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", - "http_url_to_repo": "http://example.com/brightbox/puppet.git", - "web_url": "http://example.com/brightbox/puppet", - "owner": { - "id": 4, - "name": "Brightbox", - "created_at": "2013-09-30T13:46:02Z" - }, - "name": "Puppet", - "name_with_namespace": "Brightbox / Puppet", - "path": "puppet", - "path_with_namespace": "brightbox/puppet", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "snippets_enabled": false, - "created_at": "2013-09-30T13:46:02Z", - "last_activity_at": "2013-09-30T13:46:02Z", - "namespace": { - "created_at": "2013-09-30T13:46:02Z", - "description": "", - "id": 4, - "name": "Brightbox", - "owner_id": 1, - "path": "brightbox", - "updated_at": "2013-09-30T13:46:02Z" - }, - "archived": false, - "permissions": { - "project_access": { - "access_level": 10, - "notification_level": 3 - }, - "group_access": { - "access_level": 50, - "notification_level": 3 - } - } -} -`) - -// sample org list response -var sessionPayload = []byte(` -{ - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "private_token": "dd34asd13as" -} -`) - -// sample content response for .drone.yml request -var droneYamlPayload = []byte(` -{ - "type": "file", - "encoding": "base64", - "name": ".drone.yml", - "path": ".drone.yml", - "content": "aW1hZ2U6IGdv" -} -`) - -var accessTokenPayload = []byte(`access_token=sekret&scope=api&token_type=bearer`) - -var currentUserPayload = []byte(` -{ - "id": 1, - "username": "john_smith", - "email": "john@example.com", - "name": "John Smith", - "private_token": "dd34asd13as", - "state": "active", - "created_at": "2012-05-23T08:00:58Z", - "bio": null, - "skype": "", - "linkedin": "", - "twitter": "", - "website_url": "", - "theme_id": 1, - "color_scheme_id": 2, - "is_admin": false, - "can_create_group": true, - "can_create_project": true, - "projects_limit": 100 -} -`) diff --git a/plugin/remote/gogs/gogs.go b/plugin/remote/gogs/gogs.go deleted file mode 100644 index 73038a4dc..000000000 --- a/plugin/remote/gogs/gogs.go +++ /dev/null @@ -1,198 +0,0 @@ -package gogs - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - "net/url" - "strings" - "time" - - "github.com/drone/drone/shared/model" - "github.com/gogits/go-gogs-client" -) - -type Gogs struct { - URL string - Secret string - Open bool -} - -func New(url string, secret string, open bool) *Gogs { - return &Gogs{URL: url, Secret: secret, Open: open} -} - -// Authorize handles Gogs authorization -func (r *Gogs) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { - var username = req.FormValue("username") - var password = req.FormValue("password") - var client = gogs.NewClient(r.URL, "") - - // try to fetch drone token if it exists - var accessToken = "" - tokens, err := client.ListAccessTokens(username, password) - if err != nil { - return nil, err - } - for _, token := range tokens { - if token.Name == "drone" { - accessToken = token.Sha1 - break - } - } - - // if drone token not found, create it - if accessToken == "" { - token, err := client.CreateAccessToken(username, password, gogs.CreateAccessTokenOption{Name: "drone"}) - if err != nil { - return nil, err - } - accessToken = token.Sha1 - } - - // update client - client = gogs.NewClient(r.URL, accessToken) - - // fetch user information - user, err := client.GetUserInfo(username) - if err != nil { - return nil, err - } - - var login = new(model.Login) - login.Name = user.FullName - login.Email = user.Email - login.Access = accessToken - login.Login = username - return login, nil -} - -// GetKind returns the internal identifier of this remote Gogs instance -func (r *Gogs) GetKind() string { - return model.RemoteGogs -} - -// GetHost returns the hostname of this remote Gogs instance -func (r *Gogs) GetHost() string { - uri, _ := url.Parse(r.URL) - return uri.Host -} - -// GetRepos fetches all repositories that the specified -// user has access to in the remote system. -func (r *Gogs) GetRepos(user *model.User) ([]*model.Repo, error) { - var repos []*model.Repo - - var remote = r.GetKind() - var hostname = r.GetHost() - var client = gogs.NewClient(r.URL, user.Access) - - gogsRepos, err := client.ListMyRepos() - - if err != nil { - return nil, err - } - - for _, repo := range gogsRepos { - var repoName = strings.Split(repo.FullName, "/") - if len(repoName) < 2 { - log.Println("invalid repo full_name", repo.FullName) - continue - } - var owner = repoName[0] - var name = repoName[1] - - var repo = model.Repo{ - UserID: user.ID, - Remote: remote, - Host: hostname, - Owner: owner, - Name: name, - Private: repo.Private, - CloneURL: repo.CloneUrl, - GitURL: repo.CloneUrl, - SSHURL: repo.SshUrl, - URL: repo.HtmlUrl, - Role: &model.Perm{ - Admin: repo.Permissions.Admin, - Write: repo.Permissions.Push, - Read: repo.Permissions.Pull, - }, - } - - repos = append(repos, &repo) - } - - return repos, err -} - -// GetScript fetches the build script (.drone.yml) from the remote -// repository and returns a byte array -func (r *Gogs) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { - var client = gogs.NewClient(r.URL, user.Access) - return client.GetFile(repo.Owner, repo.Name, hook.Sha, ".drone.yml") -} - -// Activate activates a repository -func (r *Gogs) Activate(user *model.User, repo *model.Repo, link string) error { - var client = gogs.NewClient(r.URL, user.Access) - - var config = map[string]string{ - "url": link, - "secret": r.Secret, - "content_type": "json", - } - var hook = gogs.CreateHookOption{ - Type: "gogs", - Config: config, - Active: true, - } - - _, err := client.CreateRepoHook(repo.Owner, repo.Name, hook) - 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 (r *Gogs) Deactivate(user *model.User, repo *model.Repo, link string) error { - return fmt.Errorf("Remove %#v in gogs not implemented", *repo) -} - -// ParseHook parses the post-commit hook from the Request body -// and returns the required data in a standard format. -func (r *Gogs) ParseHook(req *http.Request) (*model.Hook, error) { - defer req.Body.Close() - var payloadbytes, _ = ioutil.ReadAll(req.Body) - var payload, err = gogs.ParseHook(payloadbytes) - if err != nil { - return nil, err - } - - // verify the payload has the minimum amount of required data. - if payload.Repo == nil || payload.Commits == nil || len(payload.Commits) == 0 { - return nil, fmt.Errorf("Invalid Gogs post-commit Hook. Missing Repo or Commit data.") - } - - if payload.Secret != r.Secret { - return nil, fmt.Errorf("Payload secret does not match stored secret") - } - - return &model.Hook{ - Owner: payload.Repo.Owner.UserName, - Repo: payload.Repo.Name, - Sha: payload.Commits[0].Id, - Branch: payload.Branch(), - Author: payload.Commits[0].Author.UserName, - Timestamp: time.Now().UTC().String(), - Message: payload.Commits[0].Message, - }, nil -} - -func (r *Gogs) OpenRegistration() bool { - return r.Open -} - -func (r *Gogs) GetToken(user *model.User) (*model.Token, error) { - return nil, nil -} diff --git a/plugin/remote/gogs/register.go b/plugin/remote/gogs/register.go deleted file mode 100644 index aa2479e6f..000000000 --- a/plugin/remote/gogs/register.go +++ /dev/null @@ -1,24 +0,0 @@ -package gogs - -import ( - "github.com/drone/config" - "github.com/drone/drone/plugin/remote" -) - -var ( - gogsUrl = config.String("gogs-url", "") - gogsSecret = config.String("gogs-secret", "") - gogsOpen = config.Bool("gogs-open", false) -) - -// Registers the Gogs plugin using the default -// settings from the config file or environment -// variables. -func Register() { - if len(*gogsUrl) == 0 { - return - } - remote.Register( - New(*gogsUrl, *gogsSecret, *gogsOpen), - ) -} diff --git a/plugin/remote/remote.go b/plugin/remote/remote.go deleted file mode 100644 index 862e8c15d..000000000 --- a/plugin/remote/remote.go +++ /dev/null @@ -1,73 +0,0 @@ -package remote - -import ( - "net/http" - - "github.com/drone/drone/shared/model" -) - -type Remote interface { - // Authorize handles authentication with thrid party remote systems, - // such as github or bitbucket, and returns user data. - Authorize(w http.ResponseWriter, r *http.Request) (*model.Login, error) - - // GetKind returns the kind of plugin - GetKind() string - - // GetHost returns the hostname of the remote service. - GetHost() string - - // GetRepos fetches all repositories that the specified - // user has access to in the remote system. - GetRepos(user *model.User) ([]*model.Repo, error) - - // GetScript fetches the build script (.drone.yml) from the remote - // repository and returns in string format. - GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) - - // Activate activates a repository by creating the post-commit hook and - // adding the SSH deploy key, if applicable. - Activate(user *model.User, repo *model.Repo, 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(user *model.User, repo *model.Repo, link string) error - - // ParseHook parses the post-commit hook from the Request body - // and returns the required data in a standard format. - ParseHook(r *http.Request) (*model.Hook, error) - - // Registration returns true if open registration is allowed - OpenRegistration() bool - - // Get token - GetToken(*model.User) (*model.Token, error) -} - -// List of registered plugins. -var remotes []Remote - -// Register registers a plugin by name. -// -// All plugins must be registered when the application -// initializes. This should not be invoked while the application -// is running, and is not thread safe. -func Register(remote Remote) { - remotes = append(remotes, remote) -} - -// List Registered remote plugins -func Registered() []Remote { - return remotes -} - -// Lookup gets a plugin by name. -func Lookup(name string) Remote { - for _, remote := range remotes { - if remote.GetKind() == name || - remote.GetHost() == name { - return remote - } - } - return nil -} diff --git a/plugin/report/README.md b/plugin/report/README.md deleted file mode 100644 index 03260a5b1..000000000 --- a/plugin/report/README.md +++ /dev/null @@ -1,5 +0,0 @@ -cobertura.go -coveralls.go -gocov.go -junit.go -phpunit.go \ No newline at end of file diff --git a/server/app/favicon.ico b/server/app/favicon.ico deleted file mode 100644 index 81b66798a..000000000 Binary files a/server/app/favicon.ico and /dev/null differ diff --git a/server/app/favicon.png b/server/app/favicon.png deleted file mode 100644 index 661fa5ef8..000000000 Binary files a/server/app/favicon.png and /dev/null differ diff --git a/server/app/index.html b/server/app/index.html deleted file mode 100644 index 31b95a09f..000000000 --- a/server/app/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/server/app/robots.txt b/server/app/robots.txt deleted file mode 100644 index 7d329b1db..000000000 --- a/server/app/robots.txt +++ /dev/null @@ -1 +0,0 @@ -User-agent: * diff --git a/server/app/scripts/app.js b/server/app/scripts/app.js deleted file mode 100644 index 097e74aca..000000000 --- a/server/app/scripts/app.js +++ /dev/null @@ -1,248 +0,0 @@ -'use strict'; - -var app = angular.module('app', [ - 'ngRoute', - 'ui.filters', - 'angularMoment' -]); - -angular.module('app').constant('angularMomentConfig', { - preprocess: 'unix' -}); - -// First, parse the query string -var params = {}, queryString = location.hash.substring(1), - regex = /([^&=]+)=([^&]*)/g, m; -while (m = regex.exec(queryString)) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); -} - - -// if the user is authenticated we should add Basic -// auth token to each request. -if (params.access_token) { - localStorage.setItem("access_token", params.access_token); - history.replaceState({}, document.title, location.pathname); -} - - - -app.config(['$routeProvider', '$locationProvider', '$httpProvider', function($routeProvider, $locationProvider, $httpProvider) { - $routeProvider.when('/', { - templateUrl: '/static/views/home.html', - controller: 'HomeController', - title: 'Dashboard', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/sync', { - templateUrl: '/static/views/sync.html', - controller: 'SyncController', - title: 'Sync' - }) - .when('/login', { - templateUrl: '/static/views/login.html', - controller: 'LoginController', - title: 'Login' - }) - .when('/logout', { - templateUrl: '/static/views/logout.html', - controller: 'LogoutController', - title: 'Logout' - }) - .when('/gitlab', { - templateUrl: '/static/views/login_gitlab.html', - title: 'GitLab Login', - }) - .when('/gogs', { - templateUrl: '/static/views/login_gogs.html', - title: 'Gogs Setup', - }) - .when('/setup', { - templateUrl: '/static/views/setup.html', - controller: 'SetupController', - title: 'Setup' - }) - .when('/setup/:remote', { - templateUrl: '/static/views/setup.html', - controller: 'SetupController', - title: 'Setup' - }) - .when('/account/profile', { - templateUrl: '/static/views/account.html', - controller: 'UserController', - title: 'Profile', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/account/repos', { - templateUrl: '/static/views/repo_list.html', - controller: 'AccountReposController', - title: 'Repositories', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/admin/users/add', { - templateUrl: '/static/views/users_add.html', - controller: 'UserAddController', - title: 'Add User', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/admin/users/:host/:login', { - templateUrl: '/static/views/users_edit.html', - controller: 'UserEditController', - title: 'Edit User', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/admin/users', { - templateUrl: '/static/views/users.html', - controller: 'UsersController', - title: 'System Users', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/admin/settings', { - templateUrl: '/static/views/config.html', - controller: 'ConfigController', - title: 'System Settings', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/:remote/:owner/:name/settings', { - templateUrl: '/static/views/repo_edit.html', - controller: 'RepoConfigController', - title: 'Repository Settings', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/:remote/:owner/:name/:branch*\/:commit', { - templateUrl: '/static/views/commit.html', - controller: 'CommitController', - title: 'Recent Commits', - resolve: { - user: function(authService) { - return authService.getUser(); - } - } - }) - .when('/:remote/:owner/:name', { - templateUrl: '/static/views/repo.html', - controller: 'RepoController', - title: 'Recent Commits', - resolve: { - user: function(authService) { - return authService.getUser(); - }, - repo: function($route, repos) { - var remote = $route.current.params.remote; - var owner = $route.current.params.owner; - var name = $route.current.params.name; - return repos.getRepo(remote, owner, name); - } - } - }); - - // use the HTML5 History API - $locationProvider.html5Mode(true); - - $httpProvider.defaults.headers.common.Authorization = 'Bearer '+localStorage.getItem('access_token'); - - $httpProvider.interceptors.push(function($q, $location) { - return { - 'responseError': function(rejection) { - if (rejection.status == 401 && rejection.config.url != "/api/user") { - $location.path('/login'); - } - return $q.reject(rejection); - } - }; - }); -}]); - -/* also see https://coderwall.com/p/vcfo4q */ -app.run(['$location', '$rootScope', '$routeParams', 'feed', 'stdout', function($location, $rootScope, $routeParams, feed, stdout) { - - $rootScope.$on('$routeChangeStart', function (event, next) { - feed.unsubscribe(); - stdout.unsubscribe(); - }); - - $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { - document.title = current.$$route.title + ' · drone.io'; - }); - -}]); - - - - -/* Controllers */ - - - - -app.controller("AccountReposController", function($scope, $http, $location, user) { - - $scope.user = user; - - $scope.syncUser = function() { - $http({method: 'POST', url: '/api/user/sync' }).success(function(data){ - $location.search('return_to', $location.$$path).path('/sync') - }).error(function(data, status){ - if (status == 409) { - $scope.msg = 'already' - } else { - $scope.msg = 'bad' - } - $scope.$apply(); - }); - } - - // get the user details - $http({method: 'GET', url: '/api/user/repos'}). - success(function(data, status, headers, config) { - $scope.repos = (typeof data==='string')?[]:data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - $scope.active=""; - $scope.remote=""; - $scope.byActive = function(entry){ - switch (true) { - case $scope.active == "true" && !entry.active: return false; - case $scope.active == "false" && entry.active: return false; - } - return true; - }; - $scope.byRemote = function(entry){ - return $scope.remote == "" || $scope.remote == entry.remote; - }; -}); diff --git a/server/app/scripts/commit_updates.js b/server/app/scripts/commit_updates.js deleted file mode 100644 index dd4492000..000000000 --- a/server/app/scripts/commit_updates.js +++ /dev/null @@ -1,65 +0,0 @@ -;// Live commit updates - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function () { - Drone.Console = function() { - this.lineFormatter = new Drone.LineFormatter(); - } - - Drone.Console.prototype = { - lineBuffer: "", - autoFollow: false, - - start: function(el) { - if(typeof(el) === 'string') { - this.el = document.getElementById(el); - } else { - this.el = el; - } - - this.update(); - }, - - stop: function() { - this.stoppingRefresh = true; - }, - - update: function() { - if(this.lineBuffer.length > 0) { - this.el.innerHTML += this.lineBuffer; - this.lineBuffer = ''; - - if (this.autoFollow) { - window.scrollTo(0, document.body.scrollHeight); - } - } - - if(this.stoppingRefresh) { - this.stoppingRefresh = false; - } else { - window.requestAnimationFrame(this.updateScreen.bind(this)); - } - }, - - write: function(e) { - this.lineBuffer += this.lineFormatter.format(e.data); - } - }; - - // Polyfill rAF for older browsers - window.requestAnimationFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - function(callback, element) { - return window.setTimeout(function() { - callback(+new Date()); - }, 1000 / 60); - }; - - window.cancelRequestAnimationFrame = window.cancelRequestAnimationFrame || - window.cancelWebkitRequestAnimationFrame || - function(fn) { - window.clearTimeout(fn); - }; - -})(); diff --git a/server/app/scripts/controllers/build.js b/server/app/scripts/controllers/build.js deleted file mode 100644 index ad9a93a7c..000000000 --- a/server/app/scripts/controllers/build.js +++ /dev/null @@ -1 +0,0 @@ -'use strict'; diff --git a/server/app/scripts/controllers/commit.js b/server/app/scripts/controllers/commit.js deleted file mode 100644 index 1db3594a0..000000000 --- a/server/app/scripts/controllers/commit.js +++ /dev/null @@ -1,95 +0,0 @@ -/*global angular, Drone, console */ -angular.module('app').controller("CommitController", function ($scope, $http, $route, $routeParams, stdout, feed) { - 'use strict'; - - var remote = $routeParams.remote, - owner = $routeParams.owner, - name = $routeParams.name, - branch = $routeParams.branch, - commit = $routeParams.commit, - // Create lineFormatter and outputElement since we need them anyway. - lineFormatter = new Drone.LineFormatter(), - outputElement = angular.element(document.querySelector('#output')); - - var connectRemoteConsole = function (id) { - // Clear console output if connecting to new remote console (rebuild) - if (!outputElement.html() !== 0) { - outputElement.empty(); - } - // Subscribe to stdout of the remote build - stdout.subscribe(id, function (out) { - // Append new output to console - outputElement.append(lineFormatter.format(out)); - // Scroll if following - if ($scope.following) { - window.scrollTo(0, document.body.scrollHeight); - } - }); - }; - - // Subscribe to feed so we can update gui if changes to the commit happen. (Build finished, Rebuild triggered, change from Pending to Started) - feed.subscribe(function (item) { - // If event is part of the active commit currently showing. - if (item.commit.sha === commit && - item.commit.branch === branch) { - // If new status is Started, connect to remote console to get live output - if (item.commit.status === "Started") { - connectRemoteConsole(item.commit.id); - } - $scope.commit = item.commit; - $scope.$apply(); - - } else { - // we trigger an toast notification so the - // user is aware another build started - - } - }); - - // Load the repo meta-data - $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name}). - success(function (data, status, headers, config) { - $scope.repo = data; - }). - error(function (data, status, headers, config) { - console.log(data); - }); - - // Load the repo commit data - $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit}). - success(function (data, status, headers, config) { - $scope.commit = data; - - // If build has already finished, load console output from database - if (data.status !== 'Started' && data.status !== 'Pending') { - $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit + "/console"}). - success(function (data, status, headers, config) { - outputElement.append(lineFormatter.format(data)); - }). - error(function (data, status, headers, config) { - console.log(data); - }); - return; - // If build is currently running, connect to remote console; - } else if (data.status === 'Started') { - connectRemoteConsole(data.id); - } - - }). - error(function (data, status, headers, config) { - console.log(data); - }); - - $scope.following = false; - $scope.follow = function () { - $scope.following = true; - window.scrollTo(0, document.body.scrollHeight); - }; - $scope.unfollow = function () { - $scope.following = false; - }; - - $scope.rebuildCommit = function () { - $http({method: 'POST', url: '/api/repos/' + remote + '/' + owner + '/' + name + '/branches/' + branch + '/commits/' + commit + '?action=rebuild' }); - }; -}); diff --git a/server/app/scripts/controllers/conf.js b/server/app/scripts/controllers/conf.js deleted file mode 100644 index 4cc8202f6..000000000 --- a/server/app/scripts/controllers/conf.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -angular.module('app').controller("ConfigController", function($scope, $http, remotes) { - $scope.state=0; - - $scope.user = remotes.get().success(function (data) { - $scope.remotes = (typeof data==="string")?[]:data; - $scope.state = 1; - - // loop through the remotes and add each - // remote to the page scope. - for (remote in $scope.remotes) { - switch (remote.type) { - case 'github.com': - $scope.github = remote; - break; - - case 'enterprise.github.com': - $scope.githubEnterprise = remote; - break; - - case 'gitlab.com': - $scope.gitlab = remote; - break; - - case 'bitbucket.org': - $scope.bitbucket = remote; - break; - - case 'stash.atlassian.com': - $scope.stash = remote; - break; - case 'gogs': - $scope.gogs = remote; - break; - } - } - }) - .error(function (error) { - $scope.remotes = []; - $scope.state = 1; - }); -}); diff --git a/server/app/scripts/controllers/home.js b/server/app/scripts/controllers/home.js deleted file mode 100644 index 953fd9704..000000000 --- a/server/app/scripts/controllers/home.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -angular.module('app').controller("HomeController", function($scope, $http, $location, feed) { - - feed.subscribe(function(item) { - // todo toast notification - }); - - $http({method: 'GET', url: '/api/user/feed'}). - success(function(data, status, headers, config) { - $scope.feed = (typeof data==='string')?[]:data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - $scope.syncUser = function() { - $http({method: 'POST', url: '/api/user/sync' }).success(function(data){ - $location.search('return_to', $location.$$path).path('/sync') - }).error(function(data, status){ - if (status == 409) { - $scope.msg = 'already' - } else { - $scope.msg = 'bad' - } - $scope.$apply(); - }); - } - - $http({method: 'GET', url: '/api/user/repos'}). - success(function(data, status, headers, config) { - $scope.repos = (typeof data==='string')?[]:data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); -}); diff --git a/server/app/scripts/controllers/login.js b/server/app/scripts/controllers/login.js deleted file mode 100644 index fcae0f331..000000000 --- a/server/app/scripts/controllers/login.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -angular.module('app').controller("LoginController", function($scope, $http, remotes) { - $scope.state=0 - $scope.user = remotes.getLogins().success(function (data) { - $scope.remotes = (typeof data==="string")?[]:data; - $scope.state = 1; - }) - .error(function (error) { - $scope.remotes = []; - $scope.state = 1; - }); -}); \ No newline at end of file diff --git a/server/app/scripts/controllers/logout.js b/server/app/scripts/controllers/logout.js deleted file mode 100644 index 6004ef636..000000000 --- a/server/app/scripts/controllers/logout.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -angular.module('app').controller("LogoutController", function() { - console.log("logging out") - localStorage.removeItem("access_token"); - window.location.href = "/login"; -}); \ No newline at end of file diff --git a/server/app/scripts/controllers/main.js b/server/app/scripts/controllers/main.js deleted file mode 100644 index 48489fd01..000000000 --- a/server/app/scripts/controllers/main.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -angular.module('app').controller("MainCtrl", function($scope, $http, users) { - $scope.state=0 - $scope.user = users.getCurrent().success(function (user) { - $scope.user = user; - $scope.state = 1; - }) - .error(function (error) { - $scope.user = undefined; - $scope.state = 1; - }); -}); diff --git a/server/app/scripts/controllers/repo.js b/server/app/scripts/controllers/repo.js deleted file mode 100644 index 89017710a..000000000 --- a/server/app/scripts/controllers/repo.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict'; - -angular.module('app').controller("RepoController", function($scope, $filter, $http, $routeParams, $route, repos, feed, repo) { - $scope.repo = repo; - $scope.activating = false; - $scope.build_filter = 'build_history'; - $scope.layout = 'grid'; - - // subscribes to the global feed to receive - // build status updates. - feed.subscribe(function(item) { - if (item.repo.host == repo.host && - item.repo.owner == repo.owner && - item.repo.name == repo.name) { - // display a toast message with the - // commit details, allowing the user to - // reload the page. - - // Try find an existing commit for this SHA. If found, replace it - var sha_updated = $scope.commits.some(function(element, index) { - if (element.sha == item.commit.sha) - $scope.commits[index] = item.commit; - return element.sha == item.commit.sha; - }); - - // Add a build message if the SHA couldn't be found and the new build status is 'Started' - if ( ! sha_updated && item.commit.status == 'Started') { - // $scope.commits.unshift(item.commit); - $scope.msg = item; - } - - $scope.$apply(); - } else { - // we trigger a toast (or html5) notification so the - // user is aware another build started - - } - }); - - - // load the repo commit feed - repos.commits(repo.host, repo.owner, repo.name).success(function (commits) { - $scope.commits = (typeof commits==='string')?[]:commits; - $scope.state = 1; - }) - .error(function (error) { - $scope.commits = undefined; - $scope.state = 1; - }); - - //$http({method: 'GET', url: '/api/repos/'+repo.host+'/'+repo.owner+"/"+repo.name+"/feed"}). - // success(function(data, status, headers, config) { - // $scope.commits = (typeof data==='string')?[]:data; - // }). - // error(function(data, status, headers, config) { - // console.log(data); - // }); - - $scope.activate = function() { - $scope.activating = true; - - // request to create a new repository - $http({method: 'POST', url: '/api/repos/'+repo.host+'/'+repo.owner+"/"+repo.name }). - success(function(data, status, headers, config) { - $scope.repo = data; - $scope.activating = false; - }). - error(function(data, status, headers, config) { - $scope.failure = data; - $scope.activating = false; - }); - }; - - - $scope.reload = function() { - $route.reload(); - }; - - //$scope.activate = function() { - // repos.activate($scope.host, $scope.name).success(function () { - // window.location.href="/admin/users"; - // }) - // .error(function (error) { - // console.log(error); - // }); - //}; - - $scope.setCommitFilter = function(filter) { - $scope.build_filter = filter; - } - - $scope.setLayout = function(layout) { - $scope.layout = layout; - } - - $scope.filteredCommits = function() { - var filteredCommits; - switch ($scope.build_filter) { - // Latest commit for each branch (excluding PR branches) - case 'branch_summary': - filteredCommits = $filter('filter')($scope.commits, { pull_request: '' }, true); - filteredCommits = $filter('unique')($scope.commits, 'branch'); - break; - // Latest commit for each PR - case 'pull_requests': - filteredCommits = $filter('pullRequests')($scope.commits); - filteredCommits = $filter('unique')(filteredCommits, 'pull_request'); - break; - // All commits for a full build history - default: - filteredCommits = $scope.commits; - } - - return filteredCommits; - } -}); - - - - -angular.module('app').controller("RepoConfigController", function($scope, $http, $timeout, $routeParams, user) { - $scope.user = user; - $scope.saving = false; - - var remote = $routeParams.remote; - var owner = $routeParams.owner; - var name = $routeParams.name; - - // load the repo meta-data - // request admin details for the repository as well. - $http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name+"?admin=1"}). - success(function(data, status, headers, config) { - $scope.repo = data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - $scope.save = function() { - $scope.saving = true; - - // request to create a new repository - $http({method: 'PUT', url: '/api/repos/'+remote+'/'+owner+"/"+name, data: $scope.repo }). - success(function(data, status, headers, config) { - delete $scope.failure; - - // yes, for UX reasons we make this request look like it - // is taking longer than it really is. Otherwise the loading - // button just instantly flickers. - $timeout(function(){ - $scope.saving = false; - }, 1500); - }). - error(function(data, status, headers, config) { - $scope.failure = data; - $scope.saving = false; - }); - - }; -}); \ No newline at end of file diff --git a/server/app/scripts/controllers/setup.js b/server/app/scripts/controllers/setup.js deleted file mode 100644 index 07469ad33..000000000 --- a/server/app/scripts/controllers/setup.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -angular.module('app').controller("SetupController", function($scope, $http, $routeParams, $window, $location) { - - // create a remote that will be populated - // and persisted to the database. - $scope.remote = {}; - $scope.remote.type = $routeParams.remote; - $scope.remote.register = false; - $scope.window = $window - - // pre-populate the form if the remote - // type is selected and is a cloud service - // with a known URL and standard configuration. - switch($scope.remote.type) { - case 'github.com': - $scope.remote.type = "github.com" - $scope.remote.url = "https://github.com"; - $scope.remote.api = "https://api.github.com"; - break; - case 'bitbucket.org': - $scope.remote.url = "https://bitbucket.org"; - $scope.remote.api = "https://bitbucket.org"; - break; - } - - // todo(bradrydzewski) move this to the remote.js service. - $scope.save = function() { - // request to create a new repository - $http({method: 'POST', url: '/api/remotes', data: $scope.remote }). - success(function(data, status, headers, config) { - delete $scope.failure; - $location.path("/login"); - }). - error(function(data, status, headers, config) { - $scope.failure = data; - }); - }; -}); \ No newline at end of file diff --git a/server/app/scripts/controllers/sync.js b/server/app/scripts/controllers/sync.js deleted file mode 100644 index 9d3521f73..000000000 --- a/server/app/scripts/controllers/sync.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -angular.module('app').controller("SyncController", function($scope, $http, $interval, $location, $routeParams, users) { - var return_to = $routeParams.return_to - var stop = $interval(function() { - // todo(bradrydzewski) We should poll the user to see if the - // sync process is complete, using the user.syncing variable. - $interval.cancel(stop); - if (return_to != undefined) { - $location.$$search = {} - $location.path(return_to); - } else { - $location.path("/"); - } - }, 5000); -}); diff --git a/server/app/scripts/controllers/user.js b/server/app/scripts/controllers/user.js deleted file mode 100644 index e8b5769c3..000000000 --- a/server/app/scripts/controllers/user.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -angular.module('app').controller("UserController", function($scope, $http, user) { - - $scope.account = user; - - // get the user details - $http({method: 'GET', url: '/api/user'}). - success(function(data, status, headers, config) { - $scope.user = data; - $scope.userTemp = { - email : $scope.user.email, - name : $scope.user.name - }; - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - $scope.save = function() { - // request to create a new repository - $http({method: 'PUT', url: '/api/user', data: $scope.userTemp }). - success(function(data, status, headers, config) { - delete $scope.failure; - $scope.user = data; - }). - error(function(data, status, headers, config) { - $scope.failure = data; - }); - }; - $scope.cancel = function() { - delete $scope.failure; - $scope.userTemp = { - email : $scope.user.email, - name : $scope.user.name - }; - }; -}); \ No newline at end of file diff --git a/server/app/scripts/controllers/users.js b/server/app/scripts/controllers/users.js deleted file mode 100644 index 740363825..000000000 --- a/server/app/scripts/controllers/users.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -angular.module('app').controller("UsersController", function($scope, $http, user) { - - $scope.user = user; - - $http({method: 'GET', url: '/api/users'}). - success(function(data, status, headers, config) { - $scope.users = data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); -}); - -angular.module('app').controller("UserAddController", function($scope, $http, users) { - // set the default host to github ... however ... - // eventually we can improve this logic to use the hostname - // of the currently authenticated user. - $scope.host='github.com'; - $scope.name=''; - - $scope.create = function() { - users.create($scope.host, $scope.name).success(function () { - window.location.href="/admin/users"; - }) - .error(function (error) { - console.log(error); - }); - }; -}); - -angular.module('app').controller("UserEditController", function($scope, $http, $routeParams, users) { - - var host = $routeParams.host; - var name = $routeParams.login; - - users.get(host, name).success(function (user) { - $scope.account = user; - $scope.state = 1; - }) - .error(function (error) { - $scope.account = undefined; - $scope.state = 1; - }); - - $scope.delete = function() { - users.delete(host, name).success(function () { - window.location.href="/admin/users"; - }) - .error(function (error) { - console.log(error); - }); - }; -}); \ No newline at end of file diff --git a/server/app/scripts/filters/filters.js b/server/app/scripts/filters/filters.js deleted file mode 100644 index 2100369bd..000000000 --- a/server/app/scripts/filters/filters.js +++ /dev/null @@ -1,208 +0,0 @@ -'use strict'; - -(function () { - - /** - * fromNow is a helper function that returns a human readable - * string for the elapsed time between the given unix date and the - * current time (ex. 10 minutes ago). - */ - function fromNow() { - return function(date) { - return moment(new Date(date*1000)).fromNow(); - } - } - - /** - * toDuration is a helper function that returns a human readable - * string for the given duration in seconds (ex. 1 hour and 20 minutes). - */ - function toDuration() { - return function(seconds) { - return moment.duration(seconds, "seconds").humanize(); - } - } - - /** - * toDate is a helper function that returns a human readable - * string for the given unix date. - */ - function toDate() { - return function(date) { - return moment(new Date(date*1000)).format('ll'); - } - } - - /** - * shortHash is a helper function that returns the shortened - * version of a commit sha, similar to the --short flag. - */ - function shortHash() { - return function(sha) { - if (sha === undefined) { return ""; } - return sha.substr(0,10); - } - } - - /** - * gravatar is a helper function that return the user's gravatar - * image URL given an email hash. - */ - function gravatar() { - return function(hash) { - if (hash === undefined) { return ""; } - return "https://secure.gravatar.com/avatar/"+hash+"?s=48&d=mm"; - } - } - - /** - * gravatarLarge is a helper function that return the user's gravatar - * image URL given an email hash. - */ - function gravatarLarge() { - return function(hash) { - if (hash === undefined) { return ""; } - return "https://secure.gravatar.com/avatar/"+hash+"?s=128&d=mm"; - } - } - - /** - * fullName is a helper funcation that returns the full name (slug) - * for the given repository (ie drone/drone) - */ - function fullName() { - return function(repo) { - if (repo === undefined) { return ""; } - return repo.owner+"/"+repo.name; - } - } - - /** - * fullName is a helper funcation that returns the full, canonical - * path for the given repository (ie drone/drone) - */ - function fullPath() { - return function(repo) { - if (repo === undefined) { return ""; } - return repo.host+"/"+repo.owner+"/"+repo.name; - } - } - - /** - * badgeMarkdown is a helper funcation that returns a markdown string - * for the given repository's build status badge. - */ - function badgeMarkdown() { - return function(repo) { - if (repo === undefined) { return ""; } - var scheme = window.location.protocol; - var host = window.location.host; - var path = repo.host+'/'+repo.owner+'/'+repo.name; - return '[![Build Status]('+scheme+'//'+host+'/api/badge/'+path+'/status.svg?branch=master)]('+scheme+'//'+host+'/'+path+')' - } - } - - /** - * badgeMarkup is a helper funcation that returns an html string - * for the given repository's build status badge. - */ - function badgeMarkup() { - return function(repo) { - if (repo === undefined) { return ""; } - var scheme = window.location.protocol; - var host = window.location.host; - var path = repo.host+'/'+repo.owner+'/'+repo.name; - return '[![Build Status]('+scheme+'//'+host+'/api/badge/'+path+'/status.svg?branch=master)]('+scheme+'//'+host+'/'+path+')' - } - } - - /** - * pullRequests is a helper funcation that filters a list of commits - * and returns the subset of those commits that are pull requests. - */ - function pullRequests() { - return function(commits) { - var filtered = []; - angular.forEach(commits, function(commit) { - if(commit.pull_request.length != 0) { - filtered.push(commit); - } - }); - return filtered; - } - } - - /** - * remoteName is a helper funcation that returns a user-friendly - * name for the given remote type. - */ - function remoteName() { - return function(name) { - switch (name) { - case 'gitlab.com' : return 'GitLab'; - case 'github.com' : return 'GitHub'; - case 'enterprise.github.com' : return 'GitHub Enterprise'; - case 'bitbucket.org' : return 'Bitbucket'; - case 'stash.atlassian.com' : return 'Atlassian Stash'; - case 'gogs' : return 'Gogs'; - } - } - } - - /** - * remoteIcon is a helper funcation that returns an icon to represent - * the given remote type. - */ - function remoteIcon() { - return function(name) { - switch (name) { - case 'gitlab.com' : return 'fa-git-square'; - case 'github.com' : return 'fa-github-square'; - case 'enterprise.github.com' : return 'fa-github-square'; - case 'bitbucket.org' : return 'fa-bitbucket-square'; - case 'stash.atlassian.com' : return 'fa-bitbucket-square'; - case 'gogs' : return 'fa-git-square'; - } - } - } - - /** - * unique is a helper funcation that returns a unique array - * of fields from the given list of complex data strucrures. - * I copied it from Stackoverflow, so don't ask me how it works... - */ - function unique() { - return function(input, key) { - var unique = {}; - var uniqueList = []; - if (input == undefined) { - return uniqueList; - } - for(var i = 0; i < input.length; i++){ - if(typeof unique[input[i][key]] == "undefined"){ - unique[input[i][key]] = ""; - uniqueList.push(input[i]); - } - } - return uniqueList; - }; - } - - angular - .module('app') - .filter('badgeMarkdown', badgeMarkdown) - .filter('badgeMarkup', badgeMarkup) - .filter('fromNow', fromNow) - .filter('fullName', fullName) - .filter('fullPath', fullPath) - .filter('gravatar', gravatar) - .filter('gravatarLarge', gravatarLarge) - .filter('pullRequests', pullRequests) - .filter('remoteIcon', remoteIcon) - .filter('remoteName', remoteName) - .filter('shortHash', shortHash) - .filter('toDate', toDate) - .filter('toDuration', toDuration) - .filter('unique', unique); - -})(); diff --git a/server/app/scripts/line_formatter.js b/server/app/scripts/line_formatter.js deleted file mode 100644 index 5bd8635a5..000000000 --- a/server/app/scripts/line_formatter.js +++ /dev/null @@ -1,68 +0,0 @@ -;// Format ANSI to HTML - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function() { - Drone.LineFormatter = function() {}; - - Drone.LineFormatter.prototype = { - regex: /\u001B\[([0-9]+;?)*[Km]/g, - styles: [], - - format: function(s) { - // Check for newline and early exit? - s = s.replace(//g, ">"); - - var output = ""; - var current = 0; - while (m = this.regex.exec(s)) { - var part = s.substring(current, m.index); - current = this.regex.lastIndex; - - var token = s.substr(m.index, this.regex.lastIndex - m.index); - var code = token.substr(2, token.length-2); - - var pre = ""; - var post = ""; - - switch (code) { - case 'm': - case '0m': - case '39m': - case '49m': - var len = this.styles.length; - for (var i=0; i < len; i++) { - this.styles.pop(); - post += "" - } - break; - case '30;42m': pre = ''; break; - case '36m': - case '36;1m': pre = ''; break; - case '31m': - case '31;31m': pre = ''; break; - case '33m': - case '33;33m': pre = ''; break; - case '32m': - case '0;32m': pre = ''; break; - case '90m': pre = ''; break; - case 'K': - case '0K': - case '1K': - case '2K': break; - } - - if (pre !== "") { - this.styles.push(pre); - } - - output += part + pre + post; - } - - var part = s.substring(current, s.length); - output += part; - return output; - } - }; -})(); diff --git a/server/app/scripts/main.js b/server/app/scripts/main.js deleted file mode 100644 index 45d513fab..000000000 --- a/server/app/scripts/main.js +++ /dev/null @@ -1,164 +0,0 @@ -;// Format ANSI to HTML - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function() { - Drone.LineFormatter = function() {}; - - Drone.LineFormatter.prototype = { - regex: /\u001B\[([0-9]+;?)*[Km]/g, - styles: [], - - format: function(s) { - // Check for newline and early exit? - s = s.replace(//g, ">"); - - var output = ""; - var current = 0; - while (m = this.regex.exec(s)) { - var part = s.substring(current, m.index); - current = this.regex.lastIndex; - - var token = s.substr(m.index, this.regex.lastIndex - m.index); - var code = token.substr(2, token.length-2); - - var pre = ""; - var post = ""; - - switch (code) { - case 'm': - case '0m': - var len = this.styles.length; - for (var i=0; i < len; i++) { - this.styles.pop(); - post += "" - } - break; - case '30;42m': pre = ''; break; - case '36m': - case '36;1m': pre = ''; break; - case '31m': - case '31;31m': pre = ''; break; - case '33m': - case '33;33m': pre = ''; break; - case '32m': - case '0;32m': pre = ''; break; - case '90m': pre = ''; break; - case 'K': - case '0K': - case '1K': - case '2K': break; - } - - if (pre !== "") { - this.styles.push(pre); - } - - output += part + pre + post; - } - - var part = s.substring(current, s.length); - output += part; - return output; - } - }; -})(); -;// Live commit updates - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function () { - Drone.CommitUpdates = function(socket) { - if(typeof(socket) === "string") { - var url = [(window.location.protocol == 'https:' ? 'wss' : 'ws'), - '://', - window.location.host, - socket].join('') - this.socket = new WebSocket(url); - } else { - this.socket = socket; - } - - this.lineFormatter = new Drone.LineFormatter(); - this.attach(); - } - - Drone.CommitUpdates.prototype = { - lineBuffer: "", - autoFollow: false, - - startOutput: function(el) { - if(typeof(el) === 'string') { - this.el = document.getElementById(el); - } else { - this.el = el; - } - - if(!this.reqId) { - this.updateScreen(); - } - }, - - stopOutput: function() { - this.stoppingRefresh = true; - }, - - attach: function() { - this.socket.onopen = this.onOpen; - this.socket.onerror = this.onError; - this.socket.onmessage = this.onMessage.bind(this); - this.socket.onclose = this.onClose; - }, - - updateScreen: function() { - if(this.lineBuffer.length > 0) { - this.el.innerHTML += this.lineBuffer; - this.lineBuffer = ''; - - if (this.autoFollow) { - window.scrollTo(0, document.body.scrollHeight); - } - } - - if(this.stoppingRefresh) { - this.stoppingRefresh = false; - } else { - window.requestAnimationFrame(this.updateScreen.bind(this)); - } - }, - - onOpen: function() { - console.log('output websocket open'); - }, - - onError: function(e) { - console.log('websocket error: ' + e); - }, - - onMessage: function(e) { - this.lineBuffer += this.lineFormatter.format(e.data); - }, - - onClose: function(e) { - console.log('output websocket closed: ' + JSON.stringify(e)); - window.location.reload(); - } - }; - - // Polyfill rAF for older browsers - window.requestAnimationFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - function(callback, element) { - return window.setTimeout(function() { - callback(+new Date()); - }, 1000 / 60); - }; - - window.cancelRequestAnimationFrame = window.cancelRequestAnimationFrame || - window.cancelWebkitRequestAnimationFrame || - function(fn) { - window.clearTimeout(fn); - }; - -})(); \ No newline at end of file diff --git a/server/app/scripts/services/auth.js b/server/app/scripts/services/auth.js deleted file mode 100644 index 0c2b7ccd8..000000000 --- a/server/app/scripts/services/auth.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict;' - -angular.module('app').service('authService', function($q, $http) { - return{ - user : null, - // getUser will retrieve the currently authenticated - // user from the session. If no user is found a 401 - // Not Authorized status will be returned. - getUser : function() { - var _this = this; - var defer = $q.defer(); - - // if the user is already authenticated - if (_this.user != null) { - defer.resolve(_this.user); - } - - // else we need to fetch from the server - $http({method: 'GET', url: '/api/user'}). - success(function(data) { - _this.user=data; - defer.resolve(_this.user); - }). - error(function(data, status) { - _this.user=null; - defer.resolve(); - }); - - // returns a promise that this will complete - // at some future time. - return defer.promise; - } - } -}); \ No newline at end of file diff --git a/server/app/scripts/services/conf.js b/server/app/scripts/services/conf.js deleted file mode 100644 index 9b8b6ed35..000000000 --- a/server/app/scripts/services/conf.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -angular.module('app').service('confService', function($q, $http) { - return{ - getConfig : function() { - var defer = $q.defer(); - var route = '/api/config'; - $http.get(route).success(function(data){ - defer.resolve(data); - }); - return defer.promise; - } - } -}); \ No newline at end of file diff --git a/server/app/scripts/services/feed.js b/server/app/scripts/services/feed.js deleted file mode 100644 index 8d3e085ba..000000000 --- a/server/app/scripts/services/feed.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -angular.module('app').service('feed', ['$http', '$window', function($http, $window) { - - var token = localStorage.getItem('access_token'); - var proto = ($window.location.protocol == 'https:' ? 'wss' : 'ws'); - var route = [proto, "://", $window.location.host, '/api/stream/user?access_token=', token].join(''); - - var wsCallback = undefined; - var ws = new WebSocket(route); - ws.onmessage = function(event) { - var data = angular.fromJson(event.data); - if (wsCallback != undefined) { - wsCallback(data); - } - }; - - this.subscribe = function(callback) { - wsCallback = callback; - }; - - this.unsubscribe = function() { - wsCallback = undefined; - }; -}]); diff --git a/server/app/scripts/services/remote.js b/server/app/scripts/services/remote.js deleted file mode 100644 index 1ea07fff4..000000000 --- a/server/app/scripts/services/remote.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -// Service facilitates interaction with the remote API. -angular.module('app').service('remotes', ['$http', function($http) { - - this.get = function() { - return $http.get('/api/remotes'); - }; - - this.getLogins = function() { - return $http.get('/api/logins'); - }; -}]); \ No newline at end of file diff --git a/server/app/scripts/services/repo.js b/server/app/scripts/services/repo.js deleted file mode 100644 index ab7f2385e..000000000 --- a/server/app/scripts/services/repo.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -// Service facilitates interaction with the repository API. -angular.module('app').service('repos', ['$q', '$http', function($q, $http) { - - // Gets a repository by host, owner and name. - // @deprecated - this.getRepo = function(host, owner, name) { - var defer = $q.defer(); - var route = '/api/repos/'+host+'/'+owner+'/'+name; - $http.get(route).success(function(data){ - defer.resolve(data); - }); - return defer.promise; - }; - - // Gets a repository by host, owner and name. - this.get = function(host, owner, name) { - return $http.get('/api/repos/'+host+'/'+owner+'/'+name); - }; - - // Gets a repository by host, owner and name. - this.commits = function(host, owner, name) { - return $http.get('/api/repos/'+host+'/'+owner+'/'+name+'/commits'); - }; - - // Updates an existing repository - this.update = function(repo) { - // todo(bradrydzewski) add repo to the request body - return $http.post('/api/repos/'+repo.host+'/'+repo.owner+'/'+repo.name); - }; - - // Activates a repository on the backend, registering post-commit - // hooks with the remote hosting service (ie github). - this.activate = function(repo) { - // todo(bradrydzewski) add repo to the request body - return $http.post('/api/repos/'+repo.host+'/'+repo.owner+'/'+repo.name); - }; - - // Deactivate a repository sets the active flag to false, instructing - // the system to ignore all post-commit hooks for the repository. - this.deactivate = function(repo) { - return $http.delete('/api/repos/'+repo.host+'/'+repo.owner+'/'+repo.name); - }; -}]); \ No newline at end of file diff --git a/server/app/scripts/services/stdout.js b/server/app/scripts/services/stdout.js deleted file mode 100644 index 3c9d0f327..000000000 --- a/server/app/scripts/services/stdout.js +++ /dev/null @@ -1,34 +0,0 @@ -/*global angular, WebSocket, localStorage, console */ -angular.module('app').service('stdout', ['$window', function ($window) { - 'use strict'; - - var callback, - websocket, - token = localStorage.getItem('access_token'); - - this.subscribe = function (path, _callback) { - callback = _callback; - - var proto = ($window.location.protocol === 'https:' ? 'wss' : 'ws'), - route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join(''); - - websocket = new WebSocket(route); - websocket.onmessage = function (event) { - if (callback !== undefined) { - callback(event.data); - } - }; - websocket.onclose = function (event) { - console.log('websocket closed at ' + path); - }; - }; - - this.unsubscribe = function () { - callback = undefined; - if (websocket !== undefined) { - console.log('unsubscribing websocket at ' + websocket.url); - websocket.close(); - websocket = undefined; - } - }; -}]); diff --git a/server/app/scripts/services/user.js b/server/app/scripts/services/user.js deleted file mode 100644 index 2185b83e4..000000000 --- a/server/app/scripts/services/user.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -angular.module('app').service('users', ['$http', function($http) { - this.getCurrent = function() { - return $http.get('/api/user'); - }; - this.get = function(host, login) { - return $http.get('/api/users/'+host+'/'+login); - }; - this.create = function(host, login) { - return $http.post('/api/users/'+host+'/'+login); - }; - this.delete = function(host, login) { - return $http.delete('/api/users/'+host+'/'+login); - }; -}]); \ No newline at end of file diff --git a/server/app/scripts/util/commit_updates.js b/server/app/scripts/util/commit_updates.js deleted file mode 100644 index d8fd83fd7..000000000 --- a/server/app/scripts/util/commit_updates.js +++ /dev/null @@ -1,97 +0,0 @@ -;// Live commit updates - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function () { - Drone.CommitUpdates = function(socket) { - if(typeof(socket) === "string") { - var url = [(window.location.protocol == 'https:' ? 'wss' : 'ws'), - '://', - window.location.host, - socket].join('') - this.socket = new WebSocket(url); - } else { - this.socket = socket; - } - - this.lineFormatter = new Drone.LineFormatter(); - this.attach(); - } - - Drone.CommitUpdates.prototype = { - lineBuffer: "", - autoFollow: false, - - startOutput: function(el) { - if(typeof(el) === 'string') { - this.el = document.getElementById(el); - } else { - this.el = el; - } - - if(!this.reqId) { - this.updateScreen(); - } - }, - - stopOutput: function() { - this.stoppingRefresh = true; - }, - - attach: function() { - this.socket.onopen = this.onOpen; - this.socket.onerror = this.onError; - this.socket.onmessage = this.onMessage.bind(this); - this.socket.onclose = this.onClose; - }, - - updateScreen: function() { - if(this.lineBuffer.length > 0) { - this.el.innerHTML += this.lineBuffer; - this.lineBuffer = ''; - - if (this.autoFollow) { - window.scrollTo(0, document.body.scrollHeight); - } - } - - if(this.stoppingRefresh) { - this.stoppingRefresh = false; - } else { - window.requestAnimationFrame(this.updateScreen.bind(this)); - } - }, - - onOpen: function() { - console.log('output websocket open'); - }, - - onError: function(e) { - console.log('websocket error: ' + e); - }, - - onMessage: function(e) { - this.lineBuffer += this.lineFormatter.format(e.data); - }, - - onClose: function(e) { - console.log('output websocket closed: ' + JSON.stringify(e)); - } - }; - - // Polyfill rAF for older browsers - window.requestAnimationFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - function(callback, element) { - return window.setTimeout(function() { - callback(+new Date()); - }, 1000 / 60); - }; - - window.cancelRequestAnimationFrame = window.cancelRequestAnimationFrame || - window.cancelWebkitRequestAnimationFrame || - function(fn) { - window.clearTimeout(fn); - }; - -})(); diff --git a/server/app/scripts/util/line_formatter.js b/server/app/scripts/util/line_formatter.js deleted file mode 100644 index 754e6cee3..000000000 --- a/server/app/scripts/util/line_formatter.js +++ /dev/null @@ -1,66 +0,0 @@ -;// Format ANSI to HTML - -if(typeof(Drone) === 'undefined') { Drone = {}; } - -(function() { - Drone.LineFormatter = function() {}; - - Drone.LineFormatter.prototype = { - regex: /\u001B\[([0-9]+;?)*[Km]/g, - styles: [], - - format: function(s) { - // Check for newline and early exit? - s = s.replace(//g, ">"); - - var output = ""; - var current = 0; - while (m = this.regex.exec(s)) { - var part = s.substring(current, m.index); - current = this.regex.lastIndex; - - var token = s.substr(m.index, this.regex.lastIndex - m.index); - var code = token.substr(2, token.length-2); - - var pre = ""; - var post = ""; - - switch (code) { - case 'm': - case '0m': - var len = this.styles.length; - for (var i=0; i < len; i++) { - this.styles.pop(); - post += "" - } - break; - case '30;42m': pre = ''; break; - case '36m': - case '36;1m': pre = ''; break; - case '31m': - case '31;31m': pre = ''; break; - case '33m': - case '33;33m': pre = ''; break; - case '32m': - case '0;32m': pre = ''; break; - case '90m': pre = ''; break; - case 'K': - case '0K': - case '1K': - case '2K': break; - } - - if (pre !== "") { - this.styles.push(pre); - } - - output += part + pre + post; - } - - var part = s.substring(current, s.length); - output += part; - return output; - } - }; -})(); diff --git a/server/app/styles/base.less b/server/app/styles/base.less deleted file mode 100644 index 705601b64..000000000 --- a/server/app/styles/base.less +++ /dev/null @@ -1,171 +0,0 @@ -// Base less file with helper mixins - -.hidden { - display: none !important; - visibility: hidden; -} - -.invisible { - visibility: hidden; -} - - -.clearfix { - *zoom: 1; - &:before, - &:after { content: ""; display: table; } - &:after { clear: both; } -} - -.nowrap { - white-space:nowrap; -} - -.opacity(@num) { - opacity:@num; - @num2:@num*100; - filter:alpha(opacity=@num2); -} - -.grayscale(@num:1) { - -webkit-filter: grayscale(@num); - -moz-filter: grayscale(@num); - -ms-filter: grayscale(@num); - -o-filter: grayscale(@num); - filter: grayscale(@num); -} - -.transition(@transString: 0) when not (@transString = 0) { - -webkit-transition: @transString; - -moz-transition: @transString; - -ms-transition: @transString; - -o-transition: @transString; - transition: @transString; -} - -.gradient(@c1, @c2, @direction:top){ - background:@c2; - background-image: -webkit-linear-gradient(@direction, @c1, @c2); - background-image: -moz-linear-gradient(@direction, @c1, @c2); - background-image: -ms-linear-gradient(@direction, @c1, @c2); - background-image: -o-linear-gradient(@direction, @c1, @c2); - background-image: linear-gradient(@direction, @c1, @c2); -} - -.gradient2(@c1, @c2, @c3, @direction:top){ - background:@c3; - background-image: -webkit-linear-gradient(@direction, @c1, @c2, @c3); - background-image: -moz-linear-gradient(@direction, @c1, @c2, @c3); - background-image: -ms-linear-gradient(@direction, @c1, @c2, @c3); - background-image: -o-linear-gradient(@direction, @c1, @c2, @c3); - background-image: linear-gradient(@direction, @c1, @c2, @c3); -} - -.rotate(@deg){ - -webkit-transform:rotate(@deg); - -moz-transform:rotate(@deg); - -ms-transform:rotate(@deg); - -o-transform:rotate(@deg); - transform:rotate(@deg); -} - -.origin(@o){ - -webkit-transform-origin:@o; - -moz-transform-origin:@o; - -ms-transform-origin:@o; - -o-transform-origin:@o; - transform-origin:@o; -} - -.filter(@param){ - -webkit-filter:@param; - -moz-filter:@param; - -ms-filter:@param; - -o-filter:@param; - filter:@param; -} - -.animation(@a){ - -webkit-animation:@a; - -moz-animation:@a; - -ms-animation:@a; - -o-animation:@a; - animation:@a; -} - -.perspective(@a){ - -webkit-perspective:@a; - -moz-perspective:@a; - -ms-perspective:@a; - -o-perspective:@a; - perspective:@a; -} - -.border_box { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - -ms-box-sizing:border-box; - -o-box-sizing:border-box; - box-sizing:border-box; -} - -.transform(@param){ - -webkit-transform:@param; - -moz-transform:@param; - -ms-transform:@param; - -o-transform:@param; - transform:@param; -} - -.fix3d { - -webkit-transform:translate3D(0, 0, 0); //fix 3d flashing -} - -.border_box { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - -ms-box-sizing:border-box; - -o-box-sizing:border-box; - box-sizing:border-box; -} - -.invert { - -webkit-filter:invert(100%); - -moz-filter:invert(100%); - -ms-filter:invert(100%); - -o-filter:invert(100%); - filter:invert(100%); -} - -.animation(@a){ - -webkit-animation:@a; - -moz-animation:@a; - -ms-animation:@a; - -o-animation:@a; - animation:@a; -} - -.animation_delay(@a){ - -webkit-animation-delay:@a; - -moz-animation-delay:@a; - -ms-animation-delay:@a; - -o-animation-delay:@a; - animation-delay:@a; -} - -.transition_delay(@a){ - -webkit-transition-delay:@a; - -moz-transition-delay:@a; - -ms-transition-delay:@a; - -o-transition-delay:@a; - transition-delay:@a; -} - - -.columns(@a){ - -webkit-columns:@a; - -moz-columns:@a; - -ms-columns:@a; - -o-columns:@a; - columns:@a; -} diff --git a/server/app/styles/drone.css b/server/app/styles/drone.css deleted file mode 100644 index 4d3c41053..000000000 --- a/server/app/styles/drone.css +++ /dev/null @@ -1 +0,0 @@ -html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;list-style:none}.hidden{display:none!important;visibility:hidden}.invisible{visibility:hidden}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.nowrap{white-space:nowrap}.border_box{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.fix3d{-webkit-transform:translate3D(0,0,0)}.border_box{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.invert{-webkit-filter:invert(100%);-moz-filter:invert(100%);-ms-filter:invert(100%);filter:invert(100%)}.ladda-button{position:relative;background:0 0;border:0;cursor:pointer;outline:0;-webkit-appearance:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.ladda-button[data-loading=true]{cursor:default}.ladda-button:disabled{opacity:1}.ladda-button,.ladda-button .spinner,.ladda-button .label{-webkit-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;transition:.3s cubic-bezier(0.175,.885,.32,1.275) all}.ladda-button.zoom-in,.ladda-button.zoom-in .spinner,.ladda-button.zoom-in .label,.ladda-button.zoom-out,.ladda-button.zoom-out .spinner,.ladda-button.zoom-out .label{-webkit-transition:.3s ease all;transition:.3s ease all}.ladda-button.expand-right .spinner{right:.8em}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true]:after{content:'';width:20px;height:20px;display:block;border-radius:50%;border:3px solid #fff;border-right-color:rgba(0,0,0,0);border-top-color:rgba(0,0,0,0);-webkit-animation:spin 1s linear infinite;-ms-animation:spin 1s linear infinite;animation:spin 1s linear infinite;position:absolute;top:5px;right:5px}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true] .spinner{opacity:1}.ladda-button.expand-left .spinner{left:.8em}.ladda-button.expand-left[data-loading=true]{padding-left:56px!IMPORTANT}.ladda-button.expand-left[data-loading=true] .spinner{opacity:1}.ladda-button.expand-up{overflow:hidden}.ladda-button.expand-up .spinner{top:-32px;left:50%;margin-left:-16px}.ladda-button.expand-up[data-loading=true]{padding-top:3em}.ladda-button.expand-up[data-loading=true] .spinner{opacity:1;top:.8em;margin-top:0}.ladda-button.expand-down{overflow:hidden}.ladda-button.expand-down .spinner{top:3.3em;left:50%;margin-left:-16px}.ladda-button.expand-down[data-loading=true]{padding-bottom:3em}.ladda-button.expand-down[data-loading=true] .spinner{opacity:1}.ladda-button.slide-left{overflow:hidden}.ladda-button.slide-left .label{position:relative}.ladda-button.slide-left .spinner{left:100%;margin-left:-16px}.ladda-button.slide-left[data-loading=true] .label{opacity:0;left:-100%}.ladda-button.slide-left[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-right{overflow:hidden}.ladda-button.slide-right .label{position:relative}.ladda-button.slide-right .spinner{right:100%;margin-left:-16px}.ladda-button.slide-right[data-loading=true] .label{opacity:0;left:100%}.ladda-button.slide-right[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-up{overflow:hidden}.ladda-button.slide-up .label{position:relative}.ladda-button.slide-up .spinner{left:50%;margin-left:-16px;margin-top:1em}.ladda-button.slide-up[data-loading=true] .label{opacity:0;top:-1em}.ladda-button.slide-up[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.slide-down{overflow:hidden}.ladda-button.slide-down .label{position:relative}.ladda-button.slide-down .spinner{left:50%;margin-left:-16px;margin-top:-2em}.ladda-button.slide-down[data-loading=true] .label{opacity:0;top:1em}.ladda-button.slide-down[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.zoom-out{overflow:hidden}.ladda-button.zoom-out .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(2.5);-ms-transform:scale(2.5);transform:scale(2.5)}.ladda-button.zoom-out .label{position:relative;display:inline-block}.ladda-button.zoom-out[data-loading=true] .label{opacity:0;-webkit-transform:scale(0.5);-ms-transform:scale(0.5);transform:scale(0.5)}.ladda-button.zoom-out[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}.ladda-button.zoom-in{overflow:hidden}.ladda-button.zoom-in .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-ms-transform:scale(0.2);transform:scale(0.2)}.ladda-button.zoom-in .label{position:relative;display:inline-block}.ladda-button.zoom-in[data-loading=true] .label{opacity:0;-webkit-transform:scale(2.2);-ms-transform:scale(2.2);transform:scale(2.2)}.ladda-button.zoom-in[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-ms-transform:none;transform:none}.ladda-button.contract{overflow:hidden;width:100px}.ladda-button.contract .spinner{left:50%;margin-left:-16px}.ladda-button.contract[data-loading=true]{border-radius:50%;width:52px}.ladda-button.contract[data-loading=true] .label{opacity:0}.ladda-button.contract[data-loading=true] .spinner{opacity:1}.ladda-button.contract-overlay{overflow:hidden;width:100px;box-shadow:0 0 0 3000px rgba(0,0,0,0)}.ladda-button.contract-overlay .spinner{left:50%;margin-left:-16px}.ladda-button.contract-overlay[data-loading=true]{border-radius:50%;width:52px;box-shadow:0 0 0 3000px rgba(0,0,0,.8)}.ladda-button.contract-overlay[data-loading=true] .label{opacity:0}.ladda-button.contract-overlay[data-loading=true] .spinner{opacity:1}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(360deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html{height:100%}body{font-family:'Open Sans';font-weight:400;margin:0;color:#212121;background:#fff;font-size:13px;line-height:1.3;-webkit-font-smoothing:antialiased;height:100%;position:relative}[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none}#container{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-top:55px;position:relative;min-width:100%;min-height:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#header{background:#212121;position:fixed;height:55px;top:0;left:0;right:0;z-index:9;color:#fff;font-size:15px;line-height:55px;text-align:center;box-shadow:1px 1px 4px rgba(0,0,0,.5)}#header .brand{display:inline-block;font-family:Orbitron;font-size:26px;line-height:55px;text-decoration:none;text-transform:uppercase;color:#CCC}#header .burger{position:absolute;top:0;left:31px;height:55px;font-size:22px;color:#CCC}#header .burger i.fa{line-height:55px}#header .login,#header .user{position:absolute;right:0;top:0;bottom:0;white-space:nowrap;margin-right:20px;display:inline-block}#header .login a,#header .user a{color:#CCC;text-decoration:none;text-transform:uppercase;line-height:55px;font-size:15px}#header .login a img,#header .user a img{border-radius:50%;float:right;width:32px;height:32px;margin-top:10px;margin-left:20px}#body{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;min-width:100%;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#body article{width:100%}#drawer{visibility:hidden;position:fixed;z-index:10;left:0;top:55px;bottom:0;width:255px;background:#363636;-webkit-transition:all .2s;transition:all .2s;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}#drawer ul{margin-top:20px}#drawer ul a{color:#CCC;text-decoration:none;padding:10px 0 10px 30px;display:block;font-size:14px}#drawer ul a i{margin-right:10px;font-size:16px;opacity:.3;min-width:16px;display:inline-block}#drawer ul span.divider{display:block;height:1px;border-top:1px solid rgba(255,255,255,.1);margin-top:15px;margin-bottom:15px}#drawer .signout{position:absolute;bottom:20px;right:30px;color:#CCC;font-size:16px;text-transform:uppercase;text-decoration:none}#drawer .signout i{margin-left:20px}#drawer-checkbox{position:fixed;top:7px;left:10px;width:45px;height:40px;display:block;z-index:9999;opacity:0;background:0 0;border:none;cursor:pointer}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#header .fa-bars:before{content:"\f00d";color:#999}nav{padding-left:30px;background:#FFF;min-height:77px;max-height:77px;line-height:77px;font-family:'Open Sans';font-size:22px;white-space:nowrap;color:rgba(0,0,0,.7);border-bottom:1px solid #eee;position:relative;z-index:2}nav a{text-decoration:none;color:rgba(0,0,0,.7)}nav a:last-child{color:#000}nav a span.fa{margin-right:20px}nav div.options{float:right;margin-right:20px}nav div.options .pure-button{color:#FFF;text-transform:lowercase;font-size:14px;background:#f5f5f5;padding:10px 30px;color:rgba(0,0,0,.5)}nav div.options .pure-button i{margin-right:10px;margin-left:-10px}@supports ((position: -webkit-sticky) or (position: sticky)){nav{position:-webkit-sticky;position:sticky;top:55px}}@supports (position: -moz-sticky){nav{position:-moz-sticky;top:55px}}@supports (position: -webkit-sticky){nav{position:-webkit-sticky;top:55px}}.cards .card{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-right:20px;padding-bottom:20px;padding-left:20px;text-decoration:none;position:relative;color:#212121;font-family:'Open Sans'}.cards .card[data-status=Success] em{border-top:5px solid #7cb342}.cards .card[data-status=Killed] em,.cards .card[data-status=Failure] em,.cards .card[data-status=Error] em{border-top:5px solid #f44336}.cards .card[data-status=Killed] .l-box,.cards .card[data-status=Failure] .l-box,.cards .card[data-status=Error] .l-box{border:1px solid #f44336}.cards .card[data-status=Killed] .l-box:hover,.cards .card[data-status=Failure] .l-box:hover,.cards .card[data-status=Error] .l-box:hover{border:1px solid #212121}.cards .card[data-status=Killed]:after,.cards .card[data-status=Failure]:after,.cards .card[data-status=Error]:after{font-family:FontAwesome;font-size:16px;position:absolute;right:12px;top:-8px;content:"\f111";color:#f44336;min-width:16px;text-align:center}.cards .card .l-box{background:#FFF;border:1px solid #DDD;position:relative;padding:30px 20px;height:200px;-webkit-transition:.4s border linear;transition:.4s border linear;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.cards .card .l-box:hover{border:1px solid #212121}.cards .card .l-box em{position:absolute;bottom:20px;right:20px;left:20px;height:30px;line-height:30px;vertical-align:middle;text-align:right;padding-right:45px;padding-top:20px;font-size:14px;color:#666}.cards .card .l-box img{position:absolute;right:20px;bottom:20px;border-radius:50%;width:30px;height:30px}.cards .card .l-box .timeago{position:absolute;bottom:85px;left:25px;right:25px;text-align:right;font-size:14px;color:#849299;display:none}.cards .card h2{font-size:18px;font-weight:400;min-height:52px;max-height:52px;height:52px;text-align:center;vertical-align:middle;line-height:26px;color:#212121;text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.cards .card h2 .separator{margin:0}.cards .card.card-inactive .l-box{position:relative;box-shadow:none;background:#4ab1ce;color:#FFF;height:180px;border-color:#4ab1ce}.cards .card.card-inactive .l-box:hover{background:#3197b4}.cards .card.card-inactive .l-box:hover:before{background:#3197b4}.cards .card.card-inactive h2{padding-top:10px;color:#FFF}.cards .card.card-inactive em{position:absolute;border-top:1px solid rgba(255,255,255,.5);bottom:15px;font-size:13px;left:25px;right:25px;line-height:1.3;padding:0;padding-top:20px;text-align:center;display:block;height:30px;text-transform:uppercase;color:#FFF}.cards .card.card-browse-inactive,.cards .card.card-browse{text-align:center;color:#4ab1ce;font-size:16px;font-weight:700;text-transform:uppercase}.cards .card.card-browse-inactive .l-box,.cards .card.card-browse .l-box{padding-top:75px;background:#FFF;height:180px}.cards .card.card-browse-inactive .l-box{box-shadow:none}.cards .progressContainer{height:5px;background-color:#f44336;position:absolute;bottom:65px;left:20px;right:20px}.cards .progressContainer .activeProgress,.cards .progressContainer .secondaryProgress{position:absolute;top:0;left:0;bottom:0}.cards .progressContainer .activeProgress{background-color:#7cb342}.cards .progressContainer .secondaryProgress{background-color:#7cb342}.cards .tabs{font-size:inherit;padding-left:0;line-height:initial;min-height:initial;margin-bottom:10px;border-bottom:1px solid #e0e0e0}.cards .tabs .pure-button{background:#f5f5f5;color:rgba(0,0,0,.8)}.cards .tabs .pure-button.active{background:#e0e0e0}#commitpage{max-width:1180px;margin:0 auto;margin-bottom:50px;margin-top:70px}#commitpage section{margin-top:30px}#commitpage section .commits{border:1px solid #DDD;border-bottom:0 solid #DDD;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a{padding:20px 45px;display:block;border-bottom:1px solid #dadcdd;color:#212121;text-decoration:none;position:relative;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a h2{font-family:'Open Sans';font-weight:700;font-size:16px;margin-bottom:5px}#commitpage section .commits a img{border-radius:50%;margin-right:10px;float:left;display:none}#commitpage section .commits a p{color:#363636;line-height:22px;vertical-align:middle}#commitpage section .commits a[data-status]:before{background:0 0;width:7px;min-width:7px;max-width:7px;position:absolute;left:-1px;top:0;bottom:0;text-align:left;color:#fff;font-size:20px;line-height:50px;font-family:'Open Sans';padding-left:2px;overflow:hidden;content:" "}#commitpage section .commits a[data-result=Killed],#commitpage section .commits a[data-status=Error],#commitpage section .commits a[data-status=Failure]{background:#fff9f5}#commitpage section .commits a[data-result=Killed]:before,#commitpage section .commits a[data-status=Error]:before,#commitpage section .commits a[data-status=Failure]:before{background:#f44336;content:"!"}#commitpage section .commits a[data-status=Success]:before{background:#7cb342}#commitpage .date span{display:inline-block;text-align:right;font-size:14px;width:100%;padding-right:30px;margin-top:15px;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g,#loginpage .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g a,#loginpage .pure-g a{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none}#setuppage .pure-g a:hover,#loginpage .pure-g a:hover{background:#212121}#setuppage .pure-g [class*=fa-],#loginpage .pure-g [class*=fa-]{float:left;font-size:20px;position:relative;top:-3px;left:-3px;padding-right:10px;min-width:27px;min-height:20px}#setuppage .pure-g .pure-u-1 a,#loginpage .pure-g .pure-u-1 a{margin-bottom:10px}#setuppage .pure-g .pure-u-1:last-child a,#loginpage .pure-g .pure-u-1:last-child a{margin-bottom:0}#setuppage form.pure-g input[type=text],#loginpage form.pure-g input[type=text],#setuppage form.pure-g input[type=password],#loginpage form.pure-g input[type=password]{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#setuppage form.pure-g input[type=submit],#loginpage form.pure-g input[type=submit]{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none;width:100%;border:none}#setuppage form.pure-g input[type=submit]:hover,#loginpage form.pure-g input[type=submit]:hover{background:#212121}#setuppage2{margin-bottom:50px}#setuppage2 section{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section label{display:inline-block}#setuppage2 section input[type=text]{margin-top:5px;margin-bottom:10px;box-shadow:none;width:100%}#setuppage2 section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px;width:100%}#setuppage2 section .tip h2{font-size:16px;margin-bottom:20px}#setuppage2 section .tip dd{font-weight:700;color:#666;margin-top:15px;margin-bottom:5px}#setuppage2 section .tip dt{padding:.5em .6em;display:inline-block;border:1px solid #ccc;border-radius:4px;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}#syncpage{width:100%}#syncpage section{padding:40px 0 20px}#syncpage section h1{text-align:center;font-size:24px;margin-bottom:20px}#syncpage section h1 i{font-size:32px;margin-top:20px}#homepage{width:100%;background:#fafafa}#homepage section{padding:40px 0 20px}#homepage section h1{text-align:center;font-size:24px;margin-bottom:20px}#homepage section h1 i{font-size:32px;margin-top:20px}#homepage section div{max-width:1180px;margin:0 auto}#homepage section:nth-child(2){background:#FFF;padding:40px 0 20px}#homepage section:nth-child(3){border-bottom:0 solid #EEE}#homepage section .card[data-status=Killed] .l-box,#homepage section .card[data-status=Failure] .l-box,#homepage section .card[data-status=Error] .l-box{border:1px solid #f44336}#homepage section .card[data-status=Killed] .l-box:hover,#homepage section .card[data-status=Failure] .l-box:hover,#homepage section .card[data-status=Error] .l-box:hover{border:1px solid #212121}#repospage{width:100%}#repospage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#repospage section .search{margin-bottom:25px}#repospage section .search input[type=text],#repospage section .search input[type=search]{-webkit-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#repospage section .search input[type=text]:focus,#repospage section .search input[type=search]:focus{border-color:#129FEA}#repospage section .repo{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#repospage section .repo:last-child>div{border-bottom:1px solid #fff}#repospage section .repo>div{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;transition:.4s border linear}#repospage section .repo>div>div{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px}#repospage section .repo>div>div:last-child div{text-align:right}#repospage section .repo>div:hover{border:1px solid #212121}#repospage section .repo h4{font-size:20px;margin-bottom:2px;color:#212121}#repospage section .repo span{color:#666;font-size:14px}#repospage section .repo i{color:#DDD;font-size:22px;margin-left:20px;margin-top:15px}#repospage section .repo i.fa-check-circle-o{color:#7cb342}#userspage{width:100%}#userspage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#userspage section .search{margin-bottom:25px}#userspage section .search input[type=text],#userspage section .search input[type=search]{-webkit-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#userspage section .search input[type=text]:focus,#userspage section .search input[type=search]:focus{border-color:#129FEA}#userspage section .user{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#userspage section .user:last-child>div{border-bottom:1px solid #fff}#userspage section .user>div{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;transition:.4s border linear}#userspage section .user>div>div{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px;padding-right:0}#userspage section .user>div:hover{border:1px solid #212121}#userspage section .user img{border-radius:50%;width:48px;height:48px}#userspage section .user h4{font-size:20px;margin-bottom:2px;color:#212121}#userspage section .user h4 small{font-size:16px;color:#666;margin-left:5px}#userspage section .user span{color:#666;font-size:14px}#repoconfpage{width:100%}#repoconfpage section{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px;padding:20px}#repoconfpage section h2{font-size:16px;margin-bottom:15px}#repoconfpage section .markdown,#repoconfpage section .params,#repoconfpage section .key{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;min-height:50px;margin-top:10px;font-family:'Droid Sans Mono';border:1px solid #eee;padding:20px;width:100%;max-width:100%;color:#666}#repoconfpage section .markdown:focus,#repoconfpage section .params:focus,#repoconfpage section .key:focus{border-color:#129FEA;outline:0}#repoconfpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px}#repoconfpage section select,#repoconfpage section input[type=number]{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#repoconfpage section span.seconds{color:#212121;margin-left:10px}#repoconfpage section span.minutes{color:#212121}#repoconfpage section span.minutes:before{content:'('}#repoconfpage section span.minutes:after{content:')'}#accountpage{width:100%}#accountpage section{position:relative;max-width:768px;margin:0 auto;margin-top:30px;border:1px solid #eee}#accountpage section.profile>div:first-child{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;text-align:center}#accountpage section.profile>div:last-child{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px}#accountpage section.profile .fullname{font-size:14px;margin-bottom:2px;color:#666;display:block}#accountpage section.profile .email{font-size:14px;color:#666;display:block}#accountpage section.token>div:first-child div{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-align:center;padding:20px;color:#666;font-size:16px;line-height:22px}#accountpage section.token>div:first-child i{margin-right:7px}#accountpage section.token>div:last-child{-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;color:#666;line-height:22px;font-size:14px;word-break:break-all}#accountpage section h4{margin:10px 0;font-size:22px}#accountpage section h4 small{opacity:.6;font-size:16px;margin-left:10px}#accountpage section img{width:64px;height:64px;border-radius:50%}#accountpage section .notifications{position:absolute;top:0;right:0;margin:20px}#accountpage section .button-error{color:#FFF;background:#ca3c3c;padding:10px 20px;float:right}#accountpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:10px}#repopage{width:100%;background:#fafafa}#repopage section{padding:40px 0 20px}#repopage section>div{max-width:1180px;margin:0 auto}#repopage section:nth-child(even){background:#FFF}#repopage section:first-child{background:#FFF}#repopage section .card[data-status=Success]:nth-child(2) .l-box{border-color:#7cb342}#repopage section .card[data-status=Success]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Killed]:nth-child(2) .l-box,#repopage section .card[data-status=Failure]:nth-child(2) .l-box,#repopage section .card[data-status=Error]:nth-child(2) .l-box{border-color:#f44336}#repopage section .card[data-status=Killed]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Failure]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Error]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Started] em:before,#repopage section .card[data-status=Pending] em:before{-webkit-animation:progress 1s linear infinite;animation:progress 1s linear infinite;position:absolute;content:' ';height:5px;top:-5px;left:0;right:0;margin:0;background:#ffb300;background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,.55)25%,transparent 25%,transparent 50%,rgba(255,255,255,.55)50%,rgba(255,255,255,.55)75%,transparent 75%,transparent);background-image:-webkit-linear-gradient(135deg, rgba(255,255,255,.55)25%, transparent 25%, transparent 50%, rgba(255,255,255,.55)50%, rgba(255,255,255,.55)75%, transparent 75%, transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,.55)25%,transparent 25%,transparent 50%,rgba(255,255,255,.55)50%,rgba(255,255,255,.55)75%,transparent 75%,transparent);background-repeat:repeat-x;background-size:30px 30px}#repopage section .l-box em{line-height:19px;bottom:25px}#repopage section .l-box:after{font-family:FontAwesome;content:"\f104";content:"\f0d9";position:absolute;right:-20px;width:20px;text-align:center;color:rgba(0,0,0,.1);font-size:22px}#repopage section .card:last-child .l-box:after{content:''}#repopage section.nobuilds,#repopage section.inactive{text-align:center;padding-bottom:50px}#repopage section.nobuilds h1,#repopage section.inactive h1{font-size:26px;color:#212121}#repopage section.nobuilds p,#repopage section.inactive p{font-size:16px;color:#666}#repopage section.nobuilds i,#repopage section.inactive i{font-size:32px;margin-top:20px;margin-bottom:20px}#repopage section.nobuilds i.fa-file-code-o,#repopage section.inactive i.fa-file-code-o{font-size:42px;margin-top:30px}#repopage section.nobuilds .pure-button-primary,#repopage section.inactive .pure-button-primary{font-size:14px;text-transform:uppercase;background:#4ab1ce;padding:10px 30px}@-webkit-keyframes progress{to{background-position:30px 0}}@keyframes progress{to{background-position:30px 0}}#sidebar{width:240px;min-width:240px;position:relative;display:block;z-index:5;padding:30px;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#sidebar #sidebar-inner{position:fixed;width:180px}#sidebar h1{font-size:28px;font-weight:300}#sidebar h2{font-size:22px;font-weight:300;margin-bottom:20px}#sidebar dl{padding-top:23px;border-top:1px solid #ddd;margin-top:5px}#sidebar dl:first-child{padding-top:0;border-top:none;margin-top:0}#sidebar dl dt{font-size:12px;color:#849299;text-transform:uppercase;padding:3px 0}#sidebar dl dd{font-size:14px;padding:3px 0 20px}#sidebar dl a{text-transform:none}#sidebar dl small{font-size:12px}#sidebar dl .large{font-size:18px;padding-bottom:5px}#sidebar dl .large a{color:#212121}#sidebar dl .time{float:right;margin-left:8px}#sidebar dl .photo{margin-right:4px}#sidebar dl .negative{color:#f44336}#sidebar dl .photoline{display:inline-block;position:relative;top:-10px;font-weight:700}#sidebar dl .small{padding-bottom:5px;font-weight:700;font-size:12px}#sidebar .status{border:1px solid transparent;display:block;text-align:center;padding:5px 20px;border-radius:50px;text-transform:uppercase;margin:0 -5px 10px;font-weight:700}#sidebar .status:before{float:left;margin-left:-5px}#sidebar .status.status_ok{color:#7cb342;border-color:#7cb342}#sidebar .status.status_ok:before{content:"\f00c";font-family:FontAwesome}#sidebar .status.status_error{color:#f44336;border-color:#f44336}#sidebar .status.status_error:before{content:"!"}#sidebar .result{background:#4ab1ce;background:#7cb342;color:#fff;margin:-30px -30px -6px;padding:30px;position:relative}#sidebar .result .status{color:#fff;background:rgba(255,255,255,.2)}#sidebar .result .status:before{content:"\f00c";font-family:FontAwesome}#sidebar .result dl dd{padding:7px 0}#sidebar .result dl dd strong{font-size:16px}#sidebar .result[data-result=Killed],#sidebar .result[data-result=Failure],#sidebar .result[data-result=Error]{background:#f44336}#sidebar .result[data-result=Killed] .status:before,#sidebar .result[data-result=Failure] .status:before,#sidebar .result[data-result=Error] .status:before{content:"!"}#sidebar .result[data-result=Pending],#sidebar .result[data-result=Started]{background:#ffb300}#sidebar .result[data-result=Pending] .status:before,#sidebar .result[data-result=Started] .status:before{content:"\f021";-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}#main{-webkit-box-flex:2;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2}#main.output{position:relative;background:#212121}#main.output pre{margin:0 auto;padding:30px;color:#FFF;font-family:'Droid Sans Mono';font-size:13px;white-space:pre-wrap;word-break:break-all;overflow:hidden;line-height:18px}#main.output #follow button{position:fixed;right:280px;bottom:21px;z-index:100;border-radius:7px;background-color:rgba(238,238,238,.2);padding:0 1em;cursor:pointer;font-family:'Open Sans';border:none;color:#fff}#main.output[data-result=Pending]:after,#main.output[data-result=Started]:after{position:absolute;right:20px;bottom:20px;color:#9e9e9e;font-size:18px;font-family:FontAwesome;content:"\f021";-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.pure-form.pure-form-stacked label{font-size:14px;color:#666;margin-top:20px;margin-bottom:5px}.pure-form.pure-form-stacked label:first-child{margin-top:0}.pure-form.pure-form-stacked input[type=text],.pure-form.pure-form-stacked select{min-width:300px;box-shadow:none;padding:10px;border:1px solid #ccc;border-radius:0}.toggle{margin-bottom:10px}.toggle:nth-child(2){margin-top:40px}.toggle span{line-height:32px;vertical-align:middle;display:inline-block;margin-left:10px}.toggle input[type=checkbox]{max-height:0;max-width:0;opacity:0}.toggle input[type=checkbox]+label{display:inline-block;vertical-align:middle;position:relative;box-shadow:inset 0 0 0 1px #d5d5d5;text-indent:-5000px;height:30px;width:50px;border-radius:15px}.toggle input[type=checkbox]+label:before{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:rgba(19,191,17,0);-webkit-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]+label:after{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:#fff;box-shadow:inset 0 0 0 1px rgba(0,0,0,.2),0 2px 4px rgba(0,0,0,.2);-webkit-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]:checked+label:before{width:50px;background:#4ab1ce}.toggle input[type=checkbox]:checked+label:after{left:20px;box-shadow:inset 0 0 0 1px #4ab1ce,0 2px 4px rgba(0,0,0,.2)}.toast{position:fixed;bottom:50px;left:20%;right:20%;background:#363636;border-radius:3px;z-index:999;color:#FFF;padding:15px 20px;font-size:14px;box-shadow:2px 2px 2px rgba(0,0,0,.2)}.toast a,.toast a:visited,.toast a:hover,.toast a:active{color:#FFF}.toast button{float:right;background:0 0;border:none;color:#EEFF41;text-transform:uppercase;margin-left:10px}@media screen and (min-width:1180px){#repopage h2{padding-left:0}.cards .card{padding-left:0}}@media screen and (max-width:35.5em){.cards .card .l-box:after{display:none!IMPORTANT}header .username{display:none}#repopage h2{padding-left:20px}#userspage form,#repospage form{display:none}#userspage section,#repospage section{margin:0}#userspage section .user:nth-child(2)>.pure-g,#repospage section .user:nth-child(2)>.pure-g,#userspage section .repo:nth-child(2)>.pure-g,#repospage section .repo:nth-child(2)>.pure-g{border-top:1px solid #FFF}#userspage section .user:nth-child(2):hover>.pure-g,#repospage section .user:nth-child(2):hover>.pure-g,#userspage section .repo:nth-child(2):hover>.pure-g,#repospage section .repo:nth-child(2):hover>.pure-g{border-top:1px solid #212121}#userspage section .user i,#repospage section .user i,#userspage section .repo i,#repospage section .repo i{margin-left:0}#accountpage .token span{display:none}nav div.options .pure-button i{margin:0}nav div.options .pure-button span{display:none}}table.list-view{font-size:11.8px;line-height:22px;color:#666;border-spacing:0;width:100%}table.list-view tr td:first-child:before{display:block;content:".";color:transparent;font-size:0;width:4px;height:32px;float:left;margin:-5px 5px -5px 0}table.list-view tr[data-status=Pending] td,table.list-view tr[data-status=Started] td{background-color:#fffcf4}table.list-view tr[data-status=Pending]:hover td,table.list-view tr[data-status=Started]:hover td{background-color:#ffffe1}table.list-view tr[data-status=Pending] td:first-child:before,table.list-view tr[data-status=Started] td:first-child:before{background-color:#e7c600;-webkit-animation:progress 2s linear infinite;animation:progress 2s linear infinite;background-image:linear-gradient(-45deg,rgba(255,255,255,.55) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.55) 50%,rgba(255,255,255,.55) 75%,transparent 75%,transparent);background-repeat:repeat;background-size:10px 10px}table.list-view tr[data-status=Error] td,table.list-view tr[data-status=Failure] td,table.list-view tr[data-status=Killed] td{background-color:snow}table.list-view tr[data-status=Error]:hover td,table.list-view tr[data-status=Failure]:hover td,table.list-view tr[data-status=Killed]:hover td{background-color:#ffdcdc}table.list-view tr[data-status=Error] td:first-child,table.list-view tr[data-status=Failure] td:first-child,table.list-view tr[data-status=Killed] td:first-child{color:#c00}table.list-view tr[data-status=Error] td:first-child:before,table.list-view tr[data-status=Failure] td:first-child:before,table.list-view tr[data-status=Killed] td:first-child:before{background-color:#c00}table.list-view tr[data-status=Success] td{background-color:#fafffa}table.list-view tr[data-status=Success]:hover td{background-color:#dcffdc}table.list-view tr[data-status=Success] td:first-child{color:#038035}table.list-view tr[data-status=Success] td:first-child:before{background-color:#038035}table.list-view th{color:#666;white-space:nowrap;border-bottom:2px solid #fff;text-align:left;padding:5px 20px 5px 0;vertical-align:top;font-weight:700}table.list-view a{color:inherit}table.list-view td{white-space:nowrap;border-bottom:2px solid #fff;text-align:left;padding:5px 20px 5px 0;vertical-align:top}table.list-view td:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}table.list-view td:first-child a:before{margin:0 4px 0 7px}table.list-view td:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}table.list-view td:nth-child(2){width:100%;white-space:normal} diff --git a/server/app/styles/drone.less b/server/app/styles/drone.less deleted file mode 100644 index ef8917cbd..000000000 --- a/server/app/styles/drone.less +++ /dev/null @@ -1,1713 +0,0 @@ -@import "reset.less"; -@import "base.less"; -@import "ladda.less"; - -// Standard Colors -// http://www.google.com/design/spec/style/color.html#color-ui-color-palette - -@link: #68c598; -@link2: #4ab1ce; -@cfailure: #f44336; -@csuccess: #7cb342; -@cpending: #ffb300; -@c0: #212121; -@c1: #45494b; -@c2: #849299; - -@grey50: #fafafa; -@grey100: #f5f5f5; -@grey200: #eeeeee; - -// @csuccess: #68c598; -// @cfailure: #e97041; - -// Standard Fonts - -@logo: 'Orbitron'; -@sans: 'Open Sans'; -@mono: 'Droid Sans Mono'; -@fontawesome: 'FontAwesome'; - -html { - height: 100%; -} - -body { - font-family: @sans; - font-weight: normal; - margin:0px; - color: @c0; - background: #fff; - font-size: 13px; - line-height: 1.3; - -webkit-font-smoothing: antialiased; - height: 100%; - position: relative; -} - -// The ngCloak directive is used to prevent the Angular html template -// from being briefly displayed by the browser in its raw (uncompiled) -// form while the application is loading. -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - display: none; -} - -#container { - .border_box; - padding-top: 55px; - position: relative; - min-width: 100%; - min-height: 100%; - display: flex; - flex-direction: row-reverse; - justify-content: space-between; -} - -#header { - background: @c0; - position: fixed; - height: 55px; - top: 0; - left: 0; - right: 0; - z-index: 9; - color: #fff; - font-size: 15px; - line-height: 55px; - text-align: center; - box-shadow: 1px 1px 4px rgba(0,0,0,0.5); - - .brand { - display: inline-block; - font-family: @logo; - font-size: 26px; - line-height: 55px; - text-decoration: none; - text-transform: uppercase; - color: #CCC; - } - - .burger { - position: absolute; - top:0px; - left: 31px; - height:55px; - font-size: 22px; - color: #CCC; - i.fa { - line-height:55px; - } - } - - .login, - .user { - position: absolute; - right: 0px; - top: 0px; - bottom: 0px; - white-space: nowrap; - margin-right: 20px; - display: inline-block; - - a { - color: #CCC; - text-decoration: none; - text-transform: uppercase; - line-height: 55px; - font-size: 15px; - - img { - border-radius: 50%; - float: right; - width: 32px; - height: 32px; - margin-top: 10px; - margin-left: 20px; - } - } - } -} - -#body { - .border_box; - display: flex; - min-width:100%; - flex-direction: row-reverse; - justify-content: space-between; - - article { - width:100%; - } -} - -#drawer { - visibility: hidden; - position:fixed; - z-index:10; - left:0px; - top:55px; - bottom:0px; - width:255px; - background:#363636; - -webkit-transition: all 0.2s; - -moz-transition: all 0.2s; - transition: all 0.2s; - -webkit-transform: translate3d(-100%, 0, 0); - -moz-transform: translate3d(0, 0, 0); - transform: translate3d(-100%, 0, 0); - - ul { - margin-top:20px; - a { - color:#CCC; - text-decoration:none; - padding:10px 0px 10px 30px; - display:block; - font-size:14px; - i { - margin-right:10px; - font-size:16px; - opacity:0.3; - min-width:16px; - display:inline-block; - } - } - span.divider { - display:block; - height:1px; - border-top:1px solid rgba(255,255,255,0.1); - margin-top:15px; - margin-bottom:15px; - } - } - - .signout { - position:absolute; - bottom:20px; - right:30px; - color:#CCC; - font-size:16px; - text-transform:uppercase; - text-decoration:none; - i { - margin-left:20px; - } - } -} - -#drawer-checkbox { - position:fixed; - top:7px; - left:10px; - width:45px; - height:40px; - display:block; - z-index:9999; - opacity:0; - background:none; - border:none; - cursor:pointer; - &:checked ~ #drawer { - visibility: visible; - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - &:checked ~ #drawer { - visibility: visible; - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - // override the drawers icon to show - // a close symbol, so the user knows - // exactly how to minimize. - &:checked ~ #header { - .fa-bars:before { - content: "\f00d"; - color:#999; - } - } -} - -nav { - padding-left:30px; - background:#FFF; - min-height: 77px; - max-height: 77px; - line-height: 77px; - font-family: @sans; - font-size: 22px; - white-space: nowrap; - color: rgba(0, 0, 0, 0.7); - border-bottom:1px solid @grey200; - position:relative; - z-index:2; - - a { - text-decoration:none; - color: rgba(0, 0, 0, 0.7); - - &:last-child { - color: #000; - } - - span.fa { - margin-right:20px; - } - } - - div.options { - float: right; - margin-right: 20px; - - .pure-button { - color: #FFF; - text-transform: lowercase; - font-size: 14px; - background: @grey100; - padding: 10px 30px; - color: rgba(0,0,0,0.5); - i { - margin-right: 10px; - margin-left: -10px; - } - } - } -} - -@supports (position:sticky) { - nav { - position: sticky; - top:55px; - } -} -@supports (position:-moz-sticky) { - nav { - position: -moz-sticky; - top:55px; - } -} -@supports (position:-webkit-sticky) { - nav { - position: -webkit-sticky; - top:55px; - } -} - -.cards { - .card { - .border_box; - padding-right:20px; - padding-bottom:20px; - padding-left:20px; - text-decoration:none; - position:relative; - color:@c0; - font-family:@sans; - - &[data-status="Success"] { - em { - border-top:5px solid @csuccess; - } - } - - &[data-status="Killed"], - &[data-status="Failure"], - &[data-status="Error"] { - em { - border-top:5px solid @cfailure; - } - - .l-box { - border: 1px solid @cfailure; - &:hover { - border:1px solid @c0; - } - } - - &:after { - font-family:'FontAwesome'; - font-size:16px; - position:absolute; - right:12px; - top:-8px; - content:"\f111"; - color: @cfailure; - min-width:16px; - text-align:center; - } - } - - .l-box { - background:#FFF; - border:1px solid #DDD; - position:relative; - padding:30px 20px; - height:200px; - .transition(.4s border linear); - .border_box; - - - //box-shadow: inset 0 0 0 1px #dfdfdf, - // inset 0 0 0 2px #fff, - // 2px 2px 0 transparent; - - &:hover { - border:1px solid @c0; - } - - em { - position:absolute; - bottom:20px; - right:20px; - left:20px; - height:30px; - line-height:30px; - vertical-align:middle; - //text-transform:uppercase; - text-align:right; - padding-right:45px; - //border-top:1px solid #DDD; - padding-top:20px; - font-size:14px; - color:#666; - } - - img { - position:absolute; - right:20px; - bottom:20px; - border-radius:50%; - width:30px; - height:30px; - } - - .timeago { - position:absolute; - bottom:85px; - left:25px; - right:25px; - text-align:right; - font-size:14px; - color: #849299; - display:none; - } - } - - h2 { - font-size:18px; - font-weight:normal; - min-height:52px; - max-height:52px; - height:52px; - text-align:center; - vertical-align:middle; - line-height:26px; - color:@c0; - - .separator { - margin:0px 0px; - } - - text-overflow: ellipsis; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; - } - - &.card-inactive { - .l-box { - position:relative; - box-shadow: none; - background: @link2; - color:#FFF; - height:180px; - border-color:@link2; - &:hover { - background: darken(@link2,10%); - &:before { background:darken(@link2,10%); } - } - } - - h2 { - padding-top:10px; - color:#FFF; - } - - em { - position:absolute; - border-top:1px solid rgba(255,255,255,.5); - bottom: 15px; - font-size: 13px; - left: 25px; - right: 25px; - line-height:1.3; - padding:0px; - padding-top:20px; - text-align:center; - display:block; - height:30px; - text-transform:uppercase; - color:#FFF; - } - } - - &.card-browse-inactive, - &.card-browse { - text-align:center; - color: @link2; - font-size:16px; - font-weight:bold; - text-transform:uppercase; - .l-box { - padding-top:75px; - background:#FFF; - height:180px; - } - } - - &.card-browse-inactive { - .l-box { - box-shadow:none; - } - } - - - } - - .progressContainer { - height:5px; - background-color: @cfailure; - position: absolute; - bottom:65px; - left:20px; - right:20px; - - .activeProgress, .secondaryProgress { - position: absolute; - top: 0; - left: 0; - bottom: 0; - } - - .activeProgress { - background-color: @csuccess; - } - - .secondaryProgress { - background-color: @csuccess; - } - } - - .tabs { - font-size:inherit; - padding-left: 0px; - line-height: initial; - min-height:initial; - margin-bottom:10px; - border-bottom: 1px solid #e0e0e0; - - .pure-button { - background: #f5f5f5; - color: rgba(0,0,0,.8); - - &.active { - background: #e0e0e0; - } - } - } -} - -#commitpage { - max-width:1180px; - margin:0px auto; - margin-bottom:50px; - margin-top:70px; - - section { - margin-top:30px; - - .commits { - border: 1px solid #DDD; - border-bottom: 0px solid #DDD; - .border_box; - a { - padding:20px 45px; - display:block; - border-bottom: 1px solid #dadcdd; - color: @c0; - text-decoration:none; - position:relative; - .border_box; - - h2 { - font-family: @sans; - font-weight: bold; - font-size: 16px; - margin-bottom:5px; - } - - img { - border-radius:50%; - margin-right:10px; - float:left; - display:none; - } - - p { - color: #363636; - line-height:22px; - vertical-align:middle; - } - - &[data-status]:before { - background: transparent; - width: 7px; - min-width:7px; - max-width:7px; - position: absolute; - left: -1px; - top: 0px; - bottom: 0px; - text-align: left; - color: #fff; - font-size: 20px; - line-height: 50px; - font-family: @sans; - padding-left:2px; - overflow:hidden; - content: " "; - } - - &[data-result="Killed"], - &[data-status="Error"], - &[data-status="Failure"] { - background: #fff9f5; - &:before { - background: @cfailure; - content: "!"; - } - } - - &[data-status="Success"]:before { - background: @csuccess; - } - } - } - } - - .date { - span { - display:inline-block; - text-align:right; - font-size:14px; - width:100%; - padding-right:30px; - margin-top:15px; - .border_box; - } - } -} - -#setuppage, -#loginpage { - .pure-g { - padding: 30px; - border: 1px solid #DDD; - max-width:400px; - margin:0px auto; - margin-top:50px; - .border_box; - - a { - display:block; - background:@c1; - color:#fff; - padding:14px 20px; - font-size:15px; - border-radius:5px; - text-decoration:none; - &:hover { - background:@c0; - } - } - - [class*="fa-"] { - float:left; - font-size:20px; - position:relative; - top:-3px; - left:-3px; - padding-right:10px; - min-width:27px; - min-height:20px; - } - - .pure-u-1 a { - margin-bottom:10px; - } - - .pure-u-1:last-child a { - margin-bottom:0px; - } - } - form.pure-g { - input[type="text"], - input[type="password"] { - .border_box(); - width:100%; - padding:8px; - font-size:14px; - margin-bottom:15px; - border: 1px solid #DDD; - } - input[type="submit"] { - display:block; - background:@c1; - color:#fff; - padding:14px 20px; - font-size:15px; - border-radius:5px; - text-decoration:none; - width:100%; - border:none; - &:hover { - background:@c0; - } - } - } -} - -#setuppage2 { - margin-bottom:50px; - section { - .border_box; - .pure-g { - padding: 30px; - border: 1px solid #DDD; - max-width:400px; - margin:0px auto; - margin-top:50px; - .border_box; - } - label { - display:inline-block; - } - input[type='text'] { - margin-top:5px; - margin-bottom:10px; - box-shadow:none; - width:100%; - } - .pure-button-primary { - color:#FFF; - background: @link2; - padding:10px 20px; - margin-top:20px; - width:100%; - } - .tip { - h2 { - font-size: 16px; - margin-bottom: 20px; - } - dd { - font-weight:bold; - color:#666; - margin-top:15px; - margin-bottom:5px; - } - dt { - padding: .5em .6em; - display: inline-block; - border: 1px solid #ccc; - border-radius: 4px; - .border_box; - width: 100%; - } - } - } -} - -#syncpage { - width:100%; - section { - padding:40px 0px 20px 0px; - h1 { - text-align: center; - font-size: 24px; - margin-bottom:20px; - i { - font-size:32px; - margin-top:20px; - } - } - } -} - -#homepage { - width:100%; - background:@grey50; - - section { - padding:40px 0px 20px 0px; - //border-bottom:1px solid #EEE; - - h1 { - text-align: center; - font-size: 24px; - margin-bottom:20px; - i { - font-size:32px; - margin-top:20px; - } - } - - div { - max-width:1180px; - margin:0px auto; - } - - &:nth-child(2) { - background:#FFF; - padding:40px 0px 20px 0px; - } - - &:nth-child(3) { - border-bottom:0px solid #EEE; - } - - .card[data-status="Killed"], - .card[data-status="Failure"], - .card[data-status="Error"] { - .l-box { - border: 1px solid @cfailure; - &:hover { - border:1px solid @c0; - } - } - } - } -} - -#repospage { - width:100%; - section { - border-bottom:1px solid @grey200; - max-width:768px; - margin:0px auto; - margin-top:30px; - margin-bottom:30px; - - .search { - margin-bottom: 25px; - input[type="text"], - input[type="search"] { - .transition(.4s border linear); - border: 1px solid #ccc; - border-radius: 0px; - box-shadow: none; - padding: 12px; - &:focus { - border-color: #129FEA; - } - } - } - - .repo { - .border_box; - text-decoration:none; - - &:last-child > div { - border-bottom:1px solid #fff; - } - - &> div { - .border_box; - border:1px solid @grey200; - border-bottom:1px solid #fff; - .transition(.4s border linear); - - & > div { - .border_box; - padding:20px 25px; - &:last-child div { - text-align:right; - } - } - - &:hover { - border:1px solid @c0; - } - } - - h4 { - font-size:20px; - margin-bottom:2px; - color:@c0; - } - - span { - color:#666; - font-size:14px; - } - - i { - color: #DDD; - font-size: 22px; - margin-left:20px; - margin-top:15px; - &.fa-check-circle-o { - color:@csuccess; - } - } - } - } -} - -#userspage { - width:100%; - section { - border-bottom:1px solid @grey200; - max-width:768px; - margin:0px auto; - margin-top:30px; - margin-bottom:30px; - - .search { - margin-bottom: 25px; - input[type="text"], - input[type="search"] { - .transition(.4s border linear); - border: 1px solid #ccc; - border-radius: 0px; - box-shadow: none; - padding: 12px; - &:focus { - border-color: #129FEA; - } - } - } - - .user { - .border_box; - text-decoration:none; - - &:last-child > div { - border-bottom:1px solid #fff; - } - - &> div { - .border_box; - border:1px solid @grey200; - border-bottom:1px solid #fff; - .transition(.4s border linear); - - & > div { - .border_box; - padding:20px 25px; - padding-right:0px; - } - - &:hover { - border:1px solid @c0; - } - } - - - img { - border-radius:50%; - width:48px; - height:48px; - } - - h4 { - font-size:20px; - margin-bottom:2px; - color:@c0; - small { - font-size: 16px; - color:#666; - margin-left:5px; - } - } - - span { - color:#666; - font-size:14px; - } - } - } -} - -#repoconfpage { - width:100%; - section { - .border_box; - border:1px solid @grey200; - max-width:768px; - margin:0px auto; - margin-top:30px; - margin-bottom:30px; - padding:20px; - h2 { - font-size: 16px; - margin-bottom: 15px; - } - .markdown, - .params, - .key { - .border_box; - min-height:50px; - margin-top:10px; - font-family:@mono; - border:1px solid @grey200; - padding:20px; - width:100%; - max-width:100%; - color:#666; - &:focus { - border-color: #129FEA; - outline: 0; - } - } - .pure-button-primary { - color:#FFF; - background: @link2; - padding:10px 20px; - margin-top:20px; - } - select, - input[type="number"] { - .border_box(); - padding:8px; - font-size:14px; - margin-bottom:15px; - border: 1px solid #DDD; - } - span.seconds { - color:#212121; - margin-left:10px; - } - span.minutes { - color:#212121; - &:before { - content:'('; - } - &:after { - content:')'; - } - } - } -} - -#accountpage { - width:100%; - section { - position:relative; - max-width:768px; - margin:0px auto; - margin-top:30px; - border:1px solid @grey200; - - &.profile { - &> div:first-child { - .border_box; - padding:20px; - text-align:center; - } - &> div:last-child { - .border_box; - padding:20px; - } - - .fullname { - font-size:14px; - margin-bottom:2px; - color:#666; - display:block; - } - .email { - font-size:14px; - color:#666; - display:block; - } - } - - &.token { - &> div:first-child { - div { - .border_box; - text-align:center; - padding:20px; - color:#666; - font-size:16px; - line-height:22px; - } - - i { - margin-right:7px; - } - } - &> div:last-child { - .border_box; - padding:20px; - color:#666; - line-height:22px; - font-size:14px; - word-break: break-all; - } - } - - h4 { - margin:10px 0px; - font-size:22px; - small { - opacity:0.6; - font-size:16px; - margin-left:10px; - } - } - - img { - width:64px; - height:64px; - border-radius:50%; - } - - .notifications { - position:absolute; - top:0px; - right:0px; - margin:20px; - } - - .button-error { - color:#FFF; - background: rgb(202, 60, 60); - padding:10px 20px; - float:right; - } - - .pure-button-primary { - color:#FFF; - background: @link2; - padding:10px 20px; - margin-top:10px; - } - } -} - -#repopage { - width:100%; - background:@grey50; - - section { - padding:40px 0px 20px 0px; - //border-bottom:1px solid #EEE; - &> div { - max-width:1180px; - margin:0px auto; - } - - &:nth-child(even) { - background:#FFF; - } - - &:first-child { - background:#FFF; - } - - .card[data-status="Success"] { - &:nth-child(2) .l-box { - border-color: @csuccess; - &:hover { - border:1px solid @c0; - } - } - } - - .card[data-status="Killed"], - .card[data-status="Failure"], - .card[data-status="Error"] { - &:nth-child(2) .l-box { - border-color: @cfailure; - &:hover { - border:1px solid @c0; - } - } - } - - .card[data-status="Started"] em:before, - .card[data-status="Pending"] em:before { - -webkit-animation: progress 1s linear infinite; - -moz-animation: progress 1s linear infinite; - animation: progress 1s linear infinite; - position: absolute; - content: ' '; - height: 5px; - top: -5px; - left: 0px; - right: 0px; - margin: 0px; - background: @cpending; - background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.55) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0.55) 75%, transparent 75%, transparent); - background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.55) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0.55) 75%, transparent 75%, transparent); - background-repeat: repeat-x; - background-size: 30px 30px; - } - - - .l-box { - em { - line-height:19px; - bottom: 25px; - } - } - - .l-box:after { - font-family: 'FontAwesome'; - content: "\f104"; - content: "\f0d9"; - position: absolute; - right: -20px; - width: 20px; - text-align: center; - color: rgba(0,0,0,0.1); - font-size: 22px; - } - .card:last-child .l-box:after { - content:''; - } - - &.nobuilds, - &.inactive { - text-align:center; - padding-bottom:50px; - h1 { - font-size: 26px; - color: @c0; - } - p { - font-size: 16px; - color: #666; - } - i { - font-size: 32px; - margin-top: 20px; - margin-bottom: 20px; - &.fa-file-code-o { - font-size: 42px; - margin-top: 30px; - } - } - .pure-button-primary { - font-size: 14px; - text-transform: uppercase; - background: #4ab1ce; - padding: 10px 30px; - } - } - - &.nobuilds { - - } - } -} - -@-webkit-keyframes progress { - to { - background-position: 30px 0; - } -} -@-moz-keyframes progress { - to { - background-position: 30px 0; - } -} -@keyframes progress { - to { - background-position: 30px 0; - } -} - - - -#sidebar { - width: 240px; - min-width: 240px; - position: relative; - display: block; - z-index: 5; - padding: 30px; - .border_box; - - //border-left:1px solid #cdcece; - //box-shadow:-3px 0 0 rgba(0,0,0,.05); - - #sidebar-inner { - position: fixed; - width: 180px; - } - - h1 {font-size:28px; font-weight:300;} - h2 {font-size:22px; font-weight:300; margin-bottom:20px;} - dl {padding-top:23px; border-top:1px solid #ddd; margin-top:5px; - - &:first-child {padding-top:0; border-top:none; margin-top:0;} - - dt {font-size:12px; color:@c2; text-transform:uppercase; padding:3px 0;} - dd {font-size:14px; padding:3px 0 20px;} - a {text-transform:none;} - small {font-size:12px;} - .large { - font-size:18px; - padding-bottom:5px; - - a { - color: #212121; - } - - } - .time {float:right; margin-left:8px;} - .photo {margin-right:4px;} - .negative {color:@cfailure;} - .photoline {display:inline-block; position:relative; top:-10px; font-weight:bold;} - .small {padding-bottom:5px; font-weight:bold; font-size:12px;} - } - - .status { - border:1px solid transparent; - display:block; - text-align:center; - padding:5px 20px; - border-radius:50px; - text-transform:uppercase; - margin:0 -5px 10px; - font-weight:bold; - &:before {float:left; margin-left:-5px;} - &.status_ok { - color:@csuccess; border-color:@csuccess; - &:before {content:"\f00c"; font-family:'FontAwesome';} - } - &.status_error { - color:@cfailure; border-color:@cfailure; - &:before {content:"!";} - } - } - - .result { - background:@link2; - background:@csuccess; - color:#fff; - margin:-30px -30px -6px; - padding:30px; - position:relative; - .status { - color:#fff; - background:rgba(255,255,255,.2); - &:before { - content:"\f00c"; - font-family:'FontAwesome'; - } - } - dl { - dd { - padding:7px 0; - strong { font-size:16px; } - } - } - - &[data-result="Killed"], - &[data-result="Failure"], - &[data-result="Error"] { - background: @cfailure; - .status:before { - content:"!"; - } - } - - &[data-result="Pending"], - &[data-result="Started"] { - background:@cpending; - .status:before { - content:"\f021"; - -webkit-animation: spin 2s infinite linear; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; - } - } - } -} - -#main { - flex-grow: 2; - &.output { - position:relative; - //background: #424242; - background: @c0; - pre { - margin:0px auto; - padding:30px; - color:#FFF; - font-family:@mono; - font-size:13px; - white-space: pre-wrap; - word-break: break-all; - overflow: hidden; - line-height:18px; - } - - #follow button { - position: fixed; - right: 280px; - bottom: 21px; - z-index: 100; - padding: 0 1em; - border-radius: 7px; - background-color: rgba(238, 238, 238, 0.2); - padding: 0 1em; - cursor: pointer; - font-family: @sans; - border: none; - color: #fff; - } - - &[data-result="Pending"]:after, - &[data-result="Started"]:after { - position: absolute; - right:20px; - bottom:20px; - color:#9e9e9e; - font-size:18px; - font-family:@fontawesome; - content: "\f021"; - -webkit-animation: spin 2s infinite linear; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; - } - } -} - -.pure-form.pure-form-stacked { - label { - font-size:14px; - color:#666; - margin-top:20px; - margin-bottom:5px; - &:first-child { - margin-top:0px; - } - } - - input[type="text"], - select { - min-width:300px; - box-shadow:none; - padding:10px 10px; - border: 1px solid #ccc; - border-radius:0px; - } -} - -// special thanks to: -// http://www.designcouch.com/home/why/2013/09/19/ios7-style-pure-css-toggle/ -.toggle { - margin-bottom:10px; - &:nth-child(2) { - margin-top:40px; - } - span { - line-height:32px; - vertical-align:middle; - display: inline-block; - margin-left:10px; - } - input[type='checkbox'] { - max-height: 0; - max-width: 0; - opacity: 0; - - // The following provides the "container" for our toggle - // in its default (off) state - & + label { - display: inline-block; - vertical-align: middle; - position: relative; - box-shadow: inset 0 0 0px 1px #d5d5d5; - text-indent: -5000px; - height: 30px; - width: 50px; - border-radius: 15px; - - // The following provides the green background for the - // "on" state of our toggle - it is hidden when the - // switch is off - &:before { - content: ""; - position: absolute; - display: block; - height: 30px; - width: 30px; - top: 0; - left: 0; - border-radius: 15px; - background: rgba(19,191,17,0); - .transition(.25s ease-in-out); - } - - - // The following provides the actual switch - // and its drop shadow - &:after { - content: ""; - position: absolute; - display: block; - height: 30px; - width: 30px; - top: 0; - left: 0px; - border-radius: 15px; - background: white; - box-shadow: inset 0 0 0 1px rgba(0,0,0,.2), 0 2px 4px rgba(0,0,0,.2); - .transition(.25s ease-in-out); - } - } - - - // The following defines the "on" state for the switch - &:checked + label:before { - width: 50px; - background: @link2; - } - - &:checked + label:after { - left: 20px; - box-shadow: inset 0 0 0 1px @link2, 0 2px 4px rgba(0,0,0,.2); - } - } -} - - -.toast { - position: fixed; - bottom: 50px; - left: 20%; - right: 20%; - background: #363636; - border-radius: 3px; - z-index: 999; - color: #FFF; - padding: 15px 20px; - font-size: 14px; - box-shadow: 2px 2px 2px rgba(0,0,0,0.2); - - a, a:visited, a:hover, a:active { - color:#FFF; - } - - button { - float: right; - background: transparent; - border: none; - border: none; - color: #EEFF41; - text-transform: uppercase; - margin-left:10px; - } -} - -@media screen and (min-width: 1180px) { - #repopage { - h2 { - padding-left:0px; - } - } - - .cards { - .card { - padding-left:0px; - } - } -} - -@media screen and (min-width: 80em) { - -} -@media screen and (max-width: 35.5em) { - .cards { - .card { - .l-box:after { - display: none !IMPORTANT; - } - } - } - - header { - .username { - display:none; - } - } - - #repopage { - h2 { - padding-left:20px; - } - } - - #userspage, - #repospage { - form { - display:none; - } - section { - margin:0px; - .user, - .repo { - &:nth-child(2) > .pure-g { - border-top:1px solid #FFF; - } - &:nth-child(2):hover > .pure-g { - border-top:1px solid @c0; - } - i { - margin-left:0px; - } - } - } - } - - #accountpage { - .token { - span { - display:none; - } - } - } - - nav { - div.options { - .pure-button{ - i { - margin:0px; - } - span { - display:none; - } - } - } - } -} - -table.list-view { - font-size: 11.8px; - line-height: 22px; - color: #666; - border-spacing: 0; - width:100%; - - tr { - td:first-child:before { - display: block; - content: "."; - color: transparent; - font-size: 0; - width: 4px; - height: 32px; - float: left; - margin: -5px 5px -5px 0; - } - - &[data-status=Started], &[data-status=Pending] { - td { - background-color: #fffcf4; - } - &:hover td { - background-color: #ffffe1; - } - - td:first-child:before { - background-color: #e7c600; - animation: progress 2s linear infinite; - background-image: linear-gradient(-45deg, rgba(255, 255, 255, .55) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .55) 50%, rgba(255, 255, 255, .55) 75%, transparent 75%, transparent); - background-repeat: repeat; - background-size: 10px 10px; - } - } - - &[data-status=Error], &[data-status=Failure], &[data-status=Killed] { - td { - background-color: snow; - } - &:hover td { - background-color: #ffdcdc; - } - td:first-child { - color: #c00; - &:before { - background-color: #c00; - } - } - } - - &[data-status=Success] { - td { - background-color: #fafffa; - } - &:hover td { - background-color: #dcffdc; - } - td:first-child { - color: #038035; - &:before { - background-color: #038035; - } - } - } - } - - th { - color: #666; - white-space: nowrap; - border-bottom: 2px solid #fff; - text-align: left; - padding: 5px 20px 5px 0; - vertical-align: top; - font-weight: bold; - } - - - a { - color: inherit; - } - - td { - white-space: nowrap; - border-bottom: 2px solid #fff; - text-align: left; - padding: 5px 20px 5px 0; - vertical-align: top; - - &:first-child { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - a:before { - margin:0 4px 0 7px; - } - } - - &:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - } - - &:nth-child(2) { - width: 100%; - white-space: normal; - } - } -} \ No newline at end of file diff --git a/server/app/styles/ladda.less b/server/app/styles/ladda.less deleted file mode 100644 index b7d41e611..000000000 --- a/server/app/styles/ladda.less +++ /dev/null @@ -1,421 +0,0 @@ -// @see http://codepen.io/hakimel/pen/gkeha - -.ladda-button { - position: relative; - background: none; - border: 0; - cursor: pointer; - outline: 0; - - -webkit-appearance: none; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - - &[data-loading='true'] { - cursor: default; - } - - &:disabled { - opacity:1; - } - - /* - .spinner { - position: absolute; - width: 32px; - height: 32px; - top: 50%; - margin-top: -16px; - opacity: 0; - background-image: url( data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf///6+vr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs/Ly8vz8/AAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA==); - }*/ -} - -/************************************* - * EASING - */ - -.ladda-button, -.ladda-button .spinner, -.ladda-button .label { - -webkit-transition: 0.3s cubic-bezier(0.175, 0.885, 0.320, 1.275) all; - -moz-transition: 0.3s cubic-bezier(0.175, 0.885, 0.320, 1.275) all; - -ms-transition: 0.3s cubic-bezier(0.175, 0.885, 0.320, 1.275) all; - transition: 0.3s cubic-bezier(0.175, 0.885, 0.320, 1.275) all; -} - -.ladda-button.zoom-in, -.ladda-button.zoom-in .spinner, -.ladda-button.zoom-in .label, -.ladda-button.zoom-out, -.ladda-button.zoom-out .spinner, -.ladda-button.zoom-out .label { - -webkit-transition: 0.3s ease all; - -moz-transition: 0.3s ease all; - -ms-transition: 0.3s ease all; - transition: 0.3s ease all; -} - - -/************************************* - * EXPAND LEFT - */ - -.ladda-button.expand-right .spinner { - right: 0.8em; -} - -.ladda-button.expand-right { - &[data-loading='true'] { - padding-right: 56px !IMPORTANT; - } - &[data-loading='true']:after { - content:''; - width: 20px; - height: 20px; - display:block; - border-radius:50%; - - border: 3px solid #fff; - border-right-color: rgba(0, 0, 0, 0); - border-top-color: rgba(0, 0, 0, 0); - -webkit-animation: spin 1s linear infinite; - -moz-animation: spin 1s linear infinite; - -ms-animation: spin 1s linear infinite; - -o-animation: spin 1s linear infinite; - animation: spin 1s linear infinite; - position: absolute; - top: 5px; - right: 5px; - } -} - -.ladda-button.expand-right[data-loading='true'] { - padding-right: 56px !IMPORTANT; -} - .ladda-button.expand-right[data-loading='true'] .spinner { - opacity: 1; - } - - -/************************************* - * EXPAND RIGHT - */ - -.ladda-button.expand-left .spinner { - left: 0.8em; -} - -.ladda-button.expand-left[data-loading='true'] { - padding-left: 56px !IMPORTANT; -} - .ladda-button.expand-left[data-loading='true'] .spinner { - opacity: 1; - } - - -/************************************* - * EXPAND UP - */ - -.ladda-button.expand-up { - overflow: hidden; -} - .ladda-button.expand-up .spinner { - top: -32px; - left: 50%; - margin-left: -16px; - } - -.ladda-button.expand-up[data-loading='true'] { - padding-top: 3em; -} - .ladda-button.expand-up[data-loading='true'] .spinner { - opacity: 1; - top: 0.8em; - margin-top: 0; - } - - -/************************************* - * EXPAND DOWN - */ - -.ladda-button.expand-down { - overflow: hidden; -} - .ladda-button.expand-down .spinner { - top: 3.3em; - left: 50%; - margin-left: -16px; - } - -.ladda-button.expand-down[data-loading='true'] { - padding-bottom: 3em; -} - .ladda-button.expand-down[data-loading='true'] .spinner { - opacity: 1; - } - - -/************************************* - * SLIDE LEFT - */ -.ladda-button.slide-left { - overflow: hidden; -} - .ladda-button.slide-left .label { - position: relative; - } - .ladda-button.slide-left .spinner { - left: 100%; - margin-left: -16px; - } - -.ladda-button.slide-left[data-loading='true'] .label { - opacity: 0; - left: -100%; -} -.ladda-button.slide-left[data-loading='true'] .spinner { - opacity: 1; - left: 50%; -} - - -/************************************* - * SLIDE RIGHT - */ -.ladda-button.slide-right { - overflow: hidden; -} - .ladda-button.slide-right .label { - position: relative; - } - .ladda-button.slide-right .spinner { - right: 100%; - margin-left: -16px; - } - -.ladda-button.slide-right[data-loading='true'] .label { - opacity: 0; - left: 100%; -} -.ladda-button.slide-right[data-loading='true'] .spinner { - opacity: 1; - left: 50%; -} - - -/************************************* - * SLIDE UP - */ -.ladda-button.slide-up { - overflow: hidden; -} - .ladda-button.slide-up .label { - position: relative; - } - .ladda-button.slide-up .spinner { - left: 50%; - margin-left: -16px; - margin-top: 1em; - } - -.ladda-button.slide-up[data-loading='true'] .label { - opacity: 0; - top: -1em; -} -.ladda-button.slide-up[data-loading='true'] .spinner { - opacity: 1; - margin-top: -16px; -} - - -/************************************* - * SLIDE DOWN - */ -.ladda-button.slide-down { - overflow: hidden; -} - .ladda-button.slide-down .label { - position: relative; - } - .ladda-button.slide-down .spinner { - left: 50%; - margin-left: -16px; - margin-top: -2em; - } - -.ladda-button.slide-down[data-loading='true'] .label { - opacity: 0; - top: 1em; -} -.ladda-button.slide-down[data-loading='true'] .spinner { - opacity: 1; - margin-top: -16px; -} - - -/************************************* - * ZOOM-OUT - */ - -.ladda-button.zoom-out { - overflow: hidden; -} - .ladda-button.zoom-out .spinner { - left: 50%; - margin-left: -16px; - - -webkit-transform: scale( 2.5 ); - -moz-transform: scale( 2.5 ); - -ms-transform: scale( 2.5 ); - transform: scale( 2.5 ); - } - .ladda-button.zoom-out .label { - position: relative; - display: inline-block; - } - -.ladda-button.zoom-out[data-loading='true'] .label { - opacity: 0; - - -webkit-transform: scale( 0.5 ); - -moz-transform: scale( 0.5 ); - -ms-transform: scale( 0.5 ); - transform: scale( 0.5 ); -} -.ladda-button.zoom-out[data-loading='true'] .spinner { - opacity: 1; - - -webkit-transform: none; - -moz-transform: none; - -ms-transform: none; - transform: none; -} - - -/************************************* - * ZOOM-IN - */ - -.ladda-button.zoom-in { - overflow: hidden; -} - .ladda-button.zoom-in .spinner { - left: 50%; - margin-left: -16px; - - -webkit-transform: scale( 0.2 ); - -moz-transform: scale( 0.2 ); - -ms-transform: scale( 0.2 ); - transform: scale( 0.2 ); - } - .ladda-button.zoom-in .label { - position: relative; - display: inline-block; - } - -.ladda-button.zoom-in[data-loading='true'] .label { - opacity: 0; - - -webkit-transform: scale( 2.2 ); - -moz-transform: scale( 2.2 ); - -ms-transform: scale( 2.2 ); - transform: scale( 2.2 ); -} -.ladda-button.zoom-in[data-loading='true'] .spinner { - opacity: 1; - - -webkit-transform: none; - -moz-transform: none; - -ms-transform: none; - transform: none; -} - - -/************************************* - * CONTRACt - */ - -.ladda-button.contract { - overflow: hidden; - width: 100px; -} - .ladda-button.contract .spinner { - left: 50%; - margin-left: -16px; - } - -.ladda-button.contract[data-loading='true'] { - border-radius: 50%; - width: 52px; -} - .ladda-button.contract[data-loading='true'] .label { - opacity: 0; - } - .ladda-button.contract[data-loading='true'] .spinner { - opacity: 1; - } - - - -/************************************* - * OVERLAY - */ - -.ladda-button.contract-overlay { - overflow: hidden; - width: 100px; - - box-shadow: 0px 0px 0px 3000px rgba(0,0,0,0); -} - .ladda-button.contract-overlay .spinner { - left: 50%; - margin-left: -16px; - } - -.ladda-button.contract-overlay[data-loading='true'] { - border-radius: 50%; - width: 52px; - - /*outline: 10000px solid rgba( 0, 0, 0, 0.5 );*/ - box-shadow: 0px 0px 0px 3000px rgba(0,0,0,0.8); -} - .ladda-button.contract-overlay[data-loading='true'] .label { - opacity: 0; - } - .ladda-button.contract-overlay[data-loading='true'] .spinner { - opacity: 1; - } - - - - - - - - - - - - -@-webkit-keyframes spin { - 0% { -webkit-transform: rotate(0deg) } - 100% { -webkit-transform: rotate(360deg) } -} -@-moz-keyframes spin { - 0% { -moz-transform: rotate(0deg) } - 100% { -moz-transform: rotate(360deg) } -} -@-o-keyframes spin { - 0% { -o-transform: rotate(0deg) } - 100% { -o-transform: rotate(360deg) } -} -@-ms-keyframes spin { - 0% { -ms-transform: rotate(0deg) } - 100% { -ms-transform: rotate(360deg) } -} -@keyframes spin { - 0% { transform: rotate(0deg) } - 100% { transform: rotate(360deg) } -} \ No newline at end of file diff --git a/server/app/styles/reset.less b/server/app/styles/reset.less deleted file mode 100644 index 8c0eec225..000000000 --- a/server/app/styles/reset.less +++ /dev/null @@ -1,17 +0,0 @@ -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; - list-style:none; -} diff --git a/server/app/views/account.html b/server/app/views/account.html deleted file mode 100644 index 53172297c..000000000 --- a/server/app/views/account.html +++ /dev/null @@ -1,37 +0,0 @@ -
- - -
-
-
- - - -
-
-
-
-

{{ account.login }}

- {{ account.name }} - -
-
-
- -
-
-
- - api key -
-
-
-
- {{ account.token }} -
-
-
-
\ No newline at end of file diff --git a/server/app/views/commit.html b/server/app/views/commit.html deleted file mode 100644 index 787b1241e..000000000 --- a/server/app/views/commit.html +++ /dev/null @@ -1,45 +0,0 @@ - - -
-
- -
- - -
-

-	
-
diff --git a/server/app/views/commit_detail.html b/server/app/views/commit_detail.html deleted file mode 100644 index c53f9dfb3..000000000 --- a/server/app/views/commit_detail.html +++ /dev/null @@ -1,32 +0,0 @@ - -
- - commit - {{ commit.sha | shortHash}} - to {{ commit.branch }} branch - -
- - -
- - commit - {{ commit.sha | shortHash}} - to {{ commit.branch }} branch - -
- - -
- - commit - {{ commit.sha | shortHash}} - to {{ commit.branch }} branch - -
- - -
- commit {{ commit.sha | shortHash}} to {{ commit.branch }} branch -
- diff --git a/server/app/views/commit_detail_pr.html b/server/app/views/commit_detail_pr.html deleted file mode 100644 index 6669a7b38..000000000 --- a/server/app/views/commit_detail_pr.html +++ /dev/null @@ -1,31 +0,0 @@ - -
- - Pull Request - #{{ commit.pull_request }} - -
- - -
- - Pull Request - #{{ commit.pull_request }} - -
- - -
- - Pull Request - #{{ commit.pull_request }} - -
- - -
- - Pull Request #{{ commit.pull_request }} - -
- \ No newline at end of file diff --git a/server/app/views/config.html b/server/app/views/config.html deleted file mode 100644 index 8784f5978..000000000 --- a/server/app/views/config.html +++ /dev/null @@ -1,51 +0,0 @@ -
- - -
-
-
-

{{ remote.type | remoteName }}

-
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- - - Enable Self-Registration -
- - -
-
-
-
-
diff --git a/server/app/views/drawer.html b/server/app/views/drawer.html deleted file mode 100644 index 72d58fb4d..000000000 --- a/server/app/views/drawer.html +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/server/app/views/header.html b/server/app/views/header.html deleted file mode 100644 index dcaff05a4..000000000 --- a/server/app/views/header.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - -Drone - - - -
- Login -
\ No newline at end of file diff --git a/server/app/views/home.html b/server/app/views/home.html deleted file mode 100644 index 9ae565cc8..000000000 --- a/server/app/views/home.html +++ /dev/null @@ -1,54 +0,0 @@ -
- sync already in progress - bad response -
- -
- - -
-
-

Activate your Repositories

-
-
- -
- -
- -
- -
- diff --git a/server/app/views/login.html b/server/app/views/login.html deleted file mode 100644 index 6d899abaa..000000000 --- a/server/app/views/login.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - diff --git a/server/app/views/login_gogs.html b/server/app/views/login_gogs.html deleted file mode 100644 index e949dda97..000000000 --- a/server/app/views/login_gogs.html +++ /dev/null @@ -1,23 +0,0 @@ - - - -
-
-
- -
-
- -
-
- -
-
-
diff --git a/server/app/views/logout.html b/server/app/views/logout.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/server/app/views/repo.html b/server/app/views/repo.html deleted file mode 100644 index 474ca39b2..000000000 --- a/server/app/views/repo.html +++ /dev/null @@ -1,114 +0,0 @@ -
- commit - {{ msg.commit.sha | shortHash }} - is running - finished successfully - finished with errors - -
- -
- - -
-

This will be your commit stream

-

Activate this repository to start testing your commits

-
- -
- -
-

This will be your commit stream

-

Add a .drone.yml file and make a commit to trigger a build

-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - -
BuildMessageCommitCommitterDurationFinished
{{ commit.id }}{{ commit.message }}{{ commit.sha | shortHash }} ({{ commit.branch }}){{ commit.author }}{{ commit.duration | amDurationFormat:'seconds':false }}-
-
-
- -
- -
- -
- -
-
\ No newline at end of file diff --git a/server/app/views/repo_edit.html b/server/app/views/repo_edit.html deleted file mode 100644 index 77cc7c443..000000000 --- a/server/app/views/repo_edit.html +++ /dev/null @@ -1,106 +0,0 @@ -
- - - - -
-
-
-
-

Build Flags

-
- - - Enable Post Commit Hooks -
- -
- - - Enable Pull Request Hooks -
- -
- - - Enable Privileged mode -
- - -
-
-
-
- -
-
-
-
-

Build Timeout

-
- - Seconds - {{ repo.timeout / 60 | number:0 }} minutes -
- - -
-
-
-
- - -
-
-
-
-

Private Variables (YAML syntax)

- - -
-
-
-
- - -
-
-
- - - -

Status Badge

-
-
-
{{ repo | badgeMarkdown }}
-
-
-
- -
-
-
-
-

Public Key

-
{{ repo.public_key }}
-
-
-
-
- -
\ No newline at end of file diff --git a/server/app/views/repo_list.html b/server/app/views/repo_list.html deleted file mode 100644 index 83df9a992..000000000 --- a/server/app/views/repo_list.html +++ /dev/null @@ -1,42 +0,0 @@ -
- sync already in progress - bad response -
- - \ No newline at end of file diff --git a/server/app/views/sync.html b/server/app/views/sync.html deleted file mode 100644 index 2f5729e73..000000000 --- a/server/app/views/sync.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-

Syncing your Account

-
-
-
\ No newline at end of file diff --git a/server/app/views/users.html b/server/app/views/users.html deleted file mode 100644 index fda5c7f7e..000000000 --- a/server/app/views/users.html +++ /dev/null @@ -1,36 +0,0 @@ - \ No newline at end of file diff --git a/server/app/views/users_add.html b/server/app/views/users_add.html deleted file mode 100644 index 8b1fc4f4f..000000000 --- a/server/app/views/users_add.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - -
-
-
- - - -
-
-
-
-
-
- - - - - -
-
- -
-
-
-
diff --git a/server/app/views/users_edit.html b/server/app/views/users_edit.html deleted file mode 100644 index 47ea2378a..000000000 --- a/server/app/views/users_edit.html +++ /dev/null @@ -1,26 +0,0 @@ -
- - -
-
-
- - - -
-
-
-
-

{{ account.login }} {{ account.remote }}

- {{ account.name }} - - -
-
-
-
\ No newline at end of file diff --git a/server/blobstore/blobstore.go b/server/blobstore/blobstore.go deleted file mode 100644 index 200446066..000000000 --- a/server/blobstore/blobstore.go +++ /dev/null @@ -1,55 +0,0 @@ -package blobstore - -import ( - "io" - - "code.google.com/p/go.net/context" -) - -type Blobstore interface { - // Del removes an object from the blobstore. - Del(path string) error - - // Get retrieves an object from the blobstore. - Get(path string) ([]byte, error) - - // GetReader retrieves an object from the blobstore. - // It is the caller's responsibility to call Close on - // the ReadCloser when finished reading. - GetReader(path string) (io.ReadCloser, error) - - // Put inserts an object into the blobstore. - Put(path string, data []byte) error - - // PutReader inserts an object into the blobstore by - // consuming data from r until EOF. - PutReader(path string, r io.Reader) error -} - -// Del removes an object from the blobstore. -func Del(c context.Context, path string) error { - return FromContext(c).Del(path) -} - -// Get retrieves an object from the blobstore. -func Get(c context.Context, path string) ([]byte, error) { - return FromContext(c).Get(path) -} - -// GetReader retrieves an object from the blobstore. -// It is the caller's responsibility to call Close on -// the ReadCloser when finished reading. -func GetReader(c context.Context, path string) (io.ReadCloser, error) { - return FromContext(c).GetReader(path) -} - -// Put inserts an object into the blobstore. -func Put(c context.Context, path string, data []byte) error { - return FromContext(c).Put(path, data) -} - -// PutReader inserts an object into the blobstore by -// consuming data from r until EOF. -func PutReader(c context.Context, path string, r io.Reader) error { - return FromContext(c).PutReader(path, r) -} diff --git a/server/blobstore/context.go b/server/blobstore/context.go deleted file mode 100644 index 519deb114..000000000 --- a/server/blobstore/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package blobstore - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "blobstore" - -// NewContext returns a Context whose Value method returns the -// application's Blobstore data. -func NewContext(parent context.Context, store Blobstore) context.Context { - return &wrapper{parent, store} -} - -type wrapper struct { - context.Context - store Blobstore -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.store - } - return c.Context.Value(key) -} - -// FromContext returns the Blobstore associated with this context. -func FromContext(c context.Context) Blobstore { - return c.Value(reqkey).(Blobstore) -} diff --git a/server/datastore/commit.go b/server/datastore/commit.go deleted file mode 100644 index f544db276..000000000 --- a/server/datastore/commit.go +++ /dev/null @@ -1,124 +0,0 @@ -package datastore - -import ( - "code.google.com/p/go.net/context" - "github.com/drone/drone/shared/model" -) - -type Commitstore interface { - // GetCommit retrieves a commit from the - // datastore for the given ID. - GetCommit(id int64) (*model.Commit, error) - - // GetCommitSha retrieves a commit from the - // datastore for the specified repo and sha - GetCommitSha(repo *model.Repo, branch, sha string) (*model.Commit, error) - - // GetCommitLast retrieves the latest commit - // from the datastore for the specified repository - // and branch. - GetCommitLast(repo *model.Repo, branch string) (*model.Commit, error) - - // GetCommitList retrieves a list of latest commits - // from the datastore for the specified repository. - GetCommitList(repo *model.Repo, limit, offset int) ([]*model.Commit, error) - - // GetCommitListUser retrieves a list of latest commits - // from the datastore accessible to the specified user. - GetCommitListUser(user *model.User) ([]*model.CommitRepo, error) - - // GetCommitListActivity retrieves an ungrouped list of latest commits - // from the datastore accessible to the specified user. - GetCommitListActivity(user *model.User, limit, offset int) ([]*model.CommitRepo, error) - - // GetCommitPrior retrieves the latest commit - // from the datastore for the specified repository and branch. - GetCommitPrior(commit *model.Commit) (*model.Commit, error) - - // PostCommit saves a commit in the datastore. - PostCommit(commit *model.Commit) error - - // PutCommit saves a commit in the datastore. - PutCommit(commit *model.Commit) error - - // DelCommit removes the commit from the datastore. - DelCommit(commit *model.Commit) error - - // KillCommits updates all pending or started commits - // in the datastore settings the status to killed. - KillCommits() error - - // GetCommitBuildNumber retrieves the monotonically increaing build number - // from the commit's repo - GetBuildNumber(commit *model.Commit) (int64, error) -} - -// GetCommit retrieves a commit from the -// datastore for the given ID. -func GetCommit(c context.Context, id int64) (*model.Commit, error) { - return FromContext(c).GetCommit(id) -} - -// GetCommitSha retrieves a commit from the -// datastore for the specified repo and sha -func GetCommitSha(c context.Context, repo *model.Repo, branch, sha string) (*model.Commit, error) { - return FromContext(c).GetCommitSha(repo, branch, sha) -} - -// GetCommitLast retrieves the latest commit -// from the datastore for the specified repository -// and branch. -func GetCommitLast(c context.Context, repo *model.Repo, branch string) (*model.Commit, error) { - return FromContext(c).GetCommitLast(repo, branch) -} - -// GetCommitList retrieves a list of latest commits -// from the datastore for the specified repository. -func GetCommitList(c context.Context, repo *model.Repo, limit, offset int) ([]*model.Commit, error) { - return FromContext(c).GetCommitList(repo, limit, offset) -} - -// GetCommitListUser retrieves a list of latest commits -// from the datastore accessible to the specified user. -func GetCommitListUser(c context.Context, user *model.User) ([]*model.CommitRepo, error) { - return FromContext(c).GetCommitListUser(user) -} - -// GetCommitListActivity retrieves an ungrouped list of latest commits -// from the datastore accessible to the specified user. -func GetCommitListActivity(c context.Context, user *model.User, limit, offset int) ([]*model.CommitRepo, error) { - return FromContext(c).GetCommitListActivity(user, limit, offset) -} - -// GetCommitPrior retrieves the latest commit -// from the datastore for the specified repository and branch. -func GetCommitPrior(c context.Context, commit *model.Commit) (*model.Commit, error) { - return FromContext(c).GetCommitPrior(commit) -} - -// PostCommit saves a commit in the datastore. -func PostCommit(c context.Context, commit *model.Commit) error { - return FromContext(c).PostCommit(commit) -} - -// PutCommit saves a commit in the datastore. -func PutCommit(c context.Context, commit *model.Commit) error { - return FromContext(c).PutCommit(commit) -} - -// DelCommit removes the commit from the datastore. -func DelCommit(c context.Context, commit *model.Commit) error { - return FromContext(c).DelCommit(commit) -} - -// KillCommits updates all pending or started commits -// in the datastore settings the status to killed. -func KillCommits(c context.Context) error { - return FromContext(c).KillCommits() -} - -// GetBuildNumber retrieves the monotonically increaing build number -// from the commit's repo -func GetBuildNumber(c context.Context, commit *model.Commit) (int64, error) { - return FromContext(c).GetBuildNumber(commit) -} diff --git a/server/datastore/context.go b/server/datastore/context.go deleted file mode 100644 index aa54ba1d2..000000000 --- a/server/datastore/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package datastore - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "datastore" - -// NewContext returns a Context whose Value method returns the -// application's data storage objects. -func NewContext(parent context.Context, ds Datastore) context.Context { - return &wrapper{parent, ds} -} - -type wrapper struct { - context.Context - ds Datastore -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.ds - } - return c.Context.Value(key) -} - -// FromContext returns the sql.DB associated with this context. -func FromContext(c context.Context) Datastore { - return c.Value(reqkey).(Datastore) -} diff --git a/server/datastore/database/blob.go b/server/datastore/database/blob.go deleted file mode 100644 index 87614e814..000000000 --- a/server/datastore/database/blob.go +++ /dev/null @@ -1,75 +0,0 @@ -package database - -import ( - "bytes" - "io" - "io/ioutil" - - "github.com/russross/meddler" -) - -type Blob struct { - ID int64 `meddler:"blob_id,pk"` - Path string `meddler:"blob_path"` - Data string `meddler:"blob_data,gobgzip"` -} - -type Blobstore struct { - meddler.DB -} - -// Del removes an object from the blobstore. -func (db *Blobstore) Del(path string) error { - var _, err = db.Exec(rebind(blobDeleteStmt), path) - return err -} - -// Get retrieves an object from the blobstore. -func (db *Blobstore) Get(path string) ([]byte, error) { - var blob = Blob{} - var err = meddler.QueryRow(db, &blob, rebind(blobQuery), path) - return []byte(blob.Data), err -} - -// GetReader retrieves an object from the blobstore. -// It is the caller's responsibility to call Close on -// the ReadCloser when finished reading. -func (db *Blobstore) GetReader(path string) (io.ReadCloser, error) { - var blob, err = db.Get(path) - var buf = bytes.NewBuffer(blob) - return ioutil.NopCloser(buf), err -} - -// Put inserts an object into the blobstore. -func (db *Blobstore) Put(path string, data []byte) error { - var blob = Blob{} - meddler.QueryRow(db, &blob, rebind(blobQuery), path) - blob.Path = path - blob.Data = string(data) - return meddler.Save(db, blobTable, &blob) -} - -// PutReader inserts an object into the blobstore by -// consuming data from r until EOF. -func (db *Blobstore) PutReader(path string, r io.Reader) error { - var data, _ = ioutil.ReadAll(r) - return db.Put(path, data) -} - -func NewBlobstore(db meddler.DB) *Blobstore { - return &Blobstore{db} -} - -// Blob table name in database. -const blobTable = "blobs" - -const blobQuery = ` -SELECT * -FROM blobs -WHERE blob_path = ?; -` - -const blobDeleteStmt = ` -DELETE FROM blobs -WHERE blob_path = ?; -` diff --git a/server/datastore/database/blob_test.go b/server/datastore/database/blob_test.go deleted file mode 100644 index d61427df2..000000000 --- a/server/datastore/database/blob_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package database - -import ( - "bytes" - "io/ioutil" - "testing" - - "github.com/franela/goblin" -) - -func TestBlobstore(t *testing.T) { - db := mustConnectTest() - bs := NewBlobstore(db) - defer db.Close() - - g := goblin.Goblin(t) - g.Describe("Blobstore", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM blobs") - }) - - g.It("Should Put a Blob", func() { - err := bs.Put("foo", []byte("bar")) - g.Assert(err == nil).IsTrue() - }) - - g.It("Should Put a Blob reader", func() { - var buf bytes.Buffer - buf.Write([]byte("bar")) - err := bs.PutReader("foo", &buf) - g.Assert(err == nil).IsTrue() - }) - - g.It("Should Overwrite a Blob", func() { - bs.Put("foo", []byte("bar")) - bs.Put("foo", []byte("baz")) - blob, err := bs.Get("foo") - g.Assert(err == nil).IsTrue() - g.Assert(string(blob)).Equal("baz") - }) - - g.It("Should Get a Blob", func() { - bs.Put("foo", []byte("bar")) - blob, err := bs.Get("foo") - g.Assert(err == nil).IsTrue() - g.Assert(string(blob)).Equal("bar") - }) - - g.It("Should Get a Blob reader", func() { - bs.Put("foo", []byte("bar")) - r, _ := bs.GetReader("foo") - blob, _ := ioutil.ReadAll(r) - g.Assert(string(blob)).Equal("bar") - }) - - g.It("Should Del a Blob", func() { - bs.Put("foo", []byte("bar")) - err := bs.Del("foo") - g.Assert(err == nil).IsTrue() - }) - - }) -} diff --git a/server/datastore/database/commit.go b/server/datastore/database/commit.go deleted file mode 100644 index a779622cf..000000000 --- a/server/datastore/database/commit.go +++ /dev/null @@ -1,220 +0,0 @@ -package database - -import ( - "fmt" - "time" - - "github.com/drone/drone/shared/model" - "github.com/russross/meddler" -) - -type Commitstore struct { - meddler.DB -} - -func NewCommitstore(db meddler.DB) *Commitstore { - return &Commitstore{db} -} - -// GetCommit retrieves a commit from the -// datastore for the given ID. -func (db *Commitstore) GetCommit(id int64) (*model.Commit, error) { - var commit = new(model.Commit) - var err = meddler.Load(db, commitTable, commit, id) - return commit, err -} - -// GetCommitSha retrieves a commit from the -// datastore for the specified repo and sha -func (db *Commitstore) GetCommitSha(repo *model.Repo, branch, sha string) (*model.Commit, error) { - var commit = new(model.Commit) - var err = meddler.QueryRow(db, commit, rebind(commitShaQuery), repo.ID, branch, sha) - return commit, err -} - -// GetCommitLast retrieves the latest commit -// from the datastore for the specified repository -// and branch. -func (db *Commitstore) GetCommitLast(repo *model.Repo, branch string) (*model.Commit, error) { - var commit = new(model.Commit) - var err = meddler.QueryRow(db, commit, rebind(commitLastQuery), repo.ID, branch) - return commit, err -} - -// GetCommitList retrieves a list of latest commits -// from the datastore for the specified repository. -func (db *Commitstore) GetCommitList(repo *model.Repo, limit, offset int) ([]*model.Commit, error) { - var commits []*model.Commit - var err = meddler.QueryAll(db, &commits, rebind(commitListQuery), repo.ID, limit, offset) - return commits, err -} - -// GetCommitListUser retrieves a list of latest commits -// from the datastore accessible to the specified user. -func (db *Commitstore) GetCommitListUser(user *model.User) ([]*model.CommitRepo, error) { - var commits []*model.CommitRepo - var err = meddler.QueryAll(db, &commits, rebind(commitListUserQuery), user.ID) - return commits, err -} - -// GetCommitListActivity retrieves an ungrouped list of latest commits -// from the datastore accessible to the specified user. -func (db *Commitstore) GetCommitListActivity(user *model.User, limit, offset int) ([]*model.CommitRepo, error) { - var commits []*model.CommitRepo - var err = meddler.QueryAll(db, &commits, rebind(commitListActivityQuery), user.ID, limit, offset) - return commits, err -} - -// GetCommitPrior retrieves the latest commit -// from the datastore for the specified repository and branch. -func (db *Commitstore) GetCommitPrior(oldCommit *model.Commit) (*model.Commit, error) { - var commit = new(model.Commit) - var err = meddler.QueryRow(db, commit, rebind(commitPriorQuery), oldCommit.RepoID, oldCommit.Branch, oldCommit.ID) - return commit, err -} - -// PostCommit saves a commit in the datastore. -func (db *Commitstore) PostCommit(commit *model.Commit) error { - if commit.Created == 0 { - commit.Created = time.Now().UTC().Unix() - } - commit.Updated = time.Now().UTC().Unix() - return meddler.Save(db, commitTable, commit) -} - -// PutCommit saves a commit in the datastore. -func (db *Commitstore) PutCommit(commit *model.Commit) error { - if commit.Created == 0 { - commit.Created = time.Now().UTC().Unix() - } - commit.Updated = time.Now().UTC().Unix() - return meddler.Save(db, commitTable, commit) -} - -// DelCommit removes the commit from the datastore. -func (db *Commitstore) DelCommit(commit *model.Commit) error { - var _, err = db.Exec(rebind(commitDeleteStmt), commit.ID) - return err -} - -// KillCommits updates all pending or started commits -// in the datastore settings the status to killed. -func (db *Commitstore) KillCommits() error { - var _, err = db.Exec(rebind(commitKillStmt)) - return err -} - -// GetBuildNumber retrieves the build number for a commit. -func (db *Commitstore) GetBuildNumber(commit *model.Commit) (int64, error) { - row := db.QueryRow(rebind(commitGetBuildNumberStmt), commit.ID, commit.RepoID) - if row == nil { - return 0, fmt.Errorf("Unable to get build number for commit %d", commit.ID) - } - var bn int64 - err := row.Scan(&bn) - if err != nil { - return 0, err - } - return bn, nil -} - -// Commit table name in database. -const commitTable = "commits" - -// SQL statement to delete a Commit by ID. -const commitDeleteStmt = ` -DELETE FROM commits -WHERE commit_id = ? -` - -// SQL query to retrieve the latest Commits accessible -// to a specific user account -const commitListUserQuery = ` -SELECT r.repo_remote, r.repo_host, r.repo_owner, r.repo_name, c.* -FROM - commits c -,repos r -WHERE c.repo_id = r.repo_id - AND c.commit_id IN ( - SELECT max(c.commit_id) - FROM - commits c - ,repos r - ,perms p - WHERE c.repo_id = r.repo_id - AND r.repo_id = p.repo_id - AND p.user_id = ? - GROUP BY r.repo_id -) ORDER BY c.commit_created DESC; -` - -// SQL query to retrieve the ungrouped, latest Commits -// accessible to a specific user account -const commitListActivityQuery = ` -SELECT r.repo_remote, r.repo_host, r.repo_owner, r.repo_name, c.* -FROM - commits c -,repos r -,perms p -WHERE c.repo_id = r.repo_id - AND r.repo_id = p.repo_id - AND p.user_id = ? -ORDER BY c.commit_created DESC -LIMIT ? OFFSET ? -` - -// SQL query to retrieve the latest Commits across all branches. -const commitListQuery = ` -SELECT * -FROM commits -WHERE repo_id = ? -ORDER BY commit_id DESC -LIMIT ? OFFSET ? -` - -// SQL query to retrieve a Commit by branch and sha. -const commitShaQuery = ` -SELECT * -FROM commits -WHERE repo_id = ? - AND commit_branch = ? - AND commit_sha = ? -LIMIT 1 -` - -// SQL query to retrieve the most recent Commit for a branch. -const commitLastQuery = ` -SELECT * -FROM commits -WHERE repo_id = ? - AND commit_branch = ? - AND commit_pr = '' -ORDER BY commit_id DESC -LIMIT 1 -` - -// SQL query to retrieve the prior Commit (by commit_created) in the same branch and repo as the specified Commit. -const commitPriorQuery = ` -SELECT * -FROM commits -WHERE repo_id = ? - AND commit_branch = ? - AND commit_id < ? -ORDER BY commit_id DESC -LIMIT 1 -` - -// SQL statement to cancel all running Commits. -const commitKillStmt = ` -UPDATE commits SET commit_status = 'Killed' -WHERE commit_status IN ('Started', 'Pending'); -` - -// SQL statement to retrieve the build number for -// a commit -const commitGetBuildNumberStmt = ` -SELECT COUNT(1) -FROM commits -WHERE commit_id <= ? - AND repo_id = ? -` diff --git a/server/datastore/database/commit_test.go b/server/datastore/database/commit_test.go deleted file mode 100644 index 3b9501d4f..000000000 --- a/server/datastore/database/commit_test.go +++ /dev/null @@ -1,377 +0,0 @@ -package database - -import ( - "fmt" - "testing" - - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func TestCommitstore(t *testing.T) { - db := mustConnectTest() - cs := NewCommitstore(db) - rs := NewRepostore(db) - ps := NewPermstore(db) - defer db.Close() - - g := goblin.Goblin(t) - g.Describe("Commitstore", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM perms") - db.Exec("DELETE FROM repos") - db.Exec("DELETE FROM commits") - }) - - g.It("Should Put a Commit", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - err := cs.PutCommit(&commit) - g.Assert(err == nil).IsTrue() - g.Assert(commit.ID != 0).IsTrue() - }) - - g.It("Should Post a Commit", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - err := cs.PostCommit(&commit) - g.Assert(err == nil).IsTrue() - g.Assert(commit.ID != 0).IsTrue() - }) - - g.It("Should Get a Commit by ID", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusSuccess, - Created: 1398065343, - Updated: 1398065344, - } - cs.PostCommit(&commit) - getcommit, err := cs.GetCommit(commit.ID) - g.Assert(err == nil).IsTrue() - g.Assert(commit.ID).Equal(getcommit.ID) - g.Assert(commit.RepoID).Equal(getcommit.RepoID) - g.Assert(commit.Branch).Equal(getcommit.Branch) - g.Assert(commit.Sha).Equal(getcommit.Sha) - g.Assert(commit.Status).Equal(getcommit.Status) - g.Assert(commit.Created).Equal(getcommit.Created) - g.Assert(commit.Updated).Equal(getcommit.Updated) - }) - - g.It("Should Get the build number", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusSuccess, - Created: 1398065343, - Updated: 1398065344, - } - cs.PostCommit(&commit) - bn, err := cs.GetBuildNumber(&commit) - g.Assert(err == nil).IsTrue() - g.Assert(bn).Equal(int64(1)) - }) - - g.It("Should Delete a Commit", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - cs.PostCommit(&commit) - _, err1 := cs.GetCommit(commit.ID) - err2 := cs.DelCommit(&commit) - _, err3 := cs.GetCommit(commit.ID) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsTrue() - g.Assert(err3 == nil).IsFalse() - }) - - g.It("Should Kill Pending or Started Commits", func() { - commit1 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusEnqueue, - } - commit2 := model.Commit{ - RepoID: 1, - Branch: "bar", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusEnqueue, - } - cs.PutCommit(&commit1) - cs.PutCommit(&commit2) - err := cs.KillCommits() - g.Assert(err == nil).IsTrue() - getcommit1, _ := cs.GetCommit(commit1.ID) - getcommit2, _ := cs.GetCommit(commit1.ID) - g.Assert(getcommit1.Status).Equal(model.StatusKilled) - g.Assert(getcommit2.Status).Equal(model.StatusKilled) - }) - - g.It("Should Get a Commit by Sha", func() { - commit := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - cs.PostCommit(&commit) - getcommit, err := cs.GetCommitSha(&model.Repo{ID: 1}, commit.Branch, commit.Sha) - g.Assert(err == nil).IsTrue() - g.Assert(commit.ID).Equal(getcommit.ID) - g.Assert(commit.RepoID).Equal(getcommit.RepoID) - g.Assert(commit.Branch).Equal(getcommit.Branch) - g.Assert(commit.Sha).Equal(getcommit.Sha) - }) - - g.It("Should get the last Commit by Branch", func() { - commit1 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusFailure, - } - commit2 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - cs.PutCommit(&commit1) - cs.PutCommit(&commit2) - lastcommit, err := cs.GetCommitLast(&model.Repo{ID: 1}, commit1.Branch) - g.Assert(err == nil).IsTrue() - g.Assert(commit2.ID).Equal(lastcommit.ID) - g.Assert(commit2.RepoID).Equal(lastcommit.RepoID) - g.Assert(commit2.Branch).Equal(lastcommit.Branch) - g.Assert(commit2.Sha).Equal(lastcommit.Sha) - }) - - g.It("Should get the recent Commit List for a Repo", func() { - commit1 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusFailure, - } - commit2 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - cs.PutCommit(&commit1) - cs.PutCommit(&commit2) - commits, err := cs.GetCommitList(&model.Repo{ID: 1}, 20, 0) - g.Assert(err == nil).IsTrue() - g.Assert(len(commits)).Equal(2) - g.Assert(commits[0].ID).Equal(commit2.ID) - g.Assert(commits[0].RepoID).Equal(commit2.RepoID) - g.Assert(commits[0].Branch).Equal(commit2.Branch) - g.Assert(commits[0].Sha).Equal(commit2.Sha) - }) - - g.It("Should get only one last Commit from Commit List for a Repo", func() { - commit1 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusFailure, - } - commit2 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - cs.PutCommit(&commit1) - cs.PutCommit(&commit2) - commits, err := cs.GetCommitList(&model.Repo{ID: 1}, 1, 1) - g.Assert(err == nil).IsTrue() - g.Assert(len(commits)).Equal(1) - g.Assert(commits[0].ID).Equal(commit1.ID) - g.Assert(commits[0].RepoID).Equal(commit1.RepoID) - g.Assert(commits[0].Branch).Equal(commit1.Branch) - g.Assert(commits[0].Sha).Equal(commit1.Sha) - }) - - g.It("Should get the recent Commit List for a User", func() { - repo1 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - repo2 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "drone", - Name: "drone", - } - repo3 := model.Repo{ - UserID: 2, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "droneio", - Name: "drone", - } - rs.PostRepo(&repo1) - rs.PostRepo(&repo2) - commit1 := model.Commit{ - RepoID: repo1.ID, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusFailure, - } - commit2 := model.Commit{ - RepoID: repo2.ID, - Branch: "bar", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - commit3 := model.Commit{ - RepoID: 99999, - Branch: "baz", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - commit4 := model.Commit{ - RepoID: repo2.ID, - Branch: "bar", - Sha: "d923a61d8ad3d8d02db4fef0bf40a726bad0fc03", - Status: model.StatusStarted, - } - commit5 := model.Commit{ - RepoID: repo3.ID, - Branch: "bar", - Sha: "d923a61d8ad3d8d02db4fef0bf40a726bad0fc03", - Status: model.StatusStarted, - } - cs.PostCommit(&commit1) - cs.PostCommit(&commit2) - cs.PostCommit(&commit3) - cs.PostCommit(&commit4) - cs.PostCommit(&commit5) - perm1 := model.Perm{ - RepoID: repo1.ID, - UserID: 1, - Read: true, - Write: true, - Admin: true, - } - perm2 := model.Perm{ - RepoID: repo2.ID, - UserID: 1, - Read: true, - Write: true, - Admin: true, - } - ps.PostPerm(&perm1) - ps.PostPerm(&perm2) - commits, err := cs.GetCommitListUser(&model.User{ID: 1}) - g.Assert(err == nil).IsTrue() - g.Assert(len(commits)).Equal(2) - g.Assert(commits[0].RepoID).Equal(commit1.RepoID) - g.Assert(commits[0].Branch).Equal(commit1.Branch) - g.Assert(commits[0].Sha).Equal(commit1.Sha) - g.Assert(commits[1].Sha).Equal(commit4.Sha) - g.Assert(commits[1].Status).Equal(commit4.Status) - - commits, err = cs.GetCommitListActivity(&model.User{ID: 1}, 20, 0) - fmt.Println(commits) - fmt.Println(err) - g.Assert(err == nil).IsTrue() - g.Assert(len(commits)).Equal(3) - }) - - g.It("Should get only one last Commit List for a User", func() { - repo1 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - repo2 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "drone", - Name: "drone", - } - rs.PostRepo(&repo1) - rs.PostRepo(&repo2) - commit1 := model.Commit{ - RepoID: repo1.ID, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusFailure, - } - commit2 := model.Commit{ - RepoID: repo2.ID, - Branch: "bar", - Sha: "0a74b46d7d62b737b6906897f48dbeb72cfda222", - Status: model.StatusSuccess, - } - cs.PostCommit(&commit1) - cs.PostCommit(&commit2) - perm1 := model.Perm{ - RepoID: repo1.ID, - UserID: 1, - Read: true, - Write: true, - Admin: true, - } - perm2 := model.Perm{ - RepoID: repo2.ID, - UserID: 1, - Read: true, - Write: true, - Admin: true, - } - ps.PostPerm(&perm1) - ps.PostPerm(&perm2) - commits, err := cs.GetCommitListActivity(&model.User{ID: 1}, 1, 1) - g.Assert(err == nil).IsTrue() - g.Assert(len(commits)).Equal(1) - g.Assert(commits[0].RepoID).Equal(commit2.RepoID) - g.Assert(commits[0].Branch).Equal(commit2.Branch) - g.Assert(commits[0].Sha).Equal(commit2.Sha) - g.Assert(commits[0].Status).Equal(commit2.Status) - }) - - g.It("Should enforce unique Sha + Branch", func() { - commit1 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusEnqueue, - } - commit2 := model.Commit{ - RepoID: 1, - Branch: "foo", - Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Status: model.StatusEnqueue, - } - err1 := cs.PutCommit(&commit1) - err2 := cs.PutCommit(&commit2) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsFalse() - }) - }) -} diff --git a/server/datastore/database/database.go b/server/datastore/database/database.go deleted file mode 100644 index 49237181a..000000000 --- a/server/datastore/database/database.go +++ /dev/null @@ -1,90 +0,0 @@ -package database - -import ( - "database/sql" - "os" - - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/datastore/migrate" - - "github.com/BurntSushi/migration" - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - "github.com/russross/meddler" -) - -const ( - driverPostgres = "postgres" - driverSqlite = "sqlite3" - driverMysql = "mysql" -) - -// Connect is a helper function that establishes a new -// database connection and auto-generates the database -// schema. If the database already exists, it will perform -// and update as needed. -func Connect(driver, datasource string) (*sql.DB, error) { - switch driver { - case driverPostgres: - meddler.Default = meddler.PostgreSQL - case driverSqlite: - meddler.Default = meddler.SQLite - case driverMysql: - meddler.Default = meddler.MySQL - } - migration.DefaultGetVersion = migrate.GetVersion - migration.DefaultSetVersion = migrate.SetVersion - var migrations = []migration.Migrator{ - migrate.Setup, - migrate.Migrate_20142110, - migrate.Migrate_20152701, - } - return migration.Open(driver, datasource, migrations) -} - -// MustConnect is a helper function that creates a -// new database connection and auto-generates the -// database schema. An error causes a panic. -func MustConnect(driver, datasource string) *sql.DB { - db, err := Connect(driver, datasource) - if err != nil { - panic(err) - } - return db -} - -// mustConnectTest is a helper function that creates a -// new database connection using environment variables. -// If not environment varaibles are found, the default -// in-memory SQLite database is used. -func mustConnectTest() *sql.DB { - var ( - driver = os.Getenv("TEST_DRIVER") - datasource = os.Getenv("TEST_DATASOURCE") - ) - if len(driver) == 0 { - driver = driverSqlite - datasource = ":memory:" - } - db, err := Connect(driver, datasource) - if err != nil { - panic(err) - } - return db -} - -// New returns a new Datastore -func NewDatastore(db *sql.DB) datastore.Datastore { - return struct { - *Userstore - *Permstore - *Repostore - *Commitstore - }{ - NewUserstore(db), - NewPermstore(db), - NewRepostore(db), - NewCommitstore(db), - } -} diff --git a/server/datastore/database/helper.go b/server/datastore/database/helper.go deleted file mode 100644 index dc03c8432..000000000 --- a/server/datastore/database/helper.go +++ /dev/null @@ -1,32 +0,0 @@ -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 10 params before we have to allocate - rqb := make([]byte, 0, len(qb)+10) - 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) -} diff --git a/server/datastore/database/perm.go b/server/datastore/database/perm.go deleted file mode 100644 index 68905bd9f..000000000 --- a/server/datastore/database/perm.go +++ /dev/null @@ -1,62 +0,0 @@ -package database - -import ( - "github.com/drone/drone/shared/model" - "github.com/russross/meddler" -) - -type Permstore struct { - meddler.DB -} - -func NewPermstore(db meddler.DB) *Permstore { - return &Permstore{db} -} - -// GetPerm retrieves the User's permission from -// the datastore for the given repository. -func (db *Permstore) GetPerm(user *model.User, repo *model.Repo) (*model.Perm, error) { - var perm = new(model.Perm) - var err = meddler.QueryRow(db, perm, rebind(permQuery), user.ID, repo.ID) - return perm, err -} - -// PostPerm saves permission in the datastore. -func (db *Permstore) PostPerm(perm *model.Perm) error { - var _perm = new(model.Perm) - meddler.QueryRow(db, _perm, rebind(permQuery), perm.UserID, perm.RepoID) - if _perm.ID != 0 { - perm.ID = _perm.ID - } - return meddler.Save(db, permTable, perm) -} - -// PutPerm saves permission in the datastore. -func (db *Permstore) PutPerm(perm *model.Perm) error { - return meddler.Save(db, permTable, perm) -} - -// DelPerm removes permission from the datastore. -func (db *Permstore) DelPerm(perm *model.Perm) error { - var _, err = db.Exec(rebind(permDeleteStmt), perm.ID) - return err -} - -// Permission table name in database. -const permTable = "perms" - -// SQL query to retrieve a user's permission to -// access a repository. -const permQuery = ` -SELECT * -FROM perms -WHERE user_id=? -AND repo_id=? -LIMIT 1 -` - -// SQL statement to delete a User by ID. -const permDeleteStmt = ` -DELETE FROM perms -WHERE perm_id=? -` diff --git a/server/datastore/database/perm_test.go b/server/datastore/database/perm_test.go deleted file mode 100644 index 5a0fea9c2..000000000 --- a/server/datastore/database/perm_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package database - -import ( - "testing" - - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func TestPermstore(t *testing.T) { - db := mustConnectTest() - ps := NewPermstore(db) - defer db.Close() - - g := goblin.Goblin(t) - g.Describe("Permstore", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM perms") - }) - - g.It("Should Put a Perm", func() { - perm1 := model.Perm{ - UserID: 1, - RepoID: 2, - Read: true, - Write: true, - Admin: true, - } - err := ps.PutPerm(&perm1) - g.Assert(err == nil).IsTrue() - g.Assert(perm1.ID != 0).IsTrue() - }) - - g.It("Should Post a Perm", func() { - perm1 := model.Perm{ - UserID: 1, - RepoID: 2, - Read: true, - Write: true, - Admin: true, - } - err := ps.PostPerm(&perm1) - g.Assert(err == nil).IsTrue() - g.Assert(perm1.ID != 0).IsTrue() - }) - - g.It("Should Upsert a Perm", func() { - perm1 := model.Perm{ - UserID: 1, - RepoID: 2, - Read: true, - Write: true, - Admin: true, - } - ps.PostPerm(&perm1) - perm1.Read = true - perm1.Write = true - perm1.Admin = false - perm1.ID = 0 - err := ps.PostPerm(&perm1) - g.Assert(err == nil).IsTrue() - g.Assert(perm1.ID != 0).IsTrue() - getperm, err := ps.GetPerm(&model.User{ID: 1}, &model.Repo{ID: 2}) - g.Assert(err == nil).IsTrue() - g.Assert(getperm.Read).IsTrue() - g.Assert(getperm.Write).IsTrue() - g.Assert(getperm.Admin).IsFalse() - }) - - g.It("Should Get a Perm", func() { - ps.PostPerm(&model.Perm{ - UserID: 1, - RepoID: 2, - Read: true, - Write: true, - Admin: true, - }) - getperm, err := ps.GetPerm(&model.User{ID: 1}, &model.Repo{ID: 2}) - g.Assert(err == nil).IsTrue() - g.Assert(getperm.ID != 0).IsTrue() - g.Assert(getperm.Admin).IsTrue() - g.Assert(getperm.Write).IsTrue() - g.Assert(getperm.Admin).IsTrue() - g.Assert(getperm.UserID).Equal(int64(1)) - g.Assert(getperm.RepoID).Equal(int64(2)) - }) - - g.It("Should Del a Perm", func() { - perm1 := model.Perm{ - UserID: 1, - RepoID: 2, - Read: true, - Write: true, - Admin: true, - } - ps.PostPerm(&perm1) - _, err1 := ps.GetPerm(&model.User{ID: 1}, &model.Repo{ID: 2}) - err2 := ps.DelPerm(&perm1) - _, err3 := ps.GetPerm(&model.User{ID: 1}, &model.Repo{ID: 2}) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsTrue() - g.Assert(err3 == nil).IsFalse() - }) - }) -} diff --git a/server/datastore/database/repo.go b/server/datastore/database/repo.go deleted file mode 100644 index ff1381d7d..000000000 --- a/server/datastore/database/repo.go +++ /dev/null @@ -1,94 +0,0 @@ -package database - -import ( - "time" - - "github.com/drone/drone/shared/model" - "github.com/russross/meddler" -) - -type Repostore struct { - meddler.DB -} - -func NewRepostore(db meddler.DB) *Repostore { - return &Repostore{db} -} - -// GetRepo retrieves a specific repo from the -// datastore for the given ID. -func (db *Repostore) GetRepo(id int64) (*model.Repo, error) { - var repo = new(model.Repo) - var err = meddler.Load(db, repoTable, repo, id) - return repo, err -} - -// GetRepoName retrieves a repo from the datastore -// for the specified remote, owner and name. -func (db *Repostore) GetRepoName(remote, owner, name string) (*model.Repo, error) { - var repo = new(model.Repo) - var err = meddler.QueryRow(db, repo, rebind(repoNameQuery), remote, owner, name) - return repo, err -} - -// GetRepoList retrieves a list of all repos from -// the datastore accessible by the given user ID. -func (db *Repostore) GetRepoList(user *model.User) ([]*model.Repo, error) { - var repos []*model.Repo - var err = meddler.QueryAll(db, &repos, rebind(repoListQuery), user.ID) - return repos, err -} - -// PostRepo saves a repo in the datastore. -func (db *Repostore) PostRepo(repo *model.Repo) error { - if repo.Created == 0 { - repo.Created = time.Now().UTC().Unix() - } - repo.Updated = time.Now().UTC().Unix() - return meddler.Save(db, repoTable, repo) -} - -// PutRepo saves a repo in the datastore. -func (db *Repostore) PutRepo(repo *model.Repo) error { - if repo.Created == 0 { - repo.Created = time.Now().UTC().Unix() - } - repo.Updated = time.Now().UTC().Unix() - return meddler.Save(db, repoTable, repo) -} - -// DelRepo removes the repo from the datastore. -func (db *Repostore) DelRepo(repo *model.Repo) error { - var _, err = db.Exec(rebind(repoDeleteStmt), repo.ID) - return err -} - -// Repo table name in database. -const repoTable = "repos" - -// SQL statement to retrieve a Repo by name. -const repoNameQuery = ` -SELECT * -FROM repos -WHERE repo_host = ? - AND repo_owner = ? - AND repo_name = ? -LIMIT 1; -` - -// SQL statement to retrieve a list of Repos -// with permissions for the given User ID. -const repoListQuery = ` -SELECT r.* -FROM - repos r -,perms p -WHERE r.repo_id = p.repo_id - AND p.user_id = ? -` - -// SQL statement to delete a User by ID. -const repoDeleteStmt = ` -DELETE FROM repos -WHERE repo_id = ? -` diff --git a/server/datastore/database/repo_test.go b/server/datastore/database/repo_test.go deleted file mode 100644 index af9f4ba63..000000000 --- a/server/datastore/database/repo_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package database - -import ( - "testing" - - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func TestRepostore(t *testing.T) { - db := mustConnectTest() - rs := NewRepostore(db) - ps := NewPermstore(db) - defer db.Close() - - g := goblin.Goblin(t) - g.Describe("Repostore", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM perms") - db.Exec("DELETE FROM repos") - db.Exec("DELETE FROM users") - }) - - g.It("Should Put a Repo", func() { - repo := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - err := rs.PostRepo(&repo) - g.Assert(err == nil).IsTrue() - g.Assert(repo.ID != 0).IsTrue() - }) - - g.It("Should Post a Repo", func() { - repo := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - err := rs.PostRepo(&repo) - g.Assert(err == nil).IsTrue() - g.Assert(repo.ID != 0).IsTrue() - }) - - g.It("Should Get a Repo by ID", func() { - repo := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - rs.PostRepo(&repo) - getrepo, err := rs.GetRepo(repo.ID) - g.Assert(err == nil).IsTrue() - g.Assert(repo.ID).Equal(getrepo.ID) - g.Assert(repo.UserID).Equal(getrepo.UserID) - g.Assert(repo.Remote).Equal(getrepo.Remote) - g.Assert(repo.Host).Equal(getrepo.Host) - g.Assert(repo.Owner).Equal(getrepo.Owner) - g.Assert(repo.Name).Equal(getrepo.Name) - }) - - g.It("Should Get a Repo by Name", func() { - repo := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - rs.PostRepo(&repo) - getrepo, err := rs.GetRepoName(repo.Host, 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.Remote).Equal(getrepo.Remote) - g.Assert(repo.Host).Equal(getrepo.Host) - 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 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - repo2 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone-dart", - } - rs.PostRepo(&repo1) - rs.PostRepo(&repo2) - ps.PostPerm(&model.Perm{ - RepoID: repo1.ID, - UserID: 1, - Read: true, - Write: true, - Admin: true, - }) - repos, err := rs.GetRepoList(&model.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].Remote).Equal(repo1.Remote) - g.Assert(repos[0].Host).Equal(repo1.Host) - g.Assert(repos[0].Owner).Equal(repo1.Owner) - g.Assert(repos[0].Name).Equal(repo1.Name) - }) - - g.It("Should Delete a Repo", func() { - repo := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - rs.PostRepo(&repo) - _, err1 := rs.GetRepo(repo.ID) - err2 := rs.DelRepo(&repo) - _, err3 := rs.GetRepo(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 := model.Repo{ - UserID: 1, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - repo2 := model.Repo{ - UserID: 2, - Remote: "enterprise.github.com", - Host: "github.drone.io", - Owner: "bradrydzewski", - Name: "drone", - } - err1 := rs.PostRepo(&repo1) - err2 := rs.PostRepo(&repo2) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsFalse() - }) - }) -} diff --git a/server/datastore/database/user.go b/server/datastore/database/user.go deleted file mode 100644 index f6b25040d..000000000 --- a/server/datastore/database/user.go +++ /dev/null @@ -1,100 +0,0 @@ -package database - -import ( - "time" - - "github.com/drone/drone/shared/model" - "github.com/russross/meddler" -) - -type Userstore struct { - meddler.DB -} - -func NewUserstore(db meddler.DB) *Userstore { - return &Userstore{db} -} - -// GetUser retrieves a specific user from the -// datastore for the given ID. -func (db *Userstore) GetUser(id int64) (*model.User, error) { - var usr = new(model.User) - var err = meddler.Load(db, userTable, usr, id) - return usr, err -} - -// GetUserLogin retrieves a user from the datastore -// for the specified remote and login name. -func (db *Userstore) GetUserLogin(remote, login string) (*model.User, error) { - var usr = new(model.User) - var err = meddler.QueryRow(db, usr, rebind(userLoginQuery), remote, login) - return usr, err -} - -// GetUserToken retrieves a user from the datastore -// with the specified token. -func (db *Userstore) GetUserToken(token string) (*model.User, error) { - var usr = new(model.User) - var err = meddler.QueryRow(db, usr, rebind(userTokenQuery), token) - return usr, err -} - -// GetUserList retrieves a list of all users from -// the datastore that are registered in the system. -func (db *Userstore) GetUserList() ([]*model.User, error) { - var users []*model.User - var err = meddler.QueryAll(db, &users, rebind(userListQuery)) - return users, err -} - -// PostUser saves a User in the datastore. -func (db *Userstore) PostUser(user *model.User) error { - user.Created = time.Now().UTC().Unix() - user.Updated = time.Now().UTC().Unix() - return meddler.Insert(db, userTable, user) -} - -// PutUser saves a user in the datastore. -func (db *Userstore) PutUser(user *model.User) error { - user.Updated = time.Now().UTC().Unix() - return meddler.Update(db, userTable, user) -} - -// DelUser removes the user from the datastore. -func (db *Userstore) DelUser(user *model.User) error { - var _, err = db.Exec(rebind(userDeleteStmt), user.ID) - return err -} - -// User table name in database. -const userTable = "users" - -// SQL query to retrieve a User by remote login. -const userLoginQuery = ` -SELECT * -FROM users -WHERE user_remote=? -AND user_login=? -LIMIT 1 -` - -// SQL query to retrieve a User by remote login. -const userTokenQuery = ` -SELECT * -FROM users -WHERE user_token=? -LIMIT 1 -` - -// SQL query to retrieve a list of all users. -const userListQuery = ` -SELECT * -FROM users -ORDER BY user_name ASC -` - -// SQL statement to delete a User by ID. -const userDeleteStmt = ` -DELETE FROM users -WHERE user_id=? -` diff --git a/server/datastore/database/user_test.go b/server/datastore/database/user_test.go deleted file mode 100644 index 11205def8..000000000 --- a/server/datastore/database/user_test.go +++ /dev/null @@ -1,203 +0,0 @@ -package database - -import ( - "testing" - - "github.com/drone/drone/shared/model" - "github.com/franela/goblin" -) - -func TestUserstore(t *testing.T) { - db := mustConnectTest() - us := NewUserstore(db) - defer db.Close() - - g := goblin.Goblin(t) - g.Describe("Userstore", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM users") - }) - - g.It("Should Put a User", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - err := us.PostUser(&user) - g.Assert(err == nil).IsTrue() - g.Assert(user.ID != 0).IsTrue() - }) - - g.It("Should Post a User", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - err := us.PostUser(&user) - g.Assert(err == nil).IsTrue() - g.Assert(user.ID != 0).IsTrue() - }) - - g.It("Should Get a User", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Access: "f0b461ca586c27872b43a0685cbc2847", - Secret: "976f22a5eef7caacb7e678d6c52f49b1", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Gravatar: "b9015b0857e16ac4d94a0ffd9a0b79c8", - Token: "e42080dddf012c718e476da161d21ad5", - Active: true, - Admin: true, - Created: 1398065343, - Updated: 1398065344, - Synced: 1398065345, - } - us.PostUser(&user) - getuser, err := us.GetUser(user.ID) - g.Assert(err == nil).IsTrue() - g.Assert(user.ID).Equal(getuser.ID) - g.Assert(user.Login).Equal(getuser.Login) - g.Assert(user.Remote).Equal(getuser.Remote) - g.Assert(user.Access).Equal(getuser.Access) - g.Assert(user.Secret).Equal(getuser.Secret) - g.Assert(user.Name).Equal(getuser.Name) - g.Assert(user.Email).Equal(getuser.Email) - g.Assert(user.Gravatar).Equal(getuser.Gravatar) - g.Assert(user.Token).Equal(getuser.Token) - g.Assert(user.Active).Equal(getuser.Active) - g.Assert(user.Admin).Equal(getuser.Admin) - g.Assert(user.Created).Equal(getuser.Created) - g.Assert(user.Updated).Equal(getuser.Updated) - g.Assert(user.Synced).Equal(getuser.Synced) - }) - - g.It("Should Get a User By Login", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - us.PostUser(&user) - getuser, err := us.GetUserLogin(user.Remote, user.Login) - g.Assert(err == nil).IsTrue() - g.Assert(user.ID).Equal(getuser.ID) - g.Assert(user.Login).Equal(getuser.Login) - g.Assert(user.Remote).Equal(getuser.Remote) - }) - - g.It("Should Get a User By Token", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - us.PostUser(&user) - getuser, err := us.GetUserToken(user.Token) - g.Assert(err == nil).IsTrue() - g.Assert(user.ID).Equal(getuser.ID) - g.Assert(user.Token).Equal(getuser.Token) - }) - - g.It("Should Enforce Unique User Token", func() { - user1 := model.User{ - Login: "jane", - Remote: "github.com", - Name: "Jane Doe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - user2 := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - err1 := us.PostUser(&user1) - err2 := us.PostUser(&user2) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsFalse() - }) - - g.It("Should Enforce Unique User Remote and Login", func() { - user1 := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - user2 := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "ab20g0ddaf012c744e136da16aa21ad9", - } - err1 := us.PostUser(&user1) - err2 := us.PostUser(&user2) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsFalse() - }) - - g.It("Should Get a User List", func() { - user1 := model.User{ - Login: "jane", - Remote: "github.com", - Name: "Jane Doe", - Email: "foo@bar.com", - Token: "ab20g0ddaf012c744e136da16aa21ad9", - } - user2 := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - us.PostUser(&user1) - us.PostUser(&user2) - users, err := us.GetUserList() - g.Assert(err == nil).IsTrue() - g.Assert(len(users)).Equal(2) - g.Assert(users[0].Login).Equal(user1.Login) - g.Assert(users[0].Remote).Equal(user1.Remote) - g.Assert(users[0].Name).Equal(user1.Name) - g.Assert(users[0].Email).Equal(user1.Email) - g.Assert(users[0].Token).Equal(user1.Token) - }) - - g.It("Should Del a User", func() { - user := model.User{ - Login: "joe", - Remote: "github.com", - Name: "Joe Sixpack", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - us.PostUser(&user) - _, err1 := us.GetUser(user.ID) - err2 := us.DelUser(&user) - _, err3 := us.GetUser(user.ID) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsTrue() - g.Assert(err3 == nil).IsFalse() - }) - - }) -} diff --git a/server/datastore/datastore.go b/server/datastore/datastore.go deleted file mode 100644 index 76bfceafa..000000000 --- a/server/datastore/datastore.go +++ /dev/null @@ -1,8 +0,0 @@ -package datastore - -type Datastore interface { - Userstore - Permstore - Repostore - Commitstore -} diff --git a/server/datastore/migrate/helper.go b/server/datastore/migrate/helper.go deleted file mode 100644 index dfe242c60..000000000 --- a/server/datastore/migrate/helper.go +++ /dev/null @@ -1,47 +0,0 @@ -package migrate - -import ( - "strconv" - "strings" - - "github.com/russross/meddler" -) - -// transform is a helper function that transforms sql -// statements to work with multiple database types. -func transform(stmt string) string { - switch meddler.Default { - case meddler.MySQL: - stmt = strings.Replace(stmt, "AUTOINCREMENT", "AUTO_INCREMENT", -1) - stmt = strings.Replace(stmt, "BLOB", "MEDIUMBLOB", -1) - case meddler.PostgreSQL: - stmt = strings.Replace(stmt, "INTEGER PRIMARY KEY AUTOINCREMENT", "SERIAL PRIMARY KEY", -1) - stmt = strings.Replace(stmt, "BLOB", "BYTEA", -1) - } - return stmt -} - -// 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 10 params before we have to allocate - rqb := make([]byte, 0, len(qb)+10) - 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) -} diff --git a/server/datastore/migrate/migrate.go b/server/datastore/migrate/migrate.go deleted file mode 100644 index 9c8b14fc5..000000000 --- a/server/datastore/migrate/migrate.go +++ /dev/null @@ -1,164 +0,0 @@ -package migrate - -import ( - "github.com/BurntSushi/migration" -) - -// Setup is the database migration function that -// will setup the initial SQL database structure. -func Setup(tx migration.LimitedTx) error { - var stmts = []string{ - blobTable, - userTable, - repoTable, - permTable, - commitTable, - } - for _, stmt := range stmts { - _, err := tx.Exec(transform(stmt)) - if err != nil { - return err - } - } - return nil -} - -// Migrate_20142110 is a database migration on Oct-10 2014. -func Migrate_20142110(tx migration.LimitedTx) error { - var stmts = []string{ - commitRepoIndex, // index the commit table repo_id column - repoTokenColumn, // add the repo token column - repoTokenUpdate, // update the repo token column to empty string - } - for _, stmt := range stmts { - _, err := tx.Exec(transform(stmt)) - if err != nil { - return err - } - } - return nil -} - -// Migrate_20142110 is a database migration on Oct-10 2014. -func Migrate_20152701(tx migration.LimitedTx) error { - var stmts = []string{ - addUserTokenExpires, // index the commit table repo_id column - } - for _, stmt := range stmts { - _, err := tx.Exec(transform(stmt)) - if err != nil { - return err - } - } - return nil -} - -var userTable = ` -CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER PRIMARY KEY AUTOINCREMENT - ,user_remote VARCHAR(255) - ,user_login VARCHAR(255) - ,user_access VARCHAR(255) - ,user_secret VARCHAR(255) - ,user_name VARCHAR(255) - ,user_email VARCHAR(255) - ,user_gravatar VARCHAR(255) - ,user_token VARCHAR(255) - ,user_admin BOOLEAN - ,user_active BOOLEAN - ,user_syncing BOOLEAN - ,user_created INTEGER - ,user_updated INTEGER - ,user_synced INTEGER - ,UNIQUE(user_token) - ,UNIQUE(user_remote, user_login) -); -` - -var permTable = ` -CREATE TABLE IF NOT EXISTS perms ( - perm_id INTEGER PRIMARY KEY AUTOINCREMENT - ,user_id INTEGER - ,repo_id INTEGER - ,perm_read BOOLEAN - ,perm_write BOOLEAN - ,perm_admin BOOLEAN - ,perm_created INTEGER - ,perm_updated INTEGER - ,UNIQUE (repo_id, user_id) -); -` - -var repoTable = ` -CREATE TABLE IF NOT EXISTS repos ( - repo_id INTEGER PRIMARY KEY AUTOINCREMENT - ,user_id INTEGER - ,repo_remote VARCHAR(255) - ,repo_host VARCHAR(255) - ,repo_owner VARCHAR(255) - ,repo_name VARCHAR(255) - ,repo_url VARCHAR(1024) - ,repo_clone_url VARCHAR(255) - ,repo_git_url VARCHAR(255) - ,repo_ssh_url VARCHAR(255) - ,repo_active BOOLEAN - ,repo_private BOOLEAN - ,repo_privileged BOOLEAN - ,repo_post_commit BOOLEAN - ,repo_pull_request BOOLEAN - ,repo_public_key BLOB - ,repo_private_key BLOB - ,repo_params BLOB - ,repo_timeout INTEGER - ,repo_created INTEGER - ,repo_updated INTEGER - ,UNIQUE(repo_host, repo_owner, repo_name) -); -` - -var repoTokenColumn = ` -ALTER TABLE repos ADD COLUMN repo_token VARCHAR(40) -` - -var repoTokenUpdate = ` -UPDATE repos SET repo_token = ''; -` - -var commitTable = ` -CREATE TABLE IF NOT EXISTS commits ( - commit_id INTEGER PRIMARY KEY AUTOINCREMENT - ,repo_id INTEGER - ,commit_status VARCHAR(255) - ,commit_started INTEGER - ,commit_finished INTEGER - ,commit_duration INTEGER - ,commit_sha VARCHAR(255) - ,commit_branch VARCHAR(255) - ,commit_pr VARCHAR(255) - ,commit_author VARCHAR(255) - ,commit_gravatar VARCHAR(255) - ,commit_timestamp VARCHAR(255) - ,commit_message VARCHAR(255) - ,commit_yaml BLOB - ,commit_created INTEGER - ,commit_updated INTEGER - ,UNIQUE(commit_sha, commit_branch, repo_id) -); -` - -var commitRepoIndex = ` -CREATE INDEX commit_repo_id_idx ON commits (repo_id); -` - -var blobTable = ` -CREATE TABLE IF NOT EXISTS blobs ( - blob_id INTEGER PRIMARY KEY AUTOINCREMENT - ,blob_path VARCHAR(255) - ,blob_data BLOB - ,UNIQUE(blob_path) -); -` - -var addUserTokenExpires = ` -ALTER TABLE users ADD COLUMN user_access_expires INTEGER -` diff --git a/server/datastore/migrate/version.go b/server/datastore/migrate/version.go deleted file mode 100644 index ad96209e7..000000000 --- a/server/datastore/migrate/version.go +++ /dev/null @@ -1,57 +0,0 @@ -package migrate - -import ( - "github.com/BurntSushi/migration" -) - -// GetVersion gets the migration version from the database, -// creating the migration table if it does not already exist. -func GetVersion(tx migration.LimitedTx) (int, error) { - v, err := getVersion(tx) - if err != nil { - if err := createVersionTable(tx); err != nil { - return 0, err - } - return getVersion(tx) - } - return v, nil -} - -// SetVersion sets the migration version in the database, -// creating the migration table if it does not already exist. -func SetVersion(tx migration.LimitedTx, version int) error { - if err := setVersion(tx, version); err != nil { - if err := createVersionTable(tx); err != nil { - return err - } - return setVersion(tx, version) - } - return nil -} - -// setVersion updates the migration version in the database. -func setVersion(tx migration.LimitedTx, version int) error { - _, err := tx.Exec(rebind("UPDATE migration_version SET version = ?"), version) - return err -} - -// getVersion gets the migration version in the database. -func getVersion(tx migration.LimitedTx) (int, error) { - var version int - row := tx.QueryRow("SELECT version FROM migration_version") - if err := row.Scan(&version); err != nil { - return 0, err - } - return version, nil -} - -// createVersionTable creates the version table and inserts the -// initial value (0) into the database. -func createVersionTable(tx migration.LimitedTx) error { - _, err := tx.Exec("CREATE TABLE migration_version ( version INTEGER )") - if err != nil { - return err - } - _, err = tx.Exec("INSERT INTO migration_version (version) VALUES (0)") - return err -} diff --git a/server/datastore/perm.go b/server/datastore/perm.go deleted file mode 100644 index 6e257f658..000000000 --- a/server/datastore/perm.go +++ /dev/null @@ -1,77 +0,0 @@ -package datastore - -import ( - "code.google.com/p/go.net/context" - "github.com/drone/drone/shared/model" -) - -type Permstore interface { - // GetPerm retrieves the User's permission from - // the datastore for the given repository. - GetPerm(user *model.User, repo *model.Repo) (*model.Perm, error) - - // PostPerm saves permission in the datastore. - PostPerm(perm *model.Perm) error - - // PutPerm saves permission in the datastore. - PutPerm(perm *model.Perm) error - - // DelPerm removes permission from the datastore. - DelPerm(perm *model.Perm) error -} - -// GetPerm retrieves the User's permission from -// the datastore for the given repository. -func GetPerm(c context.Context, user *model.User, repo *model.Repo) (*model.Perm, error) { - // if the user is a guest they should only be granted - // read access to public repositories. - switch { - case user == nil && repo.Private: - return &model.Perm{ - Guest: true, - Read: false, - Write: false, - Admin: false}, nil - case user == nil && !repo.Private: - return &model.Perm{ - Guest: true, - Read: true, - Write: false, - Admin: false}, nil - } - - // if the user is authenticated we'll retireive the - // permission details from the database. - perm, err := FromContext(c).GetPerm(user, repo) - if perm.ID == 0 { - perm.Guest = true - } - - switch { - // if the user is a system admin grant super access. - case user.Admin == true: - perm.Read = true - perm.Write = true - perm.Admin = true - - // if the repo is public, grant read access only. - case repo.Private == false: - perm.Read = true - } - return perm, err -} - -// PostPerm saves permission in the datastore. -func PostPerm(c context.Context, perm *model.Perm) error { - return FromContext(c).PostPerm(perm) -} - -// PutPerm saves permission in the datastore. -func PutPerm(c context.Context, perm *model.Perm) error { - return FromContext(c).PutPerm(perm) -} - -// DelPerm removes permission from the datastore. -func DelPerm(c context.Context, perm *model.Perm) error { - return FromContext(c).DelPerm(perm) -} diff --git a/server/datastore/repo.go b/server/datastore/repo.go deleted file mode 100644 index 7becd50e6..000000000 --- a/server/datastore/repo.go +++ /dev/null @@ -1,62 +0,0 @@ -package datastore - -import ( - "code.google.com/p/go.net/context" - "github.com/drone/drone/shared/model" -) - -type Repostore interface { - // GetRepo retrieves a specific repo from the - // datastore for the given ID. - GetRepo(id int64) (*model.Repo, error) - - // GetRepoName retrieves a repo from the datastore - // for the specified remote, owner and name. - GetRepoName(remote, owner, name string) (*model.Repo, error) - - // GetRepoList retrieves a list of all repos from - // the datastore accessible by the given user ID. - GetRepoList(user *model.User) ([]*model.Repo, error) - - // PostRepo saves a repo in the datastore. - PostRepo(repo *model.Repo) error - - // PutRepo saves a repo in the datastore. - PutRepo(repo *model.Repo) error - - // DelRepo removes the repo from the datastore. - DelRepo(repo *model.Repo) error -} - -// GetRepo retrieves a specific repo from the -// datastore for the given ID. -func GetRepo(c context.Context, id int64) (*model.Repo, error) { - return FromContext(c).GetRepo(id) -} - -// GetRepoName retrieves a repo from the datastore -// for the specified remote, owner and name. -func GetRepoName(c context.Context, remote, owner, name string) (*model.Repo, error) { - return FromContext(c).GetRepoName(remote, owner, name) -} - -// GetRepoList retrieves a list of all repos from -// the datastore accessible by the given user ID. -func GetRepoList(c context.Context, user *model.User) ([]*model.Repo, error) { - return FromContext(c).GetRepoList(user) -} - -// PostRepo saves a repo in the datastore. -func PostRepo(c context.Context, repo *model.Repo) error { - return FromContext(c).PostRepo(repo) -} - -// PutRepo saves a repo in the datastore. -func PutRepo(c context.Context, repo *model.Repo) error { - return FromContext(c).PutRepo(repo) -} - -// DelRepo removes the repo from the datastore. -func DelRepo(c context.Context, repo *model.Repo) error { - return FromContext(c).DelRepo(repo) -} diff --git a/server/datastore/user.go b/server/datastore/user.go deleted file mode 100644 index 90da8fc34..000000000 --- a/server/datastore/user.go +++ /dev/null @@ -1,72 +0,0 @@ -package datastore - -import ( - "code.google.com/p/go.net/context" - "github.com/drone/drone/shared/model" -) - -type Userstore interface { - // GetUser retrieves a specific user from the - // datastore for the given ID. - GetUser(id int64) (*model.User, error) - - // GetUserLogin retrieves a user from the datastore - // for the specified remote and login name. - GetUserLogin(remote, login string) (*model.User, error) - - // GetUserToken retrieves a user from the datastore - // with the specified token. - GetUserToken(token string) (*model.User, error) - - // GetUserList retrieves a list of all users from - // the datastore that are registered in the system. - GetUserList() ([]*model.User, error) - - // PostUser saves a User in the datastore. - PostUser(user *model.User) error - - // PutUser saves a user in the datastore. - PutUser(user *model.User) error - - // DelUser removes the user from the datastore. - DelUser(user *model.User) error -} - -// GetUser retrieves a specific user from the -// datastore for the given ID. -func GetUser(c context.Context, id int64) (*model.User, error) { - return FromContext(c).GetUser(id) -} - -// GetUserLogin retrieves a user from the datastore -// for the specified remote and login name. -func GetUserLogin(c context.Context, remote, login string) (*model.User, error) { - return FromContext(c).GetUserLogin(remote, login) -} - -// GetUserToken retrieves a user from the datastore -// with the specified token. -func GetUserToken(c context.Context, token string) (*model.User, error) { - return FromContext(c).GetUserToken(token) -} - -// GetUserList retrieves a list of all users from -// the datastore that are registered in the system. -func GetUserList(c context.Context) ([]*model.User, error) { - return FromContext(c).GetUserList() -} - -// PostUser saves a User in the datastore. -func PostUser(c context.Context, user *model.User) error { - return FromContext(c).PostUser(user) -} - -// PutUser saves a user in the datastore. -func PutUser(c context.Context, user *model.User) error { - return FromContext(c).PutUser(user) -} - -// DelUser removes the user from the datastore. -func DelUser(c context.Context, user *model.User) error { - return FromContext(c).DelUser(user) -} diff --git a/server/handler/badge.go b/server/handler/badge.go deleted file mode 100644 index cf98eb6ec..000000000 --- a/server/handler/badge.go +++ /dev/null @@ -1,132 +0,0 @@ -package handler - -import ( - "encoding/xml" - "net/http" - - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// badges that indicate the current build status for a repository -// and branch combination. -type badge struct { - success, failure, started, err, none []byte -} - -var defaultBadge = badge{ - []byte(`buildbuildsuccesssuccess`), - []byte(`buildbuildfailurefailure`), - []byte(`buildbuildstartedstarted`), - []byte(`buildbuilderrorerror`), - []byte(`buildbuildnonenone`), -} - -var badgeStyles = map[string]badge{ - "flat": badge{ - []byte(`buildbuildsuccesssuccess`), - []byte(`buildbuildfailurefailure`), - []byte(`buildbuildstartedstarted`), - []byte(`buildbuilderrorerror`), - []byte(`buildbuildnonenone`), - }, - "flat-square": badge{ - []byte(`buildsuccess`), - []byte(`buildfailure`), - []byte(`buildstarted`), - []byte(`builderror`), - []byte(`buildnone`), - }, -} - -// 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/:host/:owner/:name/status.svg -// -func GetBadge(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - host = c.URLParams["host"] - owner = c.URLParams["owner"] - name = c.URLParams["name"] - branch = r.FormValue("branch") - style = r.FormValue("style") - ) - - // an SVG response is always served, even when error, so - // we can go ahead and set the content type appropriately. - w.Header().Set("Content-Type", "image/svg+xml") - - badge, ok := badgeStyles[style] - if !ok { - badge = defaultBadge - } - - repo, err := datastore.GetRepoName(ctx, host, owner, name) - if err != nil { - w.Write(badge.none) - return - } - if len(branch) == 0 { - branch = model.DefaultBranch - } - commit, _ := datastore.GetCommitLast(ctx, repo, branch) - - // if no commit was found then display - // the 'none' badge, instead of throwing - // an error response - if commit == nil { - w.Write(badge.none) - return - } - - switch commit.Status { - case model.StatusSuccess: - w.Write(badge.success) - case model.StatusFailure: - w.Write(badge.failure) - case model.StatusError: - w.Write(badge.err) - case model.StatusEnqueue, model.StatusStarted: - w.Write(badge.started) - default: - w.Write(badge.none) - } -} - -// 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 -// -func GetCC(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - host = c.URLParams["host"] - owner = c.URLParams["owner"] - name = c.URLParams["name"] - ) - - w.Header().Set("Content-Type", "application/xml") - - repo, err := datastore.GetRepoName(ctx, host, owner, name) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - commits, err := datastore.GetCommitList(ctx, repo, 1, 0) - if err != nil || len(commits) == 0 { - w.WriteHeader(http.StatusNotFound) - return - } - - var link = httputil.GetURL(r) + "/" + repo.Host + "/" + repo.Owner + "/" + repo.Name - var cc = model.NewCC(repo, commits[0], link) - xml.NewEncoder(w).Encode(cc) -} diff --git a/server/handler/commit.go b/server/handler/commit.go deleted file mode 100644 index 1b512784c..000000000 --- a/server/handler/commit.go +++ /dev/null @@ -1,124 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/worker" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetCommitList accepts a request to retrieve a list -// of recent commits by Repo, and retur in JSON format. -// -// GET /api/repos/:host/:owner/:name/commits?limit=:limit&offset=:offset -// -func GetCommitList(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var repo = ToRepo(c) - var limit = ToLimit(r) - var offset = ToOffset(r) - - commits, err := datastore.GetCommitList(ctx, repo, limit, offset) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - json.NewEncoder(w).Encode(commits) -} - -// GetCommit accepts a request to retrieve a commit -// from the datastore for the given repository, branch and -// commit hash. -// -// GET /api/repos/:host/:owner/:name/branches/:branch/commits/:commit -// -func GetCommit(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - branch = c.URLParams["branch"] - hash = c.URLParams["commit"] - repo = ToRepo(c) - ) - - commit, err := datastore.GetCommitSha(ctx, repo, branch, hash) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - json.NewEncoder(w).Encode(commit) -} - -// PostHook accepts a post-commit hook and parses the payload -// in order to trigger a build. The payload is specified to the -// remote system (ie GitHub) and will therefore get parsed by -// the appropriate remote plugin. -// -// POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit} -// -func PostCommit(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - branch = c.URLParams["branch"] - hash = c.URLParams["commit"] - host = c.URLParams["host"] - repo = ToRepo(c) - remote = remote.Lookup(host) - ) - - commit, err := datastore.GetCommitSha(ctx, repo, branch, hash) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - if commit.Status == model.StatusStarted || - commit.Status == model.StatusEnqueue { - w.WriteHeader(http.StatusConflict) - return - } - - commit.Status = model.StatusEnqueue - commit.Started = 0 - commit.Finished = 0 - commit.Duration = 0 - if err := datastore.PutCommit(ctx, commit); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - owner, err := datastore.GetUser(ctx, repo.UserID) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // Request a new token and update - user_token, err := remote.GetToken(owner) - if user_token != nil { - owner.Access = user_token.AccessToken - owner.Secret = user_token.RefreshToken - owner.TokenExpiry = user_token.Expiry - datastore.PutUser(ctx, owner) - } else if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // drop the items on the queue - go worker.Do(ctx, &worker.Work{ - User: owner, - Repo: repo, - Commit: commit, - Host: httputil.GetURL(r), - }) - - w.WriteHeader(http.StatusOK) -} diff --git a/server/handler/context.go b/server/handler/context.go deleted file mode 100644 index bee9f91bb..000000000 --- a/server/handler/context.go +++ /dev/null @@ -1,90 +0,0 @@ -package handler - -import ( - "net/http" - "strconv" - - "github.com/drone/drone/shared/model" - "github.com/zenazn/goji/web" -) - -// ToUser returns the User from the current -// request context. If the User does not exist -// a nil value is returned. -func ToUser(c web.C) *model.User { - var v = c.Env["user"] - if v == nil { - return nil - } - u, ok := v.(*model.User) - if !ok { - return nil - } - return u -} - -// ToRepo returns the Repo from the current -// request context. If the Repo does not exist -// a nil value is returned. -func ToRepo(c web.C) *model.Repo { - var v = c.Env["repo"] - if v == nil { - return nil - } - r, ok := v.(*model.Repo) - if !ok { - return nil - } - return r -} - -// ToRole returns the Role from the current -// request context. If the Role does not exist -// a nil value is returned. -func ToRole(c web.C) *model.Perm { - var v = c.Env["role"] - if v == nil { - return nil - } - p, ok := v.(*model.Perm) - if !ok { - return nil - } - return p -} - -// ToLimit returns the Limit from current request -// query if limit doesn't present set default offset -// equal to 20, maximum limit equal 100 -func ToLimit(r *http.Request) int { - if len(r.FormValue("limit")) == 0 { - return 20 - } - - limit, err := strconv.Atoi(r.FormValue("limit")) - if err != nil { - return 20 - } - - if limit > 100 { - return 100 - } - - return limit -} - -// ToOffset returns the Offset from current request -// query if offset doesn't present set default offset -// equal to 0 -func ToOffset(r *http.Request) int { - if len(r.FormValue("offset")) == 0 { - return 0 - } - - offset, err := strconv.Atoi(r.FormValue("offset")) - if err != nil { - return 0 - } - - return offset -} diff --git a/server/handler/hook.go b/server/handler/hook.go deleted file mode 100644 index 1b46ceb20..000000000 --- a/server/handler/hook.go +++ /dev/null @@ -1,140 +0,0 @@ -package handler - -import ( - "log" - "net/http" - "strings" - - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/worker" - "github.com/drone/drone/shared/build/script" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// PostHook accepts a post-commit hook and parses the payload -// in order to trigger a build. The payload is specified to the -// remote system (ie GitHub) and will therefore get parsed by -// the appropriate remote plugin. -// -// GET /api/hook/:host -// -func PostHook(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var host = c.URLParams["host"] - var token = c.URLParams["token"] - var remote = remote.Lookup(host) - if remote == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - // parse the hook payload - hook, err := remote.ParseHook(r) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // in some cases we have neither a hook nor error. An example - // would be GitHub sending a ping request to the URL, in which - // case we'll just exit quiely with an 'OK' - if hook == nil || strings.Contains(hook.Message, "[CI SKIP]") { - w.WriteHeader(http.StatusOK) - return - } - - // fetch the repository from the database - repo, err := datastore.GetRepoName(ctx, remote.GetHost(), hook.Owner, hook.Repo) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - // each hook contains a token to verify the sender. If the token - // is not provided or does not match, exit - if len(repo.Token) == 0 || repo.Token != token { - log.Printf("Rejected post commit hook for %s. Token mismatch\n", repo.Name) - w.WriteHeader(http.StatusUnauthorized) - return - } - - if repo.Active == false || - (repo.PostCommit == false && len(hook.PullRequest) == 0) || - (repo.PullRequest == false && len(hook.PullRequest) != 0) { - w.WriteHeader(http.StatusNotFound) - return - } - - // fetch the user from the database that owns this repo - user, err := datastore.GetUser(ctx, repo.UserID) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - // Request a new token and update - user_token, err := remote.GetToken(user) - if user_token != nil { - user.Access = user_token.AccessToken - user.Secret = user_token.RefreshToken - user.TokenExpiry = user_token.Expiry - datastore.PutUser(ctx, user) - } else if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // featch the .drone.yml file from the database - yml, err := remote.GetScript(user, repo, hook) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // verify the commit hooks branch matches the list of approved - // branches (unless it is a pull request). Note that we don't really - // care if parsing the yaml fails here. - s, _ := script.ParseBuild(string(yml)) - if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) { - w.WriteHeader(http.StatusOK) - return - } - - commit := model.Commit{ - RepoID: repo.ID, - Status: model.StatusEnqueue, - Sha: hook.Sha, - Branch: hook.Branch, - PullRequest: hook.PullRequest, - Timestamp: hook.Timestamp, - Message: hook.Message, - Config: string(yml), - } - commit.SetAuthor(hook.Author) - - // inserts the commit into the database - if err := datastore.PostCommit(ctx, &commit); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - owner, err := datastore.GetUser(ctx, repo.UserID) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // drop the items on the queue - go worker.Do(ctx, &worker.Work{ - User: owner, - Repo: repo, - Commit: &commit, - Host: httputil.GetURL(r), - }) - - w.WriteHeader(http.StatusOK) -} diff --git a/server/handler/login.go b/server/handler/login.go deleted file mode 100644 index c370815e8..000000000 --- a/server/handler/login.go +++ /dev/null @@ -1,145 +0,0 @@ -package handler - -import ( - "encoding/json" - "log" - "net/http" - - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/session" - "github.com/drone/drone/server/sync" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// 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 /login/:host -// -func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var host = c.URLParams["host"] - var redirect = "/" - var remote = remote.Lookup(host) - if remote == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - w.Header().Del("Content-Type") - - // authenticate the user - login, err := remote.Authorize(w, r) - if err != nil { - log.Println(err) - w.WriteHeader(http.StatusBadRequest) - return - } else if login == nil { - // in this case we probably just redirected - // the user, so we can exit with no error - return - } - - // get the user from the database - u, err := datastore.GetUserLogin(ctx, host, login.Login) - if err != nil { - // 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.OpenRegistration() == false { - users, err := datastore.GetUserList(ctx) - if err != nil || len(users) != 0 { - log.Println("Unable to create account. Registration is closed") - w.WriteHeader(http.StatusForbidden) - return - } - } - - // create the user account - u = model.NewUser(remote.GetKind(), login.Login, login.Email) - u.Name = login.Name - u.SetEmail(login.Email) - - // insert the user into the database - if err := datastore.PostUser(ctx, u); err != nil { - log.Println(err) - w.WriteHeader(http.StatusBadRequest) - return - } - - // the user id should NEVER equal zero - if u.ID == 0 { - log.Println("Unable to create account. User ID is zero") - w.WriteHeader(http.StatusInternalServerError) - return - } - - // if this is the first user, they - // should be an admin. - if u.ID == 1 { - u.Admin = true - } - } - - // update the user access token - // in case it changed in GitHub - u.Access = login.Access - u.Secret = login.Secret - u.Name = login.Name - u.TokenExpiry = login.Expiry - u.SetEmail(login.Email) - u.Syncing = u.IsStale() - - if err := datastore.PutUser(ctx, u); err != nil { - log.Println(err) - w.WriteHeader(http.StatusBadRequest) - return - } - - // look at the last synchronized date to determine if - // we need to re-sync the account. - // - // todo(bradrydzewski) this should move to a server/sync package and - // should be injected into this struct, just like the database code. - // - // todo(bradrydzewski) this login should be a bit more intelligent - // than the current implementation. - if u.Syncing { - redirect = "/sync" - log.Println("sync user account.", u.Login) - - // sync inside a goroutine - go sync.SyncUser(ctx, u, remote) - } - - token, err := session.GenerateToken(ctx, r, u) - if err != nil { - log.Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - redirect = redirect + "#access_token=" + token - - http.Redirect(w, r, redirect, http.StatusSeeOther) -} - -// GetLoginList accepts a request to retrive a list of -// all OAuth login options. -// -// GET /api/remotes/login -// -func GetLoginList(c web.C, w http.ResponseWriter, r *http.Request) { - var list = remote.Registered() - var logins []interface{} - for _, item := range list { - logins = append(logins, struct { - Type string `json:"type"` - Host string `json:"host"` - }{item.GetKind(), item.GetHost()}) - } - json.NewEncoder(w).Encode(&logins) -} diff --git a/server/handler/output.go b/server/handler/output.go deleted file mode 100644 index 28cca8d12..000000000 --- a/server/handler/output.go +++ /dev/null @@ -1,38 +0,0 @@ -package handler - -import ( - "io" - "net/http" - "path/filepath" - - "github.com/drone/drone/server/blobstore" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetOutput gets the commit's stdout. -// -// GET /api/repos/:host/:owner/:name/branches/:branch/commits/:commit/console -// -func GetOutput(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - host = c.URLParams["host"] - owner = c.URLParams["owner"] - name = c.URLParams["name"] - branch = c.URLParams["branch"] - hash = c.URLParams["commit"] - ) - - w.Header().Set("Content-Type", "text/plain") - - path := filepath.Join(host, owner, name, branch, hash) - rc, err := blobstore.GetReader(ctx, path) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - defer rc.Close() - io.Copy(w, rc) -} diff --git a/server/handler/repo.go b/server/handler/repo.go deleted file mode 100644 index 843baed01..000000000 --- a/server/handler/repo.go +++ /dev/null @@ -1,237 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/drone/drone/shared/sshutil" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetRepo accepts a request to retrieve a commit -// from the datastore for the given repository, branch and -// commit hash. -// -// GET /api/repos/:host/:owner/:name -// -func GetRepo(c web.C, w http.ResponseWriter, r *http.Request) { - var ( - role = ToRole(c) - repo = ToRepo(c) - ) - - // if the user is not requesting (or cannot access) - // admin data then we just return the repo as-is - if role.Admin == false { - json.NewEncoder(w).Encode(struct { - *model.Repo - Perm *model.Perm `json:"role"` - }{repo, role}) - return - } - - // else we should return restricted fields - json.NewEncoder(w).Encode(struct { - *model.Repo - PublicKey string `json:"public_key"` - Params string `json:"params"` - Perm *model.Perm `json:"role"` - }{repo, repo.PublicKey, repo.Params, role}) -} - -// DelRepo accepts a request to delete the named -// repository. -// -// DEL /api/repos/:host/:owner/:name -// -func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var repo = ToRepo(c) - - // completely remove the repository from the database - var user = ToUser(c) - var remote = remote.Lookup(repo.Host) - if remote == nil { - log.Printf("[ERROR] no remote for host '%s' found", repo.Host) - } else { - // Request a new token and update - user_token, err := remote.GetToken(user) - if err != nil { - log.Printf("[ERROR] no token for user '%s' on remote '%s' ", user.Email, repo.Host) - } else { - if user_token != nil { - user.Access = user_token.AccessToken - user.Secret = user_token.RefreshToken - user.TokenExpiry = user_token.Expiry - datastore.PutUser(ctx, user) - } - // setup the post-commit hook with the remote system and - // and deactiveate this hook/user on the remote system - var hook = fmt.Sprintf("%s/api/hook/%s/%s", httputil.GetURL(r), repo.Remote, repo.Token) - if err := remote.Deactivate(user, repo, hook); err != nil { - log.Printf("[ERROR] deactivate on remote '%s' failed: %s", repo.Host, err) - } - } - } - // fail through: if any of the actions on the remote failed - // we try to delete the repo in our datastore anyway - if err := datastore.DelRepo(ctx, repo); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } else { - w.WriteHeader(http.StatusNoContent) - } -} - -// DeactivateRepo accepts a request to deactivate the named -// repository. This will disable all builds in the system -// for this repository. -// -// POST /api/repos/:host/:owner/:name/deactivate -// -func DeactivateRepo(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var repo = ToRepo(c) - - // disable everything - repo.Active = false - repo.PullRequest = false - repo.PostCommit = false - repo.UserID = 0 - - if err := datastore.PutRepo(ctx, repo); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(repo) -} - -// PostRepo accapets a request to activate the named repository -// in the datastore. It returns a 201 status created if successful -// -// POST /api/repos/:host/:owner/:name -// -func PostRepo(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var repo = ToRepo(c) - var user = ToUser(c) - - // update the repo active flag and fields - repo.Active = true - repo.PullRequest = true - repo.PostCommit = true - repo.UserID = user.ID - repo.Timeout = 3600 // default to 1 hour - - // generate a secret key for post-commit hooks - if len(repo.Token) == 0 { - repo.Token = model.GenerateToken() - } - - // generates the rsa key - if len(repo.PublicKey) == 0 || len(repo.PrivateKey) == 0 { - key, err := sshutil.GeneratePrivateKey() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - repo.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey) - repo.PrivateKey = sshutil.MarshalPrivateKey(key) - } - - var remote = remote.Lookup(repo.Host) - if remote == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - // Request a new token and update - user_token, err := remote.GetToken(user) - if user_token != nil { - user.Access = user_token.AccessToken - user.Secret = user_token.RefreshToken - user.TokenExpiry = user_token.Expiry - datastore.PutUser(ctx, user) - } else if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // setup the post-commit hook with the remote system and - // if necessary, register the public key - var hook = fmt.Sprintf("%s/api/hook/%s/%s", httputil.GetURL(r), repo.Remote, repo.Token) - if err := remote.Activate(user, repo, hook); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := datastore.PutRepo(ctx, repo); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(repo) -} - -// PutRepo accapets 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/:host/:owner/:name -// -func PutRepo(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var repo = ToRepo(c) - var user = ToUser(c) - - // unmarshal the repository from the payload - defer r.Body.Close() - in := struct { - PostCommit *bool `json:"post_commits"` - PullRequest *bool `json:"pull_requests"` - Privileged *bool `json:"privileged"` - Params *string `json:"params"` - Timeout *int64 `json:"timeout"` - PublicKey *string `json:"public_key"` - PrivateKey *string `json:"private_key"` - }{} - if err := json.NewDecoder(r.Body).Decode(&in); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if in.Params != nil { - repo.Params = *in.Params - if _, err := repo.ParamMap(); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - } - if in.PostCommit != nil { - repo.PostCommit = *in.PostCommit - } - if in.PullRequest != nil { - repo.PullRequest = *in.PullRequest - } - if in.Privileged != nil && user.Admin { - repo.Privileged = *in.Privileged - } - if in.Timeout != nil && user.Admin { - repo.Timeout = *in.Timeout - } - if in.PrivateKey != nil && in.PublicKey != nil { - repo.PublicKey = *in.PublicKey - repo.PrivateKey = *in.PrivateKey - } - if err := datastore.PutRepo(ctx, repo); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(repo) -} diff --git a/server/handler/user.go b/server/handler/user.go deleted file mode 100644 index efd2ef067..000000000 --- a/server/handler/user.go +++ /dev/null @@ -1,183 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/sync" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetUserCurrent accepts a request to retrieve the -// currently authenticated user from the datastore -// and return in JSON format. -// -// GET /api/user -// -func GetUserCurrent(c web.C, w http.ResponseWriter, r *http.Request) { - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - // return private data for the currently authenticated - // user, specifically, their auth token. - data := struct { - *model.User - Token string `json:"token"` - }{user, user.Token} - json.NewEncoder(w).Encode(&data) -} - -// PutUser accepts a request to update the currently -// authenticated User profile. -// -// PUT /api/user -// -func PutUser(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - // unmarshal the repository from the payload - defer r.Body.Close() - in := model.User{} - if err := json.NewDecoder(r.Body).Decode(&in); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // update the user email - if len(in.Email) != 0 { - user.SetEmail(in.Email) - } - // update the user full name - if len(in.Name) != 0 { - user.Name = in.Name - } - - // update the database - if err := datastore.PutUser(ctx, user); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - json.NewEncoder(w).Encode(user) -} - -// GetRepos 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 web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - repos, err := datastore.GetRepoList(ctx, user) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(&repos) -} - -// GetUserFeed accepts a request to get the user's latest -// build feed, across all repositories, from the datastore. -// The results are encoded and returned in JSON format. -// -// GET /api/user/feed -// -func GetUserFeed(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - repos, err := datastore.GetCommitListUser(ctx, user) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(&repos) -} - -// GetUserActivity accepts a request to get the user's latest -// build activity, across all repositories, from the datastore. -// The results are encoded and returned in JSON format. -// -// GET /api/user/activity?limit=:limit&offset=:offset -// -func GetUserActivity(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var limit = ToLimit(r) - var offset = ToOffset(r) - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - repos, err := datastore.GetCommitListActivity(ctx, user, limit, offset) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(&repos) -} - -// PostUserSync accepts a request to post user sync -// -// POST /api/user/sync -// -func PostUserSync(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var user = ToUser(c) - if user == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - var remote = remote.Lookup(user.Remote) - if remote == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - if user.Syncing { - w.WriteHeader(http.StatusConflict) - return - } - - // Request a new token and update - user_token, err := remote.GetToken(user) - if user_token != nil { - user.Access = user_token.AccessToken - user.Secret = user_token.RefreshToken - user.TokenExpiry = user_token.Expiry - } else if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - user.Syncing = true - if err := datastore.PutUser(ctx, user); err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - go sync.SyncUser(ctx, user, remote) - w.WriteHeader(http.StatusNoContent) - return -} diff --git a/server/handler/users.go b/server/handler/users.go deleted file mode 100644 index 36cf40600..000000000 --- a/server/handler/users.go +++ /dev/null @@ -1,100 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/shared/model" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetUsers accepts a request to retrieve all users -// from the datastore and return encoded in JSON format. -// -// GET /api/users -// -func GetUserList(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - - users, err := datastore.GetUserList(ctx) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - json.NewEncoder(w).Encode(users) -} - -// GetUser accepts a request to retrieve a user by hostname -// and login from the datastore and return encoded in JSON -// format. -// -// GET /api/users/:host/:login -// -func GetUser(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - user = ToUser(c) - host = c.URLParams["host"] - login = c.URLParams["login"] - ) - - user, err := datastore.GetUserLogin(ctx, host, login) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(user) -} - -// 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/:host/:login -// -func PostUser(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - host = c.URLParams["host"] - login = c.URLParams["login"] - ) - - account := model.NewUser(host, login, "") - if err := datastore.PostUser(ctx, account); err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - json.NewEncoder(w).Encode(account) -} - -// 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/:host/:login -// -func DelUser(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var ( - user = ToUser(c) - host = c.URLParams["host"] - login = c.URLParams["login"] - ) - - account, err := datastore.GetUserLogin(ctx, host, login) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - if account.ID == user.ID { - w.WriteHeader(http.StatusBadRequest) - return - } - if err := datastore.DelUser(ctx, account); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusOK) -} diff --git a/server/handler/worker.go b/server/handler/worker.go deleted file mode 100644 index e6e9cf363..000000000 --- a/server/handler/worker.go +++ /dev/null @@ -1,91 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - - "github.com/drone/drone/server/worker" - "github.com/drone/drone/server/worker/director" - "github.com/drone/drone/server/worker/docker" - "github.com/drone/drone/server/worker/pool" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// GetWorkers accepts a request to retrieve the list -// of registered workers and return the results -// in JSON format. -// -// GET /api/workers -// -func GetWorkers(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - workers := pool.FromContext(ctx).List() - json.NewEncoder(w).Encode(workers) -} - -// PostWorker accepts a request to allocate a new -// worker to the pool. -// -// POST /api/workers -// -func PostWorker(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - pool := pool.FromContext(ctx) - node := r.FormValue("address") - pool.Allocate(docker.NewHost(node)) - w.WriteHeader(http.StatusOK) -} - -// Delete accepts a request to delete a worker -// from the pool. -// -// DELETE /api/workers -// -func DelWorker(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - pool := pool.FromContext(ctx) - uuid := r.FormValue("id") - - for _, worker := range pool.List() { - if worker.(*docker.Docker).UUID != uuid { - pool.Deallocate(worker) - w.WriteHeader(http.StatusNoContent) - return - } - } - w.WriteHeader(http.StatusNotFound) -} - -// GetWorkPending accepts a request to retrieve the list -// of pending work and returns in JSON format. -// -// GET /api/work/pending -// -func GetWorkPending(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - d := worker.FromContext(ctx).(*director.Director) - json.NewEncoder(w).Encode(d.GetPending()) -} - -// GetWorkStarted accepts a request to retrieve the list -// of started work and returns in JSON format. -// -// GET /api/work/started -// -func GetWorkStarted(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - d := worker.FromContext(ctx).(*director.Director) - json.NewEncoder(w).Encode(d.GetStarted()) -} - -// GetWorkAssigned accepts a request to retrieve the list -// of started work and returns in JSON format. -// -// GET /api/work/assignments -// -func GetWorkAssigned(c web.C, w http.ResponseWriter, r *http.Request) { - ctx := context.FromC(c) - d := worker.FromContext(ctx).(*director.Director) - json.NewEncoder(w).Encode(d.GetAssignemnts()) -} diff --git a/server/handler/ws.go b/server/handler/ws.go deleted file mode 100644 index 150d9ddbb..000000000 --- a/server/handler/ws.go +++ /dev/null @@ -1,203 +0,0 @@ -package handler - -import ( - "database/sql" - "log" - "net/http" - "strconv" - "time" - - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/pubsub" - "github.com/drone/drone/server/worker" - - "github.com/goji/context" - "github.com/gorilla/websocket" - "github.com/zenazn/goji/web" -) - -const ( - // Time allowed to write the message to the client. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the client. - pongWait = 60 * time.Second - - // Send pings to client with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - -// WsUser will upgrade the connection to a Websocket and will stream -// all events to the browser pertinent to the authenticated user. If the user -// is not authenticated, only public events are streamed. -func WsUser(c web.C, w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(c) - var user = ToUser(c) - - // upgrade the websocket - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - // register a channel for global events - channel := pubsub.Register(ctx, "_global") - sub := channel.Subscribe() - - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - sub.Close() - ws.Close() - }() - - go func() { - for { - select { - case msg := <-sub.Read(): - work, ok := msg.(*worker.Work) - if !ok { - break - } - - role, permerr := datastore.GetPerm(ctx, user, work.Repo) - if permerr != nil && permerr != sql.ErrNoRows { - // for debugging - log.Printf("WS: Error getting permissions for repository %s. Error: %s\n", work.Repo.Name, permerr) - } - - // user must have read access to private the repository - // in order to pass this message along - if work.Repo.Private == true && role.Read == false { - break - } - - ws.SetWriteDeadline(time.Now().Add(writeWait)) - err := ws.WriteJSON(work) - if err != nil { - ws.Close() - return - } - case <-sub.CloseNotify(): - ws.Close() - return - case <-ticker.C: - ws.SetWriteDeadline(time.Now().Add(writeWait)) - err := ws.WriteMessage(websocket.PingMessage, []byte{}) - if err != nil { - ws.Close() - return - } - } - } - }() - - readWebsocket(ws) -} - -// WsConsole will upgrade the connection to a Websocket and will stream -// the build output to the browser. -func WsConsole(c web.C, w http.ResponseWriter, r *http.Request) { - var commitID, _ = strconv.Atoi(c.URLParams["id"]) - var ctx = context.FromC(c) - var user = ToUser(c) - - commit, err := datastore.GetCommit(ctx, int64(commitID)) - if err != nil { - log.Printf("WS: Error retrieving commit by ID. %s\n", err) - w.WriteHeader(http.StatusNotFound) - return - } - repo, err := datastore.GetRepo(ctx, commit.RepoID) - if err != nil { - log.Printf("WS: Error retrieving repo by ID. %s\n", err) - w.WriteHeader(http.StatusNotFound) - return - } - role, err := datastore.GetPerm(ctx, user, repo) - if err != nil || role.Read == false { - if user == nil { - log.Println("WS: Error getting User session") - } - log.Println("WS: Error retrieving Read permission.", err) - w.WriteHeader(http.StatusNotFound) - return - } - - // find a channel that we can subscribe to - // and listen for stream updates. - channel := pubsub.Lookup(ctx, commit.ID) - if channel == nil { - log.Println("WS: Error getting build stream from channel") - w.WriteHeader(http.StatusNotFound) - return - } - sub := channel.Subscribe() - defer sub.Close() - - // upgrade the websocket - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - ws.Close() - }() - - go func() { - for { - select { - case msg := <-sub.Read(): - data, ok := msg.([]byte) - if !ok { - break - } - ws.SetWriteDeadline(time.Now().Add(writeWait)) - err := ws.WriteMessage(websocket.TextMessage, data) - if err != nil { - ws.Close() - return - } - case <-sub.CloseNotify(): - ws.Close() - return - case <-ticker.C: - ws.SetWriteDeadline(time.Now().Add(writeWait)) - err := ws.WriteMessage(websocket.PingMessage, []byte{}) - if err != nil { - ws.Close() - return - } - } - } - }() - - readWebsocket(ws) -} - -// readWebsocket will block while reading the websocket data -func readWebsocket(ws *websocket.Conn) { - defer ws.Close() - ws.SetReadLimit(512) - ws.SetReadDeadline(time.Now().Add(pongWait)) - ws.SetPongHandler(func(string) error { - ws.SetReadDeadline(time.Now().Add(pongWait)) - return nil - }) - for { - _, _, err := ws.ReadMessage() - if err != nil { - break - } - } -} diff --git a/server/main.go b/server/main.go deleted file mode 100644 index bd5e0bca6..000000000 --- a/server/main.go +++ /dev/null @@ -1,193 +0,0 @@ -package main - -import ( - "database/sql" - "flag" - "fmt" - "net/http" - "os" - "strings" - - "github.com/drone/config" - "github.com/drone/drone/server/middleware" - "github.com/drone/drone/server/pubsub" - "github.com/drone/drone/server/router" - "github.com/drone/drone/shared/build/log" - - "github.com/GeertJohan/go.rice" - - "code.google.com/p/go.net/context" - webcontext "github.com/goji/context" - "github.com/zenazn/goji/web" - - _ "github.com/drone/drone/plugin/notify/email" - "github.com/drone/drone/plugin/remote/bitbucket" - "github.com/drone/drone/plugin/remote/github" - "github.com/drone/drone/plugin/remote/gitlab" - "github.com/drone/drone/plugin/remote/gogs" - "github.com/drone/drone/server/blobstore" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/datastore/database" - "github.com/drone/drone/server/worker/director" - "github.com/drone/drone/server/worker/docker" - "github.com/drone/drone/server/worker/pool" -) - -const ( - DockerTLSWarning = `WARINING: Docker TLS cert or key not given, this may cause a build errors` -) - -var ( - // commit sha for the current build, set by - // the compile process. - version string - revision string -) - -var ( - // Database driver configuration. Defaults to sqlite - // when no database configuration specified. - datasource = config.String("database-datasource", "drone.sqlite") - driver = config.String("database-driver", "sqlite3") - - // HTTP Server settings. - port = config.String("server-port", ":8000") - sslcrt = config.String("server-ssl-cert", "") - sslkey = config.String("server-ssl-key", "") - - assets_folder = config.String("server-assets-folder", "") - - workers *pool.Pool - worker *director.Director - pub *pubsub.PubSub - - // Docker configuration details. - dockercert = config.String("docker-cert", "") - dockerkey = config.String("docker-key", "") - nodes StringArr - - db *sql.DB -) - -func main() { - log.SetPriority(log.LOG_NOTICE) - - // Parses flags. The only flag that can be passed into the - // application is the location of the configuration (.toml) file. - var conf string - flag.StringVar(&conf, "config", "", "") - flag.Parse() - - config.Var(&nodes, "worker-nodes") - - // Parses config data. The config data can be stored in a config - // file (.toml format) or environment variables, or a combo. - config.SetPrefix("DRONE_") - err := config.Parse(conf) - if err != nil { - log.Errf("Unable to parse config: %v", err) - os.Exit(1) - } - - // Setup the remote services. We need to execute these to register - // the remote plugins with the system. - // - // NOTE: this cannot be done via init() because they need to be - // executed after config.Parse - bitbucket.Register() - github.Register() - gitlab.Register() - gogs.Register() - - // setup the database and cancel all pending - // commits in the system. - db = database.MustConnect(*driver, *datasource) - go database.NewCommitstore(db).KillCommits() - - // Create the worker, director and builders - workers = pool.New() - worker = director.New() - - if nodes == nil || len(nodes) == 0 { - workers.Allocate(docker.New()) - workers.Allocate(docker.New()) - } else { - for _, node := range nodes { - if strings.HasPrefix(node, "unix://") { - workers.Allocate(docker.NewHost(node)) - } else if *dockercert != "" && *dockerkey != "" { - workers.Allocate(docker.NewHostCertFile(node, *dockercert, *dockerkey)) - } else { - fmt.Println(DockerTLSWarning) - workers.Allocate(docker.NewHost(node)) - } - } - } - - pub = pubsub.NewPubSub() - - // create handler for static resources - // if we have a configured assets folder it takes precedence over the assets - // bundled to the binary - var assetserve http.Handler - if *assets_folder != "" { - assetserve = http.FileServer(http.Dir(*assets_folder)) - } else { - assetserve = http.FileServer(rice.MustFindBox("app").HTTPBox()) - } - - http.Handle("/robots.txt", assetserve) - http.Handle("/static/", http.StripPrefix("/static", assetserve)) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - r.URL.Path = "/" - assetserve.ServeHTTP(w, r) - }) - - // create the router and add middleware - mux := router.New() - mux.Use(middleware.Options) - mux.Use(ContextMiddleware) - mux.Use(middleware.SetHeaders) - mux.Use(middleware.SetUser) - http.Handle("/api/", mux) - - // start the http server in either http or https mode, - // depending on whether a certificate was provided. - if len(*sslcrt) == 0 { - panic(http.ListenAndServe(*port, nil)) - } else { - panic(http.ListenAndServeTLS(*port, *sslcrt, *sslkey, nil)) - } -} - -// ContextMiddleware creates a new go.net/context and -// injects into the current goji context. -func ContextMiddleware(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ctx = context.Background() - ctx = datastore.NewContext(ctx, database.NewDatastore(db)) - ctx = blobstore.NewContext(ctx, database.NewBlobstore(db)) - ctx = pool.NewContext(ctx, workers) - ctx = director.NewContext(ctx, worker) - ctx = pubsub.NewContext(ctx, pub) - - // add the context to the goji web context - webcontext.Set(c, ctx) - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - -type StringArr []string - -func (s *StringArr) String() string { - return fmt.Sprint(*s) -} - -func (s *StringArr) Set(value string) error { - for _, str := range strings.Split(value, ",") { - str = strings.TrimSpace(str) - *s = append(*s, str) - } - return nil -} diff --git a/server/middleware/context.go b/server/middleware/context.go deleted file mode 100644 index c329da1ae..000000000 --- a/server/middleware/context.go +++ /dev/null @@ -1,69 +0,0 @@ -package middleware - -import ( - "github.com/drone/drone/shared/model" - "github.com/zenazn/goji/web" -) - -// UserToC sets the User in the current -// web context. -func UserToC(c *web.C, user *model.User) { - c.Env["user"] = user -} - -// RepoToC sets the User in the current -// web context. -func RepoToC(c *web.C, repo *model.Repo) { - c.Env["repo"] = repo -} - -// RoleToC sets the User in the current -// web context. -func RoleToC(c *web.C, role *model.Perm) { - c.Env["role"] = role -} - -// ToUser returns the User from the current -// request context. If the User does not exist -// a nil value is returned. -func ToUser(c *web.C) *model.User { - var v = c.Env["user"] - if v == nil { - return nil - } - u, ok := v.(*model.User) - if !ok { - return nil - } - return u -} - -// ToRepo returns the Repo from the current -// request context. If the Repo does not exist -// a nil value is returned. -func ToRepo(c *web.C) *model.Repo { - var v = c.Env["repo"] - if v == nil { - return nil - } - r, ok := v.(*model.Repo) - if !ok { - return nil - } - return r -} - -// ToRole returns the Role from the current -// request context. If the Role does not exist -// a nil value is returned. -func ToRole(c *web.C) *model.Perm { - var v = c.Env["role"] - if v == nil { - return nil - } - p, ok := v.(*model.Perm) - if !ok { - return nil - } - return p -} diff --git a/server/middleware/header.go b/server/middleware/header.go deleted file mode 100644 index 14f00da57..000000000 --- a/server/middleware/header.go +++ /dev/null @@ -1,31 +0,0 @@ -package middleware - -import ( - "net/http" - "time" - - "github.com/zenazn/goji/web" -) - -// SetHeaders is a middleware function that applies -// default headers and caching rules to each request. -func SetHeaders(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Access-Control-Allow-Origin", "*") - w.Header().Add("X-Frame-Options", "DENY") - w.Header().Add("X-Content-Type-Options", "nosniff") - w.Header().Add("X-XSS-Protection", "1; mode=block") - w.Header().Add("Cache-Control", "no-cache") - w.Header().Add("Cache-Control", "no-store") - w.Header().Add("Cache-Control", "max-age=0") - w.Header().Add("Cache-Control", "must-revalidate") - w.Header().Add("Cache-Control", "value") - w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) - w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") - if r.TLS != nil { - w.Header().Add("Strict-Transport-Security", "max-age=31536000") - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} diff --git a/server/middleware/options.go b/server/middleware/options.go deleted file mode 100644 index 24a78902c..000000000 --- a/server/middleware/options.go +++ /dev/null @@ -1,25 +0,0 @@ -package middleware - -import ( - "net/http" - - "github.com/zenazn/goji/web" -) - -// Options automatically return an appropriate "Allow" header when the -// request method is OPTIONS and the request would have otherwise been 404'd. -func Options(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Authorization") - w.Header().Set("Allow", "HEAD,GET,POST,PUT,DELETE,OPTIONS") - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - return - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} diff --git a/server/middleware/repo.go b/server/middleware/repo.go deleted file mode 100644 index 95ca02898..000000000 --- a/server/middleware/repo.go +++ /dev/null @@ -1,116 +0,0 @@ -package middleware - -import ( - "net/http" - "regexp" - - "github.com/drone/drone/server/datastore" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// SetRepo is a middleware function that retrieves -// the repository and stores in the context. -func SetRepo(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ( - ctx = context.FromC(*c) - host = c.URLParams["host"] - owner = c.URLParams["owner"] - name = c.URLParams["name"] - user = ToUser(c) - ) - - repo, err := datastore.GetRepoName(ctx, host, owner, name) - switch { - case err != nil && user == nil: - w.WriteHeader(http.StatusUnauthorized) - return - case err != nil && user != nil: - w.WriteHeader(http.StatusNotFound) - return - } - role, _ := datastore.GetPerm(ctx, user, repo) - RepoToC(c, repo) - RoleToC(c, role) - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - -// RequireRepoRead is a middleware function that verifies -// the user has read access to the repository. -func RequireRepoRead(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ( - role = ToRole(c) - user = ToUser(c) - ) - switch { - case role == nil: - w.WriteHeader(http.StatusInternalServerError) - case user == nil && role.Read == false: - w.WriteHeader(http.StatusUnauthorized) - return - case user == nil && role.Read == false: - w.WriteHeader(http.StatusUnauthorized) - return - case user != nil && role.Read == false: - w.WriteHeader(http.StatusNotFound) - return - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - -// RequireRepoAdmin is a middleware function that verifies -// the user has admin access to the repository. -func RequireRepoAdmin(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ( - role = ToRole(c) - user = ToUser(c) - ) - - // Admin access is only rquired for POST, PUT, DELETE methods. - // If this is a GET request we can proceed immediately. - if r.Method == "GET" { - h.ServeHTTP(w, r) - return - } - - switch { - case role == nil: - w.WriteHeader(http.StatusInternalServerError) - return - case user == nil && role.Admin == false: - w.WriteHeader(http.StatusUnauthorized) - return - case user != nil && role.Read == false && role.Admin == false: - w.WriteHeader(http.StatusNotFound) - return - case user != nil && role.Write == true && role.Admin == false: - if IsRebuild(r.URL.Path) { - h.ServeHTTP(w, r) - return - } - w.WriteHeader(http.StatusForbidden) - return - case user != nil && role.Read == true && role.Admin == false: - w.WriteHeader(http.StatusForbidden) - return - default: - h.ServeHTTP(w, r) - return - } - - } - return http.HandlerFunc(fn) -} - -func IsRebuild(path string) bool { - const pattern = `\/(.*)\/(.*)\/(.*)\/branches\/(.*)\/commits\/(.*)` - ok, _ := regexp.MatchString(pattern, path) - return ok -} diff --git a/server/middleware/user.go b/server/middleware/user.go deleted file mode 100644 index ff35407f6..000000000 --- a/server/middleware/user.go +++ /dev/null @@ -1,57 +0,0 @@ -package middleware - -import ( - "net/http" - - "github.com/drone/drone/server/session" - "github.com/goji/context" - "github.com/zenazn/goji/web" -) - -// SetUser is a middleware function that retrieves -// the currently authenticated user from the request -// and stores in the context. -func SetUser(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ctx = context.FromC(*c) - var user = session.GetUser(ctx, r) - if user != nil && user.ID != 0 { - UserToC(c, user) - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - -// RequireUser is a middleware function that verifies -// there is a currently authenticated user stored in -// the context. -func RequireUser(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if ToUser(c) == nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - -// RequireUserAdmin is a middleware function that verifies -// there is a currently authenticated user stored in -// the context with ADMIN privilege. -func RequireUserAdmin(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var user = ToUser(c) - switch { - case user == nil: - w.WriteHeader(http.StatusUnauthorized) - return - case user != nil && !user.Admin: - w.WriteHeader(http.StatusForbidden) - return - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} diff --git a/server/pubsub/buffer.go b/server/pubsub/buffer.go deleted file mode 100644 index d6fdf04ec..000000000 --- a/server/pubsub/buffer.go +++ /dev/null @@ -1,32 +0,0 @@ -package pubsub - -import ( - "bytes" -) - -type Buffer struct { - buf bytes.Buffer - channel *Channel -} - -func NewBuffer(channel *Channel) *Buffer { - return &Buffer{ - channel: channel, - } -} - -func (b *Buffer) Write(p []byte) (n int, err error) { - n, err = b.buf.Write(p) - b.channel.Publish(p) - return -} - -func (b *Buffer) WriteString(s string) (n int, err error) { - n, err = b.buf.WriteString(s) - b.channel.Publish([]byte(s)) - return -} - -func (b *Buffer) Bytes() []byte { - return b.buf.Bytes() -} diff --git a/server/pubsub/channel.go b/server/pubsub/channel.go deleted file mode 100644 index 33f2a1fa7..000000000 --- a/server/pubsub/channel.go +++ /dev/null @@ -1,129 +0,0 @@ -package pubsub - -import ( - "log" - "time" -) - -type Channel struct { - record bool - history []interface{} - timeout time.Duration - closed chan bool - broadcast chan interface{} - subscribe chan *Subscription - unsubscribe chan *Subscription - subscriptions map[*Subscription]bool -} - -func NewChannel(opts *Opts) *Channel { - return &Channel{ - timeout: opts.Timeout, - record: opts.Record, - history: make([]interface{}, 0), - closed: make(chan bool), - broadcast: make(chan interface{}), - subscribe: make(chan *Subscription), - unsubscribe: make(chan *Subscription), - subscriptions: make(map[*Subscription]bool), - } -} - -func (c *Channel) Publish(data interface{}) { - go func() { c.broadcast <- data }() -} - -func (c *Channel) Subscribe() *Subscription { - s := NewSubscription(c) - c.subscribe <- s - return s -} - -func (c *Channel) Close() { - go func() { c.closed <- true }() -} - -func (c *Channel) start() { - // make sure we don't bring down the application - // if somehow we encounter a nil pointer or some - // other unexpected behavior. - defer func() { - if r := recover(); r != nil { - log.Println("recoved from panic", r) - } - }() - - // timeout the channel after N duration - // ignore the timeout if set to 0 - var timeout <-chan time.Time - if c.timeout > 0 { - timeout = time.After(c.timeout) - } - - for { - select { - - case sub := <-c.unsubscribe: - delete(c.subscriptions, sub) - close(sub.send) - - case sub := <-c.subscribe: - c.subscriptions[sub] = true - - // if we are recording the output - // we should send it to the subscriber - // upon first connecting. - if c.record && len(c.history) > 0 { - history := make([]interface{}, len(c.history)) - copy(history, c.history) - go replay(sub, history) - } - - case msg := <-c.broadcast: - // if we are recording the output, append - // the message to the history - if c.record { - c.history = append(c.history, msg) - } - - // loop through each subscription and - // send the message. - for sub := range c.subscriptions { - select { - case sub.send <- msg: - // do nothing - //default: - // log.Println("subscription closed in inner select") - // sub.Close() - } - } - - case <-timeout: - log.Println("subscription's timedout channel received message") - c.Close() - - case <-c.closed: - log.Println("subscription's close channel received message") - c.stop() - return - } - } -} - -func replay(s *Subscription, history []interface{}) { - defer func() { - if r := recover(); r != nil { - log.Println("recoved from panic", r) - } - }() - - for _, msg := range history { - s.send <- msg - } -} - -func (c *Channel) stop() { - for sub := range c.subscriptions { - sub.Close() - } -} diff --git a/server/pubsub/context.go b/server/pubsub/context.go deleted file mode 100644 index cecd0f1fc..000000000 --- a/server/pubsub/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package pubsub - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "pubsub" - -// NewContext returns a Context whose Value method returns the -// PubSub module. -func NewContext(parent context.Context, pubsub *PubSub) context.Context { - return &wrapper{parent, pubsub} -} - -type wrapper struct { - context.Context - pubsub *PubSub -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.pubsub - } - return c.Context.Value(key) -} - -// FromContext returns the pool assigned to the context. -func FromContext(c context.Context) *PubSub { - return c.Value(reqkey).(*PubSub) -} diff --git a/server/pubsub/opts.go b/server/pubsub/opts.go deleted file mode 100644 index 3a8a6cd6d..000000000 --- a/server/pubsub/opts.go +++ /dev/null @@ -1,26 +0,0 @@ -package pubsub - -import ( - "time" -) - -type Opts struct { - // Timeout sets the expiration date for the channel, - // at which time it will be closed and transmission will - // stop. A zero value for means the channel will not timeout. - Timeout time.Duration - - // Record indicates the channel should record the channel - // activity and playback the full history to subscribers. - Record bool -} - -var DefaultOpts = &Opts{ - Timeout: 0, - Record: false, -} - -var ConsoleOpts = &Opts{ - Timeout: time.Minute * 120, - Record: true, -} diff --git a/server/pubsub/pubsub.go b/server/pubsub/pubsub.go deleted file mode 100644 index c649a22fd..000000000 --- a/server/pubsub/pubsub.go +++ /dev/null @@ -1,104 +0,0 @@ -package pubsub - -import ( - "sync" - - "code.google.com/p/go.net/context" -) - -type PubSub struct { - sync.Mutex - - // In-memory list of all channels being managed by the broker. - channels map[interface{}]*Channel -} - -// NewPubSub creates a new instance of the PubSub type -// and returns a pointer. -func NewPubSub() *PubSub { - return &PubSub{ - channels: make(map[interface{}]*Channel), - } -} - -// Lookup performs a thread safe operation to return a pointer -// to an existing Channel object with the given key. If the -// Channel does not exist a nil value is returned. -func (b *PubSub) Lookup(key interface{}) *Channel { - b.Lock() - defer b.Unlock() - - // find the channel in the existing list - return b.channels[key] -} - -// Register performs a thread safe operation to return a pointer -// to a Channel object for the given key. The Channel is created -// if it does not yet exist. -func (b *PubSub) Register(key interface{}) *Channel { - return b.RegisterOpts(key, DefaultOpts) -} - -// Register performs a thread safe operation to return a pointer -// to a Channel object for the given key. The Channel is created -// if it does not yet exist using custom options. -func (b *PubSub) RegisterOpts(key interface{}, opts *Opts) *Channel { - b.Lock() - defer b.Unlock() - - // find the channel in the existing list - c, ok := b.channels[key] - if ok { - return c - } - - // create the channel and register - // with the pubsub server - c = NewChannel(opts) - b.channels[key] = c - go c.start() - return c -} - -// Unregister performs a thread safe operation to delete the -// Channel with the given key. -func (b *PubSub) Unregister(key interface{}) { - b.Lock() - defer b.Unlock() - - // find the channel in the existing list - c, ok := b.channels[key] - if !ok { - return - } - c.Close() - delete(b.channels, key) - return -} - -// Lookup performs a thread safe operation to return a pointer -// to an existing Channel object with the given key. If the -// Channel does not exist a nil value is returned. -func Lookup(c context.Context, key interface{}) *Channel { - return FromContext(c).Lookup(key) -} - -// Register performs a thread safe operation to return a pointer -// to a Channel object for the given key. The Channel is created -// if it does not yet exist. -func Register(c context.Context, key interface{}) *Channel { - return FromContext(c).Register(key) -} - -// Register performs a thread safe operation to return a pointer -// to a Channel object for the given key. The Channel is created -// if it does not yet exist using custom options. -func RegisterOpts(c context.Context, key interface{}, opts *Opts) *Channel { - return FromContext(c).RegisterOpts(key, opts) -} - -// Unregister performs a thread safe operation to delete the -// Channel with the given key. -func Unregister(c context.Context, key interface{}) { - FromContext(c).Unregister(key) -} diff --git a/server/pubsub/subscribe.go b/server/pubsub/subscribe.go deleted file mode 100644 index f88cba2bc..000000000 --- a/server/pubsub/subscribe.go +++ /dev/null @@ -1,28 +0,0 @@ -package pubsub - -type Subscription struct { - channel *Channel - closed chan bool - send chan interface{} -} - -func NewSubscription(channel *Channel) *Subscription { - return &Subscription{ - channel: channel, - closed: make(chan bool), - send: make(chan interface{}), - } -} - -func (s *Subscription) Read() <-chan interface{} { - return s.send -} - -func (s *Subscription) Close() { - go func() { s.channel.unsubscribe <- s }() - go func() { s.closed <- true }() -} - -func (s *Subscription) CloseNotify() <-chan bool { - return s.closed -} diff --git a/server/router/router.go b/server/router/router.go deleted file mode 100644 index b197d0672..000000000 --- a/server/router/router.go +++ /dev/null @@ -1,85 +0,0 @@ -package router - -import ( - "regexp" - - "github.com/drone/drone/server/handler" - "github.com/drone/drone/server/middleware" - - "github.com/zenazn/goji/web" -) - -func New() *web.Mux { - mux := web.New() - - mux.Get("/api/logins", handler.GetLoginList) - mux.Get("/api/auth/:host", handler.GetLogin) - mux.Post("/api/auth/:host", handler.GetLogin) - mux.Get("/api/badge/:host/:owner/:name/status.svg", handler.GetBadge) - mux.Get("/api/badge/:host/:owner/:name/cc.xml", handler.GetCC) - mux.Get("/api/hook/:host/:token", handler.PostHook) - mux.Put("/api/hook/:host/:token", handler.PostHook) - mux.Post("/api/hook/:host/:token", handler.PostHook) - - // these routes are here for backward compatibility - // to help people troubleshoot why their upgrade isn't - // working correctly. remove at some point - mux.Get("/api/hook/:host", handler.PostHook) - mux.Put("/api/hook/:host", handler.PostHook) - mux.Post("/api/hook/:host", handler.PostHook) - //// - - streams := web.New() - streams.Get("/api/stream/stdout/:id", handler.WsConsole) - streams.Get("/api/stream/user", handler.WsUser) - mux.Handle("/api/stream/*", streams) - - repos := web.New() - repos.Use(middleware.SetRepo) - repos.Use(middleware.RequireRepoRead) - repos.Use(middleware.RequireRepoAdmin) - repos.Get(regexp.MustCompile(`^\/api\/repos\/(?P(.*))\/(?P(.*))\/(?P(.*))\/branches\/(?P(.*))\/commits\/(?P(.*))\/console$`), handler.GetOutput) - repos.Get(regexp.MustCompile(`^\/api\/repos\/(?P(.*))\/(?P(.*))\/(?P(.*))\/branches\/(?P(.*))\/commits\/(?P(.*))$`), handler.GetCommit) - repos.Post(regexp.MustCompile(`^\/api\/repos\/(?P(.*))\/(?P(.*))\/(?P(.*))\/branches\/(?P(.*))\/commits\/(?P(.*))$`), handler.PostCommit) - repos.Get("/api/repos/:host/:owner/:name/commits", handler.GetCommitList) - repos.Get("/api/repos/:host/:owner/:name", handler.GetRepo) - repos.Put("/api/repos/:host/:owner/:name", handler.PutRepo) - repos.Post("/api/repos/:host/:owner/:name", handler.PostRepo) - repos.Delete("/api/repos/:host/:owner/:name", handler.DelRepo) - repos.Post("/api/repos/:host/:owner/:name/deactivate", handler.DeactivateRepo) - mux.Handle("/api/repos/:host/:owner/:name", repos) - mux.Handle("/api/repos/:host/:owner/:name/*", repos) - - users := web.New() - users.Use(middleware.RequireUserAdmin) - users.Get("/api/users/:host/:login", handler.GetUser) - users.Post("/api/users/:host/:login", handler.PostUser) - users.Delete("/api/users/:host/:login", handler.DelUser) - users.Get("/api/users", handler.GetUserList) - mux.Handle("/api/users", users) - mux.Handle("/api/users/*", users) - - user := web.New() - user.Use(middleware.RequireUser) - user.Get("/api/user/feed", handler.GetUserFeed) - user.Get("/api/user/activity", handler.GetUserActivity) - user.Get("/api/user/repos", handler.GetUserRepos) - user.Post("/api/user/sync", handler.PostUserSync) - user.Get("/api/user", handler.GetUserCurrent) - user.Put("/api/user", handler.PutUser) - mux.Handle("/api/user", user) - mux.Handle("/api/user/*", user) - - work := web.New() - work.Use(middleware.RequireUserAdmin) - work.Get("/api/work/started", handler.GetWorkStarted) - work.Get("/api/work/pending", handler.GetWorkPending) - work.Get("/api/work/assignments", handler.GetWorkAssigned) - work.Get("/api/workers", handler.GetWorkers) - work.Post("/api/workers", handler.PostWorker) - work.Delete("/api/workers", handler.DelWorker) - mux.Handle("/api/work", work) - mux.Handle("/api/work/*", work) - - return mux -} diff --git a/server/session/session.go b/server/session/session.go deleted file mode 100644 index f199bcb4d..000000000 --- a/server/session/session.go +++ /dev/null @@ -1,86 +0,0 @@ -package session - -import ( - "fmt" - "net/http" - "time" - - "code.google.com/p/go.net/context" - "github.com/dgrijalva/jwt-go" - "github.com/drone/config" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/model" - "github.com/gorilla/securecookie" -) - -// random key used to create jwt if none -// provided in the configuration. -var random = securecookie.GenerateRandomKey(32) - -var ( - secret = config.String("session-secret", string(random)) - expires = config.Duration("session-expires", time.Hour*72) -) - -// GetUser gets the currently authenticated user for the -// http.Request. The user details will be stored as either -// a simple API token or JWT bearer token. -func GetUser(c context.Context, r *http.Request) *model.User { - switch { - case r.Header.Get("Authorization") != "": - return getUserBearer(c, r) - case r.FormValue("access_token") != "": - return getUserToken(c, r) - default: - return nil - } -} - -// GenerateToken generates a JWT token for the user session -// that can be appended to the #access_token segment to -// facilitate client-based OAuth2. -func GenerateToken(c context.Context, r *http.Request, user *model.User) (string, error) { - token := jwt.New(jwt.GetSigningMethod("HS256")) - token.Claims["user_id"] = user.ID - token.Claims["audience"] = httputil.GetURL(r) - token.Claims["expires"] = time.Now().UTC().Add(time.Hour * 72).Unix() - return token.SignedString([]byte(*secret)) -} - -// getUserToken gets the currently authenticated user for the given -// auth token. -func getUserToken(c context.Context, r *http.Request) *model.User { - var token = r.FormValue("access_token") - var user = getUserJWT(c, token) - if user != nil { - return user - } - user, _ = datastore.GetUserToken(c, token) - return user -} - -// getUserBearer gets the currently authenticated user for the given -// bearer token (JWT) -func getUserBearer(c context.Context, r *http.Request) *model.User { - var tokenstr = r.Header.Get("Authorization") - fmt.Sscanf(tokenstr, "Bearer %s", &tokenstr) - return getUserJWT(c, tokenstr) -} - -// getUserJWT is a helper function that parses the User ID -// and retrieves the User data from a JWT Token. -func getUserJWT(c context.Context, token string) *model.User { - var t, err = jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { - return []byte(*secret), nil - }) - if err != nil || !t.Valid { - return nil - } - var userid, ok = t.Claims["user_id"].(float64) - if !ok { - return nil - } - var user, _ = datastore.GetUser(c, int64(userid)) - return user -} diff --git a/server/sync/sync.go b/server/sync/sync.go deleted file mode 100644 index 6ce066acb..000000000 --- a/server/sync/sync.go +++ /dev/null @@ -1,55 +0,0 @@ -package sync - -import ( - "log" - "time" - - "code.google.com/p/go.net/context" - "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/shared/model" -) - -func SyncUser(ctx context.Context, user *model.User, remote remote.Remote) { - repos, err := remote.GetRepos(user) - if err != nil { - log.Println("Error syncing user account, listing repositories", user.Login, err) - return - } - - // insert all repositories - for _, repo := range repos { - var role = repo.Role - if err := datastore.PostRepo(ctx, repo); err != nil { - // typically we see a failure because the repository already exists - // in which case, we can retrieve the existing record to get the ID. - repo, err = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name) - if err != nil { - log.Println("Error adding repo.", user.Login, repo.Name, err) - continue - } - } - - // add user permissions - perm := model.Perm{ - UserID: user.ID, - RepoID: repo.ID, - Read: role.Read, - Write: role.Write, - Admin: role.Admin, - } - if err := datastore.PostPerm(ctx, &perm); err != nil { - log.Println("Error adding permissions.", user.Login, repo.Name, err) - continue - } - - log.Printf("Successfully synced repo. %s/%s\n", repo.Owner, repo.Name) - } - - user.Synced = time.Now().UTC().Unix() - user.Syncing = false - if err := datastore.PutUser(ctx, user); err != nil { - log.Println("Error syncing user account, updating sync date", user.Login, err) - return - } -} diff --git a/server/worker/context.go b/server/worker/context.go deleted file mode 100644 index 8e44e1fd0..000000000 --- a/server/worker/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package worker - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "worker" - -// NewContext returns a Context whose Value method returns the -// application's worker queue. -func NewContext(parent context.Context, worker Worker) context.Context { - return &wrapper{parent, worker} -} - -type wrapper struct { - context.Context - worker Worker -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.worker - } - return c.Context.Value(key) -} - -// FromContext returns the worker queue associated with this context. -func FromContext(c context.Context) Worker { - return c.Value(reqkey).(Worker) -} diff --git a/server/worker/director/context.go b/server/worker/director/context.go deleted file mode 100644 index 9f6ab2f9d..000000000 --- a/server/worker/director/context.go +++ /dev/null @@ -1,12 +0,0 @@ -package director - -import ( - "code.google.com/p/go.net/context" - "github.com/drone/drone/server/worker" -) - -// NewContext returns a Context whose Value method returns -// the director. -func NewContext(parent context.Context, w worker.Worker) context.Context { - return worker.NewContext(parent, w) -} diff --git a/server/worker/director/director.go b/server/worker/director/director.go deleted file mode 100644 index 873cd10cc..000000000 --- a/server/worker/director/director.go +++ /dev/null @@ -1,117 +0,0 @@ -package director - -import ( - "sync" - - "code.google.com/p/go.net/context" - "github.com/drone/drone/server/worker" - "github.com/drone/drone/server/worker/pool" -) - -// Director manages workloads and delegates to workers. -type Director struct { - sync.Mutex - - pending map[*worker.Work]bool - started map[*worker.Work]worker.Worker -} - -func New() *Director { - return &Director{ - pending: make(map[*worker.Work]bool), - started: make(map[*worker.Work]worker.Worker), - } -} - -// Do processes the work request async. -func (d *Director) Do(c context.Context, work *worker.Work) { - defer func() { - recover() - }() - - d.do(c, work) -} - -// do is a blocking function that waits for an -// available worker to process work. -func (d *Director) do(c context.Context, work *worker.Work) { - d.markPending(work) - var pool = pool.FromContext(c) - var worker = <-pool.Reserve() - - // var worker worker.Worker - // - // // waits for an available worker. This is a blocking - // // operation and will reject any nil workers to avoid - // // a potential panic. - // select { - // case worker = <-pool.Reserve(): - // if worker != nil { - // break - // } - // } - - d.markStarted(work, worker) - worker.Do(c, work) - d.markComplete(work) - pool.Release(worker) -} - -// GetStarted returns a list of all jobs that -// are assigned and being worked on. -func (d *Director) GetStarted() []*worker.Work { - d.Lock() - defer d.Unlock() - started := []*worker.Work{} - for work, _ := range d.started { - started = append(started, work) - } - return started -} - -// GetPending returns a list of all work that -// is pending assignment to a worker. -func (d *Director) GetPending() []*worker.Work { - d.Lock() - defer d.Unlock() - pending := []*worker.Work{} - for work, _ := range d.pending { - pending = append(pending, work) - } - return pending -} - -// GetAssignments returns a list of assignments. The -// assignment type is a structure that stores the -// work being performed and the assigned worker. -func (d *Director) GetAssignemnts() []*worker.Assignment { - d.Lock() - defer d.Unlock() - assignments := []*worker.Assignment{} - for work, _worker := range d.started { - assignment := &worker.Assignment{Work: work, Worker: _worker} - assignments = append(assignments, assignment) - } - return assignments -} - -func (d *Director) markPending(work *worker.Work) { - d.Lock() - defer d.Unlock() - delete(d.started, work) - d.pending[work] = true -} - -func (d *Director) markStarted(work *worker.Work, worker worker.Worker) { - d.Lock() - defer d.Unlock() - delete(d.pending, work) - d.started[work] = worker -} - -func (d *Director) markComplete(work *worker.Work) { - d.Lock() - defer d.Unlock() - delete(d.pending, work) - delete(d.started, work) -} diff --git a/server/worker/director/director_test.go b/server/worker/director/director_test.go deleted file mode 100644 index 601378208..000000000 --- a/server/worker/director/director_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package director - -import ( - "testing" - - "code.google.com/p/go.net/context" - "github.com/drone/drone/server/worker" - "github.com/drone/drone/server/worker/pool" - "github.com/franela/goblin" -) - -func TestDirector(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Director", func() { - - g.It("Should mark work as pending", func() { - d := New() - d.markPending(&worker.Work{}) - d.markPending(&worker.Work{}) - g.Assert(len(d.GetPending())).Equal(2) - }) - - g.It("Should mark work as started", func() { - d := New() - w1 := worker.Work{} - w2 := worker.Work{} - d.markPending(&w1) - d.markPending(&w2) - g.Assert(len(d.GetPending())).Equal(2) - d.markStarted(&w1, &mockWorker{}) - g.Assert(len(d.GetStarted())).Equal(1) - g.Assert(len(d.GetPending())).Equal(1) - d.markStarted(&w2, &mockWorker{}) - g.Assert(len(d.GetStarted())).Equal(2) - g.Assert(len(d.GetPending())).Equal(0) - }) - - g.It("Should mark work as complete", func() { - d := New() - w1 := worker.Work{} - w2 := worker.Work{} - d.markStarted(&w1, &mockWorker{}) - d.markStarted(&w2, &mockWorker{}) - g.Assert(len(d.GetStarted())).Equal(2) - d.markComplete(&w1) - g.Assert(len(d.GetStarted())).Equal(1) - d.markComplete(&w2) - g.Assert(len(d.GetStarted())).Equal(0) - }) - - g.It("Should get work assignments", func() { - d := New() - w1 := worker.Work{} - w2 := worker.Work{} - d.markStarted(&w1, &mockWorker{}) - d.markStarted(&w2, &mockWorker{}) - g.Assert(len(d.GetAssignemnts())).Equal(2) - }) - - g.It("Should recover from a panic", func() { - d := New() - d.Do(nil, nil) - g.Assert(true).Equal(true) - }) - - g.It("Should distribute work to worker", func() { - work := &worker.Work{} - workr := &mockWorker{} - c := context.Background() - p := pool.New() - p.Allocate(workr) - c = pool.NewContext(c, p) - - d := New() - d.do(c, work) - g.Assert(workr.work).Equal(work) // verify mock worker gets work - }) - - g.It("Should add director to context", func() { - d := New() - c := context.Background() - c = NewContext(c, d) - g.Assert(worker.FromContext(c)).Equal(d) - }) - }) -} - -// fake worker for testing purpose only -type mockWorker struct { - name string - work *worker.Work -} - -func (m *mockWorker) Do(c context.Context, w *worker.Work) { - m.work = w -} diff --git a/server/worker/docker/docker.go b/server/worker/docker/docker.go deleted file mode 100644 index 4f497fcee..000000000 --- a/server/worker/docker/docker.go +++ /dev/null @@ -1,197 +0,0 @@ -package docker - -import ( - "fmt" - "log" - "path/filepath" - "runtime/debug" - "time" - - "code.google.com/p/go-uuid/uuid" - "code.google.com/p/go.net/context" - "github.com/drone/drone/plugin/notify" - "github.com/drone/drone/server/blobstore" - "github.com/drone/drone/server/datastore" - "github.com/drone/drone/server/pubsub" - "github.com/drone/drone/server/worker" - "github.com/drone/drone/shared/build" - "github.com/drone/drone/shared/build/docker" - "github.com/drone/drone/shared/build/git" - "github.com/drone/drone/shared/build/repo" - "github.com/drone/drone/shared/build/script" - "github.com/drone/drone/shared/model" -) - -const dockerKind = "docker" - -type Docker struct { - UUID string `json:"uuid"` - Kind string `json:"type"` - Created int64 `json:"created"` - - docker *docker.Client -} - -func New() *Docker { - return &Docker{ - UUID: uuid.New(), - Kind: dockerKind, - Created: time.Now().UTC().Unix(), - docker: docker.New(), - } -} - -func NewHost(host string) *Docker { - return &Docker{ - UUID: uuid.New(), - Kind: dockerKind, - Created: time.Now().UTC().Unix(), - docker: docker.NewHost(host), - } -} - -func NewHostCertFile(host, cert, key string) *Docker { - docker_node, err := docker.NewHostCertFile(host, cert, key) - if err != nil { - log.Fatalln(err) - } - - return &Docker{ - UUID: uuid.New(), - Kind: dockerKind, - Created: time.Now().UTC().Unix(), - docker: docker_node, - } -} - -func (d *Docker) Do(c context.Context, r *worker.Work) { - - // ensure that we can recover from any panics to - // avoid bringing down the entire application. - defer func() { - if e := recover(); e != nil { - log.Printf("%s: %s", e, debug.Stack()) - } - }() - - // mark the build as Started and update the database - r.Commit.Status = model.StatusStarted - r.Commit.Started = time.Now().UTC().Unix() - - datastore.PutCommit(c, r.Commit) - - // notify all listeners that the build is started - commitc := pubsub.Register(c, "_global") - commitc.Publish(r) - stdoutc := pubsub.RegisterOpts(c, r.Commit.ID, pubsub.ConsoleOpts) - defer pubsub.Unregister(c, r.Commit.ID) - - // create a special buffer that will also - // write to a websocket channel - buf := pubsub.NewBuffer(stdoutc) - - // parse the parameters and build script. The script has already - // been parsed in the hook, so we can be confident it will succeed. - // that being said, we should clean this up - params, err := r.Repo.ParamMap() - if err != nil { - log.Printf("Error parsing PARAMS for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error()) - } - script, err := script.ParseBuild(script.Inject(r.Commit.Config, params)) - if err != nil { - log.Printf("Error parsing YAML for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error()) - } - - // append private parameters to the environment - // variable section of the .drone.yml file, iff - // this is not a pull request (for security purposes) - if params != nil && (r.Repo.Private || len(r.Commit.PullRequest) == 0) { - for k, v := range params { - script.Env = append(script.Env, k+"="+v) - } - } - - // TODO: handle error better? - buildNumber, err := datastore.GetBuildNumber(c, r.Commit) - if err != nil { - log.Printf("Unable to fetch build number, Err: %s", err.Error()) - } - script.Env = append(script.Env, fmt.Sprintf("DRONE_BUILD_NUMBER=%d", buildNumber)) - script.Env = append(script.Env, fmt.Sprintf("CI_BUILD_NUMBER=%d", buildNumber)) - - path := r.Repo.Host + "/" + r.Repo.Owner + "/" + r.Repo.Name - repo := &repo.Repo{ - Name: path, - Path: r.Repo.CloneURL, - Branch: r.Commit.Branch, - Commit: r.Commit.Sha, - PR: r.Commit.PullRequest, - Dir: filepath.Join("/var/cache/drone/src", git.GitPath(script.Git, path)), - Depth: git.GitDepth(script.Git), - } - - priorCommit, _ := datastore.GetCommitPrior(c, r.Commit) - - // send all "started" notifications - if script.Notifications == nil { - script.Notifications = ¬ify.Notification{} - } - script.Notifications.Send(&model.Request{ - User: r.User, - Repo: r.Repo, - Commit: r.Commit, - Host: r.Host, - Prior: priorCommit, - }) - - // create an instance of the Docker builder - builder := build.New(d.docker) - builder.Build = script - builder.Repo = repo - builder.Stdout = buf - builder.Timeout = time.Duration(r.Repo.Timeout) * time.Second - builder.Privileged = r.Repo.Privileged - - if r.Repo.Private || len(r.Commit.PullRequest) == 0 { - builder.Key = []byte(r.Repo.PrivateKey) - } - - // run the build - err = builder.Run() - - // update the build status based on the results - // from the build runner. - switch { - case err != nil: - r.Commit.Status = model.StatusError - log.Printf("Error building %s, Err: %s", r.Commit.Sha, err) - buf.WriteString(err.Error()) - case builder.BuildState == nil: - r.Commit.Status = model.StatusFailure - case builder.BuildState.ExitCode != 0: - r.Commit.Status = model.StatusFailure - default: - r.Commit.Status = model.StatusSuccess - } - - // calcualte the build finished and duration details and - // update the commit - r.Commit.Finished = time.Now().UTC().Unix() - r.Commit.Duration = (r.Commit.Finished - r.Commit.Started) - datastore.PutCommit(c, r.Commit) - blobstore.Put(c, filepath.Join(r.Repo.Host, r.Repo.Owner, r.Repo.Name, r.Commit.Branch, r.Commit.Sha), buf.Bytes()) - - // notify all listeners that the build is finished - commitc.Publish(r) - - priorCommit, _ = datastore.GetCommitPrior(c, r.Commit) - - // send all "finished" notifications - script.Notifications.Send(&model.Request{ - User: r.User, - Repo: r.Repo, - Commit: r.Commit, - Host: r.Host, - Prior: priorCommit, - }) -} diff --git a/server/worker/pool/context.go b/server/worker/pool/context.go deleted file mode 100644 index b0458dc83..000000000 --- a/server/worker/pool/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package pool - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "pool" - -// NewContext returns a Context whose Value method returns the -// worker pool. -func NewContext(parent context.Context, pool *Pool) context.Context { - return &wrapper{parent, pool} -} - -type wrapper struct { - context.Context - pool *Pool -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.pool - } - return c.Context.Value(key) -} - -// FromContext returns the pool assigned to the context. -func FromContext(c context.Context) *Pool { - return c.Value(reqkey).(*Pool) -} diff --git a/server/worker/pool/pool.go b/server/worker/pool/pool.go deleted file mode 100644 index e2f3d91f6..000000000 --- a/server/worker/pool/pool.go +++ /dev/null @@ -1,89 +0,0 @@ -package pool - -import ( - "sync" - - "github.com/drone/drone/server/worker" -) - -// TODO (bradrydzewski) ability to cancel work. -// TODO (bradrydzewski) ability to remove a worker. - -type Pool struct { - sync.Mutex - workers map[worker.Worker]bool - workerc chan worker.Worker -} - -func New() *Pool { - return &Pool{ - workers: make(map[worker.Worker]bool), - workerc: make(chan worker.Worker, 999), - } -} - -// Allocate allocates a worker to the pool to -// be available to accept work. -func (p *Pool) Allocate(w worker.Worker) bool { - if p.IsAllocated(w) { - return false - } - - p.Lock() - p.workers[w] = true - p.Unlock() - - p.workerc <- w - return true -} - -// IsAllocated is a helper function that returns -// true if the worker is currently allocated to -// the Pool. -func (p *Pool) IsAllocated(w worker.Worker) bool { - p.Lock() - defer p.Unlock() - _, ok := p.workers[w] - return ok -} - -// Deallocate removes the worker from the pool of -// available workers. If the worker is currently -// reserved and performing work it will finish, -// but no longer be given new work. -func (p *Pool) Deallocate(w worker.Worker) { - p.Lock() - defer p.Unlock() - delete(p.workers, w) -} - -// List returns a list of all Workers currently -// allocated to the Pool. -func (p *Pool) List() []worker.Worker { - p.Lock() - defer p.Unlock() - - var workers []worker.Worker - for w, _ := range p.workers { - workers = append(workers, w) - } - return workers -} - -// Reserve reserves the next available worker to -// start doing work. Once work is complete, the -// worker should be released back to the pool. -func (p *Pool) Reserve() <-chan worker.Worker { - return p.workerc -} - -// Release releases the worker back to the pool -// of available workers. -func (p *Pool) Release(w worker.Worker) bool { - if !p.IsAllocated(w) { - return false - } - - p.workerc <- w - return true -} diff --git a/server/worker/pool/pool_test.go b/server/worker/pool/pool_test.go deleted file mode 100644 index 11fdab5b8..000000000 --- a/server/worker/pool/pool_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package pool - -import ( - "testing" - - "code.google.com/p/go.net/context" - "github.com/drone/drone/server/worker" - "github.com/franela/goblin" -) - -func TestPool(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Pool", func() { - - g.It("Should allocate workers", func() { - w := mockWorker{} - pool := New() - pool.Allocate(&w) - g.Assert(len(pool.workers)).Equal(1) - g.Assert(len(pool.workerc)).Equal(1) - g.Assert(pool.workers[&w]).Equal(true) - }) - - g.It("Should not re-allocate an allocated worker", func() { - w := mockWorker{} - pool := New() - g.Assert(pool.Allocate(&w)).Equal(true) - g.Assert(pool.Allocate(&w)).Equal(false) - }) - - g.It("Should reserve a worker", func() { - w := mockWorker{} - pool := New() - pool.Allocate(&w) - g.Assert(<-pool.Reserve()).Equal(&w) - }) - - g.It("Should release a worker", func() { - w := mockWorker{} - pool := New() - pool.Allocate(&w) - g.Assert(len(pool.workerc)).Equal(1) - g.Assert(<-pool.Reserve()).Equal(&w) - g.Assert(len(pool.workerc)).Equal(0) - pool.Release(&w) - g.Assert(len(pool.workerc)).Equal(1) - g.Assert(<-pool.Reserve()).Equal(&w) - g.Assert(len(pool.workerc)).Equal(0) - }) - - g.It("Should not release an unallocated worker", func() { - w := mockWorker{} - pool := New() - g.Assert(len(pool.workers)).Equal(0) - g.Assert(len(pool.workerc)).Equal(0) - pool.Release(&w) - g.Assert(len(pool.workers)).Equal(0) - g.Assert(len(pool.workerc)).Equal(0) - pool.Release(nil) - g.Assert(len(pool.workers)).Equal(0) - g.Assert(len(pool.workerc)).Equal(0) - }) - - g.It("Should list all allocated workers", func() { - w1 := mockWorker{} - w2 := mockWorker{} - pool := New() - pool.Allocate(&w1) - pool.Allocate(&w2) - g.Assert(len(pool.workers)).Equal(2) - g.Assert(len(pool.workerc)).Equal(2) - g.Assert(len(pool.List())).Equal(2) - }) - - g.It("Should remove a worker", func() { - w1 := mockWorker{} - w2 := mockWorker{} - pool := New() - pool.Allocate(&w1) - pool.Allocate(&w2) - g.Assert(len(pool.workers)).Equal(2) - pool.Deallocate(&w1) - pool.Deallocate(&w2) - g.Assert(len(pool.workers)).Equal(0) - g.Assert(len(pool.List())).Equal(0) - }) - - g.It("Should add / retrieve from context", func() { - c := context.Background() - p := New() - c = NewContext(c, p) - g.Assert(FromContext(c)).Equal(p) - }) - }) -} - -// fake worker for testing purpose only -type mockWorker struct { - name string -} - -func (*mockWorker) Do(c context.Context, w *worker.Work) {} diff --git a/server/worker/work.go b/server/worker/work.go deleted file mode 100644 index 2b43abf9f..000000000 --- a/server/worker/work.go +++ /dev/null @@ -1,15 +0,0 @@ -package worker - -import "github.com/drone/drone/shared/model" - -type Work struct { - Host string `json:"host"` - User *model.User `json:"user"` - Repo *model.Repo `json:"repo"` - Commit *model.Commit `json:"commit"` -} - -type Assignment struct { - Work *Work - Worker Worker -} diff --git a/server/worker/worker.go b/server/worker/worker.go deleted file mode 100644 index fa99c499d..000000000 --- a/server/worker/worker.go +++ /dev/null @@ -1,15 +0,0 @@ -package worker - -import ( - "code.google.com/p/go.net/context" -) - -type Worker interface { - Do(context.Context, *Work) -} - -// Do retrieves a worker from the session and uses -// it to get work done. -func Do(c context.Context, w *Work) { - FromContext(c).Do(c, w) -} diff --git a/shared/build/build.go b/shared/build/build.go deleted file mode 100644 index cb2f60fa6..000000000 --- a/shared/build/build.go +++ /dev/null @@ -1,549 +0,0 @@ -package build - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/docker" - "github.com/drone/drone/shared/build/dockerfile" - "github.com/drone/drone/shared/build/log" - "github.com/drone/drone/shared/build/proxy" - "github.com/drone/drone/shared/build/repo" - "github.com/drone/drone/shared/build/script" -) - -// BuildState stores information about a build -// process including the Exit status and various -// Runtime statistics (coming soon). -type BuildState struct { - Started int64 - Finished int64 - ExitCode int - - // we may eventually include detailed resource - // usage statistics, including including CPU time, - // Max RAM, Max Swap, Disk space, and more. -} - -func New(dockerClient *docker.Client) *Builder { - return &Builder{ - dockerClient: dockerClient, - } -} - -// Builder represents a build process being prepared -// to run. -type Builder struct { - // Image specifies the Docker Image that will be - // used to virtualize the Build process. - Build *script.Build - - // Source specifies the Repository path of the code - // that we are testing. - // - // The source repository may be a local repository - // on the current filesystem, or a remote repository - // on GitHub, Bitbucket, etc. - Repo *repo.Repo - - // Key is an identify file, such as an RSA private key, that - // will be copied into the environments ~/.ssh/id_rsa file. - Key []byte - - // Timeout is the maximum amount of to will wait for a process - // to exit. The default is no timeout. - Timeout time.Duration - - // Privileged indicates the build should be executed in privileged - // mode. The default is false. - Privileged bool - - // Stdout specifies the builds's standard output. - // - // If stdout is nil, Run connects the corresponding file descriptor - // to the null device (os.DevNull). - Stdout io.Writer - - // BuildState contains information about an exited build, - // available after a call to Run. - BuildState *BuildState - - // Docker image that was created for - // this build. - image *docker.Image - - // Docker container was that created - // for this build. - container *docker.Run - - // Docker containers created for the - // specified services and linked to - // this build. - services []*docker.Container - - dockerClient *docker.Client -} - -func (b *Builder) Run() error { - // teardown will remove the Image and stop and - // remove the service containers after the - // build is done running. - defer b.teardown() - - // setup will create the Image and supporting - // service containers. - if err := b.setup(); err != nil { - return err - } - - // make sure build state is not nil - b.BuildState = &BuildState{} - b.BuildState.ExitCode = 0 - b.BuildState.Started = time.Now().UTC().Unix() - - c := make(chan error, 1) - go func() { - c <- b.run() - }() - - // wait for either a) the job to complete or b) the job to timeout - select { - case err := <-c: - return err - case <-time.After(b.Timeout): - log.Errf("time limit exceeded for build %s", b.Build.Name) - b.BuildState.ExitCode = 124 - b.BuildState.Finished = time.Now().UTC().Unix() - return nil - } -} - -func (b *Builder) setup() error { - - // temp directory to store all files required - // to generate the Docker image. - dir, err := ioutil.TempDir("", "drone-") - if err != nil { - return err - } - - // clean up after our mess. - defer os.RemoveAll(dir) - - // make sure the image isn't empty. this would be bad - if len(b.Build.Image) == 0 { - log.Err("Fatal Error, No Docker Image specified") - return fmt.Errorf("Error: missing Docker image") - } - - // if we're using an alias for the build name we - // should substitute it now - if alias, ok := builders[b.Build.Image]; ok { - b.Build.Image = alias.Tag - } - - // if this is a local repository we should symlink - // to the source code in our temp directory - if b.Repo.IsLocal() { - // this is where we used to use symlinks. We should - // talk to the docker team about this, since copying - // the entire repository is slow :( - // - // see https://github.com/dotcloud/docker/pull/3567 - - //src := filepath.Join(dir, "src") - //err = os.Symlink(b.Repo.Path, src) - //if err != nil { - // return err - //} - - src := filepath.Join(dir, "src") - cmd := exec.Command("cp", "-a", b.Repo.Path, src) - if err := cmd.Run(); err != nil { - return fmt.Errorf("Error: Unable to copy repository. %s", err) - } - } - - // start all services required for the build - // that will get linked to the container. - for _, service := range b.Build.Services { - - // Parse the name of the Docker image - // And then construct a fully qualified image name - owner, name, tag := parseImageName(service) - cname := fmt.Sprintf("%s/%s:%s", owner, name, tag) - - // Get the image info - img, err := b.dockerClient.Images.Inspect(cname) - if err != nil { - // Get the image if it doesn't exist - if err := b.dockerClient.Images.Pull(cname); err != nil { - return fmt.Errorf("Error: Unable to pull image %s", cname) - } - - img, err = b.dockerClient.Images.Inspect(cname) - if err != nil { - return fmt.Errorf("Error: Invalid or unknown image %s", cname) - } - } - - // debugging - log.Infof("starting service container %s", cname) - - // Run the contianer - run, err := b.dockerClient.Containers.RunDaemonPorts(cname, img.Config.ExposedPorts) - if err != nil { - return err - } - - // Get the container info - info, err := b.dockerClient.Containers.Inspect(run.ID) - if err != nil { - // on error kill the container since it hasn't yet been - // added to the array and would therefore not get - // removed in the defer statement. - b.dockerClient.Containers.Stop(run.ID, 10) - b.dockerClient.Containers.Remove(run.ID) - return err - } - - // Add the running service to the list - b.services = append(b.services, info) - } - - if err := b.writeBuildScript(dir); err != nil { - return err - } - - if err := b.writeProxyScript(dir); err != nil { - return err - } - - if err := b.writeDockerfile(dir); err != nil { - return err - } - - // debugging - log.Info("creating build image") - - // check for build container (ie bradrydzewski/go:1.2) - // and download if it doesn't already exist or it's :latest tag - if _, err := b.dockerClient.Images.Inspect(b.Build.Image); err == docker.ErrNotFound || strings.HasSuffix(b.Build.Image, ":latest") { - // download the image if it doesn't exist - if err := b.dockerClient.Images.Pull(b.Build.Image); err != nil { - return fmt.Errorf("Error: Unable to pull image %s. %s", b.Build.Image, err) - } - } else if err != nil { - log.Errf("failed to inspect image %s", b.Build.Image) - } - - // create the Docker image - id := createUID() - if err := b.dockerClient.Images.Build(id, dir); err != nil { - return err - } - - // debugging - log.Infof("copying repository to %s", b.Repo.Dir) - - // get the image details - b.image, err = b.dockerClient.Images.Inspect(id) - if err != nil { - // if we have problems with the image make sure - // we remove it before we exit - log.Errf("failed to verify build image %s", id) - return err - } - - return nil -} - -// teardown is a helper function that we can use to -// stop and remove the build container, its supporting image, -// and the supporting service containers. -func (b *Builder) teardown() error { - - defer b.dockerClient.CloseIdleConnections() - - // stop and destroy the container - if b.container != nil { - - // debugging - log.Info("removing build container") - - // stop the container, ignore error message - b.dockerClient.Containers.Stop(b.container.ID, 15) - - // remove the container, ignore error message - if err := b.dockerClient.Containers.Remove(b.container.ID); err != nil { - log.Errf("failed to delete build container %s", b.container.ID) - } - } - - // stop and destroy the container services - for i, container := range b.services { - // debugging - log.Infof("removing service container %s", b.Build.Services[i]) - - // stop the service container, ignore the error - b.dockerClient.Containers.Stop(container.ID, 15) - - // remove the service container, ignore the error - if err := b.dockerClient.Containers.Remove(container.ID); err != nil { - log.Errf("failed to delete service container %s", container.ID) - } - } - - // destroy the underlying image - if b.image != nil { - // debugging - log.Info("removing build image") - - if _, err := b.dockerClient.Images.Remove(b.image.ID); err != nil { - log.Errf("failed to completely delete build image %s. %s", b.image.ID, err.Error()) - } - } - - return nil -} - -func (b *Builder) run() error { - // create and run the container - conf := docker.Config{ - Hostname: script.DockerHostname(b.Build.Docker), - Image: b.image.ID, - AttachStdin: false, - AttachStdout: true, - AttachStderr: true, - } - - // configure if Docker should run in privileged mode - host := docker.HostConfig{ - Privileged: (b.Privileged && len(b.Repo.PR) == 0), - } - - if host.Privileged { - host.NetworkMode = script.DockerNetworkMode(b.Build.Docker) - } - - // debugging - log.Noticef("starting build %s", b.Build.Name) - - // link service containers - for i, service := range b.services { - // convert name of the image to a slug - _, name, _ := parseImageName(b.Build.Services[i]) - - // link the service container to our - // build container. - host.Links = append(host.Links, service.Name[1:]+":"+name) - } - - // where are temp files going to go? - tmpPath := "/tmp/drone" - if len(os.Getenv("DRONE_TMP")) > 0 { - tmpPath = os.Getenv("DRONE_TMP") - } - - log.Infof("temp directory is %s", tmpPath) - - if err := os.MkdirAll(tmpPath, 0777); err != nil { - return fmt.Errorf("Failed to create temp directory at %s: %s", tmpPath, err) - } - - // link cached volumes - conf.Volumes = make(map[string]struct{}) - for _, volume := range b.Build.Cache { - name := filepath.Clean(b.Repo.Name) - volume := filepath.Clean(volume) - - // with Docker, volumes must be an absolute path. If an absolute - // path is not provided, then assume it is for the repository - // working directory. - if strings.HasPrefix(volume, "/") == false { - volume = filepath.Join(b.Repo.Dir, volume) - } - - // local cache path on the host machine - // this path is going to be really long - hostpath := filepath.Join(tmpPath, name, volume) - - // check if the volume is created - if _, err := os.Stat(hostpath); err != nil { - // if does not exist then create - os.MkdirAll(hostpath, 0777) - } - - host.Binds = append(host.Binds, hostpath+":"+volume) - conf.Volumes[volume] = struct{}{} - - // debugging - log.Infof("mounting volume %s:%s", hostpath, volume) - } - - // create the container from the image - run, err := b.dockerClient.Containers.Create(&conf) - if err != nil { - return fmt.Errorf("Error: Failed to create build container. %s", err) - } - - // cache instance of docker.Run - b.container = run - - // attach to the container - go func() { - b.dockerClient.Containers.Attach(run.ID, &writer{b.Stdout, 0}) - }() - - // start the container - if err := b.dockerClient.Containers.Start(run.ID, &host); err != nil { - b.BuildState.ExitCode = 1 - b.BuildState.Finished = time.Now().UTC().Unix() - return fmt.Errorf("Error: Failed to start build container. %s", err) - } - - // wait for the container to stop - wait, err := b.dockerClient.Containers.Wait(run.ID) - if err != nil { - b.BuildState.ExitCode = 1 - b.BuildState.Finished = time.Now().UTC().Unix() - return fmt.Errorf("Error: Failed to wait for build container. %s", err) - } - - // set completion time - b.BuildState.Finished = time.Now().UTC().Unix() - - // get the exit code if possible - b.BuildState.ExitCode = wait.StatusCode - - return nil -} - -// writeDockerfile is a helper function that generates a -// Dockerfile and writes to the builds temporary directory -// so that it can be used to create the Image. -func (b *Builder) writeDockerfile(dir string) error { - var dockerfile = dockerfile.New(b.Build.Image) - dockerfile.WriteWorkdir(b.Repo.Dir) - dockerfile.WriteAdd("drone", "/usr/local/bin/") - - // upload source code if repository is stored - // on the host machine - if b.Repo.IsRemote() == false { - dockerfile.WriteAdd("src", filepath.Join(b.Repo.Dir)) - } - - switch { - case strings.HasPrefix(b.Build.Image, "bradrydzewski/"), - strings.HasPrefix(b.Build.Image, "drone/"): - // the default user for all official Drone image - // is the "ubuntu" user, since all build images - // inherit from the ubuntu cloud ISO - dockerfile.WriteUser("ubuntu") - dockerfile.WriteEnv("LANG", "en_US.UTF-8") - dockerfile.WriteEnv("LANGUAGE", "en_US:en") - dockerfile.WriteEnv("LOGNAME", "ubuntu") - dockerfile.WriteEnv("HOME", "/home/ubuntu") - dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /var/cache/drone") - dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /usr/local/bin/drone") - default: - // all other images are assumed to use - // the root user. - dockerfile.WriteUser("root") - dockerfile.WriteEnv("LOGNAME", "root") - dockerfile.WriteEnv("HOME", "/root") - } - - dockerfile.WriteAdd("proxy.sh", "/etc/drone.d/") - dockerfile.WriteEntrypoint("/bin/bash -e /usr/local/bin/drone") - - // write the Dockerfile to the temporary directory - return ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfile.Bytes(), 0700) -} - -// writeBuildScript is a helper function that -// will generate the build script file in the builder's -// temp directory to be added to the Image. -func (b *Builder) writeBuildScript(dir string) error { - f := buildfile.New() - - // add environment variables for user env - f.WriteEnv("TERM", "xterm") - f.WriteEnv("GOPATH", "/var/cache/drone") - f.WriteEnv("SHELL", "/bin/bash") - - // add environment variables about the build - f.WriteEnv("CI", "true") - f.WriteEnv("DRONE", "true") - f.WriteEnv("DRONE_REMOTE", b.Repo.Path) - f.WriteEnv("DRONE_BRANCH", b.Repo.Branch) - f.WriteEnv("DRONE_COMMIT", b.Repo.Commit) - f.WriteEnv("DRONE_PR", b.Repo.PR) - f.WriteEnv("DRONE_BUILD_DIR", b.Repo.Dir) - - // add environment variables for code coverage - // systems, like coveralls. - f.WriteEnv("CI_NAME", "DRONE") - f.WriteEnv("CI_BUILD_URL", "") - f.WriteEnv("CI_REMOTE", b.Repo.Path) - f.WriteEnv("CI_BRANCH", b.Repo.Branch) - f.WriteEnv("CI_PULL_REQUEST", b.Repo.PR) - - // add /etc/hosts entries - for _, mapping := range b.Build.Hosts { - f.WriteHost(mapping) - } - - if len(b.Key) != 0 { - f.WriteFile("$HOME/.ssh/id_rsa", b.Key, 600) - } - - // if the repository is remote then we should - // add the commands to the build script to - // clone the repository - if b.Repo.IsRemote() { - for _, cmd := range b.Repo.Commands() { - f.WriteCmd(cmd) - } - } - - // if the commit is for merging a pull request - // we should only execute the build commands, - // and omit the deploy and publish commands. - if len(b.Repo.PR) == 0 { - b.Build.Write(f, b.Repo) - } else { - // only write the build commands - b.Build.WriteBuild(f) - } - - scriptfilePath := filepath.Join(dir, "drone") - return ioutil.WriteFile(scriptfilePath, f.Bytes(), 0700) -} - -// writeProxyScript is a helper function that -// will generate the proxy.sh file in the builder's -// temp directory to be added to the Image. -func (b *Builder) writeProxyScript(dir string) error { - var proxyfile = proxy.Proxy{} - - // loop through services so that we can - // map ip address to localhost - for _, container := range b.services { - // create an entry for each port - for port := range container.NetworkSettings.Ports { - proxyfile.Set(port.Port(), container.NetworkSettings.IPAddress) - } - } - - // write the proxyfile to the temp directory - proxyfilePath := filepath.Join(dir, "proxy.sh") - return ioutil.WriteFile(proxyfilePath, proxyfile.Bytes(), 0755) -} diff --git a/shared/build/build_test.go b/shared/build/build_test.go deleted file mode 100644 index 636fff442..000000000 --- a/shared/build/build_test.go +++ /dev/null @@ -1,586 +0,0 @@ -package build - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "testing" - - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/docker" - "github.com/drone/drone/shared/build/proxy" - "github.com/drone/drone/shared/build/repo" - "github.com/drone/drone/shared/build/script" -) - -var ( - // mux is the HTTP request multiplexer used with the test server. - mux *http.ServeMux - - // server is a test HTTP server used to provide mock API responses. - server *httptest.Server - - // docker client - client *docker.Client -) - -// setup a mock docker client for testing purposes. This will use -// a test http server that can return mock responses to the docker client. -func setup() { - mux = http.NewServeMux() - server = httptest.NewServer(mux) - - url, _ := url.Parse(server.URL) - url.Scheme = "tcp" - os.Setenv("DOCKER_HOST", url.String()) - client = docker.New() -} - -func teardown() { - server.Close() -} - -// TestSetup will test our ability to successfully create a Docker -// image for the build. -func TestSetup(t *testing.T) { - setup() - defer teardown() - - // Handles a request to inspect the Go 1.2 image - // This will return a dummy image ID, so that the system knows - // the build image exists, and doens't need to be downloaded. - mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { - body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` - w.Write([]byte(body)) - }) - - // Handles a request to create the build image, with the build - // script injected. This will return a dummy stream. - mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { - body := `{"stream":"Step 1..."}` - w.Write([]byte(body)) - }) - - // Handles a request to inspect the newly created build image. Note - // that we are doing a "wildcard" url match here, since the name of - // the image will be random. This will return a dummy image ID - // to confirm the build image was created successfully. - mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) { - body := `{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }` - w.Write([]byte(body)) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.dockerClient = client - - if err := b.setup(); err != nil { - t.Errorf("Expected success, got %s", err) - } - - // verify the Image is being correctly set - if b.image == nil { - t.Errorf("Expected image not nil") - } - - expectedID := "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" - if b.image.ID != expectedID { - t.Errorf("Expected image.ID %s, got %s", expectedID, b.image.ID) - } -} - -// TestSetupEmptyImage will test our ability to handle a nil or -// blank Docker build image. We expect this to return an error. -func TestSetupEmptyImage(t *testing.T) { - b := Builder{Build: &script.Build{}} - var got, want = b.setup(), "Error: missing Docker image" - - if got == nil || got.Error() != want { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorInspectImage will test our ability to handle a -// failure when inspecting an image (i.e. bradrydzewski/mysql:latest), -// which should trigger a `docker pull`. -func TestSetupErrorInspectImage(t *testing.T) { - t.Skip() -} - -// TestSetupErrorPullImage will test our ability to handle a -// failure when pulling an image (i.e. bradrydzewski/mysql:latest) -func TestSetupErrorPullImage(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - }) - -} - -// TestSetupErrorRunDaemonPorts will test our ability to handle a -// failure when starting a service (i.e. mysql) as a daemon. -func TestSetupErrorRunDaemonPorts(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) { - data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`) - w.Write(data) - }) - - mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.Build.Services = append(b.Build.Services, "mysql") - b.dockerClient = client - - var got, want = b.setup(), docker.ErrBadRequest - if got == nil || got != want { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorServiceInspect will test our ability to handle a -// failure when a service (i.e. mysql) is started successfully, -// but cannot be queried post-start with the Docker remote API. -func TestSetupErrorServiceInspect(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) { - data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`) - w.Write(data) - }) - - mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { - body := `{ "Id":"e90e34656806", "Warnings":[] }` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - }) - - mux.HandleFunc("/v1.9/containers/e90e34656806/json", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.Build.Services = append(b.Build.Services, "mysql") - b.dockerClient = client - - var got, want = b.setup(), docker.ErrBadRequest - if got == nil || got != want { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorImagePull will test our ability to handle a -// failure when a the build image cannot be pulled from the index. -func TestSetupErrorImagePull(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/v1.9/images/create?fromImage=bradrydzewski/mysql&tag=5.5", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.Build.Services = append(b.Build.Services, "mysql") - b.dockerClient = client - - var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/mysql:5.5") - if got == nil || got.Error() != want.Error() { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorUpdate will test our ability to handle a -// failure when the build image cannot be updated -func TestSetupErrorUpdate(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/create", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "bradrydzewski/go:latest" - b.dockerClient = client - - var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/go:latest. %s", docker.ErrBadRequest) - if got == nil || got.Error() != want.Error() { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorBuild will test our ability to handle a failure -// when creating a Docker image with the injected build script, -// ssh keys, etc. -func TestSetupErrorBuild(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { - body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.dockerClient = client - - var got, want = b.setup(), docker.ErrBadRequest - if got == nil || got != want { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestSetupErrorBuildInspect will test our ability to handle a failure -// when we successfully create a Docker image with the injected build script, -// ssh keys, etc, however, we cannot inspect it post-creation using -// the Docker remote API. -func TestSetupErrorBuildInspect(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { - body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { - body := `{"stream":"Step 1..."}` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.Repo = &repo.Repo{} - b.Repo.Path = "git://github.com/drone/drone.git" - b.Build = &script.Build{} - b.Build.Image = "go1.2" - b.dockerClient = client - - var got, want = b.setup(), docker.ErrBadRequest - if got == nil || got != want { - t.Errorf("Expected error %s, got %s", want, got) - } -} - -// TestTeardown will test our ability to sucessfully teardown a -// Docker-based build environment. -func TestTeardown(t *testing.T) { - setup() - defer teardown() - - var ( - containerStopped = false - containerRemoved = false - serviceStopped = false - serviceRemoved = false - imageRemoved = false - ) - - mux.HandleFunc("/v1.9/containers/7bf9ce0ffb/stop", func(w http.ResponseWriter, r *http.Request) { - containerStopped = true - w.WriteHeader(http.StatusOK) - }) - - mux.HandleFunc("/v1.9/containers/7bf9ce0ffb", func(w http.ResponseWriter, r *http.Request) { - containerRemoved = true - w.WriteHeader(http.StatusOK) - }) - - mux.HandleFunc("/v1.9/containers/ec62dcc736/stop", func(w http.ResponseWriter, r *http.Request) { - serviceStopped = true - w.WriteHeader(http.StatusOK) - }) - - mux.HandleFunc("/v1.9/containers/ec62dcc736", func(w http.ResponseWriter, r *http.Request) { - serviceRemoved = true - w.WriteHeader(http.StatusOK) - }) - - mux.HandleFunc("/v1.9/images/c3ab8ff137", func(w http.ResponseWriter, r *http.Request) { - imageRemoved = true - w.Write([]byte(`[{"Untagged":"c3ab8ff137"},{"Deleted":"c3ab8ff137"}]`)) - }) - - b := Builder{} - b.dockerClient = client - b.services = append(b.services, &docker.Container{ID: "ec62dcc736"}) - b.container = &docker.Run{ID: "7bf9ce0ffb"} - b.image = &docker.Image{ID: "c3ab8ff137"} - b.Build = &script.Build{Services: []string{"mysql"}} - b.teardown() - - if !containerStopped { - t.Errorf("Expected Docker container was stopped") - } - - if !containerRemoved { - t.Errorf("Expected Docker container was removed") - } - - if !serviceStopped { - t.Errorf("Expected Docker mysql container was stopped") - } - - if !serviceRemoved { - t.Errorf("Expected Docker mysql container was removed") - } - - if !imageRemoved { - t.Errorf("Expected Docker image was removed") - } -} - -func TestRun(t *testing.T) { - t.Skip() -} - -func TestRunPrivileged(t *testing.T) { - setup() - defer teardown() - - var conf = docker.HostConfig{} - - mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { - body := `{ "Id":"e90e34656806", "Warnings":[] }` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) { - json.NewDecoder(r.Body).Decode(&conf) - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.BuildState = &BuildState{} - b.dockerClient = client - b.Stdout = new(bytes.Buffer) - b.image = &docker.Image{ID: "c3ab8ff137"} - b.Build = &script.Build{} - b.Repo = &repo.Repo{} - b.run() - - if conf.Privileged != false { - t.Errorf("Expected container NOT started in Privileged mode") - } - - // now lets set priviliged mode - b.Privileged = true - b.run() - - if conf.Privileged != true { - t.Errorf("Expected container IS started in Privileged mode") - } - - // now lets set priviliged mode but for a pull request - b.Privileged = true - b.Repo.PR = "55" - b.run() - - if conf.Privileged != false { - t.Errorf("Expected container NOT started in Privileged mode when PR") - } -} - -func TestRunErrorCreate(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.BuildState = &BuildState{} - b.dockerClient = client - b.Stdout = new(bytes.Buffer) - b.image = &docker.Image{ID: "c3ab8ff137"} - b.Build = &script.Build{} - b.Repo = &repo.Repo{} - if err := b.run(); err == nil || err.Error() != "Error: Failed to create build container. Bad Request" { - t.Errorf("Expected error when trying to create build container") - } -} - -func TestRunErrorStart(t *testing.T) { - setup() - defer teardown() - - var ( - containerCreated = false - containerStarted = false - ) - - mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { - containerCreated = true - body := `{ "Id":"e90e34656806", "Warnings":[] }` - w.Write([]byte(body)) - }) - - mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) { - containerStarted = true - w.WriteHeader(http.StatusBadRequest) - }) - - b := Builder{} - b.BuildState = &BuildState{} - b.dockerClient = client - b.Stdout = new(bytes.Buffer) - b.image = &docker.Image{ID: "c3ab8ff137"} - b.Build = &script.Build{} - b.Repo = &repo.Repo{} - - if err := b.run(); err == nil || err.Error() != "Error: Failed to start build container. Bad Request" { - t.Errorf("Expected error when trying to start build container") - } - - if !containerCreated { - t.Errorf("Expected Docker endpoint was invoked to create container") - } - - if !containerStarted { - t.Errorf("Expected Docker endpoint was invoked to start container") - } - - if b.container == nil || b.container.ID != "e90e34656806" { - t.Errorf("Expected build container was created with ID e90e34656806") - } -} - -func TestRunErrorWait(t *testing.T) { - t.Skip() -} - -func TestWriteProxyScript(t *testing.T) { - // temporary directory to store file - dir, _ := ioutil.TempDir("", "drone-test-") - defer os.RemoveAll(dir) - - // fake service container that we'll assume was part of the yaml - // and should be attached to the build container. - c := docker.Container{ - NetworkSettings: &docker.NetworkSettings{ - IPAddress: "172.1.4.5", - Ports: map[docker.Port][]docker.PortBinding{ - docker.NewPort("tcp", "3306"): nil, - }, - }, - } - - // this should generate the following proxy file - p := proxy.Proxy{} - p.Set("3306", "172.1.4.5") - want := p.String() - - b := Builder{} - b.services = append(b.services, &c) - b.writeProxyScript(dir) - - // persist a dummy proxy script to disk - got, err := ioutil.ReadFile(filepath.Join(dir, "proxy.sh")) - if err != nil { - t.Errorf("Expected proxy.sh file saved to disk") - } - - if string(got) != want { - t.Errorf("Expected proxy.sh value saved as %s, got %s", want, got) - } -} - -func TestWriteBuildScript(t *testing.T) { - // temporary directory to store file - dir, _ := ioutil.TempDir("", "drone-test-") - defer os.RemoveAll(dir) - - b := Builder{} - b.Build = &script.Build{ - Hosts: []string{"127.0.0.1"}} - b.Key = []byte("ssh-rsa AAA...") - b.Repo = &repo.Repo{ - Path: "git://github.com/drone/drone.git", - Branch: "master", - Commit: "e7e046b35", - PR: "123", - Dir: "/var/cache/drone/github.com/drone/drone"} - b.writeBuildScript(dir) - - // persist a dummy build script to disk - script, err := ioutil.ReadFile(filepath.Join(dir, "drone")) - if err != nil { - t.Errorf("Expected id_rsa file saved to disk") - } - - f := buildfile.New() - f.WriteEnv("TERM", "xterm") - f.WriteEnv("GOPATH", "/var/cache/drone") - f.WriteEnv("SHELL", "/bin/bash") - f.WriteEnv("CI", "true") - f.WriteEnv("DRONE", "true") - f.WriteEnv("DRONE_REMOTE", "git://github.com/drone/drone.git") - f.WriteEnv("DRONE_BRANCH", "master") - f.WriteEnv("DRONE_COMMIT", "e7e046b35") - f.WriteEnv("DRONE_PR", "123") - f.WriteEnv("DRONE_BUILD_DIR", "/var/cache/drone/github.com/drone/drone") - f.WriteEnv("CI_NAME", "DRONE") - f.WriteEnv("CI_BUILD_URL", "") - f.WriteEnv("CI_REMOTE", "git://github.com/drone/drone.git") - f.WriteEnv("CI_BRANCH", "master") - f.WriteEnv("CI_PULL_REQUEST", "123") - f.WriteHost("127.0.0.1") - f.WriteFile("$HOME/.ssh/id_rsa", []byte("ssh-rsa AAA..."), 600) - f.WriteCmd("git clone --depth=0 --recursive git://github.com/drone/drone.git /var/cache/drone/github.com/drone/drone") - f.WriteCmd("git fetch origin +refs/pull/123/head:refs/remotes/origin/pr/123") - f.WriteCmd("git checkout -qf -b pr/123 origin/pr/123") - - if string(script) != f.String() { - t.Errorf("Expected build script value saved as %s, got %s", f.String(), script) - } -} diff --git a/shared/build/buildfile/buildfile.go b/shared/build/buildfile/buildfile.go deleted file mode 100644 index 723ca1af1..000000000 --- a/shared/build/buildfile/buildfile.go +++ /dev/null @@ -1,92 +0,0 @@ -package buildfile - -import ( - "bytes" - "fmt" -) - -type Buildfile struct { - bytes.Buffer -} - -func New() *Buildfile { - b := Buildfile{} - b.WriteString(base) - return &b -} - -// WriteCmd writes a command to the build file. The -// command will be echoed back as a base16 encoded -// command so that it can be parsed and appended to -// the build output -func (b *Buildfile) WriteCmd(command string) { - // echo the command as an encoded value - b.WriteString(fmt.Sprintf("echo '#DRONE:%x'\n", command)) - // and then run the command - b.WriteString(fmt.Sprintf("%s\n", command)) -} - -// WriteCmdSilent writes a command to the build file -// but does not echo the command. -func (b *Buildfile) WriteCmdSilent(command string) { - b.WriteString(fmt.Sprintf("%s\n", command)) -} - -// WriteComment adds a comment to the build file. This -// is really only used internally for debugging purposes. -func (b *Buildfile) WriteComment(comment string) { - b.WriteString(fmt.Sprintf("#%s\n", comment)) -} - -// WriteEnv exports the environment variable as -// part of the script. The environment variables -// are not echoed back to the console, and are -// kept private by default. -func (b *Buildfile) WriteEnv(key, value string) { - b.WriteString(fmt.Sprintf("export %s=%q\n", key, value)) -} - -// WriteHost adds an entry to the /etc/hosts file. -func (b *Buildfile) WriteHost(mapping string) { - b.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] || echo %q | tee -a /etc/hosts", mapping)) - b.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && echo %q | sudo tee -a /etc/hosts", mapping)) -} - -// WriteFile add files as part of the script. -func (b *Buildfile) WriteFile(path string, file []byte, i int) { - b.WriteString(fmt.Sprintf("echo '%s' | tee %s > /dev/null\n", string(file), path)) - b.WriteCmdSilent(fmt.Sprintf("chmod %d %s", i, path)) -} - -// every build script starts with the following -// code at the start. -var base = ` -#!/bin/bash -set +e - -# drone configuration files are stored in /etc/drone.d -# execute these files prior to our build to set global -# environment variables and initialize programs (like rbenv) -if [ -d /etc/drone.d ]; then - for i in /etc/drone.d/*.sh; do - if [ -r $i ]; then - . $i - fi - done - unset i -fi - -if [ ! -d $HOME/.ssh ]; then - mkdir -p $HOME/.ssh -fi - -chmod 0700 $HOME/.ssh -echo 'StrictHostKeyChecking no' | tee $HOME/.ssh/config > /dev/null - -# be sure to exit on error and print out -# our bash commands, so we can which commands -# are executing and troubleshoot failures. -set -e - -# user-defined commands below ############################## -` diff --git a/shared/build/buildfile/buildfile_test.go b/shared/build/buildfile/buildfile_test.go deleted file mode 100644 index 080a84f6f..000000000 --- a/shared/build/buildfile/buildfile_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package buildfile - -import ( - "testing" -) - -func TestWrite(t *testing.T) { - - var f = New() - var got, want = f.String(), base - if got != want { - t.Errorf("Exepected New() returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteCmd("echo hi") - got, want = f.String(), "echo '#DRONE:6563686f206869'\necho hi\n" - if got != want { - t.Errorf("Exepected WriteCmd returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteCmdSilent("echo hi") - got, want = f.String(), "echo hi\n" - if got != want { - t.Errorf("Exepected WriteCmdSilent returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteComment("this is a comment") - got, want = f.String(), "#this is a comment\n" - if got != want { - t.Errorf("Exepected WriteComment returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteEnv("FOO", "BAR") - got, want = f.String(), "export FOO=\"BAR\"\n" - if got != want { - t.Errorf("Exepected WriteEnv returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteHost("127.0.0.1") - got, want = f.String(), "[ -f /usr/bin/sudo ] || echo \"127.0.0.1\" | tee -a /etc/hosts\n[ -f /usr/bin/sudo ] && echo \"127.0.0.1\" | sudo tee -a /etc/hosts\n" - if got != want { - t.Errorf("Exepected WriteHost returned %s, got %s", want, got) - } - - f = &Buildfile{} - f.WriteFile("$HOME/.ssh/id_rsa", []byte("ssh-rsa AAA..."), 600) - got, want = f.String(), "echo 'ssh-rsa AAA...' | tee $HOME/.ssh/id_rsa > /dev/null\nchmod 600 $HOME/.ssh/id_rsa\n" - if got != want { - t.Errorf("Exepected WriteFile returned \n%s, \ngot\n%s", want, got) - } -} diff --git a/shared/build/docker/client.go b/shared/build/docker/client.go deleted file mode 100644 index 678059b90..000000000 --- a/shared/build/docker/client.go +++ /dev/null @@ -1,391 +0,0 @@ -package docker - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httputil" - "os" - "strings" - "time" - - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/docker/pkg/term" - "github.com/docker/docker/utils" -) - -const ( - APIVERSION = 1.9 - DEFAULTHTTPPORT = 2375 - DEFAULTUNIXSOCKET = "/var/run/docker.sock" - DEFAULTPROTOCOL = "unix" - DEFAULTTAG = "latest" - VERSION = "0.8.0" -) - -// Enables verbose logging to the Terminal window -var Logging = true - -// New creates an instance of the Docker Client -func New() *Client { - return NewHost("") -} - -func NewHost(uri string) *Client { - var cli, _ = NewHostCert(uri, nil, nil) - return cli -} - -func NewHostCertFile(uri, cert, key string) (*Client, error) { - if len(key) == 0 || len(cert) == 0 { - return NewHostCert(uri, nil, nil) - } - certfile, err := ioutil.ReadFile(cert) - if err != nil { - return nil, err - } - keyfile, err := ioutil.ReadFile(key) - if err != nil { - return nil, err - } - return NewHostCert(uri, certfile, keyfile) -} - -func NewHostCert(uri string, cert, key []byte) (*Client, error) { - var host = GetHost(uri) - var proto, addr = SplitProtoAddr(host) - - var cli = new(Client) - cli.proto = proto - cli.addr = addr - cli.scheme = "http" - cli.Images = &ImageService{cli} - cli.Containers = &ContainerService{cli} - - // if no certificate is provided returns the - // client with no TLS configured. - if cert == nil || key == nil || len(cert) == 0 || len(key) == 0 { - cli.trans = &http.Transport{ - Dial: func(dial_network, dial_addr string) (net.Conn, error) { - return net.DialTimeout(cli.proto, cli.addr, 32*time.Second) - }, - } - return cli, nil - } - - // loads the key value pair in pem format - pem, err := tls.X509KeyPair(cert, key) - if err != nil { - return nil, err - } - - // setup the client TLS and store the certificate. - // also skip verification since we are (typically) - // going to be using certs for IP addresses. - cli.scheme = "https" - cli.tls = new(tls.Config) - cli.tls.InsecureSkipVerify = true - cli.tls.Certificates = []tls.Certificate{pem} - - // disable compression for local socket communication. - if cli.proto == DEFAULTPROTOCOL { - cli.trans.DisableCompression = true - } - - // creates a transport that uses the custom tls configuration - // to securely connect to remote Docker clients. - cli.trans = &http.Transport{ - TLSClientConfig: cli.tls, - Dial: func(dial_network, dial_addr string) (net.Conn, error) { - return net.DialTimeout(cli.proto, cli.addr, 32*time.Second) - }, - } - - return cli, nil -} - -// GetHost returns the Docker Host address in order to -// connect to the Docker Daemon. It implements a very -// simple set of fallthrough logic to determine which -// address to use. -func GetHost(host string) string { - // if a default value was provided this - // shoudl be used - if len(host) != 0 { - return host - } - // else attempt to use the DOCKER_HOST - // environment variable - var env = os.Getenv("DOCKER_HOST") - if len(env) != 0 { - return env - } - // else check to see if the default unix - // socket exists and return - _, err := os.Stat(DEFAULTUNIXSOCKET) - if err == nil { - return fmt.Sprintf("%s://%s", DEFAULTPROTOCOL, DEFAULTUNIXSOCKET) - } - // else return the standard TCP address - return fmt.Sprintf("tcp://0.0.0.0:%d", DEFAULTHTTPPORT) -} - -// SplitProtoAddr is a helper function that splits -// a host into Protocol and Address. -func SplitProtoAddr(host string) (string, string) { - var parts = strings.Split(host, "://") - var proto, addr string - switch { - case len(parts) == 2: - proto = parts[0] - addr = parts[1] - default: - proto = "tcp" - addr = parts[0] - } - return proto, addr -} - -type Client struct { - tls *tls.Config - trans *http.Transport - scheme string - proto string - addr string - - Images *ImageService - Containers *ContainerService -} - -var ( - // Returned if the specified resource does not exist. - ErrNotFound = errors.New("Not Found") - - // Return if something going wrong - ErrInternalServer = errors.New("Internal Server Error") - - // Returned if the caller attempts to make a call or modify a resource - // for which the caller is not authorized. - // - // The request was a valid request, the caller's authentication credentials - // succeeded but those credentials do not grant the caller permission to - // access the resource. - ErrForbidden = errors.New("Forbidden") - - // Returned if the call requires authentication and either the credentials - // provided failed or no credentials were provided. - ErrNotAuthorized = errors.New("Unauthorized") - - // Returned if the caller submits a badly formed request. For example, - // the caller can receive this return if you forget a required parameter. - ErrBadRequest = errors.New("Bad Request") -) - -// helper function used to make HTTP requests to the Docker daemon. -func (c *Client) do(method, path string, in, out interface{}) error { - // if data input is provided, serialize to JSON - var payload io.Reader - if in != nil { - buf, err := json.Marshal(in) - if err != nil { - return err - } - payload = bytes.NewBuffer(buf) - } - - // create the request - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), payload) - if err != nil { - return err - } - - // set the appropariate headers - req.Header = http.Header{} - req.Header.Set("User-Agent", "Docker-Client/"+VERSION) - req.Header.Set("Content-Type", "application/json") - - // dial the host server - req.URL.Host = c.addr - req.URL.Scheme = "http" - if c.tls != nil { - req.URL.Scheme = "https" - } - - resp, err := c.HTTPClient().Do(req) - if err != nil { - return err - } - - // make sure we defer close the body - defer resp.Body.Close() - - // Check for an http error status (ie not 200 StatusOK) - switch resp.StatusCode { - case 500: - return ErrInternalServer - case 404: - return ErrNotFound - case 403: - return ErrForbidden - case 401: - return ErrNotAuthorized - case 400: - return ErrBadRequest - } - - // Decode the JSON response - if out != nil { - return json.NewDecoder(resp.Body).Decode(out) - } - - return nil -} - -func (c *Client) hijack(method, path string, setRawTerminal bool, out io.Writer) error { - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) - if err != nil { - return err - } - - req.Header.Set("User-Agent", "Docker-Client/"+VERSION) - req.Header.Set("Content-Type", "plain/text") - req.Host = c.addr - - dial, err := c.Dial() - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") - } - return err - } - clientconn := httputil.NewClientConn(dial, nil) - defer clientconn.Close() - - // Server hijacks the connection, error 'connection closed' expected - clientconn.Do(req) - - // Hijack the connection to read / write - rwc, br := clientconn.Hijack() - defer rwc.Close() - - // launch a goroutine to copy the stream - // of build output to the writer. - errStdout := make(chan error, 1) - go func() { - var err error - if setRawTerminal { - _, err = io.Copy(out, br) - } else { - _, err = stdcopy.StdCopy(out, out, br) - } - - errStdout <- err - }() - - // wait for a response - if err := <-errStdout; err != nil { - return err - } - return nil -} - -func (c *Client) stream(method, path string, in io.Reader, out io.Writer, headers http.Header) error { - if (method == "POST" || method == "PUT") && in == nil { - in = bytes.NewReader(nil) - } - - // setup the request - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) - if err != nil { - return err - } - - // set default headers - req.Header = headers - req.Header.Set("User-Agent", "Docker-Client/0.6.4") - req.Header.Set("Content-Type", "plain/text") - - // dial the host server - req.URL.Host = c.addr - req.URL.Scheme = "http" - if c.tls != nil { - req.URL.Scheme = "https" - } - - resp, err := c.HTTPClient().Do(req) - if err != nil { - return err - } - - // make sure we defer close the body - defer resp.Body.Close() - - // Check for an http error status (ie not 200 StatusOK) - switch resp.StatusCode { - case 500: - return ErrInternalServer - case 404: - return ErrNotFound - case 403: - return ErrForbidden - case 401: - return ErrNotAuthorized - case 400: - return ErrBadRequest - } - - // If no output we exit now with no errors - if out == nil { - io.Copy(ioutil.Discard, resp.Body) - return nil - } - - // copy the output stream to the writer - if resp.Header.Get("Content-Type") == "application/json" { - var terminalFd = os.Stdin.Fd() - var isTerminal = term.IsTerminal(terminalFd) - - // it may not make sense to put this code here, but it works for - // us at the moment, and I don't feel like refactoring - return utils.DisplayJSONMessagesStream(resp.Body, out, terminalFd, isTerminal) - } - // otherwise plain text - if _, err := io.Copy(out, resp.Body); err != nil { - return err - } - - return nil -} - -func (c *Client) HTTPClient() *http.Client { - if c.trans != nil { - return &http.Client{Transport: c.trans} - } - return &http.Client{ - // WARN Leak Transport's Pooling Connection - Transport: &http.Transport{ - Dial: func(dial_network, dial_addr string) (net.Conn, error) { - return net.DialTimeout(c.proto, c.addr, 32*time.Second) - }, - }, - } -} - -func (c *Client) Dial() (net.Conn, error) { - if c.tls != nil && c.proto != "unix" { - return tls.Dial(c.proto, c.addr, c.tls) - } - return net.Dial(c.proto, c.addr) -} - -func (c *Client) CloseIdleConnections() { - if c.trans != nil { - c.trans.CloseIdleConnections() - } -} diff --git a/shared/build/docker/client_test.go b/shared/build/docker/client_test.go deleted file mode 100644 index b40421ec1..000000000 --- a/shared/build/docker/client_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package docker - -import ( - "os" - "testing" -) - -func TestHostFromEnv(t *testing.T) { - os.Setenv("DOCKER_HOST", "tcp://1.1.1.1:2375") - defer os.Setenv("DOCKER_HOST", "") - - client := New() - - if client.proto != "tcp" { - t.Fail() - } - - if client.addr != "1.1.1.1:2375" { - t.Fail() - } -} - -func TestInvalidHostFromEnv(t *testing.T) { - os.Setenv("DOCKER_HOST", "tcp:1.1.1.1:2375") // missing tcp:// prefix - defer os.Setenv("DOCKER_HOST", "") - - client := New() - - if client.addr == "1.1.1.1:2375" { - t.Fail() - } -} diff --git a/shared/build/docker/container.go b/shared/build/docker/container.go deleted file mode 100644 index 061c591e9..000000000 --- a/shared/build/docker/container.go +++ /dev/null @@ -1,146 +0,0 @@ -package docker - -import ( - "fmt" - "io" -) - -type ContainerService struct { - *Client -} - -// List only running containers. -func (c *ContainerService) List() ([]*Containers, error) { - containers := []*Containers{} - err := c.do("GET", "/containers/json?all=0", nil, &containers) - return containers, err -} - -// List all containers -func (c *ContainerService) ListAll() ([]*Containers, error) { - containers := []*Containers{} - err := c.do("GET", "/containers/json?all=1", nil, &containers) - return containers, err -} - -// Create a Container -func (c *ContainerService) Create(conf *Config) (*Run, error) { - run, err := c.create(conf) - switch { - // if no error, exit immediately - case err == nil: - return run, nil - // if error we exit, unless it is - // a NOT FOUND error, which means we just - // need to download the Image from the center - // image index - case err != nil && err != ErrNotFound: - return nil, err - } - - // attempt to pull the image - if err := c.Images.Pull(conf.Image); err != nil { - return nil, err - } - - // now that we have the image, re-try creation - return c.create(conf) -} - -func (c *ContainerService) create(conf *Config) (*Run, error) { - run := Run{} - err := c.do("POST", "/containers/create", conf, &run) - return &run, err -} - -// Start the container id -func (c *ContainerService) Start(id string, conf *HostConfig) error { - return c.do("POST", fmt.Sprintf("/containers/%s/start", id), &conf, nil) -} - -// Stop the container id -func (c *ContainerService) Stop(id string, timeout int) error { - return c.do("POST", fmt.Sprintf("/containers/%s/stop?t=%v", id, timeout), nil, nil) -} - -// Remove the container id from the filesystem. -func (c *ContainerService) Remove(id string) error { - return c.do("DELETE", fmt.Sprintf("/containers/%s", id), nil, nil) -} - -// Block until container id stops, then returns the exit code -func (c *ContainerService) Wait(id string) (*Wait, error) { - wait := Wait{} - err := c.do("POST", fmt.Sprintf("/containers/%s/wait", id), nil, &wait) - return &wait, err -} - -// Attach to the container to stream the stdout and stderr -func (c *ContainerService) Attach(id string, out io.Writer) error { - path := fmt.Sprintf("/containers/%s/attach?&stream=1&stdout=1&stderr=1", id) - return c.hijack("POST", path, false, out) -} - -// Stop the container id -func (c *ContainerService) Inspect(id string) (*Container, error) { - container := Container{} - err := c.do("GET", fmt.Sprintf("/containers/%s/json", id), nil, &container) - return &container, err -} - -// Run the container -func (c *ContainerService) Run(conf *Config, host *HostConfig, out io.Writer) (*Wait, error) { - // create the container from the image - run, err := c.Create(conf) - if err != nil { - return nil, err - } - - // attach to the container - go func() { - c.Attach(run.ID, out) - }() - - // start the container - if err := c.Start(run.ID, host); err != nil { - return nil, err - } - - // wait for the container to stop - wait, err := c.Wait(run.ID) - if err != nil { - return nil, err - } - - return wait, nil -} - -// Run the container as a Daemon -func (c *ContainerService) RunDaemon(conf *Config, host *HostConfig) (*Run, error) { - run, err := c.Create(conf) - if err != nil { - return nil, err - } - - // start the container - err = c.Start(run.ID, host) - return run, err -} - -func (c *ContainerService) RunDaemonPorts(image string, ports map[Port]struct{}) (*Run, error) { - // setup configuration - config := Config{Image: image} - config.ExposedPorts = ports - - // host configuration - host := HostConfig{} - host.PortBindings = make(map[Port][]PortBinding) - - // loop through and add ports - for port, _ := range ports { - host.PortBindings[port] = []PortBinding{{HostIp: "127.0.0.1", HostPort: ""}} - } - //127.0.0.1::%s - //map[3306/tcp:{}] map[3306/tcp:[{127.0.0.1 }]] - return c.RunDaemon(&config, &host) -} diff --git a/shared/build/docker/image.go b/shared/build/docker/image.go deleted file mode 100644 index 87d5a80a9..000000000 --- a/shared/build/docker/image.go +++ /dev/null @@ -1,124 +0,0 @@ -package docker - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "time" - - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/parsers" -) - -type Images struct { - ID string `json:"Id"` - RepoTags []string `json:",omitempty"` - Created int64 - Size int64 - VirtualSize int64 - ParentId string `json:",omitempty"` - - // DEPRECATED - Repository string `json:",omitempty"` - Tag string `json:",omitempty"` -} - -type Image struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - Container string `json:"container,omitempty"` - ContainerConfig Config `json:"container_config,omitempty"` - DockerVersion string `json:"docker_version,omitempty"` - Author string `json:"author,omitempty"` - Config *Config `json:"config,omitempty"` - Architecture string `json:"architecture,omitempty"` - OS string `json:"os,omitempty"` - Size int64 -} - -type Delete struct { - Deleted string `json:",omitempty"` - Untagged string `json:",omitempty"` -} - -type ImageService struct { - *Client -} - -// List Images -func (c *ImageService) List() ([]*Images, error) { - images := []*Images{} - err := c.do("GET", "/images/json?all=0", nil, &images) - return images, err -} - -// Create an image, either by pull it from the registry or by importing it. -func (c *ImageService) Create(image string) error { - return c.do("POST", fmt.Sprintf("/images/create?fromImage=%s", image), nil, nil) -} - -func (c *ImageService) Pull(image string) error { - name, tag := parsers.ParseRepositoryTag(image) - if len(tag) == 0 { - tag = DEFAULTTAG - } - return c.PullTag(name, tag) -} - -func (c *ImageService) PullTag(name, tag string) error { - var out io.Writer - if Logging { - out = os.Stdout - } - - path := fmt.Sprintf("/images/create?fromImage=%s&tag=%s", name, tag) - return c.stream("POST", path, nil, out, http.Header{}) -} - -// Remove the image name from the filesystem -func (c *ImageService) Remove(image string) ([]*Delete, error) { - resp := []*Delete{} - err := c.do("DELETE", fmt.Sprintf("/images/%s", image), nil, &resp) - return resp, err -} - -// Inspect the image -func (c *ImageService) Inspect(name string) (*Image, error) { - image := Image{} - err := c.do("GET", fmt.Sprintf("/images/%s/json", name), nil, &image) - return &image, err -} - -// Build the Image -func (c *ImageService) Build(tag, dir string) error { - - // tar the file - context, err := archive.Tar(dir, archive.Uncompressed) - if err != nil { - return err - } - - var body io.Reader - body = ioutil.NopCloser(context) - - // Upload the build context - v := url.Values{} - v.Set("t", tag) - v.Set("q", "1") - v.Set("rm", "1") - - // url path - path := fmt.Sprintf("/build?%s", v.Encode()) - - // set content type to tar file - headers := http.Header{} - headers.Set("Content-Type", "application/tar") - - // make the request - return c.stream("POST", path, body /*os.Stdout*/, nil, headers) -} diff --git a/shared/build/docker/structs.go b/shared/build/docker/structs.go deleted file mode 100644 index d30282f9c..000000000 --- a/shared/build/docker/structs.go +++ /dev/null @@ -1,167 +0,0 @@ -package docker - -import ( - "fmt" - "strconv" - "strings" - "time" -) - -// These are structures copied from the Docker project. -// We avoid importing the libraries due to a CGO -// depenency on libdevmapper that we'd like to avoid. - -type KeyValuePair struct { - Key string - Value string -} - -type HostConfig struct { - Binds []string - ContainerIDFile string - NetworkMode string - LxcConf []KeyValuePair - Privileged bool - PortBindings map[Port][]PortBinding - Links []string - PublishAllPorts bool -} - -type Top struct { - Titles []string - Processes [][]string -} - -type Containers struct { - ID string `json:"Id"` - Image string - Command string - Created int64 - Status string - Ports []Port - SizeRw int64 - SizeRootFs int64 - Names []string -} - -type Run struct { - ID string `json:"Id"` - Warnings []string `json:",omitempty"` -} - -type Wait struct { - StatusCode int -} - -type State struct { - Running bool - Pid int - ExitCode int - StartedAt time.Time - FinishedAt time.Time - Ghost bool -} - -type PortBinding struct { - HostIp string - HostPort string -} - -// 80/tcp -type Port string - -func (p Port) Proto() string { - parts := strings.Split(string(p), "/") - if len(parts) == 1 { - return "tcp" - } - return parts[1] -} - -func (p Port) Port() string { - return strings.Split(string(p), "/")[0] -} - -func (p Port) Int() int { - i, err := parsePort(p.Port()) - if err != nil { - panic(err) - } - return i -} - -func parsePort(rawPort string) (int, error) { - port, err := strconv.ParseUint(rawPort, 10, 16) - if err != nil { - return 0, err - } - return int(port), nil -} - -func NewPort(proto, port string) Port { - return Port(fmt.Sprintf("%s/%s", port, proto)) -} - -type PortMapping map[string]string // Deprecated - -type NetworkSettings struct { - IPAddress string - IPPrefixLen int - Gateway string - Bridge string - PortMapping map[string]PortMapping // Deprecated - Ports map[Port][]PortBinding -} - -type Config struct { - Hostname string - Domainname string - User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string // Deprecated - Can be in the format of 8080/tcp - ExposedPorts map[Port]struct{} - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - WorkingDir string - Entrypoint []string - NetworkDisabled bool -} - -type Container struct { - ID string - - Created time.Time - - Path string - Args []string - - Config *Config - State State - Image string - - NetworkSettings *NetworkSettings - - SysInitPath string - ResolvConfPath string - HostnamePath string - HostsPath string - Name string - Driver string - - Volumes map[string]string - // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. - // Easier than migrating older container configs :) - VolumesRW map[string]bool -} diff --git a/shared/build/dockerfile/dockerfile.go b/shared/build/dockerfile/dockerfile.go deleted file mode 100644 index cab6eade7..000000000 --- a/shared/build/dockerfile/dockerfile.go +++ /dev/null @@ -1,44 +0,0 @@ -package dockerfile - -import ( - "bytes" - "fmt" -) - -type Dockerfile struct { - bytes.Buffer -} - -func New(from string) *Dockerfile { - d := Dockerfile{} - d.WriteFrom(from) - return &d -} - -func (d *Dockerfile) WriteAdd(from, to string) { - d.WriteString(fmt.Sprintf("ADD %s %s\n", from, to)) -} - -func (d *Dockerfile) WriteFrom(from string) { - d.WriteString(fmt.Sprintf("FROM %s\n", from)) -} - -func (d *Dockerfile) WriteRun(cmd string) { - d.WriteString(fmt.Sprintf("RUN %s\n", cmd)) -} - -func (d *Dockerfile) WriteUser(user string) { - d.WriteString(fmt.Sprintf("USER %s\n", user)) -} - -func (d *Dockerfile) WriteEnv(key, val string) { - d.WriteString(fmt.Sprintf("ENV %s %s\n", key, val)) -} - -func (d *Dockerfile) WriteWorkdir(workdir string) { - d.WriteString(fmt.Sprintf("WORKDIR %s\n", workdir)) -} - -func (d *Dockerfile) WriteEntrypoint(entrypoint string) { - d.WriteString(fmt.Sprintf("ENTRYPOINT %s\n", entrypoint)) -} diff --git a/shared/build/dockerfile/dockerfile_test.go b/shared/build/dockerfile/dockerfile_test.go deleted file mode 100644 index cb9a63a22..000000000 --- a/shared/build/dockerfile/dockerfile_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package dockerfile - -import ( - "testing" -) - -func TestWrite(t *testing.T) { - - var f = New("ubuntu") - var got, want = f.String(), "FROM ubuntu\n" - if got != want { - t.Errorf("Exepected New() returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteAdd("src", "target") - got, want = f.String(), "ADD src target\n" - if got != want { - t.Errorf("Exepected WriteAdd returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteFrom("ubuntu") - got, want = f.String(), "FROM ubuntu\n" - if got != want { - t.Errorf("Exepected WriteFrom returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteRun("whoami") - got, want = f.String(), "RUN whoami\n" - if got != want { - t.Errorf("Exepected WriteRun returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteUser("root") - got, want = f.String(), "USER root\n" - if got != want { - t.Errorf("Exepected WriteUser returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteEnv("FOO", "BAR") - got, want = f.String(), "ENV FOO BAR\n" - if got != want { - t.Errorf("Exepected WriteEnv returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteWorkdir("/home/ubuntu") - got, want = f.String(), "WORKDIR /home/ubuntu\n" - if got != want { - t.Errorf("Exepected WriteWorkdir returned %s, got %s", want, got) - } - - f = &Dockerfile{} - f.WriteEntrypoint("/root") - got, want = f.String(), "ENTRYPOINT /root\n" - if got != want { - t.Errorf("Exepected WriteEntrypoint returned %s, got %s", want, got) - } -} diff --git a/shared/build/git/git.go b/shared/build/git/git.go deleted file mode 100644 index 74e711cc0..000000000 --- a/shared/build/git/git.go +++ /dev/null @@ -1,39 +0,0 @@ -package git - -const ( - DefaultGitDepth = 50 -) - -// Git stores the configuration details for -// executing Git commands. -type Git struct { - // Depth options instructs git to create a shallow - // clone with a history truncated to the specified - // number of revisions. - Depth *int `yaml:"depth,omitempty"` - - // The name of a directory to clone into. - Path *string `yaml:"path,omitempty"` -} - -// GitDepth returns GitDefaultDepth -// when Git.Depth is empty. -// GitDepth returns Git.Depth -// when it is not empty. -func GitDepth(g *Git) int { - if g == nil || g.Depth == nil { - return DefaultGitDepth - } - return *g.Depth -} - -// GitPath returns the given default path -// when Git.Path is empty. -// GitPath returns Git.Path -// when it is not empty. -func GitPath(g *Git, defaultPath string) string { - if g == nil || g.Path == nil { - return defaultPath - } - return *g.Path -} diff --git a/shared/build/git/git_test.go b/shared/build/git/git_test.go deleted file mode 100644 index 23eb395ba..000000000 --- a/shared/build/git/git_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package git - -import ( - "testing" -) - -func TestGitDepth(t *testing.T) { - var g *Git - var expected int - - expected = DefaultGitDepth - g = nil - if actual := GitDepth(g); actual != expected { - t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual) - } - - expected = DefaultGitDepth - g = &Git{} - if actual := GitDepth(g); actual != expected { - t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual) - } - - expected = DefaultGitDepth - g = &Git{Depth: nil} - if actual := GitDepth(g); actual != expected { - t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual) - } - - expected = 0 - g = &Git{Depth: &expected} - if actual := GitDepth(g); actual != expected { - t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual) - } - - expected = 1 - g = &Git{Depth: &expected} - if actual := GitDepth(g); actual != expected { - t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual) - } -} diff --git a/shared/build/images.go b/shared/build/images.go deleted file mode 100644 index bcd409fcb..000000000 --- a/shared/build/images.go +++ /dev/null @@ -1,239 +0,0 @@ -package build - -type image struct { - // default ports the service will run on. - // for example, 3306 for mysql. Note that a service - // may expose multiple prots, for example, Riak - // exposes 8087 and 8089. - Ports []string - - // tag of the docker image to pull in order - // to run this service. - Tag string - - // display name of the image type - Name string -} - -// List of 3rd party services (database, queue, etc) that -// are known to work with this Build utility. -var services = map[string]*image{ - - // neo4j - "neo4j": { - Ports: []string{"7474"}, - Tag: "bradrydzewski/neo4j:1.9", - Name: "neo4j", - }, - "neo4j:1.9": { - Ports: []string{"7474"}, - Tag: "bradrydzewski/neo4j:1.9", - Name: "neo4j", - }, - - // elasticsearch servers - "elasticsearch": { - Ports: []string{"9200"}, - Tag: "bradrydzewski/elasticsearch:0.90", - Name: "elasticsearch", - }, - "elasticsearch:0.20": { - Ports: []string{"9200"}, - Tag: "bradrydzewski/elasticsearch:0.20", - Name: "elasticsearch", - }, - "elasticsearch:0.90": { - Ports: []string{"9200"}, - Tag: "bradrydzewski/elasticsearch:0.90", - Name: "elasticsearch", - }, - - // redis servers - "redis": { - Ports: []string{"6379"}, - Tag: "bradrydzewski/redis:2.8", - Name: "redis", - }, - "redis:2.8": { - Ports: []string{"6379"}, - Tag: "bradrydzewski/redis:2.8", - Name: "redis", - }, - "redis:2.6": { - Ports: []string{"6379"}, - Tag: "bradrydzewski/redis:2.6", - Name: "redis", - }, - - // mysql servers - "mysql": { - Tag: "bradrydzewski/mysql:5.5", - Ports: []string{"3306"}, - Name: "mysql", - }, - "mysql:5.5": { - Tag: "bradrydzewski/mysql:5.5", - Ports: []string{"3306"}, - Name: "mysql", - }, - - // memcached - "memcached": { - Ports: []string{"11211"}, - Tag: "bradrydzewski/memcached", - Name: "memcached", - }, - - // mongodb - "mongodb": { - Ports: []string{"27017"}, - Tag: "bradrydzewski/mongodb:2.4", - Name: "mongodb", - }, - "mongodb:2.4": { - Ports: []string{"27017"}, - Tag: "bradrydzewski/mongodb:2.4", - Name: "mongodb", - }, - "mongodb:2.2": { - Ports: []string{"27017"}, - Tag: "bradrydzewski/mongodb:2.2", - Name: "mongodb", - }, - - // postgres - "postgres": { - Ports: []string{"5432"}, - Tag: "bradrydzewski/postgres:9.1", - Name: "postgres", - }, - "postgres:9.1": { - Ports: []string{"5432"}, - Tag: "bradrydzewski/postgres:9.1", - Name: "postgres", - }, - - // couchdb - "couchdb": { - Ports: []string{"5984"}, - Tag: "bradrydzewski/couchdb:1.5", - Name: "couchdb", - }, - "couchdb:1.0": { - Ports: []string{"5984"}, - Tag: "bradrydzewski/couchdb:1.0", - Name: "couchdb", - }, - "couchdb:1.4": { - Ports: []string{"5984"}, - Tag: "bradrydzewski/couchdb:1.4", - Name: "couchdb", - }, - "couchdb:1.5": { - Ports: []string{"5984"}, - Tag: "bradrydzewski/couchdb:1.5", - Name: "couchdb", - }, - - // rabbitmq - "rabbitmq": { - Ports: []string{"5672", "15672"}, - Tag: "bradrydzewski/rabbitmq:3.2", - Name: "rabbitmq", - }, - "rabbitmq:3.2": { - Ports: []string{"5672", "15672"}, - Tag: "bradrydzewski/rabbitmq:3.2", - Name: "rabbitmq", - }, - - // experimental images from 3rd parties - - "zookeeper": { - Ports: []string{"2181"}, - Tag: "jplock/zookeeper:3.4.5", - Name: "zookeeper", - }, - - // cassandra - "cassandra": { - Ports: []string{"9042", "7000", "7001", "7199", "9160", "49183"}, - Tag: "relateiq/cassandra", - Name: "cassandra", - }, - - // riak - TESTED - "riak": { - Ports: []string{"8087", "8098"}, - Tag: "guillermo/riak", - Name: "riak", - }, -} - -// List of official Drone build images. -var builders = map[string]*image{ - - // Clojure build images - "lein": {Tag: "bradrydzewski/lein"}, - - // Dart build images - "dart": {Tag: "bradrydzewski/dart:stable"}, - "dart_stable": {Tag: "bradrydzewski/dart:stable"}, - "dart_dev": {Tag: "bradrydzewski/dart:dev"}, - - // Erlang build images - "erlang": {Tag: "bradrydzewski/erlang:R16B02"}, - "erlangR16B": {Tag: "bradrydzewski/erlang:R16B"}, - "erlangR16B02": {Tag: "bradrydzewski/erlang:R16B02"}, - "erlangR16B01": {Tag: "bradrydzewski/erlang:R16B01"}, - - // GCC build images - "gcc": {Tag: "bradrydzewski/gcc:4.6"}, - "gcc4.6": {Tag: "bradrydzewski/gcc:4.6"}, - "gcc4.8": {Tag: "bradrydzewski/gcc:4.8"}, - - // Golang build images - "go": {Tag: "bradrydzewski/go:1.3"}, - "go1": {Tag: "bradrydzewski/go:1.0"}, - "go1.1": {Tag: "bradrydzewski/go:1.1"}, - "go1.2": {Tag: "bradrydzewski/go:1.2"}, - "go1.3": {Tag: "bradrydzewski/go:1.3"}, - - // Haskell build images - "haskell": {Tag: "bradrydzewski/haskell:7.4"}, - "haskell7.4": {Tag: "bradrydzewski/haskell:7.4"}, - - // Java build images - "java": {Tag: "bradrydzewski/java:openjdk7"}, - "openjdk6": {Tag: "bradrydzewski/java:openjdk6"}, - "openjdk7": {Tag: "bradrydzewski/java:openjdk7"}, - "oraclejdk7": {Tag: "bradrydzewski/java:oraclejdk7"}, - "oraclejdk8": {Tag: "bradrydzewski/java:oraclejdk8"}, - - // Node build images - "node": {Tag: "bradrydzewski/node:0.10"}, - "node0.10": {Tag: "bradrydzewski/node:0.10"}, - "node0.8": {Tag: "bradrydzewski/node:0.8"}, - - // PHP build images - "php": {Tag: "bradrydzewski/php:5.5"}, - "php5.5": {Tag: "bradrydzewski/php:5.5"}, - "php5.4": {Tag: "bradrydzewski/php:5.4"}, - - // Python build images - "python": {Tag: "bradrydzewski/python:2.7"}, - "python2.7": {Tag: "bradrydzewski/python:2.7"}, - "python3.2": {Tag: "bradrydzewski/python:3.2"}, - "python3.3": {Tag: "bradrydzewski/python:3.3"}, - "pypy": {Tag: "bradrydzewski/python:pypy"}, - - // Ruby build images - "ruby": {Tag: "bradrydzewski/ruby:2.0.0"}, - "ruby2.0.0": {Tag: "bradrydzewski/ruby:2.0.0"}, - "ruby1.9.3": {Tag: "bradrydzewski/ruby:1.9.3"}, - - // Scala build images - "scala": {Tag: "bradrydzewski/scala:2.10.3"}, - "scala2.10.3": {Tag: "bradrydzewski/scala:2.10.3"}, - "scala2.9.3": {Tag: "bradrydzewski/scala:2.9.3"}, -} diff --git a/shared/build/log/log.go b/shared/build/log/log.go deleted file mode 100644 index 404e415fb..000000000 --- a/shared/build/log/log.go +++ /dev/null @@ -1,105 +0,0 @@ -package log - -import ( - "fmt" - "io" - "os" - "sync" -) - -const ( - LOG_EMERG = iota - LOG_ALERT - LOG_CRIT - LOG_ERR - LOG_WARNING - LOG_NOTICE - LOG_INFO - LOG_DEBUG -) - -var mu sync.Mutex - -// the default Log priority -var priority int = LOG_DEBUG - -// the default Log output destination -var output io.Writer = os.Stdout - -// the log prefix -var prefix string - -// the log suffix -var suffix string = "\n" - -// SetPriority sets the default log level. -func SetPriority(level int) { - mu.Lock() - defer mu.Unlock() - priority = level -} - -// SetOutput sets the output destination. -func SetOutput(w io.Writer) { - mu.Lock() - defer mu.Unlock() - output = w -} - -// SetPrefix sets the prefix for the log message. -func SetPrefix(pre string) { - mu.Lock() - defer mu.Unlock() - prefix = pre -} - -// SetSuffix sets the suffix for the log message. -func SetSuffix(suf string) { - mu.Lock() - defer mu.Unlock() - suffix = suf -} - -func Write(out string, level int) { - mu.Lock() - defer mu.Unlock() - - // append the prefix and suffix - out = prefix + out + suffix - - if priority >= level { - output.Write([]byte(out)) - } -} - -func Debug(out string) { - Write(out, LOG_DEBUG) -} - -func Debugf(format string, a ...interface{}) { - Debug(fmt.Sprintf(format, a...)) -} - -func Info(out string) { - Write(out, LOG_INFO) -} - -func Infof(format string, a ...interface{}) { - Info(fmt.Sprintf(format, a...)) -} - -func Err(out string) { - Write(out, LOG_ERR) -} - -func Errf(format string, a ...interface{}) { - Err(fmt.Sprintf(format, a...)) -} - -func Notice(out string) { - Write(out, LOG_NOTICE) -} - -func Noticef(format string, a ...interface{}) { - Notice(fmt.Sprintf(format, a...)) -} diff --git a/shared/build/proxy/proxy.go b/shared/build/proxy/proxy.go deleted file mode 100644 index aa7637114..000000000 --- a/shared/build/proxy/proxy.go +++ /dev/null @@ -1,54 +0,0 @@ -package proxy - -import ( - "bytes" - "fmt" -) - -// bash header plus an embedded perl script that can be used -// as an alternative to socat to proxy tcp traffic. -const header = `#!/bin/bash -set +e -` - -// TODO(bradrydzewski) probably going to remove this -//echo H4sICGKv1VQAA3NvY2F0LnBsAH1SXUvDQBB8Tn7FipUmkpr6gWBKgyIiBdGixVeJ6RZP00u4S6wi8be7t3exFsWEhNzO7M7MXba34kar+FHIuEJV+I1GmNwkyV2Zv2A9Wq+xwJzWfk/IqqlhDM+lkEEf+tHp2e3lfTj6Rj5hGc/Op4Oryd3s4joJ9nbDaFGqF6Air/gVU0M2nyua1Dug76pUZmrvkDSW79ATpUZTWIsPUomrkQF3NLt7WGaVY2tUr6g6OqNJMrm+mHFT4HtXZZ4VZ6yXQn+4x3c/csCUxVNgF1S8RcrdsfcNS+gapWdWw6HPYY2/QUoRAqdOVX/1JAqEYD+ED9+j0MDm2A8EXU+eyQeF2ZxJnlgQ4ijjcRfFYp5pzwuBkvfGQiSa51jRYTiCwmVZ4z/h6Zoiqi4Q73v0Xd4Ib6ohT95IaD38AVhtB6yP5cN1tMa25fym2DpTLNtQWnqwoL+O80t8q6GRBWoN+EaHoGFjhP1uf2/Fv6zHZrFA9aMpm69bBql+16YUOF4ER8OTYxfRCjBnpUSNHSl03lu/9b8ACaSZylQDAAA= | base64 -d | gunzip > /tmp/socat && chmod +x /tmp/socat - -// this command string will check if the socat utility -// exists, and if it does, will proxy connections to -// the external IP address. -const command = "command -v socat >/dev/null && socat TCP-LISTEN:%s,fork TCP:%s:%s &\n" - -// alternative command that acts as a "polyfill" for socat -// in the event that it isn't installed on the server -const polyfill = "[ -x /tmp/socat ] && /tmp/socat TCP-LISTEN:%s,fork TCP:%s:%s &\n" - -// Proxy stores proxy configuration details mapping -// a local port to an external IP address with the -// same port number. -type Proxy map[string]string - -func (p Proxy) Set(port, ip string) { - p[port] = ip -} - -// String converts the proxy configuration details -// to a bash script. -func (p Proxy) String() string { - var buf bytes.Buffer - buf.WriteString(header) - for port, ip := range p { - buf.WriteString(fmt.Sprintf(command, port, ip, port)) - - // TODO(bradrydzewski) probably going to remove this - //buf.WriteString(fmt.Sprintf(polyfill, port, ip, port)) - } - - return buf.String() -} - -// Bytes converts the proxy configuration details -// to a bash script in byte array format. -func (p Proxy) Bytes() []byte { - return []byte(p.String()) -} diff --git a/shared/build/proxy/proxy_test.go b/shared/build/proxy/proxy_test.go deleted file mode 100644 index 7dfc450dd..000000000 --- a/shared/build/proxy/proxy_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package proxy - -import ( - "testing" -) - -func TestProxy(t *testing.T) { - // test creating a proxy with a few different - // addresses, and our ability to create the - // proxy shell script. - p := Proxy{} - p.Set("8080", "172.1.4.5") - b := p.Bytes() - - expected := header + "command -v socat >/dev/null && socat TCP-LISTEN:8080,fork TCP:172.1.4.5:8080 &\n" - if string(b) != expected { - t.Errorf("Invalid proxy got:\n%s\nwant:\n%s", string(b), expected) - } - - // test creating a proxy script when there - // are no proxy addresses added to the map - p = Proxy{} - b = p.Bytes() - if string(b) != header { - t.Errorf("Invalid empty proxy file. Expected\n%s", header) - } -} diff --git a/shared/build/repo/repo.go b/shared/build/repo/repo.go deleted file mode 100644 index 82973efa1..000000000 --- a/shared/build/repo/repo.go +++ /dev/null @@ -1,127 +0,0 @@ -package repo - -import ( - "fmt" - "strings" -) - -type Repo struct { - // The name of the Repository. This should be the - // canonical name, for example, github.com/drone/drone. - Name string - - // The path of the Repoisotry. This could be - // the remote path of a Git repository or the path of - // of the repository on the local file system. - // - // A remote path must start with http://, https://, - // git://, ssh:// or git@. Otherwise we'll assume - // the repository is located on the local filesystem. - Path string - - // (optional) Specific Branch that we should checkout - // when the Repository is cloned. If no value is - // provided we'll assume the default, master branch. - Branch string - - // (optional) Specific Commit Hash that we should - // checkout when the Repository is cloned. If no - // value is provided we'll assume HEAD. - Commit string - - // (optional) Pull Request number that we should - // checkout when the Repository is cloned. - PR string - - // (optional) The filesystem path that the repository - // will be cloned into (or copied to) inside the - // host system (Docker Container). - Dir string - - // (optional) The depth of the `git clone` command. - Depth int -} - -// IsRemote returns true if the Repository is located -// on a remote server (ie Github, Bitbucket) -func (r *Repo) IsRemote() bool { - switch { - case strings.HasPrefix(r.Path, "git://"): - return true - case strings.HasPrefix(r.Path, "git@"): - return true - case strings.HasPrefix(r.Path, "gitlab@"): - return true - case strings.HasPrefix(r.Path, "http://"): - return true - case strings.HasPrefix(r.Path, "https://"): - return true - case strings.HasPrefix(r.Path, "ssh://"): - return true - } - - return false -} - -// IsLocal returns true if the Repository is located -// on the local filesystem. -func (r *Repo) IsLocal() bool { - return !r.IsRemote() -} - -// IsGit returns true if the Repository is -// a Git repoisitory. -func (r *Repo) IsGit() bool { - switch { - case strings.HasPrefix(r.Path, "git://"): - return true - case strings.HasPrefix(r.Path, "git@"): - return true - case strings.HasPrefix(r.Path, "ssh://git@"): - return true - case strings.HasPrefix(r.Path, "gitlab@"): - return true - case strings.HasPrefix(r.Path, "ssh://gitlab@"): - return true - case strings.HasPrefix(r.Path, "https://github"): - return true - case strings.HasPrefix(r.Path, "http://github"): - return true - case strings.HasSuffix(r.Path, ".git"): - return true - } - - // we could also ping the repository to check - - return false -} - -// returns commands that can be used in a Dockerfile -// to clone the repository. -// -// TODO we should also enable Mercurial projects and SVN projects -func (r *Repo) Commands() []string { - // get the branch. default to master - // if no branch exists. - branch := r.Branch - if len(branch) == 0 { - branch = "master" - } - - cmds := []string{} - if len(r.PR) > 0 { - // If a specific PR is provided then we need to clone it. - cmds = append(cmds, fmt.Sprintf("git clone --depth=%d --recursive %s %s", r.Depth, r.Path, r.Dir)) - cmds = append(cmds, fmt.Sprintf("git fetch origin +refs/pull/%s/head:refs/remotes/origin/pr/%s", r.PR, r.PR)) - cmds = append(cmds, fmt.Sprintf("git checkout -qf -b pr/%s origin/pr/%s", r.PR, r.PR)) - } else { - // Otherwise just clone the branch. - cmds = append(cmds, fmt.Sprintf("git clone --depth=%d --recursive --branch=%s %s %s", r.Depth, branch, r.Path, r.Dir)) - // If a specific commit is provided then we'll need to check it out. - if len(r.Commit) > 0 { - cmds = append(cmds, fmt.Sprintf("git checkout -qf %s", r.Commit)) - } - } - - return cmds -} diff --git a/shared/build/repo/repo_test.go b/shared/build/repo/repo_test.go deleted file mode 100644 index b8f4d54ef..000000000 --- a/shared/build/repo/repo_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package repo - -import ( - "testing" -) - -func TestIsRemote(t *testing.T) { - repos := []struct { - path string - remote bool - }{ - {"git://github.com/foo/far", true}, - {"git://github.com/foo/far.git", true}, - {"git@github.com:foo/far", true}, - {"git@github.com:foo/far.git", true}, - {"http://github.com/foo/far.git", true}, - {"https://github.com/foo/far.git", true}, - {"ssh://baz.com/foo/far.git", true}, - {"/var/lib/src", false}, - {"/home/ubuntu/src", false}, - {"src", false}, - } - - for _, r := range repos { - repo := Repo{Path: r.path} - if remote := repo.IsRemote(); remote != r.remote { - t.Errorf("IsRemote %s was %v, expected %v", r.path, remote, r.remote) - } - } -} - -func TestIsGit(t *testing.T) { - repos := []struct { - path string - remote bool - }{ - {"git://github.com/foo/far", true}, - {"git://github.com/foo/far.git", true}, - {"git@github.com:foo/far", true}, - {"git@github.com:foo/far.git", true}, - {"http://github.com/foo/far.git", true}, - {"https://github.com/foo/far.git", true}, - {"ssh://baz.com/foo/far.git", true}, - {"svn://gcc.gnu.org/svn/gcc/branches/gccgo", false}, - {"https://code.google.com/p/go", false}, - } - - for _, r := range repos { - repo := Repo{Path: r.path} - if remote := repo.IsGit(); remote != r.remote { - t.Errorf("IsGit %s was %v, expected %v", r.path, remote, r.remote) - } - } -} diff --git a/shared/build/script/docker.go b/shared/build/script/docker.go deleted file mode 100644 index 185c293e0..000000000 --- a/shared/build/script/docker.go +++ /dev/null @@ -1,39 +0,0 @@ -package script - -const ( - DefaultDockerNetworkMode = "bridge" -) - -// Docker stores the configuration details for -// configuring docker container. -type Docker struct { - // NetworkMode (also known as `--net` option) - // Could be set only if Docker is running in privileged mode - NetworkMode *string `yaml:"net,omitempty"` - - // Hostname (also known as `--hostname` option) - // Could be set only if Docker is running in privileged mode - Hostname *string `yaml:"hostname,omitempty"` -} - -// DockerNetworkMode returns DefaultNetworkMode -// when Docker.NetworkMode is empty. -// DockerNetworkMode returns Docker.NetworkMode -// when it is not empty. -func DockerNetworkMode(d *Docker) string { - if d == nil || d.NetworkMode == nil { - return DefaultDockerNetworkMode - } - return *d.NetworkMode -} - -// DockerNetworkMode returns empty string -// when Docker.NetworkMode is empty. -// DockerNetworkMode returns Docker.NetworkMode -// when it is not empty. -func DockerHostname(d *Docker) string { - if d == nil || d.Hostname == nil { - return "" - } - return *d.Hostname -} diff --git a/shared/build/script/docker_test.go b/shared/build/script/docker_test.go deleted file mode 100644 index a145d3a3f..000000000 --- a/shared/build/script/docker_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package script - -import ( - "testing" -) - -func TestDockerNetworkMode(t *testing.T) { - var d *Docker - var expected string - - expected = DefaultDockerNetworkMode - d = nil - if actual := DockerNetworkMode(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = DefaultDockerNetworkMode - d = &Docker{} - if actual := DockerNetworkMode(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = DefaultDockerNetworkMode - d = &Docker{NetworkMode: nil} - if actual := DockerNetworkMode(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = "bridge" - d = &Docker{NetworkMode: &expected} - if actual := DockerNetworkMode(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = "host" - d = &Docker{NetworkMode: &expected} - if actual := DockerNetworkMode(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } -} - -func TestDockerHostname(t *testing.T) { - var d *Docker - var expected string - - expected = "" - d = nil - if actual := DockerHostname(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = "" - d = &Docker{} - if actual := DockerHostname(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = "" - d = &Docker{Hostname: nil} - if actual := DockerHostname(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } - - expected = "host" - d = &Docker{Hostname: &expected} - if actual := DockerHostname(d); actual != expected { - t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual) - } -} diff --git a/shared/build/script/inject.go b/shared/build/script/inject.go deleted file mode 100644 index 6d8d75281..000000000 --- a/shared/build/script/inject.go +++ /dev/null @@ -1,23 +0,0 @@ -package script - -import ( - "sort" - "strings" -) - -func Inject(script string, params map[string]string) string { - if params == nil { - return script - } - keys := []string{} - for k, _ := range params { - keys = append(keys, k) - } - sort.Sort(sort.Reverse(sort.StringSlice(keys))) - injected := script - for _, k := range keys { - v := params[k] - injected = strings.Replace(injected, "$$"+k, v, -1) - } - return injected -} diff --git a/shared/build/script/inject_test.go b/shared/build/script/inject_test.go deleted file mode 100644 index 3984bd741..000000000 --- a/shared/build/script/inject_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package script - -import ( - "github.com/franela/goblin" - "testing" -) - -func Test_Inject(t *testing.T) { - - g := goblin.Goblin(t) - g.Describe("Inject params", func() { - - g.It("Should replace vars with $$", func() { - s := "echo $$FOO $BAR" - m := map[string]string{} - m["FOO"] = "BAZ" - g.Assert("echo BAZ $BAR").Equal(Inject(s, m)) - }) - - g.It("Should not replace vars with single $", func() { - s := "echo $FOO $BAR" - m := map[string]string{} - m["FOO"] = "BAZ" - g.Assert(s).Equal(Inject(s, m)) - }) - - g.It("Should not replace vars in nil map", func() { - s := "echo $$FOO $BAR" - g.Assert(s).Equal(Inject(s, nil)) - }) - }) -} diff --git a/shared/build/script/script.go b/shared/build/script/script.go deleted file mode 100644 index 1ee261610..000000000 --- a/shared/build/script/script.go +++ /dev/null @@ -1,159 +0,0 @@ -package script - -import ( - "io/ioutil" - "strings" - - "gopkg.in/yaml.v1" - - "github.com/drone/drone/plugin/deploy" - "github.com/drone/drone/plugin/notify" - "github.com/drone/drone/plugin/publish" - "github.com/drone/drone/shared/build/buildfile" - "github.com/drone/drone/shared/build/git" - "github.com/drone/drone/shared/build/repo" -) - -func ParseBuild(data string) (*Build, error) { - build := Build{} - - // parse the build configuration file - err := yaml.Unmarshal([]byte(data), &build) - return &build, err -} - -func ParseBuildFile(filename string, params map[string]string) (*Build, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - return ParseBuild(Inject(string(data), params)) -} - -// Build stores the configuration details for -// building, testing and deploying code. -type Build struct { - // Image specifies the Docker Image that will be - // used to virtualize the Build process. - Image string - - // Name specifies a user-defined label used - // to identify the build. - Name string - - // Script specifies the build and test commands. - Script []string - - // Env specifies the environment of the build. - Env []string - - // Hosts specifies the custom IP address and - // hostname mappings. - Hosts []string - - // Cache lists a set of directories that should - // persisted between builds. - Cache []string - - // Services specifies external services, such as - // database or messaging queues, that should be - // linked to the build environment. - Services []string - - // White-list of Branches that are built. - Branches []string - - Deploy *deploy.Deploy `yaml:"deploy,omitempty"` - Publish *publish.Publish `yaml:"publish,omitempty"` - Notifications *notify.Notification `yaml:"notify,omitempty"` - - // Git specified git-specific parameters, such as - // the clone depth and path - Git *git.Git `yaml:"git,omitempty"` - - // Docker container parameters, such as - // NetworkMode and UserName - Docker *Docker `yaml:"docker,omitempty"` -} - -// Write adds all the steps to the build script, including -// build commands, deploy and publish commands. -func (b *Build) Write(f *buildfile.Buildfile, r *repo.Repo) { - // append build commands - b.WriteBuild(f) - - // write publish commands - if b.Publish != nil { - b.Publish.Write(f, r) - } - - // write deployment commands - if b.Deploy != nil { - b.Deploy.Write(f, r) - } - - // write exit value - f.WriteCmd("exit 0") -} - -// WriteBuild adds only the build steps to the build script, -// omitting publish and deploy steps. This is important for -// pull requests, where deployment would be undesirable. -func (b *Build) WriteBuild(f *buildfile.Buildfile) { - // append environment variables - for _, env := range b.Env { - parts := strings.SplitN(env, "=", 2) - if len(parts) != 2 { - continue - } - f.WriteEnv(parts[0], parts[1]) - } - - // append build commands - for _, cmd := range b.Script { - f.WriteCmd(cmd) - } -} - -func (b *Build) MatchBranch(branch string) bool { - if len(b.Branches) == 0 { - return true - } - for _, item := range b.Branches { - if item == branch { - return true - } - } - return false -} - -type Publish interface { - Write(f *buildfile.Buildfile) -} - -type Deployment interface { - Write(f *buildfile.Buildfile) -} - -type Notification interface { - Set(c Context) -} - -type Context interface { - Host() string - Owner() string - Name() string - - Branch() string - Hash() string - Status() string - Message() string - Author() string - Gravatar() string - - Duration() int64 - HumanDuration() string - - //Settings -} diff --git a/shared/build/util.go b/shared/build/util.go deleted file mode 100644 index 3829e99ea..000000000 --- a/shared/build/util.go +++ /dev/null @@ -1,83 +0,0 @@ -package build - -import ( - "crypto/rand" - "crypto/sha1" - "fmt" - "io" - "strings" -) - -// createUID is a helper function that will -// create a random, unique identifier. -func createUID() string { - c := sha1.New() - r := createRandom() - io.WriteString(c, string(r)) - s := fmt.Sprintf("%x", c.Sum(nil)) - return "drone-" + s[0:10] -} - -// createRandom creates a random block of bytes -// that we can use to generate unique identifiers. -func createRandom() []byte { - k := make([]byte, sha1.BlockSize) - if _, err := io.ReadFull(rand.Reader, k); err != nil { - return nil - } - return k -} - -// list of service aliases and their full, canonical names -var defaultServices = map[string]string{ - "cassandra": "relateiq/cassandra:latest", - "couchdb": "bradrydzewski/couchdb:1.5", - "elasticsearch": "bradrydzewski/elasticsearch:0.90", - "memcached": "bradrydzewski/memcached", - "mongodb": "bradrydzewski/mongodb:2.4", - "mysql": "bradrydzewski/mysql:5.5", - "neo4j": "bradrydzewski/neo4j:1.9", - "postgres": "bradrydzewski/postgres:9.1", - "redis": "bradrydzewski/redis:2.8", - "rabbitmq": "bradrydzewski/rabbitmq:3.2", - "riak": "guillermo/riak:latest", - "zookeeper": "jplock/zookeeper:3.4.5", -} - -// parseImageName parses a Docker image name, in the format owner/name:tag, -// and returns each segment. -// -// If the owner is blank, it is assumed to be an official drone image, -// and will be prefixed with the appropriate owner name. -// -// If the tag is empty, it is assumed to be the latest version. -func parseImageName(image string) (owner, name, tag string) { - owner = "bradrydzewski" // this will eventually change to drone - name = image - tag = "latest" - - // first we check to see if the image name is an alias - // for a known service. - // - // TODO I'm not a huge fan of this code here. Maybe it - // should get handled when the yaml is parsed, and - // convert the image and service names in the yaml - // to fully qualified names? - if cname, ok := defaultServices[image]; ok { - name = cname - } - - parts := strings.Split(name, "/") - if len(parts) == 2 { - owner = parts[0] - name = parts[1] - } - - parts = strings.Split(name, ":") - if len(parts) == 2 { - name = parts[0] - tag = parts[1] - } - - return -} diff --git a/shared/build/util_test.go b/shared/build/util_test.go deleted file mode 100644 index ae19b154c..000000000 --- a/shared/build/util_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package build - -import "testing" - -func TestParseImageName(t *testing.T) { - images := []struct { - owner string - name string - tag string - cname string - }{ - // full image name with all 3 sections present - {"johnsmith", "redis", "2.8", "johnsmith/redis:2.8"}, - // image name with no tag specified - {"johnsmith", "redis", "latest", "johnsmith/redis"}, - // image name with no owner specified - {"bradrydzewski", "redis", "2.8", "redis:2.8"}, - // image name with ownly name specified - {"bradrydzewski", "redis2", "latest", "redis2"}, - // image name that is a known alias - {"relateiq", "cassandra", "latest", "cassandra"}, - } - - for _, img := range images { - owner, name, tag := parseImageName(img.cname) - if owner != img.owner { - t.Errorf("Expected image %s with owner %s, got %s", img.cname, img.owner, owner) - } - if name != img.name { - t.Errorf("Expected image %s with name %s, got %s", img.cname, img.name, name) - } - if tag != img.tag { - t.Errorf("Expected image %s with tag %s, got %s", img.cname, img.tag, tag) - } - } -} diff --git a/shared/build/writer.go b/shared/build/writer.go deleted file mode 100644 index fce230af4..000000000 --- a/shared/build/writer.go +++ /dev/null @@ -1,72 +0,0 @@ -package build - -import ( - //"bytes" - "fmt" - "io" - - "strings" -) - -var ( - // the prefix used to determine if this is - // data that should be stripped from the output - prefix = []byte("#DRONE:") - - // default limit to use when streaming build output. - DefaultLimit = 2000000 -) - -// custom writer to intercept the build -// output -type writer struct { - io.Writer - - length int -} - -// Write appends the contents of p to the buffer. It will -// scan for DRONE special formatting codes embedded in the -// output, and will alter the output accordingly. -func (w *writer) Write(p []byte) (n int, err error) { - - // ensure we haven't exceeded the limit - if w.length > DefaultLimit { - w.Writer.Write([]byte("Truncating build output ...")) - return len(p), nil - } - - // track the number of bytes written to the - // buffer so that we can limit it. - w.length += len(p) - - lines := strings.Split(string(p), "\n") - for i, line := range lines { - - if strings.HasPrefix(line, "#DRONE:") { - var cmd string - - // extract the command (base16 encoded) - // from the output - fmt.Sscanf(line[7:], "%x", &cmd) - - // echo the decoded command - cmd = fmt.Sprintf("$ %s", cmd) - w.Writer.Write([]byte(cmd)) - - } else { - w.Writer.Write([]byte(line)) - } - - if i < len(lines)-1 { - w.Writer.Write([]byte("\n")) - } - } - - return len(p), nil -} - -// WriteString appends the contents of s to the buffer. -func (w *writer) WriteString(s string) (n int, err error) { - return w.Write([]byte(s)) -} diff --git a/shared/build/writer_test.go b/shared/build/writer_test.go deleted file mode 100644 index 476566280..000000000 --- a/shared/build/writer_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package build - -import ( - "bytes" - "testing" -) - -func TestSetupDockerfile(t *testing.T) { - var buf bytes.Buffer - - // wrap the buffer so we can analyze output - w := writer{&buf, 0} - - w.WriteString("#DRONE:676f206275696c64\n") - w.WriteString("#DRONE:676f2074657374202d76\n") - w.WriteString("PASS\n") - w.WriteString("ok github.com/garyburd/redigo/redis 0.113s\n") - - expected := `$ go build -$ go test -v -PASS -ok github.com/garyburd/redigo/redis 0.113s -` - if expected != buf.String() { - t.Errorf("Expected commands decoded and echoed correctly. got \n%s", buf.String()) - } -} diff --git a/shared/model/commit.go b/shared/model/commit.go deleted file mode 100644 index da8c0c656..000000000 --- a/shared/model/commit.go +++ /dev/null @@ -1,69 +0,0 @@ -package model - -import ( - "time" -) - -type Commit struct { - ID int64 `meddler:"commit_id,pk" json:"id"` - RepoID int64 `meddler:"repo_id" json:"-"` - Status string `meddler:"commit_status" json:"status"` - Started int64 `meddler:"commit_started" json:"started_at"` - Finished int64 `meddler:"commit_finished" json:"finished_at"` - Duration int64 `meddler:"commit_duration" json:"duration"` - Sha string `meddler:"commit_sha" json:"sha"` - Branch string `meddler:"commit_branch" json:"branch"` - PullRequest string `meddler:"commit_pr" json:"pull_request"` - Author string `meddler:"commit_author" json:"author"` - Gravatar string `meddler:"commit_gravatar" json:"gravatar"` - Timestamp string `meddler:"commit_timestamp" json:"timestamp"` - Message string `meddler:"commit_message" json:"message"` - Config string `meddler:"commit_yaml" json:"-"` - Created int64 `meddler:"commit_created" json:"created_at"` - Updated int64 `meddler:"commit_updated" json:"updated_at"` -} - -// SetAuthor sets the author's email address and calculate the Gravatar hash. -func (c *Commit) SetAuthor(email string) { - c.Author = email - c.Gravatar = CreateGravatar(email) -} - -// Returns the Short (--short) Commit Hash. -func (c *Commit) ShaShort() string { - if len(c.Sha) > 8 { - return c.Sha[:8] - } else { - return c.Sha - } -} - -// Returns the Started Date as an ISO8601 -// formatted string. -func (c *Commit) FinishedString() string { - return time.Unix(c.Finished, 0).Format("2006-01-02T15:04:05Z") -} - -type CommitRepo struct { - Remote string `meddler:"repo_remote" json:"remote"` - Host string `meddler:"repo_host" json:"host"` - Owner string `meddler:"repo_owner" json:"owner"` - Name string `meddler:"repo_name" json:"name"` - - CommitID int64 `meddler:"commit_id,pk" json:"-"` - RepoID int64 `meddler:"repo_id" json:"-"` - Status string `meddler:"commit_status" json:"status"` - Started int64 `meddler:"commit_started" json:"started_at"` - Finished int64 `meddler:"commit_finished" json:"finished_at"` - Duration int64 `meddler:"commit_duration" json:"duration"` - Sha string `meddler:"commit_sha" json:"sha"` - Branch string `meddler:"commit_branch" json:"branch"` - PullRequest string `meddler:"commit_pr" json:"pull_request"` - Author string `meddler:"commit_author" json:"author"` - Gravatar string `meddler:"commit_gravatar" json:"gravatar"` - Timestamp string `meddler:"commit_timestamp" json:"timestamp"` - Message string `meddler:"commit_message" json:"message"` - Config string `meddler:"commit_yaml" json:"-"` - Created int64 `meddler:"commit_created" json:"created_at"` - Updated int64 `meddler:"commit_updated" json:"updated_at"` -} diff --git a/shared/model/hook.go b/shared/model/hook.go deleted file mode 100644 index ef5a585e5..000000000 --- a/shared/model/hook.go +++ /dev/null @@ -1,15 +0,0 @@ -package model - -// Hook represents a subset of commit meta-data provided -// by post-commit and pull request hooks. -type Hook struct { - Owner string - Repo string - Sha string - Branch string - PullRequest string - Author string - Gravatar string - Timestamp string - Message string -} diff --git a/shared/model/login.go b/shared/model/login.go deleted file mode 100644 index c0546b2df..000000000 --- a/shared/model/login.go +++ /dev/null @@ -1,13 +0,0 @@ -package model - -// Login represents a standard subset of user meta-data -// provided by OAuth login services. -type Login struct { - ID int64 - Login string - Access string - Secret string - Name string - Email string - Expiry int64 -} diff --git a/shared/model/perm.go b/shared/model/perm.go deleted file mode 100644 index 465594b22..000000000 --- a/shared/model/perm.go +++ /dev/null @@ -1,13 +0,0 @@ -package model - -type Perm struct { - ID int64 `meddler:"perm_id,pk" json:"-"` - UserID int64 `meddler:"user_id" json:"-"` - RepoID int64 `meddler:"repo_id" json:"-"` - Read bool `meddler:"perm_read" json:"read"` - Write bool `meddler:"perm_write" json:"write"` - Admin bool `meddler:"perm_admin" json:"admin"` - Guest bool `meddler:"-" json:"guest"` - Created int64 `meddler:"perm_created" json:"-"` - Updated int64 `meddler:"perm_updated" json:"-"` -} diff --git a/shared/model/remote.go b/shared/model/remote.go deleted file mode 100644 index ddb2feb15..000000000 --- a/shared/model/remote.go +++ /dev/null @@ -1,21 +0,0 @@ -package model - -const ( - RemoteGithub = "github.com" - RemoteGitlab = "gitlab.com" - RemoteGithubEnterprise = "enterprise.github.com" - RemoteBitbucket = "bitbucket.org" - RemoteStash = "stash.atlassian.com" - RemoteGogs = "gogs" -) - -type Remote struct { - ID int64 `meddler:"remote_id,pk" json:"id"` - Type string `meddler:"remote_type" json:"type"` - Host string `meddler:"remote_host" json:"host"` - URL string `meddler:"remote_url" json:"url"` - API string `meddler:"remote_api" json:"api"` - Client string `meddler:"remote_client" json:"client"` - Secret string `meddler:"remote_secret" json:"secret"` - Open bool `meddler:"remote_open" json:"open"` -} diff --git a/shared/model/repo.go b/shared/model/repo.go deleted file mode 100644 index 4c7a208ce..000000000 --- a/shared/model/repo.go +++ /dev/null @@ -1,66 +0,0 @@ -package model - -import ( - "gopkg.in/yaml.v1" -) - -var ( - DefaultBranch = "master" - - // default build timeout, in seconds - DefaultTimeout int64 = 7200 -) - -// RepoParams represents a set of private key value parameters -// for each Repository. -type RepoParams map[string]string - -type Repo struct { - ID int64 `meddler:"repo_id,pk" json:"-"` - UserID int64 `meddler:"user_id" json:"-"` - Token string `meddler:"repo_token" json:"-"` - Remote string `meddler:"repo_remote" json:"remote"` - Host string `meddler:"repo_host" json:"host"` - Owner string `meddler:"repo_owner" json:"owner"` - Name string `meddler:"repo_name" json:"name"` - - URL string `meddler:"repo_url" json:"url"` - CloneURL string `meddler:"repo_clone_url" json:"clone_url"` - GitURL string `meddler:"repo_git_url" json:"git_url"` - SSHURL string `meddler:"repo_ssh_url" json:"ssh_url"` - - Active bool `meddler:"repo_active" json:"active"` - Private bool `meddler:"repo_private" json:"private"` - Privileged bool `meddler:"repo_privileged" json:"privileged"` - PostCommit bool `meddler:"repo_post_commit" json:"post_commits"` - PullRequest bool `meddler:"repo_pull_request" json:"pull_requests"` - PublicKey string `meddler:"repo_public_key" json:"-"` - PrivateKey string `meddler:"repo_private_key" json:"-"` - Params string `meddler:"repo_params" json:"-"` - Timeout int64 `meddler:"repo_timeout" json:"timeout"` - Created int64 `meddler:"repo_created" json:"created_at"` - Updated int64 `meddler:"repo_updated" json:"updated_at"` - - // Role defines the user's role relative to this repository. - // Note that this data is stored separately in the datastore, - // and must be joined to populate. - Role *Perm `meddler:"-" json:"role,omitempty"` -} - -func NewRepo(remote, owner, name string) (*Repo, error) { - repo := Repo{} - repo.Remote = remote - repo.Owner = owner - repo.Name = name - repo.Active = false - repo.PostCommit = true - repo.PullRequest = true - repo.Timeout = DefaultTimeout - return &repo, nil -} - -func (r *Repo) ParamMap() (map[string]string, error) { - out := map[string]string{} - err := yaml.Unmarshal([]byte(r.Params), out) - return out, err -} diff --git a/shared/model/request.go b/shared/model/request.go deleted file mode 100644 index 5cfd0abfd..000000000 --- a/shared/model/request.go +++ /dev/null @@ -1,26 +0,0 @@ -package model - -import ( - "fmt" -) - -type Request struct { - Host string `json:"-"` - User *User `json:"-"` - Repo *Repo `json:"repo"` - Commit *Commit `json:"commit"` - Prior *Commit `json:"prior_commit"` -} - -// URL returns the link to the commit in -// string format. -func (r *Request) URL() string { - return fmt.Sprintf("%s/%s/%s/%s/%s/%s", - r.Host, - r.Repo.Host, - r.Repo.Owner, - r.Repo.Name, - r.Commit.Branch, - r.Commit.Sha, - ) -} diff --git a/shared/model/status.go b/shared/model/status.go deleted file mode 100644 index 6e274d118..000000000 --- a/shared/model/status.go +++ /dev/null @@ -1,11 +0,0 @@ -package model - -const ( - StatusNone = "None" - StatusEnqueue = "Pending" - StatusStarted = "Started" - StatusSuccess = "Success" - StatusFailure = "Failure" - StatusError = "Error" - StatusKilled = "Killed" -) diff --git a/shared/model/token.go b/shared/model/token.go deleted file mode 100644 index 986429c6e..000000000 --- a/shared/model/token.go +++ /dev/null @@ -1,7 +0,0 @@ -package model - -type Token struct { - AccessToken string - RefreshToken string - Expiry int64 -} diff --git a/shared/model/user.go b/shared/model/user.go deleted file mode 100644 index 7f7f9a653..000000000 --- a/shared/model/user.go +++ /dev/null @@ -1,56 +0,0 @@ -package model - -import ( - "time" -) - -type User struct { - ID int64 `meddler:"user_id,pk" json:"-"` - Remote string `meddler:"user_remote" json:"remote"` - Login string `meddler:"user_login" json:"login"` - Access string `meddler:"user_access" json:"-"` - Secret string `meddler:"user_secret" json:"-"` - Name string `meddler:"user_name" json:"name"` - Email string `meddler:"user_email" json:"email,omitempty"` - Gravatar string `meddler:"user_gravatar" json:"gravatar"` - Token string `meddler:"user_token" json:"-"` - Admin bool `meddler:"user_admin" json:"admin"` - Active bool `meddler:"user_active" json:"active"` - Syncing bool `meddler:"user_syncing" json:"syncing"` - Created int64 `meddler:"user_created" json:"created_at"` - Updated int64 `meddler:"user_updated" json:"updated_at"` - Synced int64 `meddler:"user_synced" json:"synced_at"` - TokenExpiry int64 `meddler:"user_access_expires,zeroisnull" json:"-"` -} - -func NewUser(remote, login, email string) *User { - user := User{} - user.Token = GenerateToken() - user.Login = login - user.Remote = remote - user.Active = true - user.SetEmail(email) - return &user -} - -// SetEmail sets the email address and calculate the Gravatar hash. -func (u *User) SetEmail(email string) { - u.Email = email - u.Gravatar = CreateGravatar(email) -} - -func (u *User) IsStale() bool { - switch { - case u.Synced == 0: - return true - // refresh every 24 hours - case u.Synced+DefaultExpires < time.Now().Unix(): - return true - default: - return false - } -} - -// by default, let's expire the user -// cache after 72 hours -var DefaultExpires = int64(time.Hour.Seconds() * 72) diff --git a/shared/model/util.go b/shared/model/util.go deleted file mode 100644 index 23ca43a03..000000000 --- a/shared/model/util.go +++ /dev/null @@ -1,48 +0,0 @@ -package model - -import ( - "crypto/md5" - "crypto/rand" - "fmt" - "io" - "strings" -) - -// standard characters allowed in token string. -var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - -// default token length -var length = 40 - -// 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) - } - } - } -} - -// helper function to create a Gravatar Hash -// for the given Email address. -func CreateGravatar(email string) string { - email = strings.ToLower(strings.TrimSpace(email)) - hash := md5.New() - hash.Write([]byte(email)) - return fmt.Sprintf("%x", hash.Sum(nil)) -} diff --git a/shared/model/util_test.go b/shared/model/util_test.go deleted file mode 100644 index 07ad4afe9..000000000 --- a/shared/model/util_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package model - -import ( - "testing" -) - -func Test_CreateGravatar(t *testing.T) { - var got, want = CreateGravatar("dr_cooper@caltech.edu"), "2b77ba83e2216ddcd11fe8c24b70c2a3" - if got != want { - t.Errorf("Got gravatar hash %s, want %s", got, want) - } -} - -func Test_GenerateToken(t *testing.T) { - token := GenerateToken() - if len(token) != length { - t.Errorf("Want token length %d, got %d", length, len(token)) - } -}