diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 191381ee..00000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.git \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index f64aea78..69162700 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 diff --git a/Dockerfile b/Dockerfile index 1f1bc842..6b2ded91 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"] diff --git a/Makefile b/Makefile index 6976baee..7769a623 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/README.md b/README.md index fef19f00..3cd213cb 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/VERSION b/VERSION new file mode 100644 index 00000000..0852d432 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0-alpha \ No newline at end of file diff --git a/cli/keys.go b/cli/keys.go new file mode 100644 index 00000000..00290869 --- /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 d051834c..9b440794 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 ) @@ -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 cd42b2d3..2cd15a10 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 } @@ -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 diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml index 29dfbe42..084db1d1 100644 --- a/packaging/root/etc/drone/drone.toml +++ b/packaging/root/etc/drone/drone.toml @@ -22,40 +22,35 @@ 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="" # secret="" # api="" # 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/notify/webhook/webhook.go b/plugin/notify/webhook/webhook.go index b35b69f5..b9aff0ab 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) diff --git a/plugin/publish/bintray.go b/plugin/publish/bintray.go deleted file mode 100644 index 30b1a5b2..00000000 --- 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 00000000..9a0383e2 --- /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 00000000..31f35e1d --- /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/docker.go b/plugin/publish/docker.go index 40873d83..eda38e29 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 ffee8985..a23797b5 100644 --- a/plugin/publish/docker_test.go +++ b/plugin/publish/docker_test.go @@ -6,7 +6,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 { @@ -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") } } diff --git a/plugin/publish/github_test.go b/plugin/publish/github_test.go index 33e995b4..7fcdfaaa 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{}{ diff --git a/plugin/publish/npm/npm.go b/plugin/publish/npm/npm.go index 069396db..f300ec7c 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} 2> /dev/null)" ] +then + npm publish %s + [ -n ${_NPM_PACKAGE_TAG} ] && npm tag ${_NPM_PACKAGE_NAME} ${_NPM_PACKAGE_TAG} +else + echo "skipping publish, package ${_NPM_PACKAGE_NAME} already published" +fi +unset _NPM_PACKAGE_NAME +unset _NPM_PACKAGE_TAG +` + const ( - 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 575f249b..0ea8c4fe 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() { diff --git a/plugin/publish/publish.go b/plugin/publish/publish.go index 4ce6d4b1..8286097d 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 { diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go index 410d1bb4..c049384a 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 @@ -155,16 +157,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 +263,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, @@ -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 ae513a87..108c68d9 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 31ff84e5..3919cae6 100644 --- a/plugin/remote/github/github.go +++ b/plugin/remote/github/github.go @@ -27,9 +27,11 @@ type GitHub struct { Secret string Private bool SkipVerify bool + Orgs []string + Open bool } -func New(url, api, client, secret string, private, skipVerify bool) *GitHub { +func New(url, api, client, secret string, private, skipVerify bool, orgs []string, open bool) *GitHub { var github = GitHub{ URL: url, API: api, @@ -37,6 +39,8 @@ func New(url, api, client, secret string, private, skipVerify bool) *GitHub { Secret: secret, Private: private, SkipVerify: skipVerify, + Orgs: orgs, + Open: open, } // the API must have a trailing slash if !strings.HasSuffix(github.API, "/") { @@ -49,8 +53,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, open bool) *GitHub { + return New(DefaultURL, DefaultAPI, client, secret, false, false, orgs, open) } // Authorize handles GitHub API Authorization. @@ -92,6 +96,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 @@ -293,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/github_test.go b/plugin/remote/github/github_test.go index 071951b9..c46aede9 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,40 @@ func Test_Github(t *testing.T) { g.It("Should parse a commit hook") g.It("Should parse a pull request hook") + + g.Describe("Authorize", func() { + g.AfterEach(func() { + github.Orgs = []string{} + }) + + var resp = httptest.NewRecorder() + var state = "validstate" + var req, _ = http.NewRequest( + "GET", + fmt.Sprintf("%s/?code=sekret&state=%s", server.URL, state), + nil, + ) + req.AddCookie(&http.Cookie{Name: "github_state", Value: state}) + + g.It("Should authorize a valid user with no org restrictions", func() { + var login, err = github.Authorize(resp, req) + g.Assert(err == nil).IsTrue() + g.Assert(login == nil).IsFalse() + }) + + g.It("Should authorize a valid user in the correct org", func() { + github.Orgs = []string{"octocats-inc"} + var login, err = github.Authorize(resp, req) + g.Assert(err == nil).IsTrue() + g.Assert(login == nil).IsFalse() + }) + + g.It("Should not authorize a valid user in the wrong org", func() { + github.Orgs = []string{"acme"} + var login, err = github.Authorize(resp, req) + g.Assert(err != nil).IsTrue() + g.Assert(login == nil).IsTrue() + }) + }) }) } diff --git a/plugin/remote/github/helper.go b/plugin/remote/github/helper.go index b55895a8..16dd1f02 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 @@ -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 8bb06081..3155e61e 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") @@ -30,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 eb71403f..21d714e0 100644 --- a/plugin/remote/github/register.go +++ b/plugin/remote/github/register.go @@ -9,6 +9,8 @@ var ( // GitHub cloud configuration details githubClient = config.String("github-client", "") githubSecret = config.String("github-secret", "") + githubOrgs = config.Strings("github-orgs") + githubOpen = config.Bool("github-open", false) // GitHub Enterprise configuration details githubEnterpriseURL = config.String("github-enterprise-url", "") @@ -17,6 +19,8 @@ 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") + githubEnterpriseOpen = config.Bool("github-enterprise-open", false) ) // Registers the GitHub plugins using the default @@ -33,7 +37,7 @@ func registerGitHub() { return } remote.Register( - NewDefault(*githubClient, *githubSecret), + NewDefault(*githubClient, *githubSecret, *githubOrgs, *githubOpen), ) } @@ -53,6 +57,8 @@ func registerGitHubEnterprise() { *githubEnterpriseSecret, *githubEnterprisePrivate, *githubEnterpriseSkipVerify, + *githubEnterpriseOrgs, + *githubEnterpriseOpen, ), ) } diff --git a/plugin/remote/github/testdata/testdata.go b/plugin/remote/github/testdata/testdata.go index 6845aa0b..d84262c5 100644 --- a/plugin/remote/github/testdata/testdata.go +++ b/plugin/remote/github/testdata/testdata.go @@ -15,13 +15,22 @@ 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 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": @@ -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(` [ @@ -108,7 +175,7 @@ var emptyObjPayload = []byte(`{}`) // sample org list response var userOrgsPayload = []byte(` [ - { "login": "github", "id": 1 } + { "login": "octocats-inc", "id": 1 } ] `) diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 560678ec..73f5bf9e 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 38c33136..81d0d4e5 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 c4e7e4c4..ebd45a8b 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 9ad32a20..c1ee50f7 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 592d729f..aa2479e6 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 ad6c3f16..cfba0108 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/app/styles/drone.css b/server/app/styles/drone.css index 4b4aad86..b7467bea 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}} diff --git a/server/app/views/home.html b/server/app/views/home.html index 83fb6dd0..9ae565cc 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 @@
- -
+

{{group.branch}}

diff --git a/server/capability/capability.go b/server/capability/capability.go deleted file mode 100644 index 704e75a0..00000000 --- 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 b0ffe2ae..00000000 --- a/server/capability/capability_test.go +++ /dev/null @@ -1,24 +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{} - caps[Registration] = true - - 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, Registration)).Equal(true) - 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 7d4da039..00000000 --- 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 1f225be1..00000000 --- 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/datastore/database/user.go b/server/datastore/database/user.go index 25de0802..f6b25040 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 285702d0..2ae13603 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") @@ -70,6 +69,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/server/handler/repo.go b/server/handler/repo.go index 4f753c84..fc989e97 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 diff --git a/server/main.go b/server/main.go index 9fd47cd2..b5192380 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" @@ -41,7 +40,7 @@ const ( var ( // commit sha for the current build, set by // the compile process. - version string = "0.3-dev" + version string revision string ) @@ -56,10 +55,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 @@ -70,8 +65,6 @@ var ( nodes StringArr db *sql.DB - - caps map[string]bool ) func main() { @@ -104,9 +97,6 @@ func main() { gitlab.Register() gogs.Register() - caps = map[string]bool{} - caps[capability.Registration] = *open - // setup the database and cancel all pending // commits in the system. db = database.MustConnect(*driver, *datasource) @@ -170,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) diff --git a/shared/model/request.go b/shared/model/request.go index 594cbeb2..5cfd0abf 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/%s/%s", + r.Host, + r.Repo.Host, + r.Repo.Owner, + r.Repo.Name, + r.Commit.Branch, + r.Commit.Sha, + ) +}