From dceb83564bfe3b7dddd3e6b2141755ec03db2954 Mon Sep 17 00:00:00 2001 From: Nils Werner Date: Mon, 17 Nov 2014 22:30:44 +0100 Subject: [PATCH 01/24] Slightly more useful homepage With more active repos and a more sensible view of inactive repos --- server/app/views/home.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/app/views/home.html b/server/app/views/home.html index 68c452656..8a482c51f 100644 --- a/server/app/views/home.html +++ b/server/app/views/home.html @@ -23,10 +23,12 @@
- +

{{ commit.owner }} / {{ commit.name }}

- {{ commit.finished_at | fromNow }} + {{ commit.finished_at | fromNow }} + Started {{ commit.started_at | fromNow }} + Created {{ commit.created_at}}
@@ -35,7 +37,7 @@
- +

{{ repo.owner }} / {{ repo.name }}

Activate From b4ca338948e902e9123eab3c92c9fa30a1f4ee08 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Sun, 4 Jan 2015 15:21:35 -0800 Subject: [PATCH 02/24] ability to override the public private key for a repository via the CLI --- cli/keys.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ cli/main.go | 1 + client/repos.go | 10 ++++++++++ 3 files changed, 59 insertions(+) create mode 100644 cli/keys.go diff --git a/cli/keys.go b/cli/keys.go new file mode 100644 index 000000000..002908693 --- /dev/null +++ b/cli/keys.go @@ -0,0 +1,48 @@ +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 index d051834c8..bb5b0fdfd 100644 --- a/cli/main.go +++ b/cli/main.go @@ -39,6 +39,7 @@ func main() { NewDisableCommand(), NewRestartCommand(), NewWhoamiCommand(), + NewSetKeyCommand(), } app.Run(os.Args) diff --git a/client/repos.go b/client/repos.go index cd42b2d32..fc22badb9 100644 --- a/client/repos.go +++ b/client/repos.go @@ -38,6 +38,16 @@ func (s *RepoService) Disable(host, owner, name string) error { 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 From c90303aaf71a332131e8a6c53b2bd15376d26ef5 Mon Sep 17 00:00:00 2001 From: Grzegorz Graczyk Date: Wed, 7 Jan 2015 15:25:28 +0100 Subject: [PATCH 03/24] add --pull to docker build in docker publish plugin --- plugin/publish/docker.go | 2 +- plugin/publish/docker_test.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugin/publish/docker.go b/plugin/publish/docker.go index 387d44395..89756af39 100644 --- a/plugin/publish/docker.go +++ b/plugin/publish/docker.go @@ -89,7 +89,7 @@ func (d *Docker) Write(f *buildfile.Buildfile) { f.WriteCmd("export DOCKER_HOST=" + d.DockerHost) // Build the image - f.WriteCmd(fmt.Sprintf("docker build -t %s:%s %s", d.ImageName, buildImageTag, dockerPath)) + f.WriteCmd(fmt.Sprintf("docker build --pull -t %s:%s %s", d.ImageName, buildImageTag, dockerPath)) // Login? if d.RegistryLogin == true { diff --git a/plugin/publish/docker_test.go b/plugin/publish/docker_test.go index ffee89854..519463e5c 100644 --- a/plugin/publish/docker_test.go +++ b/plugin/publish/docker_test.go @@ -91,7 +91,7 @@ func TestPrivateRegistryNoAuth(t *testing.T) { if err != nil { t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) } - if !strings.Contains(response, "docker build -t registry/image:$(git rev-parse --short HEAD)") { + 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") } } @@ -121,8 +121,8 @@ func TestPrivateRegistryAuth(t *testing.T) { 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 -t registry/image:$(git rev-parse --short HEAD) .") { - t.Log("docker build -t registry/image:$(git rev-parse --short HEAD) .") + 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") } } @@ -173,7 +173,7 @@ func TestSingleTag(t *testing.T) { 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 -t username/image:release-0.1") { + 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") { @@ -211,7 +211,7 @@ func TestTagsNoSingle(t *testing.T) { 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 -t username/image:release-0.2") { + 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") { @@ -253,7 +253,7 @@ func TestTagsWithSingle(t *testing.T) { 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 -t username/image:release-0.3") { + 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") { @@ -313,7 +313,7 @@ func TestValidYaml(t *testing.T) { t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) } - if !strings.Contains(response, "docker build -t user/image:$(git rev-parse --short HEAD) - <") { + 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") { @@ -346,7 +346,7 @@ func TestWithoutDockerFile(t *testing.T) { t.Fatalf("Can't unmarshal script: %s\n\n", err.Error()) } - if !strings.Contains(response, "docker build -t user/image:$(git rev-parse --short HEAD) .") { + 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") } } From ce5d4edca55d9885a83170b35a1241a6dfa98203 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 12 Jan 2015 16:50:59 +0300 Subject: [PATCH 04/24] Bintray plugin --- plugin/publish/bintray.go | 1 - plugin/publish/bintray/bintray.go | 50 +++++++++++++ plugin/publish/bintray/package.go | 116 ++++++++++++++++++++++++++++++ plugin/publish/publish.go | 21 ++++-- 4 files changed, 180 insertions(+), 8 deletions(-) delete mode 100644 plugin/publish/bintray.go create mode 100644 plugin/publish/bintray/bintray.go create mode 100644 plugin/publish/bintray/package.go diff --git a/plugin/publish/bintray.go b/plugin/publish/bintray.go deleted file mode 100644 index 30b1a5b2a..000000000 --- a/plugin/publish/bintray.go +++ /dev/null @@ -1 +0,0 @@ -package publish diff --git a/plugin/publish/bintray/bintray.go b/plugin/publish/bintray/bintray.go new file mode 100644 index 000000000..9a0383e2a --- /dev/null +++ b/plugin/publish/bintray/bintray.go @@ -0,0 +1,50 @@ +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 new file mode 100644 index 000000000..31f35e1d6 --- /dev/null +++ b/plugin/publish/bintray/package.go @@ -0,0 +1,116 @@ +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/publish.go b/plugin/publish/publish.go index 4ce6d4b1f..8286097dc 100644 --- a/plugin/publish/publish.go +++ b/plugin/publish/publish.go @@ -2,6 +2,7 @@ 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" @@ -11,13 +12,14 @@ import ( // for publishing build artifacts when // a Build has succeeded type Publish struct { - 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"` + 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) { @@ -55,6 +57,11 @@ func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { 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 { From ef3fba75a908e5f7866e2f09c58fabbdc1062ca5 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 12 Jan 2015 16:51:54 +0300 Subject: [PATCH 05/24] Version from file --- Makefile | 12 ++++++++---- VERSION | 1 + cli/main.go | 2 +- server/main.go | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 VERSION diff --git a/Makefile b/Makefile index e6efdd2a0..2e9e12e01 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ SHA := $(shell git rev-parse --short HEAD) +VERSION := $(shell cat VERSION) +ITTERATION := $(shell date +%s) all: build @@ -23,8 +25,8 @@ test_postgres: 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)" github.com/drone/drone/cli - go build -o packaging/root/usr/local/bin/droned -ldflags "-X main.revision $(SHA)" github.com/drone/drone/server + 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 @@ -52,9 +54,10 @@ embed: # creates a debian package for drone to install # `sudo dpkg -i drone.deb` deb: - fpm -s dir -t deb -n drone -v 0.3 -p packaging/output/drone.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 \ @@ -68,9 +71,10 @@ deb: packaging/root/=/ rpm: - fpm -s dir -t rpm -n drone -v 0.3 -p packaging/output/drone.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 \ diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..0852d432c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0-alpha \ No newline at end of file diff --git a/cli/main.go b/cli/main.go index d051834c8..0a2e3b908 100644 --- a/cli/main.go +++ b/cli/main.go @@ -7,7 +7,7 @@ import ( var ( // commit sha for the current build. - version string = "0.3-dev" + version string revision string ) diff --git a/server/main.go b/server/main.go index 9fd47cd2b..ae41dc7b3 100644 --- a/server/main.go +++ b/server/main.go @@ -41,7 +41,7 @@ const ( var ( // commit sha for the current build, set by // the compile process. - version string = "0.3-dev" + version string revision string ) From d0b722cc8b029e3224af8b8402f19155656f5071 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 13 Jan 2015 21:57:02 -0800 Subject: [PATCH 06/24] Insert and Update Users instead of generic Save function. Check ID != 0 --- server/datastore/database/user.go | 11 +++-------- server/handler/login.go | 7 +++++++ shared/model/request.go | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/server/datastore/database/user.go b/server/datastore/database/user.go index 25de08026..f6b25040d 100644 --- a/server/datastore/database/user.go +++ b/server/datastore/database/user.go @@ -49,20 +49,15 @@ func (db *Userstore) GetUserList() ([]*model.User, error) { // PostUser saves a User in the datastore. func (db *Userstore) PostUser(user *model.User) error { - if user.Created == 0 { - user.Created = time.Now().UTC().Unix() - } + user.Created = time.Now().UTC().Unix() user.Updated = time.Now().UTC().Unix() - return meddler.Save(db, userTable, user) + return meddler.Insert(db, userTable, user) } // PutUser saves a user in the datastore. func (db *Userstore) PutUser(user *model.User) error { - if user.Created == 0 { - user.Created = time.Now().UTC().Unix() - } user.Updated = time.Now().UTC().Unix() - return meddler.Save(db, userTable, user) + return meddler.Update(db, userTable, user) } // DelUser removes the user from the datastore. diff --git a/server/handler/login.go b/server/handler/login.go index 285702d09..2f08debed 100644 --- a/server/handler/login.go +++ b/server/handler/login.go @@ -70,6 +70,13 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) { 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 { diff --git a/shared/model/request.go b/shared/model/request.go index 594cbeb2f..94075a88c 100644 --- a/shared/model/request.go +++ b/shared/model/request.go @@ -1,5 +1,9 @@ package model +import ( + "fmt" +) + type Request struct { Host string `json:"-"` User *User `json:"-"` @@ -7,3 +11,16 @@ type Request struct { 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", + r.Host, + r.Repo.Host, + r.Repo.Owner, + r.Repo.Name, + r.Commit.Branch, + r.Commit.Sha, + ) +} From ff127f034918eeb5710945cc4d5e6b19c205e3c8 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 13 Jan 2015 21:57:30 -0800 Subject: [PATCH 07/24] webhook payload should include the host URL --- plugin/notify/webhook/webhook.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/notify/webhook/webhook.go b/plugin/notify/webhook/webhook.go index b35b69f5d..b9aff0ab5 100644 --- a/plugin/notify/webhook/webhook.go +++ b/plugin/notify/webhook/webhook.go @@ -29,10 +29,11 @@ func (w *Webhook) Send(context *model.Request) error { 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.User, context.Repo, context.Commit} + }{context.Host, context.User, context.Repo, context.Commit} // data json encoded payload, err := json.Marshal(data) From 97d80a43b7d690196d6f0dd2759b6f068f3f9211 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 13 Jan 2015 23:18:31 -0800 Subject: [PATCH 08/24] fixed dep issue --- plugin/publish/docker_test.go | 1 + plugin/publish/github_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/publish/docker_test.go b/plugin/publish/docker_test.go index ffee89854..e226b52b9 100644 --- a/plugin/publish/docker_test.go +++ b/plugin/publish/docker_test.go @@ -7,6 +7,7 @@ import ( "github.com/drone/drone/shared/build/buildfile" "github.com/drone/drone/shared/build/repo" "gopkg.in/v1/yaml" + "gopkg.in/yaml.v1" ) type PublishToDrone struct { diff --git a/plugin/publish/github_test.go b/plugin/publish/github_test.go index 33e995b44..7fcdfaaa9 100644 --- a/plugin/publish/github_test.go +++ b/plugin/publish/github_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "gopkg.in/v1/yaml" + "gopkg.in/yaml.v1" ) var validcfg = map[string]interface{}{ From 27640db7a3dea5fd1b887ddb84107112aa184d9e Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 13 Jan 2015 23:24:23 -0800 Subject: [PATCH 09/24] fixed dep in docker_test.go --- plugin/publish/docker_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/publish/docker_test.go b/plugin/publish/docker_test.go index e226b52b9..f97ac8c66 100644 --- a/plugin/publish/docker_test.go +++ b/plugin/publish/docker_test.go @@ -6,7 +6,6 @@ import ( "github.com/drone/drone/shared/build/buildfile" "github.com/drone/drone/shared/build/repo" - "gopkg.in/v1/yaml" "gopkg.in/yaml.v1" ) From f209d126db0afa307accf922ce44d9a8b21887a1 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 13 Jan 2015 23:32:25 -0800 Subject: [PATCH 10/24] fixed govet issue --- shared/model/request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/model/request.go b/shared/model/request.go index 94075a88c..5cfd0abfd 100644 --- a/shared/model/request.go +++ b/shared/model/request.go @@ -15,7 +15,7 @@ type Request struct { // URL returns the link to the commit in // string format. func (r *Request) URL() string { - return fmt.Sprintf("%s/%s/%s/%s", + return fmt.Sprintf("%s/%s/%s/%s/%s/%s", r.Host, r.Repo.Host, r.Repo.Owner, From 768b29954382485f06737eacfb6741a3080ad7ed Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Thu, 15 Jan 2015 00:22:33 -0800 Subject: [PATCH 11/24] fixed incorrect method for GetRepo in Drone client --- client/repos.go | 2 +- server/app/styles/drone.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/repos.go b/client/repos.go index fc22badb9..2cd15a10c 100644 --- a/client/repos.go +++ b/client/repos.go @@ -14,7 +14,7 @@ type RepoService struct { 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("PUT", path, nil, &repo) + var err = s.run("GET", path, nil, &repo) return &repo, err } diff --git a/server/app/styles/drone.css b/server/app/styles/drone.css index 4b4aad863..b7467bea5 100644 --- a/server/app/styles/drone.css +++ b/server/app/styles/drone.css @@ -1 +1 @@ -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: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:transparent}.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:transparent;border-top-color:transparent;-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 transparent}.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 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}#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 0}#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 0}#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 0}#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 0}#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;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 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}} +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}#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}} From 86b0329d571292576d154f2a3c8132ff933527f2 Mon Sep 17 00:00:00 2001 From: Dan Carley Date: Wed, 14 Jan 2015 22:12:24 +0000 Subject: [PATCH 12/24] Change org name in GitHub testdata The example JSON taken from GitHub's API documentation doesn't indicate that `login` is the name of an organisation rather than a user. Change it to something that looks more like an org, because it will make a test that I'm about to add more readable. The endpoint name changes accordingly. --- plugin/remote/github/testdata/testdata.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/remote/github/testdata/testdata.go b/plugin/remote/github/testdata/testdata.go index 6845aa0b5..4fab791f1 100644 --- a/plugin/remote/github/testdata/testdata.go +++ b/plugin/remote/github/testdata/testdata.go @@ -21,7 +21,7 @@ func NewServer() *httptest.Server { case "/user/orgs": w.Write(userOrgsPayload) return - case "/orgs/github/repos": + case "/orgs/octocats-inc/repos": w.Write(userReposPayload) return case "/repos/octocat/Hello-World/contents/.drone.yml": @@ -108,7 +108,7 @@ var emptyObjPayload = []byte(`{}`) // sample org list response var userOrgsPayload = []byte(` [ - { "login": "github", "id": 1 } + { "login": "octocats-inc", "id": 1 } ] `) From a608f5ef82e4f9b286e58d306b32638f2b602b47 Mon Sep 17 00:00:00 2001 From: Dan Carley Date: Thu, 15 Jan 2015 11:49:29 +0000 Subject: [PATCH 13/24] Add test for GitHub remote GetOrgs() helper I'm about to re-use this in another helper, so test that it works as expected. Also fix the inline documentation which was copied from `GetOrgRepos`. --- plugin/remote/github/helper.go | 2 +- plugin/remote/github/helper_test.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/plugin/remote/github/helper.go b/plugin/remote/github/helper.go index b55895a89..5bafccd8f 100644 --- a/plugin/remote/github/helper.go +++ b/plugin/remote/github/helper.go @@ -138,7 +138,7 @@ func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error) } // GetOrgs is a helper function that returns a list of -// all org repositories. +// all orgs that a user belongs to. func GetOrgs(client *github.Client) ([]github.Organization, error) { orgs, _, err := client.Organizations.List("", nil) return orgs, err diff --git a/plugin/remote/github/helper_test.go b/plugin/remote/github/helper_test.go index 8bb060813..d5e6add33 100644 --- a/plugin/remote/github/helper_test.go +++ b/plugin/remote/github/helper_test.go @@ -12,13 +12,22 @@ func Test_Helper(t *testing.T) { 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") + + 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") From 789adf90e4127a51827d728b8ee2aaa6bda4a75c Mon Sep 17 00:00:00 2001 From: Dan Carley Date: Thu, 15 Jan 2015 11:53:22 +0000 Subject: [PATCH 14/24] Add test for GitHub remote Authorize() To test that it correctly authorises a valid user, prior to me extending it. This only tests the happy path because simulating oAuth failures is going to be quite hard at this stage. --- plugin/remote/github/github_test.go | 18 ++++++ plugin/remote/github/testdata/testdata.go | 67 +++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/plugin/remote/github/github_test.go b/plugin/remote/github/github_test.go index 071951b97..209b0bb53 100644 --- a/plugin/remote/github/github_test.go +++ b/plugin/remote/github/github_test.go @@ -1,6 +1,9 @@ package github import ( + "fmt" + "net/http" + "net/http/httptest" "testing" "github.com/drone/drone/plugin/remote/github/testdata" @@ -90,5 +93,20 @@ func Test_Github(t *testing.T) { g.It("Should parse a commit hook") g.It("Should parse a pull request hook") + + g.It("Should authorize a valid user", 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: "github_state", Value: state}) + + var login, err = github.Authorize(resp, req) + g.Assert(err == nil).IsTrue() + g.Assert(login == nil).IsFalse() + }) }) } diff --git a/plugin/remote/github/testdata/testdata.go b/plugin/remote/github/testdata/testdata.go index 4fab791f1..d84262c5b 100644 --- a/plugin/remote/github/testdata/testdata.go +++ b/plugin/remote/github/testdata/testdata.go @@ -15,6 +15,15 @@ func NewServer() *httptest.Server { // 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 @@ -56,6 +65,64 @@ func NewServer() *httptest.Server { 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(` [ From 8fa473b07a70a9f41074bb6fc29c7bb0874f0487 Mon Sep 17 00:00:00 2001 From: Dan Carley Date: Thu, 15 Jan 2015 14:39:44 +0000 Subject: [PATCH 15/24] Support org whitelists for GitHub+GHE remotes Allow the GitHub and GitHub Enterprise remotes to restrict who can login based on a user's organisation membership. This can be used as a safe addition to open registration and also ensures that access is revoked when a user is subsequently removed from the org. The default is not to restrict at all. --- README.md | 2 ++ packaging/root/etc/drone/drone.toml | 2 ++ plugin/remote/github/github.go | 18 +++++++++++++++--- plugin/remote/github/github_test.go | 28 ++++++++++++++++++++++++---- plugin/remote/github/helper.go | 22 ++++++++++++++++++++++ plugin/remote/github/helper_test.go | 15 +++++++++++++++ plugin/remote/github/register.go | 5 ++++- 7 files changed, 84 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fef19f00a..3cd213cb8 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,14 @@ open=true [github] client="" secret="" +orgs=[] [github_enterprise] client="" secret="" api="" url="" +orgs=[] private_mode=false [bitbucket] diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml index 29dfbe42e..3f74c6bcf 100644 --- a/packaging/root/etc/drone/drone.toml +++ b/packaging/root/etc/drone/drone.toml @@ -37,12 +37,14 @@ datasource="/var/lib/drone/drone.sqlite" # [github] # client="" # secret="" +# orgs=[] # [github_enterprise] # client="" # secret="" # api="" # url="" +# orgs=[] # private_mode=false # [bitbucket] diff --git a/plugin/remote/github/github.go b/plugin/remote/github/github.go index 31ff84e53..840d493f1 100644 --- a/plugin/remote/github/github.go +++ b/plugin/remote/github/github.go @@ -27,9 +27,10 @@ type GitHub struct { Secret string Private bool SkipVerify bool + Orgs []string } -func New(url, api, client, secret string, private, skipVerify bool) *GitHub { +func New(url, api, client, secret string, private, skipVerify bool, orgs []string) *GitHub { var github = GitHub{ URL: url, API: api, @@ -37,6 +38,7 @@ func New(url, api, client, secret string, private, skipVerify bool) *GitHub { Secret: secret, Private: private, SkipVerify: skipVerify, + Orgs: orgs, } // the API must have a trailing slash if !strings.HasSuffix(github.API, "/") { @@ -49,8 +51,8 @@ func New(url, api, client, secret string, private, skipVerify bool) *GitHub { return &github } -func NewDefault(client, secret string) *GitHub { - return New(DefaultURL, DefaultAPI, client, secret, false, false) +func NewDefault(client, secret string, orgs []string) *GitHub { + return New(DefaultURL, DefaultAPI, client, secret, false, false, orgs) } // Authorize handles GitHub API Authorization. @@ -92,6 +94,16 @@ func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.L 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") + } + } + var login = new(model.Login) login.ID = int64(*useremail.ID) login.Access = token.AccessToken diff --git a/plugin/remote/github/github_test.go b/plugin/remote/github/github_test.go index 209b0bb53..c46aede92 100644 --- a/plugin/remote/github/github_test.go +++ b/plugin/remote/github/github_test.go @@ -94,7 +94,11 @@ func Test_Github(t *testing.T) { g.It("Should parse a pull request hook") - g.It("Should authorize a valid user", func() { + g.Describe("Authorize", func() { + g.AfterEach(func() { + github.Orgs = []string{} + }) + var resp = httptest.NewRecorder() var state = "validstate" var req, _ = http.NewRequest( @@ -104,9 +108,25 @@ func Test_Github(t *testing.T) { ) req.AddCookie(&http.Cookie{Name: "github_state", Value: state}) - var login, err = github.Authorize(resp, req) - g.Assert(err == nil).IsTrue() - g.Assert(login == nil).IsFalse() + 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 index 5bafccd8f..16dd1f02d 100644 --- a/plugin/remote/github/helper.go +++ b/plugin/remote/github/helper.go @@ -270,3 +270,25 @@ func GetPayload(req *http.Request) []byte { } 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 index d5e6add33..3155e61e6 100644 --- a/plugin/remote/github/helper_test.go +++ b/plugin/remote/github/helper_test.go @@ -39,5 +39,20 @@ func Test_Helper(t *testing.T) { 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 index eb71403f2..52c72838c 100644 --- a/plugin/remote/github/register.go +++ b/plugin/remote/github/register.go @@ -9,6 +9,7 @@ var ( // GitHub cloud configuration details githubClient = config.String("github-client", "") githubSecret = config.String("github-secret", "") + githubOrgs = config.Strings("github-orgs") // GitHub Enterprise configuration details githubEnterpriseURL = config.String("github-enterprise-url", "") @@ -17,6 +18,7 @@ var ( 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") ) // Registers the GitHub plugins using the default @@ -33,7 +35,7 @@ func registerGitHub() { return } remote.Register( - NewDefault(*githubClient, *githubSecret), + NewDefault(*githubClient, *githubSecret, *githubOrgs), ) } @@ -53,6 +55,7 @@ func registerGitHubEnterprise() { *githubEnterpriseSecret, *githubEnterprisePrivate, *githubEnterpriseSkipVerify, + *githubEnterpriseOrgs, ), ) } From 5dd08030e3669a3c67a95bfa9877a5ec01e777fa Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Thu, 15 Jan 2015 21:35:38 -0800 Subject: [PATCH 16/24] fixed #822 --- .dockerignore | 1 - Dockerfile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 191381ee7..000000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.git \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1f1bc8421..6b2ded911 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ 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 deps build test embed install +RUN make deps build embed install EXPOSE 80 ENTRYPOINT ["/usr/local/bin/droned"] From 570a23f95204d685ccadc33ebd169ea2d7a560f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98rjan=20Bruland?= Date: Fri, 16 Jan 2015 17:36:56 +0100 Subject: [PATCH 17/24] Use Slug instead of Name for Bitbucket repos. --- plugin/remote/bitbucket/bitbucket.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go index 410d1bb41..22f3c114a 100644 --- a/plugin/remote/bitbucket/bitbucket.go +++ b/plugin/remote/bitbucket/bitbucket.go @@ -155,16 +155,16 @@ func (r *Bitbucket) GetRepos(user *model.User) ([]*model.Repo, error) { // 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.Name) - var clone = fmt.Sprintf("https://bitbucket.org/%s/%s.git", item.Owner, item.Name) - var ssh = fmt.Sprintf("git@bitbucket.org:%s/%s.git", item.Owner, item.Name) + 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.Name, + Name: item.Slug, Private: item.Private, URL: html, CloneURL: clone, @@ -261,7 +261,7 @@ func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) { return &model.Hook{ Owner: hook.Repo.Owner, - Repo: hook.Repo.Name, + Repo: hook.Repo.Slug, Sha: hook.Commits[len(hook.Commits)-1].Hash, Branch: hook.Commits[len(hook.Commits)-1].Branch, Author: author, From 307aed12bc185e7cf1783f050800982b104e59ef Mon Sep 17 00:00:00 2001 From: Matt Bostock Date: Mon, 12 Jan 2015 22:59:06 +0000 Subject: [PATCH 18/24] Move open registration setting into remote plugins ...so that it's possible to enable or disable open registration on a per-remote basis. For example, the `DRONE_REGISTRATION_OPEN` environment variable now becomes `DRONE_GITHUB_OPEN` when using GitHub as a remote. The default for open registration in this commit is `false` (disabled), which matches the existing behaviour. This is useful if you need to support both public and private remotes, e.g. GitHub.com and GitHub Enterprise, where you trust all of the private users and want to allow open registration for those but would not want all GitHub.com users to run builds on your server. Tested with GitHub and GitLab. --- packaging/root/etc/drone/drone.toml | 17 +++++------------ plugin/remote/bitbucket/bitbucket.go | 12 +++++++++--- plugin/remote/bitbucket/register.go | 3 ++- plugin/remote/github/github.go | 12 +++++++++--- plugin/remote/github/register.go | 5 ++++- plugin/remote/gitlab/gitlab.go | 8 +++++++- plugin/remote/gitlab/gitlab_test.go | 2 +- plugin/remote/gitlab/register.go | 2 ++ plugin/remote/gogs/gogs.go | 9 +++++++-- plugin/remote/gogs/register.go | 3 ++- plugin/remote/remote.go | 3 +++ server/capability/capability_test.go | 2 -- server/handler/login.go | 3 +-- server/main.go | 5 ----- 14 files changed, 52 insertions(+), 34 deletions(-) diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml index 3f74c6bcf..084db1d15 100644 --- a/packaging/root/etc/drone/drone.toml +++ b/packaging/root/etc/drone/drone.toml @@ -22,22 +22,11 @@ port=":80" driver="sqlite3" datasource="/var/lib/drone/drone.sqlite" - -##################################################################### -# Open Registration allows users to self-register for Drone. -# This is recommended if Drone is being hosted behind a -# firewall. -# -# When false, the system admin will need to manually add -# users to Drone through the admin screens. -# -# [registration] -# open=true - # [github] # client="" # secret="" # orgs=[] +# open=false # [github_enterprise] # client="" @@ -46,18 +35,22 @@ datasource="/var/lib/drone/drone.sqlite" # url="" # orgs=[] # private_mode=false +# open=false # [bitbucket] # client="" # secret="" +# open=false # [gitlab] # url="" # skip_verify=false +# open=false # [gogs] # url="" # secret="" +# open=false ##################################################################### # SMTP configuration for Drone. This is required if you plan diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go index 410d1bb41..27e137421 100644 --- a/plugin/remote/bitbucket/bitbucket.go +++ b/plugin/remote/bitbucket/bitbucket.go @@ -27,19 +27,21 @@ type Bitbucket struct { API string Client string Secret string + Open bool } -func New(url, api, client, secret string) *Bitbucket { +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) *Bitbucket { - return New(DefaultURL, DefaultAPI, client, secret) +func NewDefault(client, secret string, open bool) *Bitbucket { + return New(DefaultURL, DefaultAPI, client, secret, open) } // Authorize handles Bitbucket API Authorization @@ -269,3 +271,7 @@ func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) { Message: hook.Commits[len(hook.Commits)-1].Message, }, nil } + +func (r *Bitbucket) OpenRegistration() bool { + return r.Open +} diff --git a/plugin/remote/bitbucket/register.go b/plugin/remote/bitbucket/register.go index ae513a87a..108c68d90 100644 --- a/plugin/remote/bitbucket/register.go +++ b/plugin/remote/bitbucket/register.go @@ -9,6 +9,7 @@ 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 @@ -19,6 +20,6 @@ func Register() { return } remote.Register( - NewDefault(*bitbucketClient, *bitbucketSecret), + NewDefault(*bitbucketClient, *bitbucketSecret, *bitbucketOpen), ) } diff --git a/plugin/remote/github/github.go b/plugin/remote/github/github.go index 840d493f1..3919cae66 100644 --- a/plugin/remote/github/github.go +++ b/plugin/remote/github/github.go @@ -28,9 +28,10 @@ type GitHub struct { Private bool SkipVerify bool Orgs []string + Open bool } -func New(url, api, client, secret string, private, skipVerify bool, orgs []string) *GitHub { +func New(url, api, client, secret string, private, skipVerify bool, orgs []string, open bool) *GitHub { var github = GitHub{ URL: url, API: api, @@ -39,6 +40,7 @@ func New(url, api, client, secret string, private, skipVerify bool, orgs []strin Private: private, SkipVerify: skipVerify, Orgs: orgs, + Open: open, } // the API must have a trailing slash if !strings.HasSuffix(github.API, "/") { @@ -51,8 +53,8 @@ func New(url, api, client, secret string, private, skipVerify bool, orgs []strin return &github } -func NewDefault(client, secret string, orgs []string) *GitHub { - return New(DefaultURL, DefaultAPI, client, secret, false, false, orgs) +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. @@ -305,3 +307,7 @@ func (r *GitHub) ParsePullRequestHook(req *http.Request) (*model.Hook, error) { return &hook, nil } + +func (r *GitHub) OpenRegistration() bool { + return r.Open +} diff --git a/plugin/remote/github/register.go b/plugin/remote/github/register.go index 52c72838c..21d714e05 100644 --- a/plugin/remote/github/register.go +++ b/plugin/remote/github/register.go @@ -10,6 +10,7 @@ var ( 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", "") @@ -19,6 +20,7 @@ var ( 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 @@ -35,7 +37,7 @@ func registerGitHub() { return } remote.Register( - NewDefault(*githubClient, *githubSecret, *githubOrgs), + NewDefault(*githubClient, *githubSecret, *githubOrgs, *githubOpen), ) } @@ -56,6 +58,7 @@ func registerGitHubEnterprise() { *githubEnterprisePrivate, *githubEnterpriseSkipVerify, *githubEnterpriseOrgs, + *githubEnterpriseOpen, ), ) } diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 560678ece..73f5bf9ec 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -13,12 +13,14 @@ import ( type Gitlab struct { url string SkipVerify bool + Open bool } -func New(url string, skipVerify bool) *Gitlab { +func New(url string, skipVerify, open bool) *Gitlab { return &Gitlab{ url: url, SkipVerify: skipVerify, + Open: open, } } @@ -191,3 +193,7 @@ func (r *Gitlab) ParseHook(req *http.Request) (*model.Hook, error) { return hook, nil } + +func (r *Gitlab) OpenRegistration() bool { + return r.Open +} diff --git a/plugin/remote/gitlab/gitlab_test.go b/plugin/remote/gitlab/gitlab_test.go index 38c331364..81d0d4e53 100644 --- a/plugin/remote/gitlab/gitlab_test.go +++ b/plugin/remote/gitlab/gitlab_test.go @@ -14,7 +14,7 @@ func Test_Github(t *testing.T) { var server = testdata.NewServer() defer server.Close() - var gitlab = New(server.URL, false) + var gitlab = New(server.URL, false, false) var user = model.User{ Access: "e3b0c44298fc1c149afbf4c8996fb", } diff --git a/plugin/remote/gitlab/register.go b/plugin/remote/gitlab/register.go index c4e7e4c48..ebd45a8b9 100644 --- a/plugin/remote/gitlab/register.go +++ b/plugin/remote/gitlab/register.go @@ -8,6 +8,7 @@ import ( var ( gitlabURL = config.String("gitlab-url", "") gitlabSkipVerify = config.Bool("gitlab-skip-verify", false) + gitlabOpen = config.Bool("gitlab-open", false) ) // Registers the Gitlab plugin using the default @@ -21,6 +22,7 @@ func Register() { New( *gitlabURL, *gitlabSkipVerify, + *gitlabOpen, ), ) } diff --git a/plugin/remote/gogs/gogs.go b/plugin/remote/gogs/gogs.go index 9ad32a209..c1ee50f7c 100644 --- a/plugin/remote/gogs/gogs.go +++ b/plugin/remote/gogs/gogs.go @@ -16,10 +16,11 @@ import ( type Gogs struct { URL string Secret string + Open bool } -func New(url string, secret string) *Gogs { - return &Gogs{URL: url, Secret: secret} +func New(url string, secret string, open bool) *Gogs { + return &Gogs{URL: url, Secret: secret, Open: open} } // Authorize handles Gogs authorization @@ -181,3 +182,7 @@ func (r *Gogs) ParseHook(req *http.Request) (*model.Hook, error) { Message: payload.Commits[0].Message, }, nil } + +func (r *Gogs) OpenRegistration() bool { + return r.Open +} diff --git a/plugin/remote/gogs/register.go b/plugin/remote/gogs/register.go index 592d729f3..aa2479e6f 100644 --- a/plugin/remote/gogs/register.go +++ b/plugin/remote/gogs/register.go @@ -8,6 +8,7 @@ import ( var ( gogsUrl = config.String("gogs-url", "") gogsSecret = config.String("gogs-secret", "") + gogsOpen = config.Bool("gogs-open", false) ) // Registers the Gogs plugin using the default @@ -18,6 +19,6 @@ func Register() { return } remote.Register( - New(*gogsUrl, *gogsSecret), + New(*gogsUrl, *gogsSecret, *gogsOpen), ) } diff --git a/plugin/remote/remote.go b/plugin/remote/remote.go index ad6c3f162..cfba0108e 100644 --- a/plugin/remote/remote.go +++ b/plugin/remote/remote.go @@ -32,6 +32,9 @@ type Remote interface { // 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 } // List of registered plugins. diff --git a/server/capability/capability_test.go b/server/capability/capability_test.go index b0ffe2ae3..25c772fe8 100644 --- a/server/capability/capability_test.go +++ b/server/capability/capability_test.go @@ -9,7 +9,6 @@ import ( func TestBlobstore(t *testing.T) { caps := map[string]bool{} - caps[Registration] = true ctx := NewContext(context.Background(), caps) @@ -17,7 +16,6 @@ func TestBlobstore(t *testing.T) { g.Describe("Capabilities", func() { g.It("Should get capabilities from context", func() { - g.Assert(Enabled(ctx, Registration)).Equal(true) g.Assert(Enabled(ctx, "Fake Key")).Equal(false) }) }) diff --git a/server/handler/login.go b/server/handler/login.go index 2f08debed..2ae13603a 100644 --- a/server/handler/login.go +++ b/server/handler/login.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/drone/drone/plugin/remote" - "github.com/drone/drone/server/capability" "github.com/drone/drone/server/datastore" "github.com/drone/drone/server/session" "github.com/drone/drone/server/sync" @@ -49,7 +48,7 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) { // 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 capability.Enabled(ctx, capability.Registration) == false { + if remote.OpenRegistration() == false { users, err := datastore.GetUserList(ctx) if err != nil || len(users) != 0 { log.Println("Unable to create account. Registration is closed") diff --git a/server/main.go b/server/main.go index ae41dc7b3..73fd0637f 100644 --- a/server/main.go +++ b/server/main.go @@ -56,10 +56,6 @@ var ( sslcrt = config.String("server-ssl-cert", "") sslkey = config.String("server-ssl-key", "") - // Enable self-registration. When false, the system admin - // must grant user access. - open = config.Bool("registration-open", false) - workers *pool.Pool worker *director.Director pub *pubsub.PubSub @@ -105,7 +101,6 @@ func main() { gogs.Register() caps = map[string]bool{} - caps[capability.Registration] = *open // setup the database and cancel all pending // commits in the system. From c48c6ebc88ccfaf9f5b807b6c4e3932e2c2a0e56 Mon Sep 17 00:00:00 2001 From: Matt Bostock Date: Fri, 16 Jan 2015 18:50:26 +0000 Subject: [PATCH 19/24] Remove capability package It's no longer used. This commit can always be reverted later if it's needed again. --- server/capability/capability.go | 23 -------------------- server/capability/capability_test.go | 22 ------------------- server/capability/const.go | 5 ----- server/capability/context.go | 32 ---------------------------- server/main.go | 6 ------ 5 files changed, 88 deletions(-) delete mode 100644 server/capability/capability.go delete mode 100644 server/capability/capability_test.go delete mode 100644 server/capability/const.go delete mode 100644 server/capability/context.go diff --git a/server/capability/capability.go b/server/capability/capability.go deleted file mode 100644 index 704e75a0a..000000000 --- a/server/capability/capability.go +++ /dev/null @@ -1,23 +0,0 @@ -package capability - -import ( - "code.google.com/p/go.net/context" -) - -type Capability map[string]bool - -// Get the capability value from the map. -func (c Capability) Get(key string) bool { - return c[key] -} - -// Sets the capability value in the map. -func (c Capability) Set(key string, value bool) { - c[key] = value -} - -// Enabled returns true if the capability is -// enabled in the system. -func Enabled(c context.Context, key string) bool { - return FromContext(c).Get(key) -} diff --git a/server/capability/capability_test.go b/server/capability/capability_test.go deleted file mode 100644 index 25c772fe8..000000000 --- a/server/capability/capability_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package capability - -import ( - "testing" - - "code.google.com/p/go.net/context" - "github.com/franela/goblin" -) - -func TestBlobstore(t *testing.T) { - caps := map[string]bool{} - - ctx := NewContext(context.Background(), caps) - - g := goblin.Goblin(t) - g.Describe("Capabilities", func() { - - g.It("Should get capabilities from context", func() { - g.Assert(Enabled(ctx, "Fake Key")).Equal(false) - }) - }) -} diff --git a/server/capability/const.go b/server/capability/const.go deleted file mode 100644 index 7d4da039c..000000000 --- a/server/capability/const.go +++ /dev/null @@ -1,5 +0,0 @@ -package capability - -const ( - Registration = "REGISTRATION" -) diff --git a/server/capability/context.go b/server/capability/context.go deleted file mode 100644 index 1f225be15..000000000 --- a/server/capability/context.go +++ /dev/null @@ -1,32 +0,0 @@ -package capability - -import ( - "code.google.com/p/go.net/context" -) - -const reqkey = "capability" - -// NewContext returns a Context whose Value method returns the -// application's Blobstore data. -func NewContext(parent context.Context, caps Capability) context.Context { - return &wrapper{parent, caps} -} - -type wrapper struct { - context.Context - caps Capability -} - -// Value returns the named key from the context. -func (c *wrapper) Value(key interface{}) interface{} { - if key == reqkey { - return c.caps - } - return c.Context.Value(key) -} - -// FromContext returns the capability map for the -// current context. -func FromContext(c context.Context) Capability { - return c.Value(reqkey).(Capability) -} diff --git a/server/main.go b/server/main.go index 73fd0637f..b5192380c 100644 --- a/server/main.go +++ b/server/main.go @@ -26,7 +26,6 @@ import ( "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/capability" "github.com/drone/drone/server/datastore" "github.com/drone/drone/server/datastore/database" "github.com/drone/drone/server/worker/director" @@ -66,8 +65,6 @@ var ( nodes StringArr db *sql.DB - - caps map[string]bool ) func main() { @@ -100,8 +97,6 @@ func main() { gitlab.Register() gogs.Register() - caps = map[string]bool{} - // setup the database and cancel all pending // commits in the system. db = database.MustConnect(*driver, *datasource) @@ -165,7 +160,6 @@ func ContextMiddleware(c *web.C, h http.Handler) http.Handler { ctx = pool.NewContext(ctx, workers) ctx = director.NewContext(ctx, worker) ctx = pubsub.NewContext(ctx, pub) - ctx = capability.NewContext(ctx, caps) // add the context to the goji web context webcontext.Set(c, ctx) From b8b01c2db47eaa69327717fa95e55ca1f3b1512e Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Fri, 16 Jan 2015 22:11:36 -0800 Subject: [PATCH 20/24] partial fix for issue #812 --- server/handler/repo.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/handler/repo.go b/server/handler/repo.go index 4f753c849..fc989e971 100644 --- a/server/handler/repo.go +++ b/server/handler/repo.go @@ -149,6 +149,10 @@ func PutRepo(c web.C, w http.ResponseWriter, r *http.Request) { 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 From d860133d284995f829eed44e4f84805df4b6d3c7 Mon Sep 17 00:00:00 2001 From: Ke Zhu Date: Mon, 19 Jan 2015 22:48:28 -0500 Subject: [PATCH 21/24] npm publish only when version not exist --- plugin/publish/npm/npm.go | 31 ++++++++++++++++++------------- plugin/publish/npm/npm_test.go | 11 +++++------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/plugin/publish/npm/npm.go b/plugin/publish/npm/npm.go index 069396dbd..c51982c10 100644 --- a/plugin/publish/npm/npm.go +++ b/plugin/publish/npm/npm.go @@ -18,8 +18,22 @@ 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})" ] +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 ( - CmdPublish = "npm publish %s" CmdAlwaysAuth = "npm set always-auth true" CmdSetRegistry = "npm config set registry %s" ) @@ -43,10 +57,6 @@ type NPM struct { // and publish to a repository Password string `yaml:"password,omitempty"` - // Fails if the package name and version combination already - // exists in the registry. Overwrites when the "--force" flag is set. - Force bool `yaml:"force"` - // The registry URL of custom npm repository Registry string `yaml:"registry,omitempty"` @@ -94,16 +104,11 @@ func (n *NPM) Write(f *buildfile.Buildfile) { f.WriteCmd(CmdAlwaysAuth) } - var cmd = fmt.Sprintf(CmdPublish, n.Folder) - if len(n.Tag) != 0 { - cmd += fmt.Sprintf(" --tag %s", n.Tag) + if len(n.Folder) == 0 { + n.Folder = "." } - if n.Force { - cmd += " --force" - } - - f.WriteCmd(cmd) + f.WriteString(fmt.Sprintf(CmdPublish, n.Folder, n.Tag, n.Folder)) } func (n *NPM) GetCondition() *condition.Condition { diff --git a/plugin/publish/npm/npm_test.go b/plugin/publish/npm/npm_test.go index 575f249b1..0ea8c4fec 100644 --- a/plugin/publish/npm/npm_test.go +++ b/plugin/publish/npm/npm_test.go @@ -31,23 +31,21 @@ func Test_NPM(t *testing.T) { n.Write(b) out := b.String() - g.Assert(strings.Contains(out, "\nnpm publish /path/to/repo\n")).Equal(true) + 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 set force", func() { + 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", - Folder: "/path/to/repo", - Force: true, } n.Write(b) - g.Assert(strings.Contains(b.String(), "\nnpm publish /path/to/repo --force\n")).Equal(true) + g.Assert(strings.Contains(b.String(), "npm publish .\n")).Equal(true) }) g.It("Should set tag", func() { @@ -61,7 +59,8 @@ func Test_NPM(t *testing.T) { } n.Write(b) - g.Assert(strings.Contains(b.String(), "\nnpm publish /path/to/repo --tag 1.0.0\n")).Equal(true) + 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() { From 5c9b5f65b9562b1b3cc00b694c05d8658bab1b13 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Wed, 21 Jan 2015 07:40:27 -0800 Subject: [PATCH 22/24] added apt-get update to yaml prior to installing packages --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index f64aea786..69162700a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,6 +5,7 @@ env: - GOROOT=/usr/local/go - PATH=$PATH:$GOROOT/bin:$GOPATH/bin script: + - sudo apt-get update 1> /dev/null 2> /dev/null - sudo apt-get -y install zip libsqlite3-dev sqlite3 rpm 1> /dev/null 2> /dev/null - gem install fpm - rbenv rehash From 77e059ae98915589f96398e53667b8b3a6e38b01 Mon Sep 17 00:00:00 2001 From: Andy Gardner Date: Thu, 22 Jan 2015 11:33:10 +0200 Subject: [PATCH 23/24] Exclude Pull Requests from the branches. Currently they are included because the AngularJS "filter" filter defaults to a substring match rather than a strict equality comparison, so filtering by '' includes everything. https://docs.angularjs.org/api/ng/filter/filter --- server/app/views/repo.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/app/views/repo.html b/server/app/views/repo.html index bb121dc62..f64b9dcdd 100644 --- a/server/app/views/repo.html +++ b/server/app/views/repo.html @@ -34,8 +34,7 @@

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

- -
+