squash and merge local branch

This commit is contained in:
Brad Rydzewski 2019-02-19 15:56:41 -08:00
parent 6d144de735
commit 5b6a3d8ff4
2348 changed files with 82051 additions and 618145 deletions

112
.drone.jsonnet Normal file
View file

@ -0,0 +1,112 @@
# defines the target version of Go. Please do not
# update this variable unless it has been previously
# approved on the mailing list.
local golang = "golang:1.11";
# defines a temporary volume so that the Go cache can
# be shared with all pipeine steps.
local volumes = [
{
name: "gopath",
temp: {},
},
];
# defines the default Go cache location as a volume
# that is mounted into pipeline steps.
local mounts = [
{
name: "gopath",
path: "/go",
},
];
# defines a pipeline step that builds and publishes
# a docker image to a docker remote registry.
local docker(name, os, arch) = {
name: "publish_" + name,
image: "plugins/docker",
settings: {
repo: "drone/" + name,
auto_tag: true,
auto_tag_suffix: os + "-" + arch,
username: { from_secret: "docker_username" },
password: { from_secret: "docker_password" },
dockerfile: "docker/Dockerfile." + name + "." + os + "." + arch,
},
when: {
event: [ "push", "tag" ],
},
};
# defines a pipeline step that creates and publishes
# a docker manifest to a docker remote registry.
local manifest(name) = {
name: name,
image: "plugins/manifest:1",
settings: {
ignore_missing: true,
spec: "docker/manifest." + name + ".tmpl",
username: { from_secret: "docker_username" },
password: { from_secret: "docker_password" },
},
when: {
event: [ "push", "tag" ],
},
};
# defines a pipeline that builds, tests and publishes
# docker images for the Drone agent, server and controller.
local pipeline(name, os, arch) = {
kind: "pipeline",
name: name,
volumes: volumes,
platform: {
os: os,
arch: arch,
},
steps: [
{
name: "test",
image: golang,
volumes: mounts,
commands: [ "go test -v ./..." ],
},
{
name: "build",
image: golang,
volumes: mounts,
commands: [
"go build -o release/"+ os +"/" + arch + "/drone-server",
"go build -o release/"+ os +"/" + arch + "/drone-agent",
"go build -o release/"+ os +"/" + arch + "/drone-controller",
],
when: {
event: [ "push", "tag" ],
},
},
docker("agent", os, arch),
docker("controller", os, arch),
docker("server", os, arch),
],
};
[
pipeline("linux-amd64", "linux", "amd64"),
pipeline("linux-arm", "linux", "arm"),
pipeline("linux-arm64", "linux", "arm64"),
{
kind: "pipeline",
name: "manifest",
steps: [
manifest("server"),
manifest("agent"),
manifest("controller"),
],
depends_on: [
"linux-amd64",
"linux-arm",
"linux-arm64",
],
},
]

View file

@ -1,28 +0,0 @@
#!/bin/sh
# only execute this script as part of the pipeline.
[ -z "$CI" ] && echo "missing ci environment variable" && exit 2
# only execute the script when github token exists.
[ -z "$SSH_KEY" ] && echo "missing ssh key" && exit 3
# write the ssh key.
mkdir /root/.ssh
echo -n "$SSH_KEY" > /root/.ssh/id_rsa
chmod 600 /root/.ssh/id_rsa
# add github.com to our known hosts.
touch /root/.ssh/known_hosts
chmod 600 /root/.ssh/known_hosts
ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 2> /dev/null
# clone the extras project.
set -e
set -x
git clone git@github.com:drone/drone-enterprise.git extras
# build a static binary with the build number and extra features.
go build -ldflags '-extldflags "-static" -X github.com/drone/drone/version.VersionDev=build.'${DRONE_BUILD_NUMBER} -o release/drone-server github.com/drone/drone/extras/cmd/drone-server
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-X github.com/drone/drone/version.VersionDev=build.'${DRONE_BUILD_NUMBER} -o release/drone-agent github.com/drone/drone/cmd/drone-agent
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '-X github.com/drone/drone/version.VersionDev=build.'${DRONE_BUILD_NUMBER} -o release/linux/arm64/drone-agent github.com/drone/drone/cmd/drone-agent
GOOS=linux GOARCH=arm CGO_ENABLED=0 GOARM=7 go build -ldflags '-X github.com/drone/drone/version.VersionDev=build.'${DRONE_BUILD_NUMBER} -o release/linux/arm/drone-agent github.com/drone/drone/cmd/drone-agent

View file

@ -1,143 +1,306 @@
workspace:
base: /go
path: src/github.com/drone/drone
---
kind: pipeline
name: linux-amd64
pipeline:
test:
image: golang:1.8
commands:
- go get -u github.com/drone/drone-ui/dist
- go get -u golang.org/x/tools/cmd/cover
- go get -u golang.org/x/net/context
- go get -u golang.org/x/net/context/ctxhttp
- go get -u github.com/golang/protobuf/proto
- go get -u github.com/golang/protobuf/protoc-gen-go
- go test -cover $(go list ./... | grep -v /vendor/)
platform:
os: linux
arch: amd64
test_postgres:
image: golang:1.8
environment:
- DATABASE_DRIVER=postgres
- DATABASE_CONFIG=host=postgres user=postgres dbname=postgres sslmode=disable
commands:
- go test github.com/drone/drone/store/datastore
steps:
- name: test
image: golang:1.11
commands:
- go test -v ./...
volumes:
- name: gopath
path: /go
test_mysql:
image: golang:1.8
environment:
- DATABASE_DRIVER=mysql
- DATABASE_CONFIG=root@tcp(mysql:3306)/test?parseTime=true
commands:
- go test github.com/drone/drone/store/datastore
- name: build
image: golang:1.11
commands:
- go build -o release/linux/amd64/drone-server
- go build -o release/linux/amd64/drone-agent
- go build -o release/linux/amd64/drone-controller
volumes:
- name: gopath
path: /go
when:
event:
- push
- tag
build:
image: golang:1.8
commands: sh .drone.sh
secrets: [ ssh_key ]
when:
event: [ push, tag ]
publish_server_alpine:
image: plugins/docker
repo: drone/drone
dockerfile: Dockerfile.alpine
secrets: [ docker_username, docker_password ]
tag: [ alpine ]
when:
branch: master
event: push
publish_server:
image: plugins/docker
repo: drone/drone
secrets: [ docker_username, docker_password ]
tag: [ latest ]
when:
branch: master
event: push
publish_agent_alpine:
image: plugins/docker
- name: publish_agent
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-amd64
dockerfile: docker/Dockerfile.agent.linux.amd64
password:
from_secret: docker_password
repo: drone/agent
dockerfile: Dockerfile.agent.alpine
secrets: [ docker_username, docker_password ]
tag: [ alpine ]
when:
branch: master
event: push
username:
from_secret: docker_username
when:
event:
- push
- tag
publish_agent_arm:
image: plugins/docker
- name: publish_controller
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-amd64
dockerfile: docker/Dockerfile.controller.linux.amd64
password:
from_secret: docker_password
repo: drone/controller
username:
from_secret: docker_username
when:
event:
- push
- tag
- name: publish_server
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-amd64
dockerfile: docker/Dockerfile.server.linux.amd64
password:
from_secret: docker_password
repo: drone/server
username:
from_secret: docker_username
when:
event:
- push
- tag
volumes:
- name: gopath
temp: {}
---
kind: pipeline
name: linux-arm
platform:
os: linux
arch: arm
steps:
- name: test
image: golang:1.11
commands:
- go test -v ./...
volumes:
- name: gopath
path: /go
- name: build
image: golang:1.11
commands:
- go build -o release/linux/arm/drone-server
- go build -o release/linux/arm/drone-agent
- go build -o release/linux/arm/drone-controller
volumes:
- name: gopath
path: /go
when:
event:
- push
- tag
- name: publish_agent
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm
dockerfile: docker/Dockerfile.agent.linux.arm
password:
from_secret: docker_password
repo: drone/agent
dockerfile: Dockerfile.agent.linux.arm
secrets: [ docker_username, docker_password ]
tag: [ linux-arm ]
when:
branch: master
event: push
username:
from_secret: docker_username
when:
event:
- push
- tag
publish_agent_arm64:
image: plugins/docker
- name: publish_controller
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm
dockerfile: docker/Dockerfile.controller.linux.arm
password:
from_secret: docker_password
repo: drone/controller
username:
from_secret: docker_username
when:
event:
- push
- tag
- name: publish_server
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm
dockerfile: docker/Dockerfile.server.linux.arm
password:
from_secret: docker_password
repo: drone/server
username:
from_secret: docker_username
when:
event:
- push
- tag
volumes:
- name: gopath
temp: {}
---
kind: pipeline
name: linux-arm64
platform:
os: linux
arch: arm64
steps:
- name: test
image: golang:1.11
commands:
- go test -v ./...
volumes:
- name: gopath
path: /go
- name: build
image: golang:1.11
commands:
- go build -o release/linux/arm64/drone-server
- go build -o release/linux/arm64/drone-agent
- go build -o release/linux/arm64/drone-controller
volumes:
- name: gopath
path: /go
when:
event:
- push
- tag
- name: publish_agent
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm64
dockerfile: docker/Dockerfile.agent.linux.arm64
password:
from_secret: docker_password
repo: drone/agent
dockerfile: Dockerfile.agent.linux.arm64
secrets: [ docker_username, docker_password ]
tag: [ linux-arm64 ]
when:
branch: master
event: push
username:
from_secret: docker_username
when:
event:
- push
- tag
publish_agent_amd64:
image: plugins/docker
repo: drone/agent
dockerfile: Dockerfile.agent
secrets: [ docker_username, docker_password ]
tag: [ latest ]
when:
branch: master
event: push
- name: publish_controller
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm64
dockerfile: docker/Dockerfile.controller.linux.arm64
password:
from_secret: docker_password
repo: drone/controller
username:
from_secret: docker_username
when:
event:
- push
- tag
release_server_alpine:
image: plugins/docker
repo: drone/drone
dockerfile: Dockerfile.alpine
secrets: [ docker_username, docker_password ]
tag: [ 0.8-alpine ]
when:
event: tag
- name: publish_server
image: plugins/docker
settings:
auto_tag: true
auto_tag_suffix: linux-arm64
dockerfile: docker/Dockerfile.server.linux.arm64
password:
from_secret: docker_password
repo: drone/server
username:
from_secret: docker_username
when:
event:
- push
- tag
release_agent_alpine:
image: plugins/docker
repo: drone/agent
dockerfile: Dockerfile.agent.alpine
secrets: [ docker_username, docker_password ]
tag: [ 0.8-alpine ]
when:
event: tag
volumes:
- name: gopath
temp: {}
release_server:
image: plugins/docker
repo: drone/drone
secrets: [ docker_username, docker_password ]
tag: [ 0.8, 0.8.9 ]
when:
event: tag
---
kind: pipeline
name: manifest
release_agent:
image: plugins/docker
repo: drone/agent
dockerfile: Dockerfile.agent
secrets: [ docker_username, docker_password ]
tag: [ 0.8, 0.8.9 ]
when:
event: tag
platform:
os: linux
arch: amd64
services:
postgres:
image: postgres:9.6
environment:
- POSTGRES_USER=postgres
mysql:
image: mysql:5.6.27
environment:
- MYSQL_DATABASE=test
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
steps:
- name: server
image: plugins/manifest:1
settings:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/manifest.server.tmpl
username:
from_secret: docker_username
when:
event:
- push
- tag
- name: agent
image: plugins/manifest:1
settings:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/manifest.agent.tmpl
username:
from_secret: docker_username
when:
event:
- push
- tag
- name: controller
image: plugins/manifest:1
settings:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/manifest.controller.tmpl
username:
from_secret: docker_username
when:
event:
- push
- tag
depends_on:
- linux-amd64
- linux-arm
- linux-arm64
...

View file

8
.gitignore vendored
View file

@ -1,10 +1,8 @@
drone/drone
.vscode
*.sqlite
*.txt
*.out
*.key
.env
extras/
.env.*
release/
server/swagger/files/*.json
.idea/

View file

@ -1,13 +1,13 @@
1. Install go 1.9 or later
2. Install dependencies
go get -u github.com/drone/drone-ui/dist
go get -u golang.org/x/net/context
go get -u golang.org/x/net/context/ctxhttp
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
1. Clone the repository
2. Install go 1.11 or later with Go modules enabled
3. Install binaries to $GOPATH/bin
go install github.com/drone/drone/cmd/drone-agent
go install github.com/drone/drone/cmd/drone-controller
go install github.com/drone/drone/cmd/drone-server
4. Start the server at localhost:8080
export DRONE_GITHUB_CLIENT_ID=...
export DRONE_GITHUB_CLIENT_SECRET=...
drone-server

View file

@ -1,13 +0,0 @@
# docker build --rm -t drone/drone .
FROM drone/ca-certs
EXPOSE 8000 9000 80 443
ENV DATABASE_DRIVER=sqlite3
ENV DATABASE_CONFIG=/var/lib/drone/drone.sqlite
ENV GODEBUG=netdns=go
ENV XDG_CACHE_HOME /var/lib/drone
ADD release/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

View file

@ -1,10 +0,0 @@
# docker build --rm -f Dockerfile.agent -t drone/agent .
FROM drone/ca-certs
ENV GODEBUG=netdns=go
ADD release/drone-agent /bin/
EXPOSE 3000
HEALTHCHECK CMD ["/bin/drone-agent", "ping"]
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -1,12 +0,0 @@
FROM alpine:3.7
RUN apk add -U --no-cache ca-certificates
ENV GODEBUG=netdns=go
ADD release/drone-agent /bin/
EXPOSE 3000
HEALTHCHECK CMD ["/bin/drone-agent", "ping"]
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -1,9 +0,0 @@
FROM drone/ca-certs
ENV GODEBUG=netdns=go
ENV DRONE_PLATFORM=linux/arm
ADD release/linux/arm/drone-agent /bin/
EXPOSE 3000
HEALTHCHECK CMD ["/bin/drone-agent", "ping"]
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -1,9 +0,0 @@
FROM drone/ca-certs
ENV GODEBUG=netdns=go
ENV DRONE_PLATFORM=linux/arm64
ADD release/linux/arm64/drone-agent /bin/
EXPOSE 3000
HEALTHCHECK CMD ["/bin/drone-agent", "ping"]
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -1,13 +0,0 @@
FROM alpine:3.7
EXPOSE 8000 9000 80 443
RUN apk add -U --no-cache ca-certificates
ENV DATABASE_DRIVER=sqlite3
ENV DATABASE_CONFIG=/var/lib/drone/drone.sqlite
ENV GODEBUG=netdns=go
ENV XDG_CACHE_HOME /var/lib/drone
ADD release/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

111
LICENSE
View file

@ -1,80 +1,67 @@
The Drone Community Edition (the "Community Edition") is licensed under the
Apache License, Version 2.0 (the "Apache License"). You may obtain a copy of
the Apache License at
Copyright 2019 Drone.IO, Inc.
http://www.apache.org/licenses/LICENSE-2.0
Source code in this repository is variously licensed under the
Apache License Version 2.0, an Apache compatible license, or the
Drone Non-Commercial License. Source code in a given file is
licensed under the Drone Non-Commercial License unless otherwise
noted at the beginning of the file.
The Drone Enterprise Edition (the "Enterprise Edition") is licensed under
the Drone Enterprise License, Version 1.1 (the "Enterprise License"). A copy
of the Enterprise License is provided below.
-----------------------------------------------------------------
The source files in this repository have a header indicating which license
they are under. The BUILDING file provides instructions for creating the
Community Edition distribution subject to the terms of the Apache License.
Drone Non-Commercial License
-----------------------------------------------------------------------------
Contributor: Drone.IO, Inc.
Licensor: Drone.IO, Inc
Licensed Work: Drone Enterprise Edition
Source Code: https://github.com/drone/drone
Additional Use Grant: Usage of the software is free for entities with both:
(a) annual gross revenue under (USD) $1 million
(according to GAAP, or the equivalent in its country
of domicile); and (b) less than (USD) $5 million in
aggregate debt and equity funding.
This license lets you use and share this software for free,
with a trial-length time limit on commercial use. Specifically:
Change Date: 2022-01-01
If you follow the rules below, you may do everything with this
software that would otherwise infringe either the contributor's
copyright in it, any patent claim the contributor can license
that covers this software as of the contributor's latest
contribution, or both.
Change License: Apache-2.0
1. You must limit use of this software in any manner primarily
intended for or directed toward commercial advantage or
private monetary compensation to a trial period of 32
consecutive calendar days. This limit does not apply to use in
developing feedback, modifications, or extensions that you
contribute back to those giving this license.
Notice
2. Ensure everyone who gets a copy of this software from you, in
source code or any other form, gets the text of this license
and the contributor and source code lines above.
The Drone Enterprise License (this document, or the "License") is not an Open
Source license. However, the Licensed Work will eventually be made available
under an Open Source License, as stated in this License.
3. Do not make any legal claim against anyone for infringing any
patent claim they would infringe by using this software alone,
accusing this software, with or without changes, alone or as
part of a larger application.
-----------------------------------------------------------------------------
You are excused for unknowingly breaking rule 1 if you stop
doing anything requiring this license within 30 days of
learning you broke the rule.
Drone Enterprise License 1.1
**This software comes as is, without any warranty at all. As far
as the law allows, the contributor will not be liable for any
damages related to this software or this license, for any kind of
legal claim.**
Terms
-----------------------------------------------------------------
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.
Contributor waives the terms of rule 1 for companies meeting all
the following criteria, counting all subsidiaries and affiliated
entities as one:
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
1. worldwide annual gross revenue under $5 million US dollars,
per generally accepted accounting principles
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
2. less than $5 million US dollars in all-time aggregate debt and
equity financing
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
3. less than 15,000 total pipelines executed using this software
in the immediately preceding, year-long period
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
Contributor will not revoke this waiver, but may change terms for
future versions of the software.

152
Taskfile.yml Normal file
View file

@ -0,0 +1,152 @@
# https://taskfile.org
version: '2'
tasks:
install:
cmds:
- task: install-server
- task: install-agent
- task: install-controller
install-server:
dir: cmd/drone-server
cmds: [ go install -v ]
env:
GO111MODULE: on
install-agent:
dir: cmd/drone-agent
cmds: [ go install ]
env:
GO111MODULE: 'on'
install-controller:
dir: cmd/drone-controller
cmds: [ go install ]
env:
GO111MODULE: on
build:
cmds:
- task: build-agent
- task: build-server
- task: build-controller
build-agent:
cmds:
- task: build-base
vars: { name: agent }
build-controller:
cmds:
- task: build-base
vars: { name: controller }
build-server:
cmds:
- task: build-base
vars: { name: server }
build-base:
env:
GOOS: linux
GOARCH: amd64
CGO_ENABLED: '0'
GO111MODULE: 'on'
cmds:
- cmd: >
go build -o release/linux/amd64/drone-{{.name}}
github.com/drone/drone/cmd/drone-{{.name}}
cleanup:
cmds:
- rm -rf release
docker:
cmds:
- task: docker-controller
- task: docker-agent
- task: docker-server
docker-agent:
cmds:
- task: docker-base
vars: { name: agent, image: drone/agent }
docker-controller:
cmds:
- task: docker-base
vars: { name: controller, image: drone/controller }
docker-server:
cmds:
- task: docker-base
vars: { name: server, image: drone/drone }
docker-base:
vars:
GIT_BRANCH:
sh: git rev-parse --abbrev-ref HEAD
cmds:
- cmd: docker rmi {{.image}}
ignore_error: true
- cmd: docker rmi {{.image}}:{{.GIT_BRANCH}}
ignore_error: true
- cmd: >
docker build --rm
-f docker/Dockerfile.{{.name}}.linux.amd64
-t {{.image}} .
- cmd: >
docker tag {{.image}} {{.image}}:{{.GIT_BRANCH}}
test:
cmds:
- go test ./...
env:
GO111MODULE: 'on'
test-mysql:
env:
DRONE_DATABASE_DRIVER: mysql
DRONE_DATABASE_DATASOURCE: root@tcp(localhost:3306)/test?parseTime=true
GO111MODULE: 'on'
cmds:
- cmd: docker kill mysql
silent: true
ignore_error: true
- cmd: >
docker run
-p 3306:3306
--env MYSQL_DATABASE=test
--env MYSQL_ALLOW_EMPTY_PASSWORD=yes
--name mysql
--detach
--rm
mysql:8
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
- cmd: go test -count=1 github.com/drone/drone/store/...
- cmd: docker kill mysql
test-postgres:
env:
DRONE_DATABASE_DRIVER: postgres
DRONE_DATABASE_DATASOURCE: host=localhost user=postgres dbname=postgres sslmode=disable
GO111MODULE: 'on'
cmds:
- cmd: docker kill postgres
ignore_error: true
silent: true
- silent: true
cmd: >
docker run
-p 5432:5432
--env POSTGRES_USER=postgres
--name postgres
--detach
--rm
postgres:9-alpine
- cmd: go test -count=1 github.com/drone/drone/store/...
- cmd: docker kill postgres
silent: true

View file

@ -1,484 +0,0 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"github.com/cncd/pipeline/pipeline"
"github.com/cncd/pipeline/pipeline/backend"
"github.com/cncd/pipeline/pipeline/backend/docker"
"github.com/cncd/pipeline/pipeline/multipart"
"github.com/cncd/pipeline/pipeline/rpc"
"github.com/drone/signal"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/tevino/abool"
"github.com/urfave/cli"
oldcontext "golang.org/x/net/context"
)
func loop(c *cli.Context) error {
filter := rpc.Filter{
Labels: map[string]string{
"platform": c.String("platform"),
},
Expr: c.String("filter"),
}
hostname := c.String("hostname")
if len(hostname) == 0 {
hostname, _ = os.Hostname()
}
if c.BoolT("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
if c.Bool("pretty") {
log.Logger = log.Output(
zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: c.BoolT("nocolor"),
},
)
}
counter.Polling = c.Int("max-procs")
counter.Running = 0
if c.BoolT("healthcheck") {
go http.ListenAndServe(":3000", nil)
}
// TODO pass version information to grpc server
// TODO authenticate to grpc server
// grpc.Dial(target, ))
conn, err := grpc.Dial(
c.String("server"),
grpc.WithInsecure(),
grpc.WithPerRPCCredentials(&credentials{
username: c.String("username"),
password: c.String("password"),
}),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: c.Duration("keepalive-time"),
Timeout: c.Duration("keepalive-timeout"),
}),
)
if err != nil {
return err
}
defer conn.Close()
client := rpc.NewGrpcClient(conn)
sigterm := abool.New()
ctx := metadata.NewOutgoingContext(
context.Background(),
metadata.Pairs("hostname", hostname),
)
ctx = signal.WithContextFunc(ctx, func() {
println("ctrl+c received, terminating process")
sigterm.Set()
})
var wg sync.WaitGroup
parallel := c.Int("max-procs")
wg.Add(parallel)
for i := 0; i < parallel; i++ {
go func() {
defer wg.Done()
for {
if sigterm.IsSet() {
return
}
r := runner{
client: client,
filter: filter,
hostname: hostname,
}
if err := r.run(ctx); err != nil {
log.Error().Err(err).Msg("pipeline done with error")
return
}
}
}()
}
wg.Wait()
return nil
}
// NOTE we need to limit the size of the logs and files that we upload.
// The maximum grpc payload size is 4194304. So until we implement streaming
// for uploads, we need to set these limits below the maximum.
const (
maxLogsUpload = 2000000 // this is per step
maxFileUpload = 1000000
)
type runner struct {
client rpc.Peer
filter rpc.Filter
hostname string
}
func (r *runner) run(ctx context.Context) error {
log.Debug().
Msg("request next execution")
meta, _ := metadata.FromOutgoingContext(ctx)
ctxmeta := metadata.NewOutgoingContext(context.Background(), meta)
// get the next job from the queue
work, err := r.client.Next(ctx, r.filter)
if err != nil {
return err
}
if work == nil {
return nil
}
timeout := time.Hour
if minutes := work.Timeout; minutes != 0 {
timeout = time.Duration(minutes) * time.Minute
}
counter.Add(
work.ID,
timeout,
extractRepositoryName(work.Config), // hack
extractBuildNumber(work.Config), // hack
)
defer counter.Done(work.ID)
logger := log.With().
Str("repo", extractRepositoryName(work.Config)). // hack
Str("build", extractBuildNumber(work.Config)). // hack
Str("id", work.ID).
Logger()
logger.Debug().
Msg("received execution")
// new docker engine
engine, err := docker.NewEnv()
if err != nil {
logger.Error().
Err(err).
Msg("cannot create docker client")
return err
}
ctx, cancel := context.WithTimeout(ctxmeta, timeout)
defer cancel()
cancelled := abool.New()
go func() {
logger.Debug().
Msg("listen for cancel signal")
if werr := r.client.Wait(ctx, work.ID); werr != nil {
cancelled.SetTo(true)
logger.Warn().
Err(werr).
Msg("cancel signal received")
cancel()
} else {
logger.Debug().
Msg("stop listening for cancel signal")
}
}()
go func() {
for {
select {
case <-ctx.Done():
logger.Debug().
Msg("pipeline done")
return
case <-time.After(time.Minute):
logger.Debug().
Msg("pipeline lease renewed")
r.client.Extend(ctx, work.ID)
}
}
}()
state := rpc.State{}
state.Started = time.Now().Unix()
err = r.client.Init(ctxmeta, work.ID, state)
if err != nil {
logger.Error().
Err(err).
Msg("pipeline initialization failed")
}
var uploads sync.WaitGroup
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
loglogger := logger.With().
Str("image", proc.Image).
Str("stage", proc.Alias).
Logger()
part, rerr := rc.NextPart()
if rerr != nil {
return rerr
}
uploads.Add(1)
var secrets []string
for _, secret := range work.Config.Secrets {
if secret.Mask {
secrets = append(secrets, secret.Value)
}
}
loglogger.Debug().Msg("log stream opened")
limitedPart := io.LimitReader(part, maxLogsUpload)
logstream := rpc.NewLineWriter(r.client, work.ID, proc.Alias, secrets...)
io.Copy(logstream, limitedPart)
loglogger.Debug().Msg("log stream copied")
file := &rpc.File{}
file.Mime = "application/json+logs"
file.Proc = proc.Alias
file.Name = "logs.json"
file.Data, _ = json.Marshal(logstream.Lines())
file.Size = len(file.Data)
file.Time = time.Now().Unix()
loglogger.Debug().
Msg("log stream uploading")
if serr := r.client.Upload(ctxmeta, work.ID, file); serr != nil {
loglogger.Error().
Err(serr).
Msg("log stream upload error")
}
loglogger.Debug().
Msg("log stream upload complete")
defer func() {
loglogger.Debug().
Msg("log stream closed")
uploads.Done()
}()
part, rerr = rc.NextPart()
if rerr != nil {
return nil
}
// TODO should be configurable
limitedPart = io.LimitReader(part, maxFileUpload)
file = &rpc.File{}
file.Mime = part.Header().Get("Content-Type")
file.Proc = proc.Alias
file.Name = part.FileName()
file.Data, _ = ioutil.ReadAll(limitedPart)
file.Size = len(file.Data)
file.Time = time.Now().Unix()
file.Meta = map[string]string{}
for key, value := range part.Header() {
file.Meta[key] = value[0]
}
loglogger.Debug().
Str("file", file.Name).
Str("mime", file.Mime).
Msg("file stream uploading")
if serr := r.client.Upload(ctxmeta, work.ID, file); serr != nil {
loglogger.Error().
Err(serr).
Str("file", file.Name).
Str("mime", file.Mime).
Msg("file stream upload error")
}
loglogger.Debug().
Str("file", file.Name).
Str("mime", file.Mime).
Msg("file stream upload complete")
return nil
})
defaultTracer := pipeline.TraceFunc(func(state *pipeline.State) error {
proclogger := logger.With().
Str("image", state.Pipeline.Step.Image).
Str("stage", state.Pipeline.Step.Alias).
Int("exit_code", state.Process.ExitCode).
Bool("exited", state.Process.Exited).
Logger()
procState := rpc.State{
Proc: state.Pipeline.Step.Alias,
Exited: state.Process.Exited,
ExitCode: state.Process.ExitCode,
Started: time.Now().Unix(), // TODO do not do this
Finished: time.Now().Unix(),
}
defer func() {
proclogger.Debug().
Msg("update step status")
if uerr := r.client.Update(ctxmeta, work.ID, procState); uerr != nil {
proclogger.Debug().
Err(uerr).
Msg("update step status error")
}
proclogger.Debug().
Msg("update step status complete")
}()
if state.Process.Exited {
return nil
}
if state.Pipeline.Step.Environment == nil {
state.Pipeline.Step.Environment = map[string]string{}
}
state.Pipeline.Step.Environment["DRONE_MACHINE"] = r.hostname
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "success"
state.Pipeline.Step.Environment["CI_BUILD_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
state.Pipeline.Step.Environment["CI_BUILD_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["DRONE_BUILD_STATUS"] = "success"
state.Pipeline.Step.Environment["DRONE_BUILD_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
state.Pipeline.Step.Environment["DRONE_BUILD_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["CI_JOB_STATUS"] = "success"
state.Pipeline.Step.Environment["CI_JOB_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
state.Pipeline.Step.Environment["CI_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["DRONE_JOB_STATUS"] = "success"
state.Pipeline.Step.Environment["DRONE_JOB_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
state.Pipeline.Step.Environment["DRONE_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
if state.Pipeline.Error != nil {
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "failure"
state.Pipeline.Step.Environment["CI_JOB_STATUS"] = "failure"
state.Pipeline.Step.Environment["DRONE_BUILD_STATUS"] = "failure"
state.Pipeline.Step.Environment["DRONE_JOB_STATUS"] = "failure"
}
return nil
})
err = pipeline.New(work.Config,
pipeline.WithContext(ctx),
pipeline.WithLogger(defaultLogger),
pipeline.WithTracer(defaultTracer),
pipeline.WithEngine(engine),
).Run()
state.Finished = time.Now().Unix()
state.Exited = true
if err != nil {
switch xerr := err.(type) {
case *pipeline.ExitError:
state.ExitCode = xerr.Code
default:
state.ExitCode = 1
state.Error = err.Error()
}
if cancelled.IsSet() {
state.ExitCode = 137
}
}
logger.Debug().
Str("error", state.Error).
Int("exit_code", state.ExitCode).
Msg("pipeline complete")
logger.Debug().
Msg("uploading logs")
uploads.Wait()
logger.Debug().
Msg("uploading logs complete")
logger.Debug().
Str("error", state.Error).
Int("exit_code", state.ExitCode).
Msg("updating pipeline status")
err = r.client.Done(ctxmeta, work.ID, state)
if err != nil {
logger.Error().Err(err).
Msg("updating pipeline status failed")
} else {
logger.Debug().
Msg("updating pipeline status complete")
}
return nil
}
type credentials struct {
username string
password string
}
func (c *credentials) GetRequestMetadata(oldcontext.Context, ...string) (map[string]string, error) {
return map[string]string{
"username": c.username,
"password": c.password,
}, nil
}
func (c *credentials) RequireTransportSecurity() bool {
return false
}
// extract repository name from the configuration
func extractRepositoryName(config *backend.Config) string {
return config.Stages[0].Steps[0].Environment["DRONE_REPO"]
}
// extract build number from the configuration
func extractBuildNumber(config *backend.Config) string {
return config.Stages[0].Steps[0].Environment["DRONE_BUILD_NUMBER"]
}

View file

@ -0,0 +1,206 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package config
import (
"fmt"
"net/url"
"os"
"strings"
"github.com/dustin/go-humanize"
"github.com/kelseyhightower/envconfig"
)
// IMPORTANT please do not add new configuration parameters unless it has
// been discussed on the mailing list. We are attempting to reduce the
// number of configuration parameters, and may reject pull requests that
// introduce new parameters. (mailing list https://discourse.drone.io)
// default runner hostname.
var hostname string
func init() {
hostname, _ = os.Hostname()
if hostname == "" {
hostname = "localhost"
}
}
type (
// Config provides the system configuration.
Config struct {
Docker Docker
Logging Logging
Registries Registries
Runner Runner
RPC RPC
Server Server
Secrets Secrets
}
// Docker provides docker configuration
Docker struct {
Config string `envconfig:"DRONE_DOCKER_CONFIG"`
}
// Logging provides the logging configuration.
Logging struct {
Debug bool `envconfig:"DRONE_LOGS_DEBUG"`
Trace bool `envconfig:"DRONE_LOGS_TRACE"`
Color bool `envconfig:"DRONE_LOGS_COLOR"`
Pretty bool `envconfig:"DRONE_LOGS_PRETTY"`
Text bool `envconfig:"DRONE_LOGS_TEXT"`
}
// Registries provides the registry configuration.
Registries struct {
Endpoint string `envconfig:"DRONE_REGISTRY_ENDPOINT"`
Password string `envconfig:"DRONE_REGISTRY_SECRET"`
SkipVerify bool `envconfig:"DRONE_REGISTRY_SKIP_VERIFY"`
}
// Secrets provides the secret configuration.
Secrets struct {
Endpoint string `envconfig:"DRONE_SECRET_ENDPOINT"`
Password string `envconfig:"DRONE_SECRET_SECRET"`
SkipVerify bool `envconfig:"DRONE_SECRET_SKIP_VERIFY"`
}
// RPC provides the rpc configuration.
RPC struct {
Server string `envconfig:"DRONE_RPC_SERVER"`
Secret string `envconfig:"DRONE_RPC_SECRET"`
Debug bool `envconfig:"DRONE_RPC_DEBUG"`
Host string `envconfig:"DRONE_RPC_HOST"`
Proto string `envconfig:"DRONE_RPC_PROTO"`
// Hosts map[string]string `envconfig:"DRONE_RPC_EXTRA_HOSTS"`
}
// Runner provides the runner configuration.
Runner struct {
Platform string `envconfig:"DRONE_RUNNER_PLATFORM" default:"linux/amd64"`
OS string `envconfig:"DRONE_RUNNER_OS"`
Arch string `envconfig:"DRONE_RUNNER_ARCH"`
Kernel string `envconfig:"DRONE_RUNNER_KERNEL"`
Variant string `envconfig:"DRONE_RUNNER_VARIANT"`
Machine string `envconfig:"DRONE_RUNNER_NAME"`
Capacity int `envconfig:"DRONE_RUNNER_CAPACITY" default:"2"`
Labels map[string]string `envconfig:"DRONE_RUNNER_LABELS"`
Volumes []string `envconfig:"DRONE_RUNNER_VOLUMES"`
Networks []string `envconfig:"DRONE_RUNNER_NETWORKS"`
Devices []string `envconfig:"DRONE_RUNNER_DEVICES"`
Privileged []string `envconfig:"DRONE_RUNNER_PRIVILEGED_IMAGES"`
Environ map[string]string `envconfig:"DRONE_RUNNER_ENVIRON"`
Limits struct {
MemSwapLimit Bytes `envconfig:"DRONE_LIMIT_MEM_SWAP"`
MemLimit Bytes `envconfig:"DRONE_LIMIT_MEM"`
ShmSize Bytes `envconfig:"DRONE_LIMIT_SHM_SIZE"`
CPUQuota int64 `envconfig:"DRONE_LIMIT_CPU_QUOTA"`
CPUShares int64 `envconfig:"DRONE_LIMIT_CPU_SHARES"`
CPUSet string `envconfig:"DRONE_LIMIT_CPU_SET"`
}
}
// Server provides the server configuration.
Server struct {
Addr string `envconfig:"-"`
Host string `envconfig:"DRONE_SERVER_HOST" default:"localhost:8080"`
Proto string `envconfig:"DRONE_SERVER_PROTO" default:"http"`
}
)
// Environ returns the settings from the environment.
func Environ() (Config, error) {
cfg := Config{}
err := envconfig.Process("", &cfg)
defaultRunner(&cfg)
defaultCallback(&cfg)
return cfg, err
}
func defaultRunner(c *Config) {
if c.Runner.Machine == "" {
c.Runner.Machine = hostname
}
parts := strings.Split(c.Runner.Platform, "/")
if len(parts) == 2 && c.Runner.OS == "" {
c.Runner.OS = parts[0]
}
if len(parts) == 2 && c.Runner.Arch == "" {
c.Runner.Arch = parts[1]
}
}
func defaultCallback(c *Config) {
// this is legacy, remove in a future release
if c.RPC.Server != "" {
uri, err := url.Parse(c.RPC.Server)
if err == nil {
c.RPC.Host = uri.Host
c.RPC.Proto = uri.Scheme
}
}
if c.RPC.Host == "" {
c.RPC.Host = c.Server.Host
}
if c.RPC.Proto == "" {
c.RPC.Proto = c.Server.Proto
}
}
// Bytes stores number bytes (e.g. megabytes)
type Bytes int64
// Decode implements a decoder that parses a string representation
// of bytes into the number of bytes it represents.
func (b *Bytes) Decode(value string) error {
v, err := humanize.ParseBytes(value)
*b = Bytes(v)
return err
}
// Int64 returns the int64 value of the Byte.
func (b *Bytes) Int64() int64 {
return int64(*b)
}
// String returns the string value of the Byte.
func (b *Bytes) String() string {
return fmt.Sprint(*b)
}
// UserCreate stores account information used to bootstrap
// the admin user account when the system initializes.
type UserCreate struct {
Username string
Machine bool
Admin bool
Token string
}
// Decode implements a decoder that extracts user information
// from the environment variable string.
func (u *UserCreate) Decode(value string) error {
for _, param := range strings.Split(value, ",") {
parts := strings.Split(param, ":")
if len(parts) != 2 {
continue
}
key := parts[0]
val := parts[1]
switch key {
case "username":
u.Username = val
case "token":
u.Token = val
case "machine":
u.Machine = val == "true"
case "admin":
u.Admin = val == "true"
}
}
return nil
}

View file

@ -1,145 +0,0 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/drone/drone/version"
"github.com/urfave/cli"
)
// the file implements some basic healthcheck logic based on the
// following specification:
// https://github.com/mozilla-services/Dockerflow
func init() {
http.HandleFunc("/varz", handleStats)
http.HandleFunc("/healthz", handleHeartbeat)
http.HandleFunc("/version", handleVersion)
}
func handleHeartbeat(w http.ResponseWriter, r *http.Request) {
if counter.Healthy() {
w.WriteHeader(200)
} else {
w.WriteHeader(500)
}
}
func handleVersion(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Add("Content-Type", "text/json")
json.NewEncoder(w).Encode(versionResp{
Source: "https://github.com/drone/drone",
Version: version.Version.String(),
})
}
func handleStats(w http.ResponseWriter, r *http.Request) {
if counter.Healthy() {
w.WriteHeader(200)
} else {
w.WriteHeader(500)
}
w.Header().Add("Content-Type", "text/json")
counter.writeTo(w)
}
type versionResp struct {
Version string `json:"version"`
Source string `json:"source"`
}
// default statistics counter
var counter = &state{
Metadata: map[string]info{},
}
type state struct {
sync.Mutex `json:"-"`
Polling int `json:"polling_count"`
Running int `json:"running_count"`
Metadata map[string]info `json:"running"`
}
type info struct {
ID string `json:"id"`
Repo string `json:"repository"`
Build string `json:"build_number"`
Started time.Time `json:"build_started"`
Timeout time.Duration `json:"build_timeout"`
}
func (s *state) Add(id string, timeout time.Duration, repo, build string) {
s.Lock()
s.Polling--
s.Running++
s.Metadata[id] = info{
ID: id,
Repo: repo,
Build: build,
Timeout: timeout,
Started: time.Now().UTC(),
}
s.Unlock()
}
func (s *state) Done(id string) {
s.Lock()
s.Polling++
s.Running--
delete(s.Metadata, id)
s.Unlock()
}
func (s *state) Healthy() bool {
s.Lock()
defer s.Unlock()
now := time.Now()
buf := time.Hour // 1 hour buffer
for _, item := range s.Metadata {
if now.After(item.Started.Add(item.Timeout).Add(buf)) {
return false
}
}
return true
}
func (s *state) writeTo(w io.Writer) (int, error) {
s.Lock()
out, _ := json.Marshal(s)
s.Unlock()
return w.Write(out)
}
// handles pinging the endpoint and returns an error if the
// agent is in an unhealthy state.
func pinger(c *cli.Context) error {
resp, err := http.Get("http://localhost:3000/healthz")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("agent returned non-200 status code")
}
return nil
}

View file

@ -1,59 +0,0 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"testing"
"time"
)
func TestHealthy(t *testing.T) {
s := state{}
s.Metadata = map[string]info{}
s.Add("1", time.Hour, "octocat/hello-world", "42")
if got, want := s.Metadata["1"].ID, "1"; got != want {
t.Errorf("got ID %s, want %s", got, want)
}
if got, want := s.Metadata["1"].Timeout, time.Hour; got != want {
t.Errorf("got duration %v, want %v", got, want)
}
if got, want := s.Metadata["1"].Repo, "octocat/hello-world"; got != want {
t.Errorf("got repository name %s, want %s", got, want)
}
s.Metadata["1"] = info{
Timeout: time.Hour,
Started: time.Now().UTC(),
}
if s.Healthy() == false {
t.Error("want healthy status when timeout not exceeded, got false")
}
s.Metadata["1"] = info{
Started: time.Now().UTC().Add(-(time.Minute * 30)),
}
if s.Healthy() == false {
t.Error("want healthy status when timeout+buffer not exceeded, got false")
}
s.Metadata["1"] = info{
Started: time.Now().UTC().Add(-(time.Hour + time.Minute)),
}
if s.Healthy() == true {
t.Error("want unhealthy status when timeout+buffer not exceeded, got true")
}
}

View file

@ -1,118 +1,123 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"time"
"context"
"github.com/drone/drone/version"
"github.com/drone/drone-runtime/engine/docker"
"github.com/drone/drone/cmd/drone-agent/config"
"github.com/drone/drone/operator/manager/rpc"
"github.com/drone/drone/operator/runner"
"github.com/drone/drone/plugin/registry"
"github.com/drone/drone/plugin/secret"
"github.com/drone/signal"
"github.com/sirupsen/logrus"
_ "github.com/joho/godotenv/autoload"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "drone-agent"
app.Version = version.Version.String()
app.Usage = "drone agent"
app.Action = loop
app.Commands = []cli.Command{
{
Name: "ping",
Usage: "ping the agent",
Action: pinger,
},
}
app.Flags = []cli.Flag{
cli.StringFlag{
EnvVar: "DRONE_SERVER",
Name: "server",
Usage: "drone server address",
Value: "localhost:9000",
},
cli.StringFlag{
EnvVar: "DRONE_USERNAME",
Name: "username",
Usage: "drone auth username",
Value: "x-oauth-basic",
},
cli.StringFlag{
EnvVar: "DRONE_PASSWORD,DRONE_SECRET",
Name: "password",
Usage: "server-agent shared password",
},
cli.BoolTFlag{
EnvVar: "DRONE_DEBUG",
Name: "debug",
Usage: "enable agent debug mode",
},
cli.BoolFlag{
EnvVar: "DRONE_DEBUG_PRETTY",
Name: "pretty",
Usage: "enable pretty-printed debug output",
},
cli.BoolTFlag{
EnvVar: "DRONE_DEBUG_NOCOLOR",
Name: "nocolor",
Usage: "disable colored debug output",
},
cli.StringFlag{
EnvVar: "DRONE_HOSTNAME,HOSTNAME",
Name: "hostname",
Usage: "agent hostname",
},
cli.StringFlag{
EnvVar: "DRONE_PLATFORM",
Name: "platform",
Usage: "restrict builds by platform conditions",
Value: "linux/amd64",
},
cli.StringFlag{
EnvVar: "DRONE_FILTER",
Name: "filter",
Usage: "filter expression to restrict builds by label",
},
cli.IntFlag{
EnvVar: "DRONE_MAX_PROCS",
Name: "max-procs",
Usage: "agent parallel builds",
Value: 1,
},
cli.BoolTFlag{
EnvVar: "DRONE_HEALTHCHECK",
Name: "healthcheck",
Usage: "enable healthcheck endpoint",
},
cli.DurationFlag{
EnvVar: "DRONE_KEEPALIVE_TIME",
Name: "keepalive-time",
Usage: "after a duration of this time of no activity, the agent pings the server to check if the transport is still alive",
},
cli.DurationFlag{
EnvVar: "DRONE_KEEPALIVE_TIMEOUT",
Name: "keepalive-timeout",
Usage: "after pinging for a keepalive check, the agent waits for a duration of this time before closing the connection if no activity",
Value: time.Second * 20,
},
config, err := config.Environ()
if err != nil {
logrus.WithError(err).Fatalln("invalid configuration")
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
initLogging(config)
ctx := signal.WithContext(
context.Background(),
)
secrets := secret.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
)
auths := registry.Combine(
registry.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
),
registry.FileSource(
config.Docker.Config,
),
registry.EndpointSource(
config.Registries.Endpoint,
config.Registries.Password,
config.Registries.SkipVerify,
),
)
manager := rpc.NewClient(
config.RPC.Proto+"://"+config.RPC.Host,
config.RPC.Secret,
)
if config.RPC.Debug {
manager.SetDebug(true)
}
if config.Logging.Trace {
manager.SetDebug(true)
}
engine, err := docker.NewEnv()
if err != nil {
logrus.WithError(err).
Fatalln("cannot load the docker engine")
}
r := &runner.Runner{
Platform: config.Runner.Platform,
OS: config.Runner.OS,
Arch: config.Runner.Arch,
Kernel: config.Runner.Kernel,
Variant: config.Runner.Variant,
Engine: engine,
Manager: manager,
Registry: auths,
Secrets: secrets,
Volumes: config.Runner.Volumes,
Networks: config.Runner.Networks,
Devices: config.Runner.Devices,
Privileged: config.Runner.Privileged,
Machine: config.Runner.Machine,
Labels: config.Runner.Labels,
Environ: config.Runner.Environ,
Limits: runner.Limits{
MemSwapLimit: int64(config.Runner.Limits.MemSwapLimit),
MemLimit: int64(config.Runner.Limits.MemLimit),
ShmSize: int64(config.Runner.Limits.ShmSize),
CPUQuota: config.Runner.Limits.CPUQuota,
CPUShares: config.Runner.Limits.CPUShares,
CPUSet: config.Runner.Limits.CPUSet,
},
}
if err := r.Start(ctx, config.Runner.Capacity); err != nil {
logrus.WithError(err).
Warnln("program terminated")
}
}
// helper funciton configures the logging.
func initLogging(c config.Config) {
if c.Logging.Debug {
logrus.SetLevel(logrus.DebugLevel)
}
if c.Logging.Trace {
logrus.SetLevel(logrus.TraceLevel)
}
if c.Logging.Text {
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: c.Logging.Color,
DisableColors: !c.Logging.Color,
})
} else {
logrus.SetFormatter(&logrus.JSONFormatter{
PrettyPrint: c.Logging.Pretty,
})
}
}

View file

@ -0,0 +1,187 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package config
import (
"fmt"
"os"
"strings"
"github.com/dustin/go-humanize"
"github.com/kelseyhightower/envconfig"
)
// IMPORTANT please do not add new configuration parameters unless it has
// been discussed on the mailing list. We are attempting to reduce the
// number of configuration parameters, and may reject pull requests that
// introduce new parameters. (mailing list https://discourse.drone.io)
// default runner hostname.
var hostname string
func init() {
hostname, _ = os.Hostname()
if hostname == "" {
hostname = "localhost"
}
}
type (
// Config provides the system configuration.
Config struct {
Docker Docker
Logging Logging
Registries Registries
Runner Runner
RPC RPC
Server Server
Secrets Secrets
}
// Docker provides docker configuration
Docker struct {
Config string `envconfig:"DRONE_DOCKER_CONFIG"`
}
// Logging provides the logging configuration.
Logging struct {
Debug bool `envconfig:"DRONE_LOGS_DEBUG"`
Trace bool `envconfig:"DRONE_LOGS_TRACE"`
Color bool `envconfig:"DRONE_LOGS_COLOR"`
Pretty bool `envconfig:"DRONE_LOGS_PRETTY"`
Text bool `envconfig:"DRONE_LOGS_TEXT"`
}
// Registries provides the registry configuration.
Registries struct {
Endpoint string `envconfig:"DRONE_REGISTRY_ENDPOINT"`
Password string `envconfig:"DRONE_REGISTRY_SECRET"`
SkipVerify bool `envconfig:"DRONE_REGISTRY_SKIP_VERIFY"`
}
// Secrets provides the secret configuration.
Secrets struct {
Endpoint string `envconfig:"DRONE_SECRET_ENDPOINT"`
Password string `envconfig:"DRONE_SECRET_SECRET"`
SkipVerify bool `envconfig:"DRONE_SECRET_SKIP_VERIFY"`
}
// RPC provides the rpc configuration.
RPC struct {
Server string `envconfig:"DRONE_RPC_SERVER"`
Secret string `envconfig:"DRONE_RPC_SECRET"`
Debug bool `envconfig:"DRONE_RPC_DEBUG"`
Host string `envconfig:"DRONE_RPC_HOST"`
Proto string `envconfig:"DRONE_RPC_PROTO"`
// Hosts map[string]string `envconfig:"DRONE_RPC_EXTRA_HOSTS"`
}
// Runner provides the runner configuration.
Runner struct {
Platform string `envconfig:"DRONE_RUNNER_PLATFORM" default:"linux/amd64"`
OS string `envconfig:"DRONE_RUNNER_OS"`
Arch string `envconfig:"DRONE_RUNNER_ARCH"`
Kernel string `envconfig:"DRONE_RUNNER_KERNEL"`
Variant string `envconfig:"DRONE_RUNNER_VARIANT"`
Machine string `envconfig:"DRONE_RUNNER_NAME"`
Capacity int `envconfig:"DRONE_RUNNER_CAPACITY" default:"2"`
Labels map[string]string `envconfig:"DRONE_RUNNER_LABELS"`
Volumes []string `envconfig:"DRONE_RUNNER_VOLUMES"`
Networks []string `envconfig:"DRONE_RUNNER_NETWORKS"`
Devices []string `envconfig:"DRONE_RUNNER_DEVICES"`
Privileged []string `envconfig:"DRONE_RUNNER_PRIVILEGED_IMAGES"`
Environ map[string]string `envconfig:"DRONE_RUNNER_ENVIRON"`
Limits struct {
MemSwapLimit Bytes `envconfig:"DRONE_LIMIT_MEM_SWAP"`
MemLimit Bytes `envconfig:"DRONE_LIMIT_MEM"`
ShmSize Bytes `envconfig:"DRONE_LIMIT_SHM_SIZE"`
CPUQuota int64 `envconfig:"DRONE_LIMIT_CPU_QUOTA"`
CPUShares int64 `envconfig:"DRONE_LIMIT_CPU_SHARES"`
CPUSet string `envconfig:"DRONE_LIMIT_CPU_SET"`
}
}
// Server provides the server configuration.
Server struct {
Addr string `envconfig:"-"`
Host string `envconfig:"DRONE_SERVER_HOST" default:"localhost:8080"`
Proto string `envconfig:"DRONE_SERVER_PROTO" default:"http"`
}
)
// Environ returns the settings from the environment.
func Environ() (Config, error) {
cfg := Config{}
err := envconfig.Process("", &cfg)
defaultRunner(&cfg)
return cfg, err
}
func defaultRunner(c *Config) {
if c.Runner.Machine == "" {
c.Runner.Machine = hostname
}
parts := strings.Split(c.Runner.Platform, "/")
if len(parts) == 2 && c.Runner.OS == "" {
c.Runner.OS = parts[0]
}
if len(parts) == 2 && c.Runner.Arch == "" {
c.Runner.Arch = parts[1]
}
}
// Bytes stores number bytes (e.g. megabytes)
type Bytes int64
// Decode implements a decoder that parses a string representation
// of bytes into the number of bytes it represents.
func (b *Bytes) Decode(value string) error {
v, err := humanize.ParseBytes(value)
*b = Bytes(v)
return err
}
// Int64 returns the int64 value of the Byte.
func (b *Bytes) Int64() int64 {
return int64(*b)
}
// String returns the string value of the Byte.
func (b *Bytes) String() string {
return fmt.Sprint(*b)
}
// UserCreate stores account information used to bootstrap
// the admin user account when the system initializes.
type UserCreate struct {
Username string
Machine bool
Admin bool
Token string
}
// Decode implements a decoder that extracts user information
// from the environment variable string.
func (u *UserCreate) Decode(value string) error {
for _, param := range strings.Split(value, ",") {
parts := strings.Split(param, ":")
if len(parts) != 2 {
continue
}
key := parts[0]
val := parts[1]
switch key {
case "username":
u.Username = val
case "token":
u.Token = val
case "machine":
u.Machine = val == "true"
case "admin":
u.Admin = val == "true"
}
}
return nil
}

View file

@ -0,0 +1,147 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package main
import (
"context"
"os"
"strconv"
"github.com/drone/drone-runtime/engine"
"github.com/drone/drone-runtime/engine/docker"
"github.com/drone/drone-runtime/engine/kube"
"github.com/drone/drone/cmd/drone-controller/config"
"github.com/drone/drone/operator/manager/rpc"
"github.com/drone/drone/operator/runner"
"github.com/drone/drone/plugin/registry"
"github.com/drone/drone/plugin/secret"
"github.com/drone/signal"
"github.com/sirupsen/logrus"
_ "github.com/joho/godotenv/autoload"
)
func main() {
config, err := config.Environ()
if err != nil {
logrus.WithError(err).Fatalln("invalid configuration")
}
initLogging(config)
ctx := signal.WithContext(
context.Background(),
)
secrets := secret.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
)
auths := registry.Combine(
registry.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
),
registry.FileSource(
config.Docker.Config,
),
registry.EndpointSource(
config.Registries.Endpoint,
config.Registries.Password,
config.Registries.SkipVerify,
),
)
manager := rpc.NewClient(
config.RPC.Proto+"://"+config.RPC.Host,
config.RPC.Secret,
)
if config.RPC.Debug {
manager.SetDebug(true)
}
if config.Logging.Trace {
manager.SetDebug(true)
}
var engine engine.Engine
if isKubernetes() {
engine, err = kube.NewFile("", "", config.Runner.Machine)
if err != nil {
logrus.WithError(err).
Fatalln("cannot create the kubernetes client")
}
} else {
engine, err = docker.NewEnv()
if err != nil {
logrus.WithError(err).
Fatalln("cannot load the docker engine")
}
}
r := &runner.Runner{
Platform: config.Runner.Platform,
OS: config.Runner.OS,
Arch: config.Runner.Arch,
Kernel: config.Runner.Kernel,
Variant: config.Runner.Variant,
Engine: engine,
Manager: manager,
Registry: auths,
Secrets: secrets,
Volumes: config.Runner.Volumes,
Networks: config.Runner.Networks,
Devices: config.Runner.Devices,
Privileged: config.Runner.Privileged,
Machine: config.Runner.Machine,
Labels: config.Runner.Labels,
Environ: config.Runner.Environ,
Limits: runner.Limits{
MemSwapLimit: int64(config.Runner.Limits.MemSwapLimit),
MemLimit: int64(config.Runner.Limits.MemLimit),
ShmSize: int64(config.Runner.Limits.ShmSize),
CPUQuota: config.Runner.Limits.CPUQuota,
CPUShares: config.Runner.Limits.CPUShares,
CPUSet: config.Runner.Limits.CPUSet,
},
}
id, err := strconv.ParseInt(os.Getenv("DRONE_STAGE_ID"), 10, 64)
if err != nil {
logrus.WithError(err).
Fatalln("cannot parse stage ID")
}
if err := r.Run(ctx, id); err != nil {
logrus.WithError(err).
Warnln("program terminated")
}
}
func isKubernetes() bool {
return os.Getenv("KUBERNETES_SERVICE_HOST") != ""
}
// helper funciton configures the logging.
func initLogging(c config.Config) {
if c.Logging.Debug {
logrus.SetLevel(logrus.DebugLevel)
}
if c.Logging.Trace {
logrus.SetLevel(logrus.TraceLevel)
}
if c.Logging.Text {
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: c.Logging.Color,
DisableColors: !c.Logging.Color,
})
} else {
logrus.SetFormatter(&logrus.JSONFormatter{
PrettyPrint: c.Logging.Pretty,
})
}
}

View file

@ -0,0 +1,113 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package bootstrap
import (
"context"
"errors"
"time"
"github.com/dchest/uniuri"
"github.com/drone/drone/logger"
"github.com/drone/drone/core"
"github.com/sirupsen/logrus"
)
var errMissingToken = errors.New("You must provide the machine account token")
// New returns a new account bootstrapper.
func New(users core.UserStore) *Bootstrapper {
return &Bootstrapper{
users: users,
}
}
// Bootstrapper bootstraps the system with the initial account.
type Bootstrapper struct {
users core.UserStore
}
// Bootstrap creates the user account. If the account already exists,
// no account is created, and a nil error is returned.
func (b *Bootstrapper) Bootstrap(ctx context.Context, user *core.User) error {
if user.Login == "" {
return nil
}
log := logrus.WithFields(
logrus.Fields{
"login": user.Login,
"admin": user.Admin,
"machine": user.Machine,
"token": user.Hash,
},
)
log.Debugln("bootstrap: create account")
existingUser, err := b.users.FindLogin(ctx, user.Login)
if err == nil {
ctx = logger.WithContext(ctx, log)
return b.update(ctx, user, existingUser)
}
if user.Machine && user.Hash == "" {
log.Errorln("bootstrap: cannot create account, missing token")
return errMissingToken
}
user.Active = true
user.Created = time.Now().Unix()
user.Updated = time.Now().Unix()
if user.Hash == "" {
user.Hash = uniuri.NewLen(32)
}
err = b.users.Create(ctx, user)
if err != nil {
log = log.WithError(err)
log.Errorln("bootstrap: cannot create account")
return err
}
log = log.WithField("token", user.Hash)
log.Infoln("bootstrap: account created")
return nil
}
func (b *Bootstrapper) update(ctx context.Context, src, dst *core.User) error {
log := logger.FromContext(ctx)
log.Debugln("bootstrap: updating account")
var updated bool
if src.Hash != dst.Hash && src.Hash != "" {
log.Infoln("bootstrap: found updated user token")
dst.Hash = src.Hash
updated = true
}
if src.Machine != dst.Machine {
log.Infoln("bootstrap: found updated machine flag")
dst.Machine = src.Machine
updated = true
}
if src.Admin != dst.Admin {
log.Infoln("bootstrap: found updated admin flag")
dst.Admin = src.Admin
updated = true
}
if !updated {
log.Debugln("bootstrap: account already up-to-date")
return nil
}
dst.Updated = time.Now().Unix()
err := b.users.Update(ctx, dst)
if err != nil {
log = log.WithError(err)
log.Errorln("bootstrap: cannot update account")
return err
}
log.Infoln("bootstrap: account successfully updated")
return nil
}

View file

@ -0,0 +1,170 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package bootstrap
import (
"context"
"database/sql"
"io/ioutil"
"testing"
"github.com/drone/drone/mock"
"github.com/drone/drone/core"
"github.com/dchest/uniuri"
"github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
)
var noContext = context.TODO()
func init() {
logrus.SetOutput(ioutil.Discard)
}
func TestBootstrap(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: true,
Admin: true,
Hash: uniuri.NewLen(32),
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(nil, sql.ErrNoRows)
store.EXPECT().Create(gomock.Any(), dummyUser).Return(nil)
err := New(store).Bootstrap(noContext, dummyUser)
if err != nil {
t.Error(err)
}
}
func TestBootstrap_GenerateHash(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: false,
Admin: true,
Hash: "",
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(nil, sql.ErrNoRows)
store.EXPECT().Create(gomock.Any(), dummyUser).Return(nil)
err := New(store).Bootstrap(noContext, dummyUser)
if err != nil {
t.Error(err)
}
if got, want := len(dummyUser.Hash), 32; got != want {
t.Errorf("Want generated hash length %d, got %d", want, got)
}
}
func TestBootstrap_Empty(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "",
}
store := mock.NewMockUserStore(controller)
err := New(store).Bootstrap(noContext, dummyUser)
if err != nil {
t.Error(err)
}
}
func TestBootstrap_Exists_WithoutUpdates(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: true,
Admin: true,
Hash: uniuri.NewLen(32),
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(dummyUser, nil)
err := New(store).Bootstrap(noContext, dummyUser)
if err != nil {
t.Error(err)
}
}
func TestBootstrap_Exists_WithUpdates(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: true,
Admin: true,
Hash: uniuri.NewLen(32),
}
existingUser := &core.User{
Login: "octocat",
Machine: false,
Admin: false,
Hash: uniuri.NewLen(32),
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(existingUser, nil)
store.EXPECT().Update(gomock.Any(), existingUser).Return(nil)
err := New(store).Bootstrap(noContext, dummyUser)
if err != nil {
t.Error(err)
}
}
func TestBootstrap_MissingTokenError(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: true,
Admin: true,
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(nil, sql.ErrNoRows)
err := New(store).Bootstrap(noContext, dummyUser)
if err != errMissingToken {
t.Errorf("Expect missing token error")
}
}
func TestBootstrap_CreateError(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dummyUser := &core.User{
Login: "octocat",
Machine: true,
Admin: true,
Hash: uniuri.NewLen(32),
}
store := mock.NewMockUserStore(controller)
store.EXPECT().FindLogin(gomock.Any(), dummyUser.Login).Return(nil, sql.ErrNoRows)
store.EXPECT().Create(gomock.Any(), dummyUser).Return(sql.ErrConnDone)
err := New(store).Bootstrap(noContext, dummyUser)
if err != sql.ErrConnDone {
t.Errorf("Expect error creating user")
}
}

View file

@ -0,0 +1,485 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"os"
"strings"
"time"
"github.com/dchest/uniuri"
"github.com/dustin/go-humanize"
"github.com/kelseyhightower/envconfig"
"gopkg.in/yaml.v2"
)
// IMPORTANT please do not add new configuration parameters unless it has
// been discussed on the mailing list. We are attempting to reduce the
// number of configuration parameters, and may reject pull requests that
// introduce new parameters. (mailing list https://discourse.drone.io)
// default runner hostname.
var hostname string
func init() {
hostname, _ = os.Hostname()
if hostname == "" {
hostname = "localhost"
}
}
type (
// Config provides the system configuration.
Config struct {
License string `envconfig:"DRONE_LICENSE"`
Authn Authentication
Agent Agent
Cron Cron
Cloning Cloning
Database Database
Docker Docker
HTTP HTTP
Logging Logging
// Prometheus Prometheus
Proxy Proxy
Registration Registration
Registries Registries
Repository Repository
Runner Runner
Nomad Nomad
Kube Kubernetes
RPC RPC
S3 S3
Secrets Secrets
Server Server
Session Session
Status Status
Users Users
Webhook Webhook
Yaml Yaml
// Remote configurations
Bitbucket Bitbucket
Gitea Gitea
Github Github
GitLab GitLab
Gogs Gogs
Stash Stash
}
// Cloning provides the cloning configuration.
Cloning struct {
AlwaysAuth bool `envconfig:"DRONE_GIT_ALWAYS_AUTH"`
Username string `envconfig:"DRONE_GIT_USERNAME"`
Password string `envconfig:"DRONE_GIT_PASSWORD"`
Image string `envconfig:"DRONE_GIT_IMAGE"`
Pull string `envconfig:"DRONE_GIT_IMAGE_PULL" default:"IfNotExists"`
}
// Cron provides the cron configuration.
Cron struct {
Disabled bool `envconfig:"DRONE_CRON_DISABLED"`
Interval time.Duration `envconfig:"DRONE_CRON_INTERVAL" default:"30m"`
}
// Database provides the database configuration.
Database struct {
Driver string `envconfig:"DRONE_DATABASE_DRIVER" default:"sqlite3"`
Datasource string `envconfig:"DRONE_DATABASE_DATASOURCE" default:"core.sqlite"`
Secret string `envconfig:"DRONE_DATABASE_SECRET"`
}
// Docker provides docker configuration
Docker struct {
Config string `envconfig:"DRONE_DOCKER_CONFIG"`
}
// Kubernetes provides kubernetes configuration
Kubernetes struct {
Enabled bool `envconfig:"DRONE_KUBERNETES_ENABLED"`
Namespace string `envconfig:"DRONE_KUBERNETES_NAMESPACE"`
Path string `envconfig:"DRONE_KUBERNETES_CONFIG_PATH"`
URL string `envconfig:"DRONE_KUBERNETES_CONFIG_URL"`
TTL int `envconfig:"DRONE_KUBERNETES_TTL_AFTER_FINISHED" default:"300"`
ServiceAccountName string `envconfig:"DRONE_KUBERNETES_SERVICE_ACCOUNT"`
PullPolicy string `envconfig:"DRONE_KUBERNETES_IMAGE_PULL" default:"Always"`
Image string `envconfig:"DRONE_KUBERNETES_IMAGE"`
}
// Nomad configuration.
Nomad struct {
Enabled bool `envconfig:"DRONE_NOMAD_ENABLED"`
Datacenters []string `envconfig:"DRONE_NOMAD_DATACENTER" default:"dc1"`
Namespace string `envconfig:"DRONE_NOMAD_NAMESPACE"`
Region string `envconfig:"DRONE_NOMAD_REGION"`
Prefix string `envconfig:"DRONE_NOMAD_JOB_PREFIX" default:"drone-job-"`
Image string `envconfig:"DRONE_NOMAD_IMAGE"`
ImagePull bool `envconfig:"DRONE_NOMAD_IMAGE_PULL"`
Memory int `envconfig:"DRONE_NOMAD_DEFAULT_RAM" default:"1024"`
CPU int `envconfig:"DRONE_NOMAD_DEFAULT_CPU" default:"500"`
}
// License provides license configuration
License struct {
Key string `envconfig:"DRONE_LICENSE"`
Endpoint string `envconfig:"DRONE_LICENSE_ENDPOINT"`
}
// Logging provides the logging configuration.
Logging struct {
Debug bool `envconfig:"DRONE_LOGS_DEBUG"`
Trace bool `envconfig:"DRONE_LOGS_TRACE"`
Color bool `envconfig:"DRONE_LOGS_COLOR"`
Pretty bool `envconfig:"DRONE_LOGS_PRETTY"`
Text bool `envconfig:"DRONE_LOGS_TEXT"`
}
// Repository provides the repository configuration.
Repository struct {
Filter []string `envconfig:"DRONE_REPOSITORY_FILTER"`
}
// Registries provides the registry configuration.
Registries struct {
Endpoint string `envconfig:"DRONE_REGISTRY_ENDPOINT"`
Password string `envconfig:"DRONE_REGISTRY_SECRET"`
SkipVerify bool `envconfig:"DRONE_REGISTRY_SKIP_VERIFY"`
}
// Secrets provides the secret configuration.
Secrets struct {
Endpoint string `envconfig:"DRONE_SECRET_ENDPOINT"`
Password string `envconfig:"DRONE_SECRET_SECRET"`
SkipVerify bool `envconfig:"DRONE_SECRET_SKIP_VERIFY"`
}
// RPC provides the rpc configuration.
RPC struct {
Server string `envconfig:"DRONE_RPC_SERVER"`
Secret string `envconfig:"DRONE_RPC_SECRET"`
Debug bool `envconfig:"DRONE_RPC_DEBUG"`
Host string `envconfig:"DRONE_RPC_HOST"`
Proto string `envconfig:"DRONE_RPC_PROTO"`
// Hosts map[string]string `envconfig:"DRONE_RPC_EXTRA_HOSTS"`
}
Agent struct {
Enabled bool `envconfig:"DRONE_AGENTS_ENABLED"`
}
// Runner provides the runner configuration.
Runner struct {
Local bool `envconfig:"DRONE_RUNNER_LOCAL"`
Image string `envconfig:"DRONE_RUNNER_IMAGE" default:"drone/controller:1.0.0-rc.5"`
Platform string `envconfig:"DRONE_RUNNER_PLATFORM" default:"linux/amd64"`
OS string `envconfig:"DRONE_RUNNER_OS"`
Arch string `envconfig:"DRONE_RUNNER_ARCH"`
Kernel string `envconfig:"DRONE_RUNNER_KERNEL"`
Variant string `envconfig:"DRONE_RUNNER_VARIANT"`
Machine string `envconfig:"DRONE_RUNNER_NAME"`
Capacity int `envconfig:"DRONE_RUNNER_CAPACITY" default:"2"`
Labels map[string]string `envconfig:"DRONE_RUNNER_LABELS"`
Volumes []string `envconfig:"DRONE_RUNNER_VOLUMES"`
Networks []string `envconfig:"DRONE_RUNNER_NETWORKS"`
Devices []string `envconfig:"DRONE_RUNNER_DEVICES"`
Privileged []string `envconfig:"DRONE_RUNNER_PRIVILEGED_IMAGES"`
Environ map[string]string `envconfig:"DRONE_RUNNER_ENVIRON"`
Limits struct {
MemSwapLimit Bytes `envconfig:"DRONE_LIMIT_MEM_SWAP"`
MemLimit Bytes `envconfig:"DRONE_LIMIT_MEM"`
ShmSize Bytes `envconfig:"DRONE_LIMIT_SHM_SIZE"`
CPUQuota int64 `envconfig:"DRONE_LIMIT_CPU_QUOTA"`
CPUShares int64 `envconfig:"DRONE_LIMIT_CPU_SHARES"`
CPUSet string `envconfig:"DRONE_LIMIT_CPU_SET"`
}
}
// Server provides the server configuration.
Server struct {
Addr string `envconfig:"-"`
Host string `envconfig:"DRONE_SERVER_HOST" default:"localhost:8080"`
Port string `envconfig:"DRONE_SERVER_PORT" default:":8080"`
Proto string `envconfig:"DRONE_SERVER_PROTO" default:"http"`
Acme bool `envconfig:"DRONE_TLS_AUTOCERT"`
Cert string `envconfig:"DRONE_TLS_CERT"`
Key string `envconfig:"DRONE_TLS_KEY"`
}
// Proxy provides proxy server configuration.
Proxy struct {
Addr string `envconfig:"-"`
Host string `envconfig:"DRONE_SERVER_PROXY_HOST"`
Proto string `envconfig:"DRONE_SERVER_PROXY_PROTO"`
}
// Registration configuration.
Registration struct {
Closed bool `envconfig:"DRONE_REGISTRATION_CLOSED"`
}
// Authentication Controller configuration
Authentication struct {
Endpoint string `envconfig:"DRONE_AUTHENTICATION_ENDPOINT"`
Token string `envconfig:"DRONE_AUTHENTICATION_TOKEN"`
SkipVerify bool `envconfig:"DRONE_AUTHENTICATION_SKIP_VERIFY"`
}
// Session provides the session configuration.
Session struct {
Timeout time.Duration `envconfig:"DRONE_COOKIE_TIMEOUT" default:"720h"`
Secret string `envconfig:"DRONE_COOKIE_SECRET"`
}
// Status provides status configurations.
Status struct {
Disabled bool `envconfig:"DRONE_STATUS_DISABLED"`
Name string `envconfig:"DRONE_STATUS_NAME"`
}
// Users provides the user configuration.
Users struct {
Create UserCreate `envconfig:"DRONE_USER_CREATE"`
Filter []string `envconfig:"DRONE_USER_FILTER"`
MinAge time.Duration `envconfig:"DRONE_MIN_AGE"`
}
// Webhook provides the webhook configuration.
Webhook struct {
Endpoint []string `envconfig:"DRONE_WEBHOOK_ENDPOINT"`
Secret string `envconfig:"DRONE_WEBHOOK_SECRET"`
SkipVerify bool `envconfig:"DRONE_WEBHOOK_SKIP_VERIFY"`
}
// Yaml provides the yaml webhook configuration.
Yaml struct {
Endpoint string `envconfig:"DRONE_YAML_ENDPOINT"`
Secret string `envconfig:"DRONE_YAML_SECRET"`
SkipVerify bool `envconfig:"DRONE_YAML_SKIP_VERIFY"`
}
//
// Source code management.
//
// Bitbucket provides the bitbucket client configuration.
Bitbucket struct {
ClientID string `envconfig:"DRONE_BITBUCKET_CLIENT_ID"`
ClientSecret string `envconfig:"DRONE_BITBUCKET_CLIENT_SECRET"`
SkipVerify bool `envconfig:"DRONE_BITBUCKET_SKIP_VERIFY"`
Debug bool `envconfig:"DRONE_BITBUCKET_DEBUG"`
}
// Gitea provides the gitea client configuration.
Gitea struct {
Server string `envconfig:"DRONE_GITEA_SERVER"`
SkipVerify bool `envconfig:"DRONE_GITEA_SKIP_VERIFY"`
Debug bool `envconfig:"DRONE_GITEA_DEBUG"`
}
// Github provides the github client configuration.
Github struct {
Server string `envconfig:"DRONE_GITHUB_SERVER" default:"https://github.com"`
APIServer string `envconfig:"DRONE_GITHUB_API_SERVER"`
ClientID string `envconfig:"DRONE_GITHUB_CLIENT_ID"`
ClientSecret string `envconfig:"DRONE_GITHUB_CLIENT_SECRET"`
SkipVerify bool `envconfig:"DRONE_GITHUB_SKIP_VERIFY"`
Scope []string `envconfig:"DRONE_GITHUB_SCOPE" default:"repo,repo:status,user:email,read:org"`
RateLimit int `envconfig:"DRONE_GITHUB_USER_RATELIMIT"`
Debug bool `envconfig:"DRONE_GITHUB_DEBUG"`
}
// GitLab provides the gitlab client configuration.
GitLab struct {
Server string `envconfig:"DRONE_GITLAB_SERVER" default:"https://gitlab.com"`
ClientID string `envconfig:"DRONE_GITLAB_CLIENT_ID"`
ClientSecret string `envconfig:"DRONE_GITLAB_CLIENT_SECRET"`
SkipVerify bool `envconfig:"DRONE_GITLAB_SKIP_VERIFY"`
Debug bool `envconfig:"DRONE_GITLAB_DEBUG"`
}
// Gogs provides the gogs client configuration.
Gogs struct {
Server string `envconfig:"DRONE_GOGS_SERVER"`
SkipVerify bool `envconfig:"DRONE_GOGS_SKIP_VERIFY"`
Debug bool `envconfig:"DRONE_GOGS_DEBUG"`
}
// Stash provides the stash client configuration.
Stash struct {
Server string `envconfig:"DRONE_STASH_SERVER"`
ConsumerKey string `envconfig:"DRONE_STASH_CONSUMER_KEY"`
ConsumerSecret string `envconfig:"DRONE_STASH_CONSUMER_SECRET"`
PrivateKey string `envconfig:"DRONE_STASH_PRIVATE_KEY"`
SkipVerify bool `envconfig:"DRONE_STASH_SKIP_VERIFY"`
Debug bool `envconfig:"DRONE_STASH_DEBUG"`
}
// S3 provides the storage configuration.
S3 struct {
Bucket string `envconfig:"DRONE_S3_BUCKET"`
Prefix string `envconfig:"DRONE_S3_PREFIX"`
}
// HTTP provides http configuration.
HTTP struct {
AllowedHosts []string `envconfig:"DRONE_HTTP_ALLOWED_HOSTS"`
HostsProxyHeaders []string `envconfig:"DRONE_HTTP_PROXY_HEADERS"`
SSLRedirect bool `envconfig:"DRONE_HTTP_SSL_REDIRECT"`
SSLTemporaryRedirect bool `envconfig:"DRONE_HTTP_SSL_TEMPORARY_REDIRECT"`
SSLHost string `envconfig:"DRONE_HTTP_SSL_HOST"`
SSLProxyHeaders map[string]string `envconfig:"DRONE_HTTP_SSL_PROXY_HEADERS"`
STSSeconds int64 `envconfig:"DRONE_HTTP_STS_SECONDS"`
STSIncludeSubdomains bool `envconfig:"DRONE_HTTP_STS_INCLUDE_SUBDOMAINS"`
STSPreload bool `envconfig:"DRONE_HTTP_STS_PRELOAD"`
ForceSTSHeader bool `envconfig:"DRONE_HTTP_STS_FORCE_HEADER"`
BrowserXSSFilter bool `envconfig:"DRONE_HTTP_BROWSER_XSS_FILTER" default:"true"`
FrameDeny bool `envconfig:"DRONE_HTTP_FRAME_DENY" default:"true"`
ContentTypeNosniff bool `envconfig:"DRONE_HTTP_CONTENT_TYPE_NO_SNIFF"`
ContentSecurityPolicy string `envconfig:"DRONE_HTTP_CONTENT_SECURITY_POLICY"`
ReferrerPolicy string `envconfig:"DRONE_HTTP_REFERRER_POLICY"`
}
)
// Environ returns the settings from the environment.
func Environ() (Config, error) {
cfg := Config{}
err := envconfig.Process("", &cfg)
defaultAddress(&cfg)
defaultProxy(&cfg)
defaultRunner(&cfg)
defaultSession(&cfg)
defaultCallback(&cfg)
configureGithub(&cfg)
return cfg, err
}
// String returns the configuration in string format.
func (c *Config) String() string {
out, _ := yaml.Marshal(c)
return string(out)
}
func defaultAddress(c *Config) {
if c.Server.Key != "" || c.Server.Cert != "" || c.Server.Acme {
c.Server.Port = ":443"
c.Server.Proto = "https"
}
c.Server.Addr = c.Server.Proto + "://" + c.Server.Host
}
func defaultProxy(c *Config) {
if c.Proxy.Host == "" {
c.Proxy.Host = c.Server.Host
}
if c.Proxy.Proto == "" {
c.Proxy.Proto = c.Server.Proto
}
c.Proxy.Addr = c.Proxy.Proto + "://" + c.Proxy.Host
}
func defaultCallback(c *Config) {
if c.RPC.Host == "" {
c.RPC.Host = c.Server.Host
}
if c.RPC.Proto == "" {
c.RPC.Proto = c.Server.Proto
}
}
func defaultRunner(c *Config) {
if c.Runner.Machine == "" {
c.Runner.Machine = hostname
}
parts := strings.Split(c.Runner.Platform, "/")
if len(parts) == 2 && c.Runner.OS == "" {
c.Runner.OS = parts[0]
}
if len(parts) == 2 && c.Runner.Arch == "" {
c.Runner.Arch = parts[1]
}
}
func defaultSession(c *Config) {
if c.Session.Secret == "" {
c.Session.Secret = uniuri.NewLen(32)
}
}
func configureGithub(c *Config) {
if c.Github.APIServer != "" {
return
}
if c.Github.Server == "https://github.com" {
c.Github.APIServer = "https://api.github.com"
} else {
c.Github.APIServer = strings.TrimSuffix(c.Github.Server, "/") + "/api/v3"
}
}
// Bytes stores number bytes (e.g. megabytes)
type Bytes int64
// Decode implements a decoder that parses a string representation
// of bytes into the number of bytes it represents.
func (b *Bytes) Decode(value string) error {
v, err := humanize.ParseBytes(value)
*b = Bytes(v)
return err
}
// Int64 returns the int64 value of the Byte.
func (b *Bytes) Int64() int64 {
return int64(*b)
}
// String returns the string value of the Byte.
func (b *Bytes) String() string {
return fmt.Sprint(*b)
}
// UserCreate stores account information used to bootstrap
// the admin user account when the system initializes.
type UserCreate struct {
Username string
Machine bool
Admin bool
Token string
}
// Decode implements a decoder that extracts user information
// from the environment variable string.
func (u *UserCreate) Decode(value string) error {
for _, param := range strings.Split(value, ",") {
parts := strings.Split(param, ":")
if len(parts) != 2 {
continue
}
key := parts[0]
val := parts[1]
switch key {
case "username":
u.Username = val
case "token":
u.Token = val
case "machine":
u.Machine = val == "true"
case "admin":
u.Admin = val == "true"
}
}
return nil
}

View file

@ -0,0 +1,245 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net/http"
"net/http/httputil"
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/driver/bitbucket"
"github.com/drone/go-scm/scm/driver/gitea"
"github.com/drone/go-scm/scm/driver/github"
"github.com/drone/go-scm/scm/driver/gitlab"
"github.com/drone/go-scm/scm/driver/gogs"
"github.com/drone/go-scm/scm/driver/stash"
"github.com/drone/go-scm/scm/transport/oauth1"
"github.com/drone/go-scm/scm/transport/oauth2"
"github.com/google/wire"
"github.com/sirupsen/logrus"
)
// wire set for loading the scm client.
var clientSet = wire.NewSet(
provideClient,
)
// provideBitbucketClient is a Wire provider function that
// returns a Source Control Management client based on the
// environment configuration.
func provideClient(config config.Config) *scm.Client {
switch {
case config.Bitbucket.ClientID != "":
return provideBitbucketClient(config)
case config.Github.ClientID != "":
return provideGithubClient(config)
case config.Gitea.Server != "":
return provideGiteaClient(config)
case config.GitLab.ClientID != "":
return provideGitlabClient(config)
case config.Gogs.Server != "":
return provideGogsClient(config)
case config.Stash.ConsumerKey != "":
return provideStashClient(config)
}
logrus.Fatalln("main: source code management system not configured")
return nil
}
// provideBitbucketClient is a Wire provider function that
// returns a Bitbucket Cloud client based on the environment
// configuration.
func provideBitbucketClient(config config.Config) *scm.Client {
client := bitbucket.NewDefault()
client.Client = &http.Client{
Transport: &oauth2.Transport{
Source: &oauth2.Refresher{
ClientID: config.Bitbucket.ClientID,
ClientSecret: config.Bitbucket.ClientSecret,
Endpoint: "https://bitbucket.org/site/oauth2/access_token",
Source: oauth2.ContextTokenSource(),
},
},
}
if config.Bitbucket.Debug {
client.DumpResponse = httputil.DumpResponse
}
return client
}
// provideGithubClient is a Wire provider function that returns
// a GitHub client based on the environment configuration.
func provideGithubClient(config config.Config) *scm.Client {
client, err := github.New(config.Github.APIServer)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create the GitHub client")
}
if config.Github.Debug {
client.DumpResponse = httputil.DumpResponse
}
client.Client = &http.Client{
Transport: &oauth2.Transport{
Source: oauth2.ContextTokenSource(),
Base: defaultTransport(config.Github.SkipVerify),
},
}
return client
}
// provideGiteaClient is a Wire provider function that returns
// a Gitea client based on the environment configuration.
func provideGiteaClient(config config.Config) *scm.Client {
client, err := gitea.New(config.Gitea.Server)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create the Gitea client")
}
if config.Gitea.Debug {
client.DumpResponse = httputil.DumpResponse
}
client.Client = &http.Client{
Transport: &oauth2.Transport{
Scheme: oauth2.SchemeToken,
Source: oauth2.ContextTokenSource(),
Base: defaultTransport(config.Gitea.SkipVerify),
},
}
return client
}
// provideGitlabClient is a Wire provider function that returns
// a GitLab client based on the environment configuration.
func provideGitlabClient(config config.Config) *scm.Client {
logrus.WithField("server", config.GitLab.Server).
WithField("client", config.GitLab.ClientID).
WithField("skip_verify", config.GitLab.SkipVerify).
Debugln("main: creating the GitLab client")
client, err := gitlab.New(config.GitLab.Server)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create the GitLab client")
}
if config.GitLab.Debug {
client.DumpResponse = httputil.DumpResponse
}
client.Client = &http.Client{
Transport: &oauth2.Transport{
Source: oauth2.ContextTokenSource(),
Base: defaultTransport(config.GitLab.SkipVerify),
},
}
return client
}
// provideGogsClient is a Wire provider function that returns
// a Gogs client based on the environment configuration.
func provideGogsClient(config config.Config) *scm.Client {
logrus.WithField("server", config.Gogs.Server).
WithField("skip_verify", config.Gogs.SkipVerify).
Debugln("main: creating the Gogs client")
client, err := gogs.New(config.Gogs.Server)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create the Gogs client")
}
if config.Gogs.Debug {
client.DumpResponse = httputil.DumpResponse
}
client.Client = &http.Client{
Transport: &oauth2.Transport{
Scheme: oauth2.SchemeToken,
Source: oauth2.ContextTokenSource(),
Base: defaultTransport(config.Gogs.SkipVerify),
},
}
return client
}
// provideStashClient is a Wire provider function that returns
// a Stash client based on the environment configuration.
func provideStashClient(config config.Config) *scm.Client {
logrus.WithField("server", config.Stash.Server).
WithField("skip_verify", config.Stash.SkipVerify).
Debugln("main: creating the Stash client")
privateKey, err := parsePrivateKeyFile(config.Stash.PrivateKey)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot parse the Stash Private Key")
}
client, err := stash.New(config.Stash.Server)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create the Stash client")
}
if config.Stash.Debug {
client.DumpResponse = httputil.DumpResponse
}
client.Client = &http.Client{
Transport: &oauth1.Transport{
ConsumerKey: config.Stash.ConsumerKey,
PrivateKey: privateKey,
Source: oauth1.ContextTokenSource(),
Base: defaultTransport(config.Stash.SkipVerify),
},
}
return client
}
// defaultClient provides a default http.Client. If skipverify
// is true, the default transport will skip ssl verification.
func defaultClient(skipverify bool) *http.Client {
client := &http.Client{}
client.Transport = defaultTransport(skipverify)
return client
}
// defaultTransport provides a default http.Transport. If
// skipverify is true, the transport will skip ssl verification.
func defaultTransport(skipverify bool) http.RoundTripper {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: skipverify,
},
}
}
// parsePrivateKeyFile is a helper function that parses an
// RSA Private Key file encoded in PEM format.
func parsePrivateKeyFile(path string) (*rsa.PrivateKey, error) {
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return parsePrivateKey(d)
}
// parsePrivateKey is a helper function that parses an RSA
// Private Key encoded in PEM format.
func parsePrivateKey(data []byte) (*rsa.PrivateKey, error) {
p, _ := pem.Decode(data)
return x509.ParsePKCS1PrivateKey(p.Bytes)
}

View file

@ -0,0 +1,53 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/service/license"
"github.com/drone/go-scm/scm"
"github.com/google/wire"
"github.com/sirupsen/logrus"
)
// wire set for loading the license.
var licenseSet = wire.NewSet(
provideLicense,
license.NewService,
)
// provideLicense is a Wire provider function that returns a
// license loaded from a license file.
func provideLicense(client *scm.Client, config config.Config) *core.License {
l, err := license.Load(config.License)
if config.License == "" {
l = license.Trial(client.Driver.String())
} else if err != nil {
logrus.WithError(err).
Fatalln("main: invalid or expired license")
}
logrus.WithFields(
logrus.Fields{
"kind": l.Kind,
"expires": l.Expires,
"repo.limit": l.Repos,
"user.limit": l.Users,
"build.limit": l.Builds,
},
).Debugln("main: license loaded")
return l
}

View file

@ -0,0 +1,164 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/bitbucket"
"github.com/drone/go-login/login/github"
"github.com/drone/go-login/login/gitlab"
"github.com/drone/go-login/login/gogs"
"github.com/drone/go-login/login/stash"
"github.com/drone/go-scm/scm/transport/oauth2"
"github.com/google/wire"
"github.com/sirupsen/logrus"
)
// wire set for loading the authenticator.
var loginSet = wire.NewSet(
provideLogin,
provideRefresher,
)
// provideLogin is a Wire provider function that returns an
// autenticator based on the environment configuration.
func provideLogin(config config.Config) login.Middleware {
switch {
case config.Bitbucket.ClientID != "":
return provideBitbucketLogin(config)
case config.Github.ClientID != "":
return provideGithubLogin(config)
case config.Gitea.Server != "":
return provideGiteaLogin(config)
case config.GitLab.ClientID != "":
return provideGitlabLogin(config)
case config.Gogs.Server != "":
return provideGogsLogin(config)
case config.Stash.ConsumerKey != "":
return provideStashLogin(config)
}
logrus.Fatalln("main: source code management system not configured")
return nil
}
// provideBitbucketLogin is a Wire provider function that
// returns a Bitbucket Cloud autenticator based on the
// environment configuration.
func provideBitbucketLogin(config config.Config) login.Middleware {
if config.Bitbucket.ClientID == "" {
return nil
}
return &bitbucket.Config{
ClientID: config.Bitbucket.ClientID,
ClientSecret: config.Bitbucket.ClientSecret,
RedirectURL: config.Server.Addr + "/login",
}
}
// provideGithubLogin is a Wire provider function that returns
// a GitHub autenticator based on the environment configuration.
func provideGithubLogin(config config.Config) login.Middleware {
if config.Github.ClientID == "" {
return nil
}
return &github.Config{
ClientID: config.Github.ClientID,
ClientSecret: config.Github.ClientSecret,
Scope: config.Github.Scope,
Server: config.Github.Server,
Client: defaultClient(config.Github.SkipVerify),
Logger: logrus.StandardLogger(),
}
}
// provideGiteaLogin is a Wire provider function that returns
// a Gitea autenticator based on the environment configuration.
func provideGiteaLogin(config config.Config) login.Middleware {
if config.Gitea.Server == "" {
return nil
}
return &gogs.Config{
Label: "drone",
Login: "/login/form",
Server: config.Gitea.Server,
Client: defaultClient(config.Gitea.SkipVerify),
}
}
// provideGitlabLogin is a Wire provider function that returns
// a GitLab autenticator based on the environment configuration.
func provideGitlabLogin(config config.Config) login.Middleware {
if config.GitLab.ClientID == "" {
return nil
}
return &gitlab.Config{
ClientID: config.GitLab.ClientID,
ClientSecret: config.GitLab.ClientSecret,
RedirectURL: config.Server.Addr + "/login",
Server: config.GitLab.Server,
Client: defaultClient(config.GitLab.SkipVerify),
}
}
// provideGogsLogin is a Wire provider function that returns
// a Gogs autenticator based on the environment configuration.
func provideGogsLogin(config config.Config) login.Middleware {
if config.Gogs.Server == "" {
return nil
}
return &gogs.Config{
Label: "drone",
Login: "/login/form",
Server: config.Gogs.Server,
Client: defaultClient(config.Gogs.SkipVerify),
}
}
// provideStashLogin is a Wire provider function that returns
// a Stash autenticator based on the environment configuration.
func provideStashLogin(config config.Config) login.Middleware {
if config.Stash.ConsumerKey == "" {
return nil
}
privateKey, err := stash.ParsePrivateKeyFile(config.Stash.PrivateKey)
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot parse Private Key file")
}
return &stash.Config{
Address: config.Stash.Server,
ConsumerKey: config.Stash.ConsumerKey,
ConsumerSecret: config.Stash.ConsumerSecret,
PrivateKey: privateKey,
CallbackURL: config.Server.Addr + "/login",
Client: defaultClient(config.Stash.SkipVerify),
}
}
// provideRefresher is a Wire provider function that returns
// an oauth token refresher for Bitbucket.
func provideRefresher(config config.Config) *oauth2.Refresher {
if config.Bitbucket.ClientID != "" {
return &oauth2.Refresher{
ClientID: config.Bitbucket.ClientID,
ClientSecret: config.Bitbucket.ClientSecret,
Endpoint: "https://bitbucket.org/site/oauth2/access_token",
Source: oauth2.ContextTokenSource(),
}
}
return nil
}

View file

@ -0,0 +1,102 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
spec "github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/plugin/admission"
"github.com/drone/drone/plugin/config"
"github.com/drone/drone/plugin/registry"
"github.com/drone/drone/plugin/secret"
"github.com/drone/drone/plugin/webhook"
"github.com/drone/go-scm/scm"
"github.com/google/wire"
)
// wire set for loading plugins.
var pluginSet = wire.NewSet(
provideAdmissionPlugin,
provideConfigPlugin,
provideRegistryPlugin,
provideSecretPlugin,
provideWebhookPlugin,
)
// provideAdmissionPlugin is a Wire provider function that
// returns an admission plugin based on the environment
// configuration.
func provideAdmissionPlugin(client *scm.Client, orgs core.OrganizationService, users core.UserService, config spec.Config) core.AdmissionService {
return admission.Combine(
admission.Membership(orgs, config.Users.Filter),
admission.Open(config.Registration.Closed),
admission.Nobot(users, config.Users.MinAge),
)
}
// provideConfigPlugin is a Wire provider function that returns
// a yaml configuration plugin based on the environment
// configuration.
func provideConfigPlugin(client *scm.Client, contents core.FileService, conf spec.Config) core.ConfigService {
return config.Combine(
config.Global(
conf.Yaml.Endpoint,
conf.Yaml.Secret,
conf.Yaml.SkipVerify,
),
config.Repository(contents),
)
}
// provideRegistryPlugin is a Wire provider function that
// returns a registry plugin based on the environment
// configuration.
func provideRegistryPlugin(config spec.Config) core.RegistryService {
return registry.Combine(
registry.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
),
registry.FileSource(
config.Docker.Config,
),
registry.EndpointSource(
config.Registries.Endpoint,
config.Registries.Password,
config.Registries.SkipVerify,
),
)
}
// provideSecretPlugin is a Wire provider function that returns
// a secret plugin based on the environment configuration.
func provideSecretPlugin(config spec.Config) core.SecretService {
return secret.External(
config.Secrets.Endpoint,
config.Secrets.Password,
config.Secrets.SkipVerify,
)
}
// provideWebhookPlugin is a Wire provider function that returns
// a webhook plugin based on the environment configuration.
func provideWebhookPlugin(config spec.Config) core.WebhookSender {
return webhook.New(
config.Webhook.Endpoint,
config.Webhook.Secret,
)
}

View file

@ -0,0 +1,78 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone-runtime/engine/docker"
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/operator/manager"
"github.com/drone/drone/operator/runner"
"github.com/google/wire"
"github.com/sirupsen/logrus"
)
// wire set for loading the server.
var runnerSet = wire.NewSet(
provideRunner,
)
// provideRunner is a Wire provider function that returns a
// local build runner configured from the environment.
func provideRunner(
manager manager.BuildManager,
secrets core.SecretService,
registry core.RegistryService,
config config.Config,
) *runner.Runner {
// the local runner is only created when the nomad or
// kubernetes scheduler are disabled
if config.Nomad.Enabled || config.Kube.Enabled || config.Agent.Enabled {
return nil
}
engine, err := docker.NewEnv()
if err != nil {
logrus.WithError(err).
Fatalln("cannot load the docker engine")
return nil
}
return &runner.Runner{
Platform: config.Runner.Platform,
OS: config.Runner.OS,
Arch: config.Runner.Arch,
Kernel: config.Runner.Kernel,
Variant: config.Runner.Variant,
Engine: engine,
Manager: manager,
Secrets: secrets,
Registry: registry,
Volumes: config.Runner.Volumes,
Networks: config.Runner.Networks,
Devices: config.Runner.Devices,
Privileged: config.Runner.Privileged,
Machine: config.Runner.Machine,
Labels: config.Runner.Labels,
Environ: config.Runner.Environ,
Limits: runner.Limits{
MemSwapLimit: int64(config.Runner.Limits.MemSwapLimit),
MemLimit: int64(config.Runner.Limits.MemLimit),
ShmSize: int64(config.Runner.Limits.ShmSize),
CPUQuota: config.Runner.Limits.CPUQuota,
CPUShares: config.Runner.Limits.CPUShares,
CPUSet: config.Runner.Limits.CPUSet,
},
}
}

View file

@ -0,0 +1,139 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/scheduler/docker"
"github.com/drone/drone/scheduler/kube"
"github.com/drone/drone/scheduler/nomad"
"github.com/drone/drone/scheduler/queue"
"github.com/google/wire"
"github.com/sirupsen/logrus"
)
// wire set for loading the scheduler.
var schedulerSet = wire.NewSet(
provideScheduler,
)
// provideScheduler is a Wire provider function that returns a
// scheduler based on the environment configuration.
func provideScheduler(store core.StageStore, config config.Config) core.Scheduler {
switch {
case config.Agent.Enabled:
return provideQueueScheduler(store, config)
case config.Kube.Enabled:
return provideKubernetesScheduler(config)
case config.Nomad.Enabled:
return provideNomadScheduler(config)
default:
return provideQueueScheduler(store, config)
// return provideDockerScheduler(config)
}
}
// provideDockerScheduler is a Wire provider function that
// returns an in-memory Docker scheduler.
func provideDockerScheduler(config config.Config) core.Scheduler {
logrus.Info("main: local docker runner enabled")
return docker.New()
}
// provideKubernetesScheduler is a Wire provider function that
// returns a nomad kubernetes from the environment configuration.
func provideKubernetesScheduler(config config.Config) core.Scheduler {
logrus.Info("main: kubernetes runtime enabled")
sched, err := kube.FromConfig(kube.Config{
Namespace: config.Kube.Namespace,
ServiceAccount: config.Kube.ServiceAccountName,
ConfigURL: config.Kube.URL,
ConfigPath: config.Kube.Path,
TTL: config.Kube.TTL,
Image: config.Kube.Image,
ImagePullPolicy: config.Kube.PullPolicy,
ImagePrivileged: config.Runner.Privileged,
// LimitMemory: config.Nomad.Memory,
// LimitCompute: config.Nomad.CPU,
// RequestMemory: config.Nomad.Memory,
// RequestCompute: config.Nomad.CPU,
CallbackHost: config.RPC.Host,
CallbackProto: config.RPC.Proto,
CallbackSecret: config.RPC.Secret,
SecretToken: config.Secrets.Password,
SecretEndpoint: config.Secrets.Endpoint,
SecretInsecure: config.Secrets.SkipVerify,
RegistryToken: config.Registries.Password,
RegistryEndpoint: config.Registries.Endpoint,
RegistryInsecure: config.Registries.SkipVerify,
LogDebug: config.Logging.Debug,
LogTrace: config.Logging.Trace,
LogPretty: config.Logging.Pretty,
LogText: config.Logging.Text,
})
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create kubernetes client")
}
return sched
}
// provideNomadScheduler is a Wire provider function that returns
// a nomad scheduler from the environment configuration.
func provideNomadScheduler(config config.Config) core.Scheduler {
logrus.Info("main: nomad runtime enabled")
sched, err := nomad.FromConfig(nomad.Config{
Datacenter: config.Nomad.Datacenters,
Namespace: config.Nomad.Namespace,
Region: config.Nomad.Region,
DockerImage: config.Nomad.Image,
DockerImagePull: config.Nomad.ImagePull,
DockerImagePriv: config.Runner.Privileged,
DockerHost: "",
DockerHostWin: "",
// LimitMemory: config.Nomad.Memory,
// LimitCompute: config.Nomad.CPU,
RequestMemory: config.Nomad.Memory,
RequestCompute: config.Nomad.CPU,
CallbackHost: config.RPC.Host,
CallbackProto: config.RPC.Proto,
CallbackSecret: config.RPC.Secret,
SecretToken: config.Secrets.Password,
SecretEndpoint: config.Secrets.Endpoint,
SecretInsecure: config.Secrets.SkipVerify,
RegistryToken: config.Registries.Password,
RegistryEndpoint: config.Registries.Endpoint,
RegistryInsecure: config.Registries.SkipVerify,
LogDebug: config.Logging.Debug,
LogTrace: config.Logging.Trace,
LogPretty: config.Logging.Pretty,
LogText: config.Logging.Text,
})
if err != nil {
logrus.WithError(err).
Fatalln("main: cannot create nomad client")
}
return sched
}
// provideQueueScheduler is a Wire provider function that
// returns an in-memory scheduler for use by the built-in
// docker runner, and by remote agents.
func provideQueueScheduler(store core.StageStore, config config.Config) core.Scheduler {
logrus.Info("main: nomad runtime enabled")
return queue.New(store)
}

View file

@ -0,0 +1,95 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"net/http"
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/handler/api"
"github.com/drone/drone/handler/web"
"github.com/drone/drone/metric"
"github.com/drone/drone/operator/manager"
"github.com/drone/drone/operator/manager/rpc"
"github.com/drone/drone/server"
"github.com/google/wire"
"github.com/go-chi/chi"
"github.com/unrolled/secure"
)
// wire set for loading the server.
var serverSet = wire.NewSet(
manager.New,
metric.NewServer,
api.New,
web.New,
provideRouter,
provideRPC,
provideServer,
provideServerOptions,
)
// provideRouter is a Wire provider function that returns a
// router that is serves the provided handlers.
func provideRouter(api api.Server, web web.Server, rpc http.Handler, metrics *metric.Server) *chi.Mux {
r := chi.NewRouter()
r.Mount("/metrics", metrics)
r.Mount("/api", api.Handler())
r.Mount("/rpc", rpc)
r.Mount("/", web.Handler())
return r
}
// provideRPC is a Wire provider function that returns an rpc
// handler that exposes the build manager to a remote agent.
func provideRPC(m manager.BuildManager, config config.Config) http.Handler {
return rpc.NewServer(m, config.RPC.Secret)
}
// provideServer is a Wire provider function that returns an
// http server that is configured from the environment.
func provideServer(handler *chi.Mux, config config.Config) *server.Server {
return &server.Server{
Acme: config.Server.Acme,
Addr: config.Server.Port,
Cert: config.Server.Cert,
Key: config.Server.Key,
Host: config.Server.Host,
Handler: handler,
}
}
// provideServerOptions is a Wire provider function that returns
// the http web server security option from the environment.
func provideServerOptions(config config.Config) secure.Options {
return secure.Options{
AllowedHosts: config.HTTP.AllowedHosts,
HostsProxyHeaders: config.HTTP.HostsProxyHeaders,
SSLRedirect: config.HTTP.SSLRedirect,
SSLTemporaryRedirect: config.HTTP.SSLTemporaryRedirect,
SSLHost: config.HTTP.SSLHost,
SSLProxyHeaders: config.HTTP.SSLProxyHeaders,
STSSeconds: config.HTTP.STSSeconds,
STSIncludeSubdomains: config.HTTP.STSIncludeSubdomains,
STSPreload: config.HTTP.STSPreload,
ForceSTSHeader: config.HTTP.ForceSTSHeader,
FrameDeny: config.HTTP.FrameDeny,
ContentTypeNosniff: config.HTTP.ContentTypeNosniff,
BrowserXssFilter: config.HTTP.BrowserXSSFilter,
ContentSecurityPolicy: config.HTTP.ContentSecurityPolicy,
ReferrerPolicy: config.HTTP.ReferrerPolicy,
}
}

View file

@ -0,0 +1,136 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/livelog"
"github.com/drone/drone/pubsub"
"github.com/drone/drone/service/commit"
"github.com/drone/drone/service/content"
"github.com/drone/drone/service/content/cache"
"github.com/drone/drone/service/hook"
"github.com/drone/drone/service/hook/parser"
"github.com/drone/drone/service/netrc"
"github.com/drone/drone/service/org"
"github.com/drone/drone/service/repo"
"github.com/drone/drone/service/status"
"github.com/drone/drone/service/syncer"
"github.com/drone/drone/service/token"
"github.com/drone/drone/service/user"
"github.com/drone/drone/session"
"github.com/drone/drone/trigger"
"github.com/drone/drone/trigger/cron"
"github.com/drone/drone/version"
"github.com/drone/go-scm/scm"
"github.com/google/wire"
)
// wire set for loading the services.
var serviceSet = wire.NewSet(
commit.New,
cron.New,
livelog.New,
orgs.New,
parser.New,
pubsub.New,
repo.New,
token.Renewer,
trigger.New,
user.New,
provideContentService,
provideHookService,
provideNetrcService,
provideSession,
provideStatusService,
provideSyncer,
provideSystem,
)
// provideContentService is a Wire provider function that
// returns a contents service wrapped with a simple LRU cache.
func provideContentService(client *scm.Client, renewer core.Renewer) core.FileService {
return cache.Contents(
contents.New(client, renewer),
)
}
// provideHookService is a Wire provider function that returns a
// hook service based on the environment configuration.
func provideHookService(client *scm.Client, renewer core.Renewer, config config.Config) core.HookService {
return hook.New(client, config.Proxy.Addr, renewer)
}
// provideNetrcService is a Wire provider function that returns
// a netrc service based on the environment configuration.
func provideNetrcService(client *scm.Client, renewer core.Renewer, config config.Config) core.NetrcService {
return netrc.New(
client,
renewer,
config.Cloning.AlwaysAuth,
config.Cloning.Username,
config.Cloning.Password,
)
}
// provideSession is a Wire provider function that returns a
// user session based on the environment configuration.
func provideSession(store core.UserStore, config config.Config) core.Session {
return session.New(store, session.NewConfig(
config.Session.Secret,
config.Session.Timeout),
)
}
// provideUserService is a Wire provider function that returns a
// user service based on the environment configuration.
func provideStatusService(client *scm.Client, renewer core.Renewer, config config.Config) core.StatusService {
return status.New(client, renewer, status.Config{
Base: config.Server.Addr,
Name: config.Status.Name,
Disabled: config.Status.Disabled,
})
}
// provideSyncer is a Wire provider function that returns a
// repository synchronizer.
func provideSyncer(repoz core.RepositoryService,
repos core.RepositoryStore,
users core.UserStore,
batch core.Batcher,
config config.Config) core.Syncer {
sync := syncer.New(repoz, repos, users, batch)
// the user can define a filter that limits which
// repositories can be synchronized and stored in the
// database.
if filter := config.Repository.Filter; len(filter) > 0 {
sync.SetFilter(syncer.NamespaceFilter(filter))
}
return sync
}
// provideSyncer is a Wire provider function that returns the
// system details structure.
func provideSystem(config config.Config) *core.System {
return &core.System{
Proto: config.Server.Proto,
Host: config.Server.Host,
Link: config.Server.Addr,
Version: version.Version.String(),
}
}

View file

@ -0,0 +1,117 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/metric"
"github.com/drone/drone/store/batch"
"github.com/drone/drone/store/build"
"github.com/drone/drone/store/cron"
"github.com/drone/drone/store/logs"
"github.com/drone/drone/store/perm"
"github.com/drone/drone/store/repos"
"github.com/drone/drone/store/secret"
"github.com/drone/drone/store/shared/db"
"github.com/drone/drone/store/shared/encrypt"
"github.com/drone/drone/store/stage"
"github.com/drone/drone/store/step"
"github.com/drone/drone/store/user"
"github.com/google/wire"
)
// wire set for loading the stores.
var storeSet = wire.NewSet(
provideDatabase,
provideEncrypter,
provideBuildStore,
provideLogStore,
provideRepoStore,
provideStageStore,
provideUserStore,
batch.New,
cron.New,
perm.New,
secret.New,
step.New,
)
// provideDatabase is a Wire provider function that provides a
// database connection, configured from the environment.
func provideDatabase(config config.Config) (*db.DB, error) {
return db.Connect(
config.Database.Driver,
config.Database.Datasource,
)
}
// provideEncrypter is a Wire provider function that provides a
// database encrypter, configured from the environment.
func provideEncrypter(config config.Config) (encrypt.Encrypter, error) {
return encrypt.New(config.Database.Secret)
}
// provideBuildStore is a Wire provider function that provides a
// build datastore, configured from the environment, with metrics
// enabled.
func provideBuildStore(db *db.DB) core.BuildStore {
builds := build.New(db)
metric.BuildCount(builds)
metric.PendingBuildCount(builds)
metric.RunningBuildCount(builds)
return builds
}
// provideLogStore is a Wire provider function that provides a
// log datastore, configured from the environment.
func provideLogStore(db *db.DB, config config.Config) core.LogStore {
if config.S3.Bucket == "" {
return logs.New(db)
}
return logs.NewS3Env(
config.S3.Bucket,
config.S3.Prefix,
)
}
// provideStageStore is a Wire provider function that provides a
// stage datastore, configured from the environment, with metrics
// enabled.
func provideStageStore(db *db.DB) core.StageStore {
stages := stage.New(db)
metric.PendingJobCount(stages)
metric.RunningJobCount(stages)
return stages
}
// provideRepoStore is a Wire provider function that provides a
// user datastore, configured from the environment, with metrics
// enabled.
func provideRepoStore(db *db.DB) core.RepositoryStore {
repos := repos.New(db)
metric.RepoCount(repos)
return repos
}
// provideUserStore is a Wire provider function that provides a
// user datastore, configured from the environment, with metrics
// enabled.
func provideUserStore(db *db.DB) core.UserStore {
users := user.New(db)
metric.UserCount(users)
return users
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -15,26 +15,150 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/drone/drone/version"
"github.com/drone/drone/cmd/drone-server/bootstrap"
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/core"
"github.com/drone/drone/operator/runner"
"github.com/drone/drone/server"
"github.com/drone/drone/trigger/cron"
"github.com/drone/signal"
_ "github.com/joho/godotenv/autoload"
"github.com/urfave/cli"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
func main() {
app := cli.NewApp()
app.Name = "drone-server"
app.Version = version.Version.String()
app.Usage = "drone server"
app.Action = server
app.Flags = flags
app.Before = before
var envfile string
flag.StringVar(&envfile, "env-file", ".env", "Read in a file of environment variables")
flag.Parse()
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
godotenv.Load(envfile)
config, err := config.Environ()
if err != nil {
logger := logrus.WithError(err)
logger.Fatalln("main: invalid configuration")
}
initLogging(config)
ctx := signal.WithContext(
context.Background(),
)
// if trace level logging is enabled, output the
// configuration parameters.
if logrus.IsLevelEnabled(logrus.TraceLevel) {
fmt.Println(config.String())
}
app, err := InitializeApplication(config)
if err != nil {
logger := logrus.WithError(err)
logger.Fatalln("main: cannot initialize server")
}
// optionally bootstrap the system with administrative or
// machine users configured in the environment.
err = bootstrap.New(app.users).Bootstrap(ctx, &core.User{
Login: config.Users.Create.Username,
Machine: config.Users.Create.Machine,
Admin: config.Users.Create.Admin,
Hash: config.Users.Create.Token,
})
if err != nil {
logger := logrus.WithError(err)
logger.Fatalln("cannot bootstrap user account")
}
g := errgroup.Group{}
g.Go(func() error {
logrus.WithFields(
logrus.Fields{
"proto": config.Server.Proto,
"host": config.Server.Host,
"port": config.Server.Port,
"url": config.Server.Addr,
"acme": config.Server.Acme,
},
).Infoln("starting the http server")
return app.server.ListenAndServe(ctx)
})
// launches the cron runner in a goroutine. If the cron
// runner is disabled, the goroutine exits immediately
// without error.
g.Go(func() (err error) {
if config.Cron.Disabled {
return nil
}
logrus.WithField("interval", config.Cron.Interval.String()).
Infoln("starting the cron scheduler")
return app.cron.Start(ctx, config.Cron.Interval)
})
// launches the build runner in a goroutine. If the local
// runner is disabled (because nomad or kubernetes is enabled)
// then the goroutine exits immediately without error.
g.Go(func() (err error) {
if app.runner == nil {
return nil
}
logrus.WithField("threads", config.Runner.Capacity).
Infoln("main: starting the local build runner")
return app.runner.Start(ctx, config.Runner.Capacity)
})
if err := g.Wait(); err != nil {
logrus.WithError(err).Fatalln("program terminated")
}
}
// helper funciton configures the logging.
func initLogging(c config.Config) {
if c.Logging.Debug {
logrus.SetLevel(logrus.DebugLevel)
}
if c.Logging.Trace {
logrus.SetLevel(logrus.TraceLevel)
}
if c.Logging.Text {
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: c.Logging.Color,
DisableColors: !c.Logging.Color,
})
} else {
logrus.SetFormatter(&logrus.JSONFormatter{
PrettyPrint: c.Logging.Pretty,
})
}
}
// application is the main struct for the Drone server.
type application struct {
cron *cron.Scheduler
runner *runner.Runner
server *server.Server
users core.UserStore
}
// newApplication creates a new application struct.
func newApplication(
cron *cron.Scheduler,
runner *runner.Runner,
server *server.Server,
users core.UserStore) application {
return application{
users: users,
cron: cron,
server: server,
runner: runner,
}
}

View file

@ -1,752 +0,0 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/sync/errgroup"
"github.com/cncd/logging"
"github.com/cncd/pipeline/pipeline/rpc/proto"
"github.com/cncd/pubsub"
"github.com/drone/drone/plugins/sender"
"github.com/drone/drone/remote"
"github.com/drone/drone/router"
"github.com/drone/drone/router/middleware"
droneserver "github.com/drone/drone/server"
"github.com/drone/drone/store"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/contrib/ginrus"
"github.com/urfave/cli"
oldcontext "golang.org/x/net/context"
)
var flags = []cli.Flag{
cli.BoolFlag{
EnvVar: "DRONE_DEBUG",
Name: "debug",
Usage: "enable server debug mode",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_HOST,DRONE_HOST",
Name: "server-host",
Usage: "server fully qualified url (<scheme>://<host>)",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_ADDR",
Name: "server-addr",
Usage: "server address",
Value: ":8000",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_CERT",
Name: "server-cert",
Usage: "server ssl cert path",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_KEY",
Name: "server-key",
Usage: "server ssl key path",
},
cli.BoolFlag{
EnvVar: "DRONE_LETS_ENCRYPT",
Name: "lets-encrypt",
Usage: "enable let's encrypt",
},
cli.BoolFlag{
EnvVar: "DRONE_QUIC",
Name: "quic",
Usage: "enable quic",
},
cli.StringFlag{
EnvVar: "DRONE_WWW",
Name: "www",
Usage: "serve the website from disk",
Hidden: true,
},
cli.StringSliceFlag{
EnvVar: "DRONE_ADMIN",
Name: "admin",
Usage: "list of admin users",
},
cli.StringSliceFlag{
EnvVar: "DRONE_ORGS",
Name: "orgs",
Usage: "list of approved organizations",
},
cli.BoolFlag{
EnvVar: "DRONE_OPEN",
Name: "open",
Usage: "enable open user registration",
},
cli.StringFlag{
EnvVar: "DRONE_REPO_CONFIG",
Name: "repo-config",
Usage: "file path for the drone config",
Value: ".drone.yml",
},
cli.DurationFlag{
EnvVar: "DRONE_SESSION_EXPIRES",
Name: "session-expires",
Usage: "session expiration time",
Value: time.Hour * 72,
},
cli.StringSliceFlag{
EnvVar: "DRONE_ESCALATE",
Name: "escalate",
Usage: "images to run in privileged mode",
Value: &cli.StringSlice{
"plugins/docker",
"plugins/gcr",
"plugins/ecr",
},
},
cli.StringSliceFlag{
EnvVar: "DRONE_VOLUME",
Name: "volume",
},
cli.StringSliceFlag{
EnvVar: "DRONE_NETWORK",
Name: "network",
},
cli.StringFlag{
EnvVar: "DRONE_AGENT_SECRET,DRONE_SECRET",
Name: "agent-secret",
Usage: "server-agent shared password",
},
cli.StringFlag{
EnvVar: "DRONE_SECRET_ENDPOINT",
Name: "secret-service",
Usage: "secret plugin endpoint",
},
cli.StringFlag{
EnvVar: "DRONE_REGISTRY_ENDPOINT",
Name: "registry-service",
Usage: "registry plugin endpoint",
},
cli.StringFlag{
EnvVar: "DRONE_GATEKEEPER_ENDPOINT",
Name: "gating-service",
Usage: "gated build endpoint",
},
cli.StringFlag{
EnvVar: "DRONE_DATABASE_DRIVER,DATABASE_DRIVER",
Name: "driver",
Usage: "database driver",
Value: "sqlite3",
},
cli.StringFlag{
EnvVar: "DRONE_DATABASE_DATASOURCE,DATABASE_CONFIG",
Name: "datasource",
Usage: "database driver configuration string",
Value: "drone.sqlite",
},
cli.StringFlag{
EnvVar: "DRONE_PROMETHEUS_AUTH_TOKEN",
Name: "prometheus-auth-token",
Usage: "token to secure prometheus metrics endpoint",
Value: "",
},
//
// resource limit parameters
//
cli.Int64Flag{
EnvVar: "DRONE_LIMIT_MEM_SWAP",
Name: "limit-mem-swap",
Usage: "maximum swappable memory allowed in bytes",
},
cli.Int64Flag{
EnvVar: "DRONE_LIMIT_MEM",
Name: "limit-mem",
Usage: "maximum memory allowed in bytes",
},
cli.Int64Flag{
EnvVar: "DRONE_LIMIT_SHM_SIZE",
Name: "limit-shm-size",
Usage: "docker compose /dev/shm allowed in bytes",
},
cli.Int64Flag{
EnvVar: "DRONE_LIMIT_CPU_QUOTA",
Name: "limit-cpu-quota",
Usage: "impose a cpu quota",
},
cli.Int64Flag{
EnvVar: "DRONE_LIMIT_CPU_SHARES",
Name: "limit-cpu-shares",
Usage: "change the cpu shares",
},
cli.StringFlag{
EnvVar: "DRONE_LIMIT_CPU_SET",
Name: "limit-cpu-set",
Usage: "set the cpus allowed to execute containers",
},
//
// remote parameters
//
cli.BoolFlag{
EnvVar: "DRONE_GITHUB",
Name: "github",
Usage: "github driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_URL",
Name: "github-server",
Usage: "github server address",
Value: "https://github.com",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_CONTEXT",
Name: "github-context",
Usage: "github status context",
Value: "continuous-integration/drone",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_CLIENT",
Name: "github-client",
Usage: "github oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_SECRET",
Name: "github-secret",
Usage: "github oauth2 client secret",
},
cli.StringSliceFlag{
EnvVar: "DRONE_GITHUB_SCOPE",
Name: "github-scope",
Usage: "github oauth scope",
Value: &cli.StringSlice{
"repo",
"repo:status",
"user:email",
"read:org",
},
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_GIT_USERNAME",
Name: "github-git-username",
Usage: "github machine user username",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_GIT_PASSWORD",
Name: "github-git-password",
Usage: "github machine user password",
},
cli.BoolTFlag{
EnvVar: "DRONE_GITHUB_MERGE_REF",
Name: "github-merge-ref",
Usage: "github pull requests use merge ref",
},
cli.BoolFlag{
EnvVar: "DRONE_GITHUB_PRIVATE_MODE",
Name: "github-private-mode",
Usage: "github is running in private mode",
},
cli.BoolFlag{
EnvVar: "DRONE_GITHUB_SKIP_VERIFY",
Name: "github-skip-verify",
Usage: "github skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS",
Name: "gogs",
Usage: "gogs driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GOGS_URL",
Name: "gogs-server",
Usage: "gogs server address",
Value: "https://github.com",
},
cli.StringFlag{
EnvVar: "DRONE_GOGS_GIT_USERNAME",
Name: "gogs-git-username",
Usage: "gogs service account username",
},
cli.StringFlag{
EnvVar: "DRONE_GOGS_GIT_PASSWORD",
Name: "gogs-git-password",
Usage: "gogs service account password",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS_PRIVATE_MODE",
Name: "gogs-private-mode",
Usage: "gogs private mode enabled",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS_SKIP_VERIFY",
Name: "gogs-skip-verify",
Usage: "gogs skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_GITEA",
Name: "gitea",
Usage: "gitea driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GITEA_URL",
Name: "gitea-server",
Usage: "gitea server address",
Value: "https://try.gitea.io",
},
cli.StringFlag{
EnvVar: "DRONE_GITEA_CONTEXT",
Name: "gitea-context",
Usage: "gitea status context",
Value: "continuous-integration/drone",
},
cli.StringFlag{
EnvVar: "DRONE_GITEA_GIT_USERNAME",
Name: "gitea-git-username",
Usage: "gitea service account username",
},
cli.StringFlag{
EnvVar: "DRONE_GITEA_GIT_PASSWORD",
Name: "gitea-git-password",
Usage: "gitea service account password",
},
cli.BoolFlag{
EnvVar: "DRONE_GITEA_PRIVATE_MODE",
Name: "gitea-private-mode",
Usage: "gitea private mode enabled",
},
cli.BoolFlag{
EnvVar: "DRONE_GITEA_SKIP_VERIFY",
Name: "gitea-skip-verify",
Usage: "gitea skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_BITBUCKET",
Name: "bitbucket",
Usage: "bitbucket driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_BITBUCKET_CLIENT",
Name: "bitbucket-client",
Usage: "bitbucket oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_BITBUCKET_SECRET",
Name: "bitbucket-secret",
Usage: "bitbucket oauth2 client secret",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB",
Name: "gitlab",
Usage: "gitlab driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_URL",
Name: "gitlab-server",
Usage: "gitlab server address",
Value: "https://gitlab.com",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_CLIENT",
Name: "gitlab-client",
Usage: "gitlab oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_SECRET",
Name: "gitlab-secret",
Usage: "gitlab oauth2 client secret",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_GIT_USERNAME",
Name: "gitlab-git-username",
Usage: "gitlab service account username",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_GIT_PASSWORD",
Name: "gitlab-git-password",
Usage: "gitlab service account password",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB_SKIP_VERIFY",
Name: "gitlab-skip-verify",
Usage: "gitlab skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB_PRIVATE_MODE",
Name: "gitlab-private-mode",
Usage: "gitlab is running in private mode",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB_V3_API",
Name: "gitlab-v3-api",
Usage: "gitlab is running the v3 api",
},
cli.BoolFlag{
EnvVar: "DRONE_STASH",
Name: "stash",
Usage: "stash driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_URL",
Name: "stash-server",
Usage: "stash server address",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_CONSUMER_KEY",
Name: "stash-consumer-key",
Usage: "stash oauth1 consumer key",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_CONSUMER_RSA",
Name: "stash-consumer-rsa",
Usage: "stash oauth1 private key file",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_CONSUMER_RSA_STRING",
Name: "stash-consumer-rsa-string",
Usage: "stash oauth1 private key string",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_GIT_USERNAME",
Name: "stash-git-username",
Usage: "stash service account username",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_GIT_PASSWORD",
Name: "stash-git-password",
Usage: "stash service account password",
},
cli.BoolFlag{
EnvVar: "DRONE_STASH_SKIP_VERIFY",
Name: "stash-skip-verify",
Usage: "stash skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_CODING",
Name: "coding",
Usage: "coding driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_CODING_URL",
Name: "coding-server",
Usage: "coding server address",
Value: "https://coding.net",
},
cli.StringFlag{
EnvVar: "DRONE_CODING_CLIENT",
Name: "coding-client",
Usage: "coding oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_CODING_SECRET",
Name: "coding-secret",
Usage: "coding oauth2 client secret",
},
cli.StringSliceFlag{
EnvVar: "DRONE_CODING_SCOPE",
Name: "coding-scope",
Usage: "coding oauth scope",
Value: &cli.StringSlice{
"user",
"project",
"project:depot",
},
},
cli.StringFlag{
EnvVar: "DRONE_CODING_GIT_MACHINE",
Name: "coding-git-machine",
Usage: "coding machine name",
Value: "git.coding.net",
},
cli.StringFlag{
EnvVar: "DRONE_CODING_GIT_USERNAME",
Name: "coding-git-username",
Usage: "coding machine user username",
},
cli.StringFlag{
EnvVar: "DRONE_CODING_GIT_PASSWORD",
Name: "coding-git-password",
Usage: "coding machine user password",
},
cli.BoolFlag{
EnvVar: "DRONE_CODING_SKIP_VERIFY",
Name: "coding-skip-verify",
Usage: "coding skip ssl verification",
},
cli.DurationFlag{
EnvVar: "DRONE_KEEPALIVE_MIN_TIME",
Name: "keepalive-min-time",
Usage: "server-side enforcement policy on the minimum amount of time a client should wait before sending a keepalive ping.",
},
}
func server(c *cli.Context) error {
// debug level if requested by user
if c.Bool("debug") {
logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.WarnLevel)
}
// must configure the drone_host variable
if c.String("server-host") == "" {
logrus.Fatalln("DRONE_HOST is not properly configured")
}
if !strings.Contains(c.String("server-host"), "://") {
logrus.Fatalln(
"DRONE_HOST must be <scheme>://<hostname> format",
)
}
if strings.HasSuffix(c.String("server-host"), "/") {
logrus.Fatalln(
"DRONE_HOST must not have trailing slash",
)
}
remote_, err := SetupRemote(c)
if err != nil {
logrus.Fatal(err)
}
store_ := setupStore(c)
setupEvilGlobals(c, store_, remote_)
// we are switching from gin to httpservermux|treemux,
// so if this code looks strange, that is why.
tree := setupTree(c)
// setup the server and start the listener
handler := router.Load(
tree,
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
middleware.Version,
middleware.Config(c),
middleware.Store(c, store_),
middleware.Remote(remote_),
)
var g errgroup.Group
// start the grpc server
g.Go(func() error {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
logrus.Error(err)
return err
}
auther := &authorizer{
password: c.String("agent-secret"),
}
s := grpc.NewServer(
grpc.StreamInterceptor(auther.streamInterceptor),
grpc.UnaryInterceptor(auther.unaryIntercaptor),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: c.Duration("keepalive-min-time"),
}),
)
ss := new(droneserver.DroneServer)
ss.Queue = droneserver.Config.Services.Queue
ss.Logger = droneserver.Config.Services.Logs
ss.Pubsub = droneserver.Config.Services.Pubsub
ss.Remote = remote_
ss.Store = store_
ss.Host = droneserver.Config.Server.Host
proto.RegisterDroneServer(s, ss)
err = s.Serve(lis)
if err != nil {
logrus.Error(err)
return err
}
return nil
})
// start the server with tls enabled
if c.String("server-cert") != "" {
g.Go(func() error {
return http.ListenAndServe(":http", http.HandlerFunc(redirect))
})
g.Go(func() error {
serve := &http.Server{
Addr: ":https",
Handler: handler,
TLSConfig: &tls.Config{
NextProtos: []string{"http/1.1"}, // disable h2 because Safari :(
},
}
return serve.ListenAndServeTLS(
c.String("server-cert"),
c.String("server-key"),
)
})
return g.Wait()
}
// start the server without tls enabled
if !c.Bool("lets-encrypt") {
return http.ListenAndServe(
c.String("server-addr"),
handler,
)
}
// start the server with lets encrypt enabled
// listen on ports 443 and 80
address, err := url.Parse(c.String("server-host"))
if err != nil {
return err
}
dir := cacheDir()
os.MkdirAll(dir, 0700)
manager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(address.Host),
Cache: autocert.DirCache(dir),
}
g.Go(func() error {
return http.ListenAndServe(":http", manager.HTTPHandler(http.HandlerFunc(redirect)))
})
g.Go(func() error {
serve := &http.Server{
Addr: ":https",
Handler: handler,
TLSConfig: &tls.Config{
GetCertificate: manager.GetCertificate,
NextProtos: []string{"http/1.1"}, // disable h2 because Safari :(
},
}
return serve.ListenAndServeTLS("", "")
})
return g.Wait()
}
// HACK please excuse the message during this period of heavy refactoring.
// We are currently transitioning from storing services (ie database, queue)
// in the gin.Context to storing them in a struct. We are also moving away
// from gin to gorilla. We will temporarily use global during our refactoring
// which will be removing in the final implementation.
func setupEvilGlobals(c *cli.Context, v store.Store, r remote.Remote) {
// storage
droneserver.Config.Storage.Files = v
droneserver.Config.Storage.Config = v
// services
droneserver.Config.Services.Queue = setupQueue(c, v)
droneserver.Config.Services.Logs = logging.New()
droneserver.Config.Services.Pubsub = pubsub.New()
droneserver.Config.Services.Pubsub.Create(context.Background(), "topic/events")
droneserver.Config.Services.Registries = setupRegistryService(c, v)
droneserver.Config.Services.Secrets = setupSecretService(c, v)
droneserver.Config.Services.Senders = sender.New(v, v)
droneserver.Config.Services.Environ = setupEnvironService(c, v)
droneserver.Config.Services.Limiter = setupLimiter(c, v)
if endpoint := c.String("gating-service"); endpoint != "" {
droneserver.Config.Services.Senders = sender.NewRemote(endpoint)
}
// limits
droneserver.Config.Pipeline.Limits.MemSwapLimit = c.Int64("limit-mem-swap")
droneserver.Config.Pipeline.Limits.MemLimit = c.Int64("limit-mem")
droneserver.Config.Pipeline.Limits.ShmSize = c.Int64("limit-shm-size")
droneserver.Config.Pipeline.Limits.CPUQuota = c.Int64("limit-cpu-quota")
droneserver.Config.Pipeline.Limits.CPUShares = c.Int64("limit-cpu-shares")
droneserver.Config.Pipeline.Limits.CPUSet = c.String("limit-cpu-set")
// server configuration
droneserver.Config.Server.Cert = c.String("server-cert")
droneserver.Config.Server.Key = c.String("server-key")
droneserver.Config.Server.Pass = c.String("agent-secret")
droneserver.Config.Server.Host = strings.TrimRight(c.String("server-host"), "/")
droneserver.Config.Server.Port = c.String("server-addr")
droneserver.Config.Server.RepoConfig = c.String("repo-config")
droneserver.Config.Server.SessionExpires = c.Duration("session-expires")
droneserver.Config.Pipeline.Networks = c.StringSlice("network")
droneserver.Config.Pipeline.Volumes = c.StringSlice("volume")
droneserver.Config.Pipeline.Privileged = c.StringSlice("escalate")
// droneserver.Config.Server.Open = cli.Bool("open")
// droneserver.Config.Server.Orgs = sliceToMap(cli.StringSlice("orgs"))
// droneserver.Config.Server.Admins = sliceToMap(cli.StringSlice("admin"))
// prometheus
droneserver.Config.Prometheus.AuthToken = c.String("prometheus-auth-token")
}
type authorizer struct {
username string
password string
}
func (a *authorizer) streamInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if err := a.authorize(stream.Context()); err != nil {
return err
}
return handler(srv, stream)
}
func (a *authorizer) unaryIntercaptor(ctx oldcontext.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if err := a.authorize(ctx); err != nil {
return nil, err
}
return handler(ctx, req)
}
func (a *authorizer) authorize(ctx context.Context) error {
if md, ok := metadata.FromContext(ctx); ok {
if len(md["password"]) > 0 && md["password"][0] == a.password {
return nil
}
return errors.New("invalid agent token")
}
return errors.New("missing agent token")
}
func redirect(w http.ResponseWriter, req *http.Request) {
var serverHost string = droneserver.Config.Server.Host
serverHost = strings.TrimPrefix(serverHost, "http://")
serverHost = strings.TrimPrefix(serverHost, "https://")
req.URL.Scheme = "https"
req.URL.Host = serverHost
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently)
}
func cacheDir() string {
const base = "golang-autocert"
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
return filepath.Join(xdg, base)
}
return filepath.Join(os.Getenv("HOME"), ".cache", base)
}

View file

@ -1,202 +0,0 @@
// Copyright 2018 Drone.IO Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"time"
"github.com/cncd/queue"
"github.com/dimfeld/httptreemux"
"github.com/drone/drone/model"
"github.com/drone/drone/plugins/registry"
"github.com/drone/drone/plugins/secrets"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket"
"github.com/drone/drone/remote/bitbucketserver"
"github.com/drone/drone/remote/coding"
"github.com/drone/drone/remote/gitea"
"github.com/drone/drone/remote/github"
"github.com/drone/drone/remote/gitlab"
"github.com/drone/drone/remote/gitlab3"
"github.com/drone/drone/remote/gogs"
"github.com/drone/drone/server/web"
"github.com/drone/drone/store"
"github.com/drone/drone/store/datastore"
"github.com/urfave/cli"
)
func setupStore(c *cli.Context) store.Store {
return datastore.New(
c.String("driver"),
c.String("datasource"),
)
}
func setupQueue(c *cli.Context, s store.Store) queue.Queue {
return model.WithTaskStore(queue.New(), s)
}
func setupSecretService(c *cli.Context, s store.Store) model.SecretService {
return secrets.New(s)
}
func setupRegistryService(c *cli.Context, s store.Store) model.RegistryService {
return registry.New(s)
}
func setupEnvironService(c *cli.Context, s store.Store) model.EnvironService {
return nil
}
func setupLimiter(c *cli.Context, s store.Store) model.Limiter {
return new(model.NoLimit)
}
func setupPubsub(c *cli.Context) {}
func setupStream(c *cli.Context) {}
func setupGatingService(c *cli.Context) {}
// helper function to setup the remote from the CLI arguments.
func SetupRemote(c *cli.Context) (remote.Remote, error) {
switch {
case c.Bool("github"):
return setupGithub(c)
case c.Bool("gitlab"):
return setupGitlab(c)
case c.Bool("bitbucket"):
return setupBitbucket(c)
case c.Bool("stash"):
return setupStash(c)
case c.Bool("gogs"):
return setupGogs(c)
case c.Bool("gitea"):
return setupGitea(c)
case c.Bool("coding"):
return setupCoding(c)
default:
return nil, fmt.Errorf("version control system not configured")
}
}
// helper function to setup the Bitbucket remote from the CLI arguments.
func setupBitbucket(c *cli.Context) (remote.Remote, error) {
return bitbucket.New(
c.String("bitbucket-client"),
c.String("bitbucket-secret"),
), nil
}
// helper function to setup the Gogs remote from the CLI arguments.
func setupGogs(c *cli.Context) (remote.Remote, error) {
return gogs.New(gogs.Opts{
URL: c.String("gogs-server"),
Username: c.String("gogs-git-username"),
Password: c.String("gogs-git-password"),
PrivateMode: c.Bool("gogs-private-mode"),
SkipVerify: c.Bool("gogs-skip-verify"),
})
}
// helper function to setup the Gitea remote from the CLI arguments.
func setupGitea(c *cli.Context) (remote.Remote, error) {
return gitea.New(gitea.Opts{
URL: c.String("gitea-server"),
Context: c.String("gitea-context"),
Username: c.String("gitea-git-username"),
Password: c.String("gitea-git-password"),
PrivateMode: c.Bool("gitea-private-mode"),
SkipVerify: c.Bool("gitea-skip-verify"),
})
}
// helper function to setup the Stash remote from the CLI arguments.
func setupStash(c *cli.Context) (remote.Remote, error) {
return bitbucketserver.New(bitbucketserver.Opts{
URL: c.String("stash-server"),
Username: c.String("stash-git-username"),
Password: c.String("stash-git-password"),
ConsumerKey: c.String("stash-consumer-key"),
ConsumerRSA: c.String("stash-consumer-rsa"),
ConsumerRSAString: c.String("stash-consumer-rsa-string"),
SkipVerify: c.Bool("stash-skip-verify"),
})
}
// helper function to setup the Gitlab remote from the CLI arguments.
func setupGitlab(c *cli.Context) (remote.Remote, error) {
if c.Bool("gitlab-v3-api") {
return gitlab3.New(gitlab3.Opts{
URL: c.String("gitlab-server"),
Client: c.String("gitlab-client"),
Secret: c.String("gitlab-secret"),
Username: c.String("gitlab-git-username"),
Password: c.String("gitlab-git-password"),
PrivateMode: c.Bool("gitlab-private-mode"),
SkipVerify: c.Bool("gitlab-skip-verify"),
})
}
return gitlab.New(gitlab.Opts{
URL: c.String("gitlab-server"),
Client: c.String("gitlab-client"),
Secret: c.String("gitlab-secret"),
Username: c.String("gitlab-git-username"),
Password: c.String("gitlab-git-password"),
PrivateMode: c.Bool("gitlab-private-mode"),
SkipVerify: c.Bool("gitlab-skip-verify"),
})
}
// helper function to setup the GitHub remote from the CLI arguments.
func setupGithub(c *cli.Context) (remote.Remote, error) {
return github.New(github.Opts{
URL: c.String("github-server"),
Context: c.String("github-context"),
Client: c.String("github-client"),
Secret: c.String("github-secret"),
Scopes: c.StringSlice("github-scope"),
Username: c.String("github-git-username"),
Password: c.String("github-git-password"),
PrivateMode: c.Bool("github-private-mode"),
SkipVerify: c.Bool("github-skip-verify"),
MergeRef: c.BoolT("github-merge-ref"),
})
}
// helper function to setup the Coding remote from the CLI arguments.
func setupCoding(c *cli.Context) (remote.Remote, error) {
return coding.New(coding.Opts{
URL: c.String("coding-server"),
Client: c.String("coding-client"),
Secret: c.String("coding-secret"),
Scopes: c.StringSlice("coding-scope"),
Machine: c.String("coding-git-machine"),
Username: c.String("coding-git-username"),
Password: c.String("coding-git-password"),
SkipVerify: c.Bool("coding-skip-verify"),
})
}
func setupTree(c *cli.Context) *httptreemux.ContextMux {
tree := httptreemux.NewContextMux()
web.New(
web.WithDir(c.String("www")),
web.WithSync(time.Hour*72),
).Register(tree)
return tree
}
func before(c *cli.Context) error { return nil }

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,26 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package model
//+build wireinject
package main
import (
"crypto/rand"
"fmt"
"testing"
"github.com/drone/drone/cmd/drone-server/config"
"github.com/google/wire"
)
func TestBuildTrim(t *testing.T) {
d := make([]byte, 2000)
rand.Read(d)
b := Build{}
b.Message = fmt.Sprintf("%X", d)
if len(b.Message) != 4000 {
t.Errorf("Failed to generate 4000 byte test string")
}
b.Trim()
if len(b.Message) != 2000 {
t.Errorf("Failed to trim text string to 2000 bytes")
}
func InitializeApplication(config config.Config) (application, error) {
wire.Build(
clientSet,
licenseSet,
loginSet,
pluginSet,
runnerSet,
schedulerSet,
serverSet,
serviceSet,
storeSet,
newApplication,
)
return application{}, nil
}

View file

@ -0,0 +1,98 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
import (
"github.com/drone/drone/cmd/drone-server/config"
"github.com/drone/drone/handler/api"
"github.com/drone/drone/handler/web"
"github.com/drone/drone/livelog"
"github.com/drone/drone/metric"
"github.com/drone/drone/operator/manager"
"github.com/drone/drone/pubsub"
"github.com/drone/drone/service/commit"
"github.com/drone/drone/service/hook/parser"
"github.com/drone/drone/service/license"
"github.com/drone/drone/service/org"
"github.com/drone/drone/service/repo"
"github.com/drone/drone/service/token"
"github.com/drone/drone/service/user"
"github.com/drone/drone/store/batch"
"github.com/drone/drone/store/cron"
"github.com/drone/drone/store/perm"
"github.com/drone/drone/store/secret"
"github.com/drone/drone/store/step"
"github.com/drone/drone/trigger"
cron2 "github.com/drone/drone/trigger/cron"
)
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
// Injectors from wire.go:
func InitializeApplication(config2 config.Config) (application, error) {
client := provideClient(config2)
refresher := provideRefresher(config2)
db, err := provideDatabase(config2)
if err != nil {
return application{}, err
}
userStore := provideUserStore(db)
renewer := token.Renewer(refresher, userStore)
commitService := commit.New(client, renewer)
cronStore := cron.New(db)
repositoryStore := provideRepoStore(db)
fileService := provideContentService(client, renewer)
configService := provideConfigPlugin(client, fileService, config2)
statusService := provideStatusService(client, renewer, config2)
buildStore := provideBuildStore(db)
stageStore := provideStageStore(db)
scheduler := provideScheduler(stageStore, config2)
webhookSender := provideWebhookPlugin(config2)
triggerer := trigger.New(configService, commitService, statusService, buildStore, scheduler, repositoryStore, userStore, webhookSender)
cronScheduler := cron2.New(commitService, cronStore, repositoryStore, userStore, triggerer)
corePubsub := pubsub.New()
logStore := provideLogStore(db, config2)
logStream := livelog.New()
netrcService := provideNetrcService(client, renewer, config2)
encrypter, err := provideEncrypter(config2)
if err != nil {
return application{}, err
}
secretStore := secret.New(db, encrypter)
stepStore := step.New(db)
system := provideSystem(config2)
buildManager := manager.New(buildStore, configService, corePubsub, logStore, logStream, netrcService, repositoryStore, scheduler, secretStore, statusService, stageStore, stepStore, system, userStore, webhookSender)
secretService := provideSecretPlugin(config2)
registryService := provideRegistryPlugin(config2)
runner := provideRunner(buildManager, secretService, registryService, config2)
hookService := provideHookService(client, renewer, config2)
coreLicense := provideLicense(client, config2)
licenseService := license.NewService(userStore, repositoryStore, buildStore, coreLicense)
permStore := perm.New(db)
repositoryService := repo.New(client, renewer)
session := provideSession(userStore, config2)
batcher := batch.New(db)
syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2)
server := api.New(buildStore, cronStore, corePubsub, hookService, logStore, coreLicense, licenseService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, triggerer, userStore, webhookSender)
organizationService := orgs.New(client, renewer)
userService := user.New(client)
admissionService := provideAdmissionPlugin(client, organizationService, userService, config2)
hookParser := parser.New(client)
middleware := provideLogin(config2)
options := provideServerOptions(config2)
webServer := web.New(admissionService, buildStore, client, hookParser, coreLicense, licenseService, middleware, repositoryStore, session, syncer, triggerer, userStore, userService, webhookSender, options, system)
handler := provideRPC(buildManager, config2)
metricServer := metric.NewServer(session)
mux := provideRouter(server, webServer, handler, metricServer)
serverServer := provideServer(mux, config2)
mainApplication := newApplication(cronScheduler, runner, serverServer, userStore)
return mainApplication, nil
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,21 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package coding
package core
import (
"testing"
import "context"
"github.com/franela/goblin"
)
func Test_util(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Coding util", func() {
g.It("Should form project full name", func() {
g.Assert(projectFullName("gk", "prj")).Equal("gk/prj")
})
})
// AdmissionService grants access to the system. The service can
// be used to restrict access to authorized users, such as
// members of an organiozation in your soruce control management
// system.
type AdmissionService interface {
Admit(context.Context, *User) error
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,24 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package client
package core
import (
"encoding/json"
)
import "context"
const (
currentUserUrl = "/user"
)
func (c *Client) CurrentUser() (User, error) {
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
var user User
contents, err := c.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
// Batch represents a Batch request to synchronize the local
// repository and permission store for a user account.
type Batch struct {
Insert []*Repository `json:"insert"`
Update []*Repository `json:"update"`
Revoke []*Repository `json:"revoke"`
}
// Batcher batch updates the user account.
type Batcher interface {
Batch(context.Context, *User, *Batch) error
}

94
core/build.go Normal file
View file

@ -0,0 +1,94 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
// Build represents a build execution.
type Build struct {
ID int64 `db:"build_id" json:"id"`
RepoID int64 `db:"build_repo_id" json:"repo_id"`
Trigger string `db:"build_trigger" json:"trigger"`
Number int64 `db:"build_number" json:"number"`
Parent int64 `db:"build_parent" json:"parent,omitempty"`
Status string `db:"build_status" json:"status"`
Error string `db:"build_error" json:"error,omitempty"`
Event string `db:"build_event" json:"event"`
Action string `db:"build_action" json:"action"`
Link string `db:"build_link" json:"link"`
Timestamp int64 `db:"build_timestamp" json:"timestamp"`
Title string `db:"build_title" json:"title,omitempty"`
Message string `db:"build_message" json:"message"`
Before string `db:"build_before" json:"before"`
After string `db:"build_after" json:"after"`
Ref string `db:"build_ref" json:"ref"`
Fork string `db:"build_source_repo" json:"source_repo"`
Source string `db:"build_source" json:"source"`
Target string `db:"build_target" json:"target"`
Author string `db:"build_author" json:"author_login"`
AuthorName string `db:"build_author_name" json:"author_name"`
AuthorEmail string `db:"build_author_email" json:"author_email"`
AuthorAvatar string `db:"build_author_avatar" json:"author_avatar"`
Sender string `db:"build_sender" json:"sender"`
Params map[string]string `db:"build_params" json:"params,omitempty"`
Deploy string `db:"build_deploy" json:"deploy_to,omitempty"`
Started int64 `db:"build_started" json:"started"`
Finished int64 `db:"build_finished" json:"finished"`
Created int64 `db:"build_created" json:"created"`
Updated int64 `db:"build_updated" json:"updated"`
Version int64 `db:"build_version" json:"version"`
Stages []*Stage `db:"-" json:"stages,omitempty"`
}
// BuildStore defines operations for working with builds.
type BuildStore interface {
// Find returns a build from the datastore.
Find(context.Context, int64) (*Build, error)
// FindNumber returns a build from the datastore by build number.
FindNumber(context.Context, int64, int64) (*Build, error)
// FindLast returns the last build from the datastore by ref.
FindRef(context.Context, int64, string) (*Build, error)
// List returns a list of builds from the datastore by repository id.
List(context.Context, int64, int, int) ([]*Build, error)
// ListRef returns a list of builds from the datastore by ref.
ListRef(context.Context, int64, string, int, int) ([]*Build, error)
// Pending returns a list of pending builds from the
// datastore by repository id (DEPRECATED).
Pending(context.Context) ([]*Build, error)
// Running returns a list of running builds from the
// datastore by repository id (DEPRECATED).
Running(context.Context) ([]*Build, error)
// Create persists a build to the datastore.
Create(context.Context, *Build, []*Stage) error
// Update updates a build in the datastore.
Update(context.Context, *Build) error
// Delete deletes a build from the datastore.
Delete(context.Context, *Build) error
// Purge deletes builds from the database where the build number is less than n.
Purge(context.Context, int64, int64) error
// Count returns a count of builds.
Count(context.Context) (int64, error)
}

5
core/build_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

59
core/commit.go Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// Commit represents a git commit.
Commit struct {
Sha string
Ref string
Message string
Author *Committer
Committer *Committer
Link string
}
// Committer represents the commit author.
Committer struct {
Name string
Email string
Date int64
Login string
Avatar string
}
// Change represents a file change in a commit.
Change struct {
Path string
Added bool
Renamed bool
Deleted bool
}
// CommitService provides access to the commit history from
// the external source code management service (e.g. GitHub).
CommitService interface {
// Find returns the commit information by sha.
Find(ctx context.Context, user *User, repo, sha string) (*Commit, error)
// FindRef returns the commit information by reference.
FindRef(ctx context.Context, user *User, repo, ref string) (*Commit, error)
// ListChanges returns the files change by sha or reference.
ListChanges(ctx context.Context, user *User, repo, sha, ref string) ([]*Change, error)
}
)

40
core/config.go Normal file
View file

@ -0,0 +1,40 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// Config represents a pipeline config file.
Config struct {
Data string `json:"data"`
Kind string `json:"kind"`
}
// ConfigArgs represents a request for the pipeline
// configuration file (e.g. .drone.yml)
ConfigArgs struct {
User *User `json:"-"`
Repo *Repository `json:"repo,omitempty"`
Build *Build `json:"build,omitempty"`
Config *Config `json:"config,omitempty"`
}
// ConfigService provides pipeline configuration from an
// external service.
ConfigService interface {
Find(context.Context, *ConfigArgs) (*Config, error)
}
)

117
core/cron.go Normal file
View file

@ -0,0 +1,117 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"errors"
"time"
"github.com/gosimple/slug"
"github.com/robfig/cron"
)
var (
errCronExprInvalid = errors.New("Invalid Cronjob Expression")
errCronNameInvalid = errors.New("Invalid Cronjob Name")
errCronBranchInvalid = errors.New("Invalid Cronjob Branch")
)
type (
// Cron defines a cron job.
Cron struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
Expr string `json:"expr"`
Next int64 `json:"next"`
Prev int64 `json:"prev"`
Event string `json:"event"`
Branch string `json:"branch"`
Target string `json:"target,omitempty"`
Disabled bool `json:"disabled"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Version int64 `json:"version"`
}
// CronStore persists cron information to storage.
CronStore interface {
// List returns a cron list from the datastore.
List(context.Context, int64) ([]*Cron, error)
// Ready returns a cron list from the datastore ready for execution.
Ready(context.Context, int64) ([]*Cron, error)
// Find returns a cron job from the datastore.
Find(context.Context, int64) (*Cron, error)
// FindName returns a cron job from the datastore.
FindName(context.Context, int64, string) (*Cron, error)
// Create persists a new cron job to the datastore.
Create(context.Context, *Cron) error
// Update persists an updated cron job to the datastore.
Update(context.Context, *Cron) error
// Delete deletes a cron job from the datastore.
Delete(context.Context, *Cron) error
}
)
// Validate validates the required fields and formats.
func (c *Cron) Validate() error {
_, err := cron.Parse(c.Expr)
if err != nil {
return errCronExprInvalid
}
switch {
case c.Name == "":
return errCronNameInvalid
case c.Name != slug.Make(c.Name):
return errCronNameInvalid
case c.Branch == "":
return errCronBranchInvalid
default:
return nil
}
}
// SetExpr sets the cron expression name and updates
// the next execution date.
func (c *Cron) SetExpr(expr string) error {
_, err := cron.Parse(expr)
if err != nil {
return errCronExprInvalid
}
c.Expr = expr
return c.Update()
}
// SetName sets the cronjob name.
func (c *Cron) SetName(name string) {
c.Name = slug.Make(name)
}
// Update updates the next Cron execution date.
func (c *Cron) Update() error {
sched, err := cron.Parse(c.Expr)
if err != nil {
return err
}
c.Next = sched.Next(time.Now()).Unix()
return nil
}

5
core/cron_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !cgo
package core
package datastore
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
// Hook event constants.
const (
EventPush = "push"
EventPullRequest = "pull_request"
EventTag = "tag"
EventPromote = "promote"
EventRollback = "rollback"
)

40
core/file.go Normal file
View file

@ -0,0 +1,40 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// File represents the raw file contents in the remote
// version control system.
File struct {
Data []byte
Hash []byte
}
// FileArgs provides repository and commit details required
// to fetch the file from the remote source code management
// service.
FileArgs struct {
Commit string
Ref string
}
// FileService provides access to contents of files in
// the remote source code management service (e.g. GitHub).
FileService interface {
Find(ctx context.Context, user *User, repo, commit, ref, path string) (*File, error)
}
)

66
core/hook.go Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"net/http"
)
// Hook action constants.
const (
ActionOpen = "open"
ActionCreate = "create"
ActionDelete = "delete"
ActionSync = "sync"
)
// Hook represents the payload of a post-commit hook.
type Hook struct {
Parent int64 `json:"parent"`
Trigger string `json:"trigger"`
Event string `json:"event"`
Action string `json:"action"`
Link string `json:"link"`
Timestamp int64 `json:"timestamp"`
Title string `json:"title"`
Message string `json:"message"`
Before string `json:"before"`
After string `json:"after"`
Ref string `json:"ref"`
Fork string `json:"hook"`
Source string `json:"source"`
Target string `json:"target"`
Author string `json:"author_login"`
AuthorName string `json:"author_name"`
AuthorEmail string `json:"author_email"`
AuthorAvatar string `json:"author_avatar"`
Deployment string `json:"deploy_to"`
Sender string `json:"sender"`
Params map[string]string `json:"params"`
}
// HookService manages post-commit hooks in the external
// source code management service (e.g. GitHub).
type HookService interface {
Create(ctx context.Context, user *User, repo *Repository) error
Delete(ctx context.Context, user *User, repo *Repository) error
}
// HookParser parses a post-commit hook from the source
// code management system, and returns normalized data.
type HookParser interface {
Parse(req *http.Request, secretFunc func(string) string) (*Hook, *Repository, error)
}

5
core/hook_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

72
core/license.go Normal file
View file

@ -0,0 +1,72 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"errors"
"time"
)
// License types.
const (
LicenseFoss = "foss"
LicensePeronal = "personal"
LicenseStandard = "standard"
LicenseTrial = "trial"
)
// ErrUserLimit is returned when attempting to create a new
// user but the maximum number of allowed user accounts
// is exceeded.
var ErrUserLimit = errors.New("User limit exceeded")
// ErrRepoLimit is returned when attempting to create a new
// repository but the maximum number of allowed repositories
// is exceeded.
var ErrRepoLimit = errors.New("Repository limit exceeded")
// ErrBuildLimit is returned when attempting to create a new
// build but the maximum number of allowed builds is exceeded.
var ErrBuildLimit = errors.New("Build limit exceeded")
type (
// License defines software license parameters.
License struct {
Expires time.Time `json:"expires_at,omitempty"`
Kind string `json:"kind,omitempty"`
Repos int64 `json:"repos,omitempty"`
Users int64 `json:"users,omitempty"`
Builds int64 `json:"builds,omitempty"`
Nodes int64 `json:"nodes,omitempty"`
}
// LicenseService provides access to the license
// service and can be used to check for violations
// and expirations.
LicenseService interface {
// Exceeded returns true if the system has exceeded
// its limits as defined in the license.
Exceeded(context.Context) (bool, error)
// Expired returns true if the license is expired.
Expired(context.Context) bool
}
)
// Expired returns true if the license is expired.
func (l *License) Expired() bool {
return l.Expires.IsZero() == false && time.Now().After(l.Expires)
}

5
core/license_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

70
core/logs.go Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"io"
)
// Line represents a line in the logs.
type Line struct {
Number int `json:"pos"`
Message string `json:"out"`
Timestamp int64 `json:"time"`
}
// LogStore persists build output to storage.
type LogStore interface {
// Find returns a log stream from the datastore.
Find(ctx context.Context, stage int64) (io.ReadCloser, error)
// Create writes copies the log stream from Reader r to the datastore.
Create(ctx context.Context, stage int64, r io.Reader) error
// Update writes copies the log stream from Reader r to the datastore.
Update(ctx context.Context, stage int64, r io.Reader) error
// Delete purges the log stream from the datastore.
Delete(ctx context.Context, stage int64) error
}
// LogStream manages a live stream of logs.
type LogStream interface {
// Create creates the log stream for the step ID.
Create(context.Context, int64) error
// Delete deletes the log stream for the step ID.
Delete(context.Context, int64) error
// Writes writes to the log stream.
Write(context.Context, int64, *Line) error
// Tail tails the log stream.
Tail(context.Context, int64) (<-chan *Line, <-chan error)
// Info returns internal stream information.
Info(context.Context) *LogStreamInfo
}
// LogStreamInfo provides internal stream information. This can
// be used to monitor the number of registered streams and
// subscribers.
type LogStreamInfo struct {
// Streams is a key-value pair where the key is the step
// identifier, and the value is the count of subscribers
// streaming the logs.
Streams map[int64]int `json:"streams"`
}

58
core/netrc.go Normal file
View file

@ -0,0 +1,58 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"fmt"
"net/url"
)
type (
// Netrc contains login and initialization information used by
// an automated login process.
Netrc struct {
Machine string `json:"machine"`
Login string `json:"login"`
Password string `json:"password"`
}
// NetrcService returns a valid netrc file that can be used
// to authenticate and clone a private repository. If
// authentication is not required or enabled, a nil Netrc
// file and nil error are returned.
NetrcService interface {
Create(context.Context, *User, *Repository) (*Netrc, error)
}
)
// SetMachine sets the netrc machine from a URL value.
func (n *Netrc) SetMachine(address string) error {
url, err := url.Parse(address)
if err != nil {
return err
}
n.Machine = url.Hostname()
return nil
}
// String returns the string representation of a netrc file.
func (n *Netrc) String() string {
return fmt.Sprintf("machine %s login %s password %s",
n.Machine,
n.Login,
n.Password,
)
}

5
core/netrc_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,26 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package remote
package core
// AuthError represents remote authentication error.
type AuthError struct {
Err string
Description string
URI string
import "context"
// Organization represents an organization in the source
// code management system (e.g. GitHub).
type Organization struct {
Name string
Avatar string
}
// Error implements error interface.
func (ae *AuthError) Error() string {
err := ae.Err
if ae.Description != "" {
err += " " + ae.Description
}
if ae.URI != "" {
err += " " + ae.URI
}
return err
// OrganizationService provides access to organization and
// team access in the external source code management system
// (e.g. GitHub).
type OrganizationService interface {
List(context.Context, *User) ([]*Organization, error)
}
// check interface
var _ error = new(AuthError)

68
core/perm.go Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// Perm represents an individuals repository
// permission.
Perm struct {
UserID int64 `db:"perm_user_id" json:"-"`
RepoUID string `db:"perm_repo_uid" json:"-"`
Read bool `db:"perm_read" json:"read"`
Write bool `db:"perm_write" json:"write"`
Admin bool `db:"perm_admin" json:"admin"`
Synced int64 `db:"perm_synced" json:"-"`
Created int64 `db:"perm_created" json:"-"`
Updated int64 `db:"perm_updated" json:"-"`
}
// Collaborator represents a project collaborator,
// and provides the account and repository permissions
// details.
Collaborator struct {
UserID int64 `db:"perm_user_id" json:"user_id"`
RepoUID string `db:"perm_repo_uid" json:"repo_id"`
Login string `db:"user_login" json:"login"`
Avatar string `db:"user_avatar" json:"avatar"`
Read bool `db:"perm_read" json:"read"`
Write bool `db:"perm_write" json:"write"`
Admin bool `db:"perm_admin" json:"admin"`
Synced int64 `db:"perm_synced" json:"synced"`
Created int64 `db:"perm_created" json:"created"`
Updated int64 `db:"perm_updated" json:"updated"`
}
// PermStore defines operations for working with
// repostiory permissions.
PermStore interface {
// Find returns a project member from the
// datastore.
Find(ctx context.Context, repoUID string, userID int64) (*Perm, error)
// List returns a list of project members from the
// datastore.
List(ctx context.Context, repoUID string) ([]*Collaborator, error)
// Update persists an updated project member
// to the datastore.
Update(context.Context, *Perm) error
// Delete deletes a project member from the
// datastore.
Delete(context.Context, *Perm) error
}
)

37
core/pubsub.go Normal file
View file

@ -0,0 +1,37 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
// Message defines a build change.
type Message struct {
Repository string
Visibility string
Data []byte
}
// Pubsub provides publish subscriber capablities, distributing
// messages from multiple publishers to multiple subscribers.
type Pubsub interface {
// Publish publishes the message to all subscribers.
Publish(context.Context, *Message) error
// Subscribe subscribes to the message broker.
Subscribe(context.Context) (<-chan *Message, <-chan error)
// Subscribers returns a count of subscribers.
Subscribers() int
}

60
core/registry.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"github.com/drone/drone-yaml/yaml"
)
const (
// RegistryPull policy allows pulling from a registry.
RegistryPull = "pull"
// RegistryPush Policy allows pushing to a registry for
// all event types except pull requests.
RegistryPush = "push"
// RegistryPushPullRequest Policy allows pushing to a
// registry for all event types, including pull requests.
RegistryPushPullRequest = "push-pull-request"
)
type (
// Registry represents a docker registry with credentials.
Registry struct {
Address string `json:"address"`
Username string `json:"username"`
Password string `json:"password"`
Policy string `json:"policy"`
}
// RegistryArgs provides arguments for requesting
// registry credentials from the remote service.
RegistryArgs struct {
Repo *Repository `json:"repo,omitempty"`
Build *Build `json:"build,omitempty"`
Conf *yaml.Manifest `json:"-"`
}
// RegistryService provides registry credentials from an
// external service.
RegistryService interface {
// List returns registry credentials from the global
// remote registry plugin.
List(context.Context, *RegistryArgs) ([]*Registry, error)
}
)

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package model
package core
// ResourceLimit is the resource limit to set on pipeline steps
type ResourceLimit struct {
MemSwapLimit int64
MemLimit int64
ShmSize int64
CPUQuota int64
CPUShares int64
CPUSet string
import "context"
// Renewer renews the user account authorization. If
// successful, the user token and token expiry attributes
// are updated, and persisted to the datastore.
type Renewer interface {
Renew(ctx context.Context, user *User, force bool) error
}

118
core/repo.go Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
// Repository visibility.
const (
VisibilityPublic = "public"
VisibilityPrivate = "private"
VisibilityInternal = "internal"
)
// Version control systems.
const (
VersionControlGit = "git"
VersionControlMercurial = "hg"
)
type (
// Repository represents a source code repository.
Repository struct {
ID int64 `json:"id"`
UID string `json:"uid"`
UserID int64 `json:"user_id"`
Namespace string `json:"namespace"`
Name string `json:"name"`
Slug string `json:"slug"`
SCM string `json:"scm"`
HTTPURL string `json:"git_http_url"`
SSHURL string `json:"git_ssh_url"`
Link string `json:"link"`
Branch string `json:"default_branch"`
Private bool `json:"private"`
Visibility string `json:"visibility"`
Active bool `json:"active"`
Config string `json:"config_path"`
Trusted bool `json:"trusted"`
Protected bool `json:"protected"`
Timeout int64 `json:"timeout"`
Counter int64 `json:"counter"`
Synced int64 `json:"synced"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Version int64 `json:"version"`
Signer string `json:"-"`
Secret string `json:"-"`
Build *Build `json:"build,omitempty"`
Perms *Perm `json:"permissions,omitempty"`
}
// RepositoryStore defines operations for working with repositories.
RepositoryStore interface {
// List returns a repository list from the datastore.
List(context.Context, int64) ([]*Repository, error)
// ListLatest returns a unique repository list form
// the datastore with the most recent build.
ListLatest(context.Context, int64) ([]*Repository, error)
// ListRecent returns a non-unique repository list form
// the datastore with the most recent builds.
ListRecent(context.Context, int64) ([]*Repository, error)
// ListIncomplete returns a non-unique repository list form
// the datastore with incmoplete builds.
ListIncomplete(context.Context) ([]*Repository, error)
// Find returns a repository from the datastore.
Find(context.Context, int64) (*Repository, error)
// FindName returns a named repository from the datastore.
FindName(context.Context, string, string) (*Repository, error)
// Create persists a new repository in the datastore.
Create(context.Context, *Repository) error
// Activate persists the activated repository to the datastore.
Activate(context.Context, *Repository) error
// Update persists repository changes to the datastore.
Update(context.Context, *Repository) error
// Delete deletes a repository from the datastore.
Delete(context.Context, *Repository) error
// Count returns a count of activated repositories.
Count(context.Context) (int64, error)
// Increment returns an incremented build number
Increment(context.Context, *Repository) (*Repository, error)
}
// RepositoryService provides access to repository information
// in the remote source code management system (e.g. GitHub).
RepositoryService interface {
// List returns a list of repositories.
List(ctx context.Context, user *User) ([]*Repository, error)
// Find returns the named repository details.
Find(ctx context.Context, user *User, repo string) (*Repository, error)
// FindPerm returns the named repository permissions.
FindPerm(ctx context.Context, user *User, repo string) (*Perm, error)
}
)

50
core/sched.go Normal file
View file

@ -0,0 +1,50 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
// Filter provides filter criteria to limit stages requested
// from the scheduler.
type Filter struct {
Kind string
Type string
OS string
Arch string
Kernel string
Variant string
Labels map[string]string
}
// Scheduler schedules Build stages for execution.
type Scheduler interface {
// Schedule schedules the stage for execution.
Schedule(context.Context, *Stage) error
// Request requests the next stage scheduled for execution.
Request(context.Context, Filter) (*Stage, error)
// Cancel cancels scheduled or running jobs associated
// with the parent build ID.
Cancel(context.Context, int64) error
// Cancelled blocks and listens for a cancellation event and
// returns true if the build has been cancelled.
Cancelled(context.Context, int64) (bool, error)
// Stats provides statistics for underlying scheduler. The
// data format is scheduler-specific.
Stats(context.Context) (interface{}, error)
}

105
core/secret.go Normal file
View file

@ -0,0 +1,105 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"errors"
"regexp"
"github.com/drone/drone-yaml/yaml"
)
var (
errSecretNameInvalid = errors.New("Invalid Secret Name")
errSecretDataInvalid = errors.New("Invalid Secret Value")
)
type (
// Secret represents a secret variable, such as a password or token,
// that is provided to the build at runtime.
Secret struct {
ID int64 `json:"id,omitempty"`
RepoID int64 `json:"repo_id,omitempty"`
Name string `json:"name,omitempty"`
Data string `json:"data,omitempty"`
PullRequest bool `json:"pull_request,omitempty"`
PullRequestPush bool `json:"pull_request_push,omitempty"`
}
// SecretArgs provides arguments for requesting secrets
// from the remote service.
SecretArgs struct {
Name string `json:"name"`
Repo *Repository `json:"repo,omitempty"`
Build *Build `json:"build,omitempty"`
Conf *yaml.Manifest `json:"-"`
}
// SecretStore manages repository secrets.
SecretStore interface {
// List returns a secret list from the datastore.
List(context.Context, int64) ([]*Secret, error)
// Find returns a secret from the datastore.
Find(context.Context, int64) (*Secret, error)
// FindName returns a secret from the datastore.
FindName(context.Context, int64, string) (*Secret, error)
// Create persists a new secret to the datastore.
Create(context.Context, *Secret) error
// Update persists an updated secret to the datastore.
Update(context.Context, *Secret) error
// Delete deletes a secret from the datastore.
Delete(context.Context, *Secret) error
}
// SecretService provides secrets from an external service.
SecretService interface {
// Find returns a named secret from the global remote service.
Find(context.Context, *SecretArgs) (*Secret, error)
}
)
// Validate validates the required fields and formats.
func (s *Secret) Validate() error {
switch {
case len(s.Name) == 0:
return errSecretNameInvalid
case len(s.Data) == 0:
return errSecretDataInvalid
case slugRE.MatchString(s.Name):
return errSecretNameInvalid
default:
return nil
}
}
// Copy makes a copy of the secret without the value.
func (s *Secret) Copy() *Secret {
return &Secret{
ID: s.ID,
RepoID: s.RepoID,
Name: s.Name,
PullRequest: s.PullRequest,
PullRequestPush: s.PullRequestPush,
}
}
// slug regular expression
var slugRE = regexp.MustCompile("[^a-zA-Z0-9-_.]+")

71
core/secret_test.go Normal file
View file

@ -0,0 +1,71 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core
import "testing"
func TestSecretValidate(t *testing.T) {
tests := []struct {
secret *Secret
error error
}{
{
secret: &Secret{Name: "password", Data: "correct-horse-battery-staple"},
error: nil,
},
{
secret: &Secret{Name: ".some_random-password", Data: "correct-horse-battery-staple"},
error: nil,
},
{
secret: &Secret{Name: "password", Data: ""},
error: errSecretDataInvalid,
},
{
secret: &Secret{Name: "", Data: "correct-horse-battery-staple"},
error: errSecretNameInvalid,
},
{
secret: &Secret{Name: "docker/password", Data: "correct-horse-battery-staple"},
error: errSecretNameInvalid,
},
}
for i, test := range tests {
got, want := test.secret.Validate(), test.error
if got != want {
t.Errorf("Want error %v, got %v at index %d", want, got, i)
}
}
}
func TestSecretSafeCopy(t *testing.T) {
before := Secret{
ID: 1,
RepoID: 2,
Name: "docker_password",
Data: "correct-horse-battery-staple",
PullRequest: true,
PullRequestPush: true,
}
after := before.Copy()
if got, want := after.ID, before.ID; got != want {
t.Errorf("Want secret ID %d, got %d", want, got)
}
if got, want := after.RepoID, before.RepoID; got != want {
t.Errorf("Want secret RepoID %d, got %d", want, got)
}
if got, want := after.Name, before.Name; got != want {
t.Errorf("Want secret Name %s, got %s", want, got)
}
if got, want := after.PullRequest, before.PullRequest; got != want {
t.Errorf("Want secret PullRequest %v, got %v", want, got)
}
if got, want := after.PullRequestPush, before.PullRequestPush; got != want {
t.Errorf("Want secret PullRequest %v, got %v", want, got)
}
if after.Data != "" {
t.Errorf("Expect secret is empty after copy")
}
}

33
core/session.go Normal file
View file

@ -0,0 +1,33 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "net/http"
// Session provides session management for
// authenticated users.
type Session interface {
// Create creates a new user session and writes the
// session to the http.Response.
Create(http.ResponseWriter, *User) error
// Delete deletes the user session from the http.Response.
Delete(http.ResponseWriter) error
// Get returns the session from the http.Request. If no
// session exists a nil user is returned. Returning an
// error is optional, for debugging purposes only.
Get(*http.Request) (*User, error)
}

105
core/stage.go Normal file
View file

@ -0,0 +1,105 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// Stage represents a stage of build execution.
Stage struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
BuildID int64 `json:"build_id"`
Number int `json:"number"`
Name string `json:"name"`
Kind string `json:"kind,omitempty"`
Type string `json:"type,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ErrIgnore bool `json:"errignore"`
ExitCode int `json:"exit_code"`
Machine string `json:"machine,omitempty"`
OS string `json:"os"`
Arch string `json:"arch"`
Variant string `json:"variant,omitempty"`
Kernel string `json:"kernel,omitempty"`
Limit int `json:"limit,omitempty"`
Started int64 `json:"started"`
Stopped int64 `json:"stopped"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Version int64 `json:"version"`
OnSuccess bool `json:"on_success"`
OnFailure bool `json:"on_failure"`
DependsOn []string `json:"depends_on,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Steps []*Step `json:"steps,omitempty"`
}
// StageStore persists build stage information to storage.
StageStore interface {
// List returns a build stage list from the datastore.
List(context.Context, int64) ([]*Stage, error)
// List returns a build stage list from the datastore
// where the stage is incomplete (pending or running).
ListIncomplete(ctx context.Context) ([]*Stage, error)
// ListSteps returns a build stage list from the datastore,
// with the individual steps included.
ListSteps(context.Context, int64) ([]*Stage, error)
// ListState returns a build stage list from the database
// across all repositories.
ListState(context.Context, string) ([]*Stage, error)
// Find returns a build stage from the datastore by ID.
Find(context.Context, int64) (*Stage, error)
// FindNumber returns a stage from the datastore by number.
FindNumber(context.Context, int64, int) (*Stage, error)
// Create persists a new stage to the datastore.
Create(context.Context, *Stage) error
// Update persists an updated stage to the datastore.
Update(context.Context, *Stage) error
}
)
// IsDone returns true if the step has a completed state.
func (s *Stage) IsDone() bool {
switch s.Status {
case StatusWaiting,
StatusPending,
StatusRunning,
StatusBlocked:
return false
default:
return true
}
}
// IsFailed returns true if the step has failed
func (s *Stage) IsFailed() bool {
switch s.Status {
case StatusFailing,
StatusKilled,
StatusError:
return true
default:
return false
}
}

71
core/stage_test.go Normal file
View file

@ -0,0 +1,71 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core
import "testing"
var statusDone = []string{
StatusDeclined,
StatusError,
StatusFailing,
StatusKilled,
StatusSkipped,
StatusPassing,
}
var statusNotDone = []string{
StatusWaiting,
StatusPending,
StatusRunning,
StatusBlocked,
}
var statusFailed = []string{
StatusError,
StatusFailing,
StatusKilled,
}
var statusNotFailed = []string{
StatusDeclined,
StatusSkipped,
StatusPassing,
StatusWaiting,
StatusPending,
StatusRunning,
StatusBlocked,
}
func TestStageIsDone(t *testing.T) {
for _, status := range statusDone {
v := Stage{Status: status}
if v.IsDone() == false {
t.Errorf("Expect status %s is done", status)
}
}
for _, status := range statusNotDone {
v := Stage{Status: status}
if v.IsDone() == true {
t.Errorf("Expect status %s is not done", status)
}
}
}
func TestStageIsFailed(t *testing.T) {
for _, status := range statusFailed {
v := Stage{Status: status}
if v.IsFailed() == false {
t.Errorf("Expect status %s is failed", status)
}
}
for _, status := range statusNotFailed {
v := Stage{Status: status}
if v.IsFailed() == true {
t.Errorf("Expect status %s is not failed", status)
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,36 +12,43 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package model
package core
const (
EventPush = "push"
EventPull = "pull_request"
EventTag = "tag"
EventDeploy = "deployment"
)
import "context"
// Status types.
const (
StatusSkipped = "skipped"
StatusPending = "pending"
StatusRunning = "running"
StatusSuccess = "success"
StatusFailure = "failure"
StatusKilled = "killed"
StatusError = "error"
StatusBlocked = "blocked"
StatusDeclined = "declined"
StatusWaiting = "waiting_on_dependencies"
StatusPending = "pending"
StatusRunning = "running"
StatusPassing = "success"
StatusFailing = "failure"
StatusKilled = "killed"
StatusError = "error"
)
const (
RepoGit = "git"
RepoHg = "hg"
RepoFossil = "fossil"
RepoPerforce = "perforce"
)
type (
// Status represents a commit status.
Status struct {
State string
Label string
Desc string
Target string
}
const (
VisibilityPublic = "public"
VisibilityPrivate = "private"
VisibilityInternal = "internal"
// StatusInput provides the necessary metadata to
// set the commit or deployment status.
StatusInput struct {
Repo *Repository
Build *Build
}
// StatusService sends the commit status to an external
// external source code management service (e.g. GitHub).
StatusService interface {
Send(ctx context.Context, user *User, req *StatusInput) error
}
)

65
core/step.go Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import "context"
type (
// Step represents an individual step in the stage.
Step struct {
ID int64 `json:"id"`
StageID int64 `json:"step_id"`
Number int `json:"number"`
Name string `json:"name"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ErrIgnore bool `json:"errignore,omitempty"`
ExitCode int `json:"exit_code"`
Started int64 `json:"started,omitempty"`
Stopped int64 `json:"stopped,omitempty"`
Version int64 `json:"version"`
}
// StepStore persists build step information to storage.
StepStore interface {
// List returns a build stage list from the datastore.
List(context.Context, int64) ([]*Step, error)
// Find returns a build stage from the datastore by ID.
Find(context.Context, int64) (*Step, error)
// FindNumber returns a stage from the datastore by number.
FindNumber(context.Context, int64, int) (*Step, error)
// Create persists a new stage to the datastore.
Create(context.Context, *Step) error
// Update persists an updated stage to the datastore.
Update(context.Context, *Step) error
}
)
// IsDone returns true if the step has a completed state.
func (s *Step) IsDone() bool {
switch s.Status {
case StatusWaiting,
StatusPending,
StatusRunning,
StatusBlocked:
return false
default:
return true
}
}

23
core/step_test.go Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core
import "testing"
func TestStepIsDone(t *testing.T) {
for _, status := range statusDone {
v := Step{Status: status}
if v.IsDone() == false {
t.Errorf("Expect status %s is done", status)
}
}
for _, status := range statusNotDone {
v := Step{Status: status}
if v.IsDone() == true {
t.Errorf("Expect status %s is not done", status)
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package session
package core
import (
"testing"
)
func TestSetPerm(t *testing.T) {
import "context"
// Syncer synchonrizes the account repository list.
type Syncer interface {
Sync(context.Context, *User) (*Batch, error)
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,18 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
package core
import (
"fmt"
)
type APIClientErr struct {
Message string
URL string
Cause error
}
func (e APIClientErr) Error() string {
return fmt.Sprintf("%s (Requested %s): %v", e.Message, e.URL, e.Cause)
// System stores system information.
type System struct {
Proto string `json:"proto,omitempty"`
Host string `json:"host,omitempty"`
Link string `json:"link,omitempty"`
Version string `json:"version,omitempty"`
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Drone.IO Inc.
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,24 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package client
package core
import (
"encoding/json"
)
import "context"
// Trigger types
const (
currentUserUrl = "/user"
TriggerHook = "@hook"
TriggerCron = "@cron"
)
func (c *Client) CurrentUser() (User, error) {
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
var user User
contents, err := c.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
// Triggerer is responsible for triggering a Build from an
// incoming drone. If a build is skipped a nil value is
// returned.
type Triggerer interface {
Trigger(context.Context, *Repository, *Hook) (*Build, error)
}

5
core/trigger_test.go Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core

96
core/user.go Normal file
View file

@ -0,0 +1,96 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
"errors"
"github.com/asaskevich/govalidator"
)
var (
errUsernameLen = errors.New("Invalid username length")
errUsernameChar = errors.New("Invalid character in username")
)
type (
// User represents a user of the system.
User struct {
ID int64 `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
Machine bool `json:"machine"`
Admin bool `json:"admin"`
Active bool `json:"active"`
Avatar string `json:"avatar"`
Syncing bool `json:"syncing"`
Synced int64 `json:"synced"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
LastLogin int64 `json:"last_login"`
Token string `json:"-"`
Refresh string `json:"-"`
Expiry int64 `json:"-"`
Hash string `json:"-"`
}
// UserStore defines operations for working with users.
UserStore interface {
// Find returns a user from the datastore.
Find(context.Context, int64) (*User, error)
// FindLogin returns a user from the datastore by username.
FindLogin(context.Context, string) (*User, error)
// FindToken returns a user from the datastore by token.
FindToken(context.Context, string) (*User, error)
// List returns a list of users from the datastore.
List(context.Context) ([]*User, error)
// Create persists a new user to the datastore.
Create(context.Context, *User) error
// Update persists an updated user to the datastore.
Update(context.Context, *User) error
// Delete deletes a user from the datastore.
Delete(context.Context, *User) error
// Count returns a count of active users.
Count(context.Context) (int64, error)
}
// UserService provides access to user account
// resources in the remote system (e.g. GitHub).
UserService interface {
// Find returns the authenticated user.
Find(ctx context.Context, access, refresh string) (*User, error)
}
)
// Validate valides the user and returns an error if the
// validation fails.
func (u *User) Validate() error {
switch {
case !govalidator.IsByteLength(u.Login, 1, 50):
return errUsernameLen
case !govalidator.Matches(u.Login, "^[a-zA-Z0-9_-]+$"):
return errUsernameChar
default:
return nil
}
}

62
core/user_test.go Normal file
View file

@ -0,0 +1,62 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package core
import (
"testing"
)
func TestValidateUser(t *testing.T) {
tests := []struct {
user *User
err error
}{
{
user: &User{Login: ""},
err: errUsernameLen,
},
{
user: &User{Login: "©"}, // non ascii character
err: errUsernameChar,
},
{
user: &User{Login: "소주"}, // non ascii character
err: errUsernameChar,
},
{
user: &User{Login: "foo/bar"},
err: errUsernameChar,
},
{
user: &User{Login: "this-is-a-really-really-really-really-long-username"},
err: errUsernameLen,
},
{
user: &User{Login: "octocat"},
err: nil,
},
{
user: &User{Login: "OctO-Cat_01"},
err: nil,
},
}
for i, test := range tests {
got := test.user.Validate()
if got == nil && test.err == nil {
continue
}
if got == nil && test.err != nil {
t.Errorf("Expected error: %q at index %d", test.err, i)
continue
}
if got != nil && test.err == nil {
t.Errorf("Unexpected error: %q at index %d", got, i)
continue
}
if got, want := got.Error(), test.err.Error(); got != want {
t.Errorf("Want error %q, got %q at index %d", want, got, i)
}
}
}

59
core/webhook.go Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"context"
)
// Webhook event types.
const (
WebhookEventBuild = "build"
WebhookEventRepo = "repo"
WebhookEventUser = "user"
)
// Webhook action types.
const (
WebhookActionCreated = "created"
WebhookActionUpdated = "updated"
WebhookActionDeleted = "deleted"
WebhookActionEnabled = "enabled"
WebhookActionDisabled = "disabled"
)
type (
// Webhook defines an integration endpoint.
Webhook struct {
Endpoint string `json:"endpoint,omitempty"`
Signer string `json:"-"`
SkipVerify bool `json:"skip_verify,omitempty"`
}
// WebhookData provides the webhook data.
WebhookData struct {
Event string `json:"-"`
Action string `json:"action"`
User *User `json:"user,omitempty"`
Repo *Repository `json:"repo,omitempty"`
Build *Build `json:"build,omitempty"`
}
// WebhookSender sends the webhook payload.
WebhookSender interface {
// Send sends the webhook to the global endpoint.
Send(context.Context, *WebhookData) error
}
)

View file

@ -0,0 +1,14 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
ENV GODEBUG netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_RUNNER_PLATFORM=linux/amd64
ENV DRONE_RUNNER_CAPACITY=1
ADD release/linux/amd64/drone-agent /bin/
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -0,0 +1,10 @@
FROM drone/ca-certs
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm
ENV DRONE_RUNNER_PLATFORM=linux/arm
ENV DRONE_RUNNER_CAPACITY=1
ENV DRONE_RUNNER_VARIANT=v7
ADD release/linux/arm/drone-agent /bin/
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -0,0 +1,10 @@
FROM drone/ca-certs
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm64
ENV DRONE_RUNNER_PLATFORM=linux/arm64
ENV DRONE_RUNNER_CAPACITY=1
ENV DRONE_RUNNER_VARIANT=v8
ADD release/linux/arm64/drone-agent /bin/
ENTRYPOINT ["/bin/drone-agent"]

View file

@ -0,0 +1,12 @@
FROM microsoft/nanoserver:1709
USER ContainerAdministrator
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=windows
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_RUNNER_PLATFORM=windows/amd64
ENV DRONE_RUNNER_KERNEL=1709
ENV DRONE_RUNNER_CAPACITY=1
ADD release/windows/amd64/drone-agent.exe /drone-agent.exe
ENTRYPOINT [ "\\drone-agent.exe" ]

View file

@ -0,0 +1,12 @@
FROM microsoft/nanoserver:1803
USER ContainerAdministrator
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=windows
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_RUNNER_PLATFORM=windows/amd64
ENV DRONE_RUNNER_KERNEL=1803
ENV DRONE_RUNNER_CAPACITY=1
ADD release/windows/amd64/drone-agent.exe /drone-agent.exe
ENTRYPOINT [ "\\drone-agent.exe" ]

View file

@ -0,0 +1,14 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
ENV GODEBUG netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_RUNNER_PLATFORM=linux/amd64
ENV DRONE_RUNNER_CAPACITY=1
ADD release/linux/amd64/drone-controller /bin/
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/bin/drone-controller"]

View file

@ -0,0 +1,15 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm
ENV DRONE_RUNNER_PLATFORM=linux/arm
ENV DRONE_RUNNER_CAPACITY=1
ENV DRONE_RUNNER_VARIANT=v7
ADD release/linux/arm/drone-controller /bin/
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/bin/drone-controller"]

View file

@ -0,0 +1,15 @@
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm64
ENV DRONE_RUNNER_PLATFORM=linux/arm64
ENV DRONE_RUNNER_CAPACITY=1
ENV DRONE_RUNNER_VARIANT=v8
ADD release/linux/arm64/drone-controller /bin/
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/bin/drone-controller"]

View file

@ -0,0 +1,12 @@
FROM microsoft/nanoserver:1803
USER ContainerAdministrator
ENV GODEBUG=netdns=go
ENV DRONE_RUNNER_OS=windows
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_RUNNER_PLATFORM=windows/amd64
ENV DRONE_RUNNER_KERNEL=1803
ENV DRONE_RUNNER_CAPACITY=1
ADD release/windows/amd64/drone-controller.exe /drone-controller.exe
ENTRYPOINT [ "\\drone-controller.exe" ]

View file

@ -0,0 +1,22 @@
# docker build --rm -f docker/Dockerfile -t drone/drone .
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 80 443
VOLUME /data
ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/amd64/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

View file

@ -0,0 +1,22 @@
# docker build --rm -f docker/Dockerfile -t drone/drone .
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 80 443
VOLUME /data
ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/arm/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

View file

@ -0,0 +1,22 @@
# docker build --rm -f docker/Dockerfile -t drone/drone .
FROM alpine:3.6 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3.6
EXPOSE 80 443
VOLUME /data
ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm64
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ADD release/linux/arm64/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

View file

@ -0,0 +1,31 @@
image: drone/agent:{{#if build.tag}}{{trimPrefix build.tag "v"}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: drone/agent:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: drone/agent:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
-
image: drone/agent:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux
variant: v7
-
image: drone/agent:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}windows-amd64
platform:
architecture: amd64
os: windows
variant: 1803

View file

@ -0,0 +1,31 @@
image: drone/controller:{{#if build.tag}}{{trimPrefix build.tag "v"}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: drone/controller:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: drone/controller:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
-
image: drone/controller:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux
variant: v7
-
image: drone/controller:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}windows-amd64
platform:
architecture: amd64
os: windows
variant: 1803

View file

@ -0,0 +1,25 @@
image: drone/drone:{{#if build.tag}}{{trimPrefix build.tag "v"}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: drone/drone:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: drone/drone:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
-
image: drone/drone:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux
variant: v7

98
go.mod Normal file
View file

@ -0,0 +1,98 @@
module github.com/drone/drone
require (
docker.io/go-docker v1.0.0
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e
github.com/Microsoft/go-winio v0.4.11
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f
github.com/aws/aws-sdk-go v1.15.57
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
github.com/bmatcuk/doublestar v1.1.1
github.com/coreos/go-semver v0.2.0
github.com/davecgh/go-spew v1.1.1
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff
github.com/docker/go-connections v0.3.0
github.com/docker/go-units v0.3.3
github.com/drone/drone-go v0.0.0-20190217024616-3e8b71333e59
github.com/drone/drone-runtime v0.0.0-20190123233515-16c002539b15
github.com/drone/drone-ui v0.0.0-20190212070020-c372640c766f
github.com/drone/drone-yaml v0.0.0-20190122234417-98eb77b4c58a
github.com/drone/envsubst v1.0.1
github.com/drone/go-license v1.0.2
github.com/drone/go-login v1.0.3
github.com/drone/go-scm v1.0.9
github.com/drone/signal v1.0.0
github.com/dustin/go-humanize v1.0.0
github.com/ghodss/yaml v1.0.0
github.com/go-chi/chi v3.3.3+incompatible
github.com/go-chi/cors v1.0.0
github.com/go-ini/ini v1.39.0
github.com/go-sql-driver/mysql v1.4.0
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506
github.com/golang/mock v1.1.1
github.com/golang/protobuf v1.2.0
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c
github.com/google/go-cmp v0.2.0
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
github.com/google/wire v0.2.1
github.com/googleapis/gnostic v0.2.0
github.com/gorhill/cronexpr v0.0.0-20140423231348-a557574d6c02
github.com/gosimple/slug v1.3.0
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f
github.com/h2non/gock v1.0.10
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-cleanhttp v0.5.0
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6
github.com/hashicorp/go-rootcerts v1.0.0
github.com/hashicorp/golang-lru v0.5.0
github.com/hashicorp/nomad v0.0.0-20190125003214-134391155854
github.com/imdario/mergo v0.3.6
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
github.com/joho/godotenv v1.3.0
github.com/json-iterator/go v1.1.5
github.com/kelseyhightower/envconfig v1.3.0
github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v1.9.0
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/mitchellh/go-homedir v1.0.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec v1.0.1
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/pkg/errors v0.8.0
github.com/prometheus/client_golang v0.8.0
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/segmentio/ksuid v1.0.2
github.com/sirupsen/logrus v0.0.0-20181103062819-44067abb194b
github.com/spf13/pflag v1.0.3
github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606
golang.org/x/crypto v0.0.0-20181012144002-a92615f3c490
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba
golang.org/x/text v0.3.0
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c
google.golang.org/appengine v1.2.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd
k8s.io/apimachinery v0.0.0-20181204150028-eb8c8024849b
k8s.io/client-go v10.0.0+incompatible
k8s.io/klog v0.1.0
sigs.k8s.io/yaml v1.1.0
)

194
go.sum Normal file
View file

@ -0,0 +1,194 @@
docker.io/go-docker v1.0.0 h1:VdXS/aNYQxyA9wdLD5z8Q8Ro688/hG8HzKxYVEVbE6s=
docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f h1:y2hSFdXeA1y5z5f0vfNO0Dg5qVY036qzlz3Pds0B92o=
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.15.57 h1:inht07/mRNnvV4uAjjVgTVD7/rF+j0mXllYcNQxDgGA=
github.com/aws/aws-sdk-go v1.15.57/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866 h1:98WJ4YCdjmB7uyrdT3P4A2Oa1hiRPKoa/0zInG6UnfQ=
github.com/dchest/authcookie v0.0.0-20120917135355-fbdef6e99866/go.mod h1:x7AK2h2QzaXVEFi1tbMYMDuvHcCEr1QdMDrg3hkW24Q=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff h1:FKH02LHYqSmeWd3GBh0KIkM8JBpw3RrShgtcWShdWJg=
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/drone/drone-go v0.0.0-20190217024616-3e8b71333e59 h1:tH7rJBME3tKWunUmqcg6NyyXNVX2vbNpJl7p/p/vX88=
github.com/drone/drone-go v0.0.0-20190217024616-3e8b71333e59/go.mod h1:qVb1k1w9X5jgoGyLtbnfWNnd4XZfAwokxBmiutbpGqw=
github.com/drone/drone-runtime v0.0.0-20190123233515-16c002539b15 h1:uwgJGp/Rsu1I2UdP+ozDJrW33EbMjfO5ILAd4ePoSAw=
github.com/drone/drone-runtime v0.0.0-20190123233515-16c002539b15/go.mod h1:I+wJO4yvngCUAro6wKjkMbuPPDI/jRynqU0LTW+8J44=
github.com/drone/drone-ui v0.0.0-20190212070020-c372640c766f h1:iNutUxLvlEY9rVB+v/KEefXnYGxz70LcN4PFCYig2F0=
github.com/drone/drone-ui v0.0.0-20190212070020-c372640c766f/go.mod h1:NBtVWW7NNJpD9+huMD/5TAE1db2nrEh0i35/9Rf1MPI=
github.com/drone/drone-yaml v0.0.0-20190122234417-98eb77b4c58a h1:lkYg2gkLiuTtVgIUgBj5EX2EMj6sERIYfyM0RN1YzuY=
github.com/drone/drone-yaml v0.0.0-20190122234417-98eb77b4c58a/go.mod h1:JclcdvMwnrxyy25H3YQRxVrsj2u4GdI6L6NgJpEIp00=
github.com/drone/envsubst v1.0.1 h1:NOOStingM2sbBwsIUeQkKUz8ShwCUzmqMxWrpXItfPE=
github.com/drone/envsubst v1.0.1/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
github.com/drone/go-license v1.0.2 h1:7OwndfYk+Lp/cGHkxe4HUn/Ysrrw3WYH2pnd99yrkok=
github.com/drone/go-license v1.0.2/go.mod h1:fGRHf+F1cEaw3YVYiJ6js3G3dVhcxyS617RnNRUMsms=
github.com/drone/go-login v1.0.3 h1:YmZMUoWWd3QrgmobC1DcExFjW7w2ZEBO1R1VeeobIRU=
github.com/drone/go-login v1.0.3/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
github.com/drone/go-scm v1.0.9 h1:Bn8K4YZa4P7jgJCGs8SJwPeefIGrj8oe4UGeM7KBgP8=
github.com/drone/go-scm v1.0.9/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw=
github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI=
github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.39.0 h1:/CyW/jTlZLjuzy52jc1XnhJm6IUKEuunpJFpecywNeI=
github.com/go-ini/ini v1.39.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506 h1:zDlw+wgyXdfkRuvFCdEDUiPLmZp2cvf/dWHazY0a5VM=
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/wire v0.2.1 h1:TYj4Z2qjqxa2ufb34UJqVeO9aznL+i0fLO6TqThKZ7Y=
github.com/google/wire v0.2.1/go.mod h1:ptBl5bWD3nzmJHVNwYHV3v4wdtKzBMlU2YbtKQCG9GI=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gorhill/cronexpr v0.0.0-20140423231348-a557574d6c02 h1:Spo+4PFAGDqULAsZ7J69MOxq4/fwgZ0zvmDTBqpq7yU=
github.com/gorhill/cronexpr v0.0.0-20140423231348-a557574d6c02/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
github.com/gosimple/slug v1.3.0 h1:NKQyQMjKkgCpD/Vd+wKtFc7N60bJNCLDubKU/UDKMFI=
github.com/gosimple/slug v1.3.0/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM=
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
github.com/h2non/gock v1.0.10 h1:EzHYzKKSLN4xk0w193uAy3tp8I3+L1jmaI2Mjg4lCgU=
github.com/h2non/gock v1.0.10/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 h1:qCv4319q2q7XKn0MQbi8p37hsJ+9Xo8e6yojA73JVxk=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6/go.mod h1:fXcdFsQoipQa7mwORhKad5jmDCeSy/RCGzWA08PO0lM=
github.com/hashicorp/go-rootcerts v1.0.0 h1:ueI78wUjYExhCvMLow4icJnayNNFRgy0d9EGs/a1T44=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/nomad v0.0.0-20190125003214-134391155854 h1:L7WhLZt2ory/kQWxqkMwOiBpIoa4BWoadN7yx8LHEtk=
github.com/hashicorp/nomad v0.0.0-20190125003214-134391155854/go.mod h1:WRaKjdO1G2iqi86TvTjIYtKTyxg4pl7NLr9InxtWaI0=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM=
github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4/go.mod h1:cojhOHk1gbMeklOyDP2oKKLftefXoJreOQGOrXk+Z38=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/segmentio/ksuid v1.0.2 h1:9yBfKyw4ECGTdALaF09Snw3sLJmYIX6AbPJrAy6MrDc=
github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU=
github.com/sirupsen/logrus v0.0.0-20181103062819-44067abb194b h1:dnSVC38LseSWVhj3WWqD3worKZY+EqoWwi+MMAKgPbs=
github.com/sirupsen/logrus v0.0.0-20181103062819-44067abb194b/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606 h1:dU9yXzNi9rl6Mou7+3npdfPyeFPb2+7BHs3zL47bhPY=
github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181012144002-a92615f3c490 h1:va0qYsIOza3Nlf2IncFyOql4/3XUq3vfge/Ad64bhlM=
golang.org/x/crypto v0.0.0-20181012144002-a92615f3c490/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc=
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd h1:5aHsneN62ehs/tdtS9tWZlhVk68V7yms/Qw7nsGmvCA=
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apimachinery v0.0.0-20181204150028-eb8c8024849b h1:NBYMVxACHvRjnsH8rkNm2ICFZlXznkXYEefUdEpcueY=
k8s.io/apimachinery v0.0.0-20181204150028-eb8c8024849b/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34=
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk=
k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

49
handler/api/acl/acl.go Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"net/http"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/handler/api/render"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/logger"
)
// AuthorizeUser returns an http.Handler middleware that authorizes only
// authenticated users to proceed to the next handler in the chain. Guest users
// are rejected with a 401 unauthorized error.
func AuthorizeUser(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, ok := request.UserFrom(r.Context())
if !ok {
render.Unauthorized(w, errors.ErrUnauthorized)
logger.FromRequest(r).
Debugln("api: authentication required")
} else {
next.ServeHTTP(w, r)
}
})
}
// AuthorizeAdmin returns an http.Handler middleware that authorizes only
// system administrators to proceed to the next handler in the chain.
func AuthorizeAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := request.UserFrom(r.Context())
if !ok {
render.Unauthorized(w, errors.ErrUnauthorized)
logger.FromRequest(r).
Debugln("api: authentication required")
} else if !user.Admin {
render.Forbidden(w, errors.ErrForbidden)
logger.FromRequest(r).
Debugln("api: administrative access required")
} else {
next.ServeHTTP(w, r)
}
})
}

129
handler/api/acl/acl_test.go Normal file
View file

@ -0,0 +1,129 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/core"
"github.com/sirupsen/logrus"
)
func init() {
logrus.SetOutput(ioutil.Discard)
}
var (
mockUser = &core.User{
ID: 1,
Login: "octocat",
Admin: false,
}
mockRepo = &core.Repository{
ID: 1,
UID: "42",
Namespace: "octocat",
Name: "hello-world",
Slug: "octocat/hello-world",
Counter: 42,
Branch: "master",
Private: true,
Visibility: core.VisibilityPrivate,
}
)
func TestAuthorizeUser(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
AuthorizeUser(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// use dummy status code to signal the next handler in
// the middleware chain was properly invoked.
w.WriteHeader(http.StatusTeapot)
}),
).ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
func TestAuthorizeUserErr(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
AuthorizeUser(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
}),
).ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
func TestAuthorizeAdmin(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), &core.User{Admin: true}),
)
AuthorizeAdmin(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// use dummy status code to signal the next handler in
// the middleware chain was properly invoked.
w.WriteHeader(http.StatusTeapot)
}),
).ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
func TestAuthorizeAdminUnauthorized(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
AuthorizeAdmin(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
}),
).ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
func TestAuthorizeAdminForbidden(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), &core.User{Admin: false}),
)
AuthorizeAdmin(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
}),
).ServeHTTP(w, r)
if got, want := w.Code, http.StatusForbidden; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}

133
handler/api/acl/check.go Normal file
View file

@ -0,0 +1,133 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"net/http"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/handler/api/render"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/logger"
"github.com/drone/drone/core"
"github.com/go-chi/chi"
"github.com/sirupsen/logrus"
)
// CheckReadAccess returns an http.Handler middleware that authorizes only
// authenticated users with read repository access to proceed to the next
// handler in the chain.
func CheckReadAccess() func(http.Handler) http.Handler {
return CheckAccess(true, false, false)
}
// CheckWriteAccess returns an http.Handler middleware that authorizes only
// authenticated users with write repository access to proceed to the next
// handler in the chain.
func CheckWriteAccess() func(http.Handler) http.Handler {
return CheckAccess(true, true, false)
}
// CheckAdminAccess returns an http.Handler middleware that authorizes only
// authenticated users with admin repository access to proceed to the next
// handler in the chain.
func CheckAdminAccess() func(http.Handler) http.Handler {
return CheckAccess(true, true, true)
}
// CheckAccess returns an http.Handler middleware that authorizes only
// authenticated users with the required read, write or admin access
// permissions to the requested repository resource.
func CheckAccess(read, write, admin bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
owner = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
log := logger.FromRequest(r).
WithField("namespace", owner).
WithField("name", name)
user, ok := request.UserFrom(ctx)
switch {
case ok == false && write == true:
render.Unauthorized(w, errors.ErrUnauthorized)
log.Debugln("api: authentication required for write access")
return
case ok == false && admin == true:
render.Unauthorized(w, errors.ErrUnauthorized)
log.Debugln("api: authentication required for admin access")
return
case ok == true && user.Admin == true:
log.Debugln("api: root access granted")
next.ServeHTTP(w, r)
return
}
repo, noRepo := request.RepoFrom(ctx)
if !noRepo {
// this should never happen. the repository
// should always be injected into the context
// by an upstream handler in the chain.
log.Errorln("api: null repository in context")
render.NotFound(w, errors.ErrNotFound)
return
}
log = log.WithField("visibility", repo.Visibility)
switch {
case admin == true: // continue
case write == true: // continue
case repo.Visibility == core.VisibilityPublic:
log.Debugln("api: read access granted")
next.ServeHTTP(w, r)
return
case ok == false:
render.Unauthorized(w, errors.ErrUnauthorized)
log.Debugln("api: authentication required")
return
case ok == true && repo.Visibility == core.VisibilityInternal:
log.Debugln("api: read access granted")
next.ServeHTTP(w, r)
return
}
perm, ok := request.PermFrom(ctx)
if !ok {
render.NotFound(w, errors.ErrNotFound)
log.Debugln("api: repository permissions not found")
return
}
log = log.WithFields(
logrus.Fields{
"read": perm.Read,
"write": perm.Write,
"admin": perm.Admin,
},
)
switch {
case read == true && perm.Read == false:
render.NotFound(w, errors.ErrNotFound)
log.Debugln("api: read access required")
case write == true && perm.Write == false:
render.NotFound(w, errors.ErrNotFound)
log.Debugln("api: write access required")
case admin == true && perm.Admin == false:
render.NotFound(w, errors.ErrNotFound)
log.Debugln("api: admin access required")
default:
log.Debug("api: access granted")
next.ServeHTTP(w, r.WithContext(
request.WithPerm(ctx, perm),
))
}
})
}
}

View file

@ -0,0 +1,786 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/core"
"github.com/google/go-cmp/cmp"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
)
var noContext = context.Background()
// this test verifies that a 401 unauthorized error is written to
// the response if the client is not authenticated and repository
// visibility is internal or private.
func TestCheckAccess_Guest_Unauthorized(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithRepo(noContext, mockRepo),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrUnauthorized
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user is not authenticated BUT
// the repository is publicly visible.
func TestCheckAccess_Guest_PublicVisibility(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockRepo := *mockRepo
mockRepo.Visibility = core.VisibilityPublic
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithRepo(noContext, &mockRepo),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 401 unauthorized error is written to
// the response if the repository visibility is internal, and the
// client is not authenticated.
func TestCheckAccess_Guest_InternalVisibility(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockRepo := *mockRepo
mockRepo.Visibility = core.VisibilityInternal
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithRepo(noContext, &mockRepo),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user is authenticated AND
// the repository is publicly visible.
func TestCheckAccess_Authenticated_PublicVisibility(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockRepo := *mockRepo
mockRepo.Visibility = core.VisibilityPublic
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithUser(
request.WithRepo(noContext, &mockRepo), mockUser),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user is authenticated AND
// the repository has internal visible.
func TestCheckAccess_Authenticated_InternalVisibility(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockRepo := *mockRepo
mockRepo.Visibility = core.VisibilityInternal
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithUser(
request.WithRepo(noContext, &mockRepo), mockUser),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 404 not found error is written to
// the response if the repository is not found AND the user is
// authenticated.
func TestCheckAccess_Authenticated_RepositoryNotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies that a 404 not found error is written to
// the response if the user does not have permissions to access
// the repository.
func TestCheckAccess_Permission_NotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithUser(
request.WithRepo(noContext, mockRepo), mockUser),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user has read access to the
// repository.
func TestCheckReadAccess(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
readAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: true,
Write: false,
Admin: false,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
readAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 404 not found error is written to
// the response if the user lacks read access to the repository.
func TestCheckReadAccess_InsufficientPermissions(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
noAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: false,
Write: false,
Admin: false,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
noAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckReadAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user has write access to the
// repository.
func TestCheckWriteAccess(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
writeAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: true,
Write: true,
Admin: false,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
writeAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckWriteAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 404 not found error is written to
// the response if the user lacks write access to the repository.
//
// TODO(bradrydzewski) we should consider returning a 403 forbidden
// if the user has read access.
func TestCheckWriteAccess_InsufficientPermissions(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
noAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: true,
Write: false,
Admin: false,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
noAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckWriteAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the user has admin access to the
// repository.
func TestCheckAdminAccess(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
noAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: true,
Write: true,
Admin: true,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
noAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckAdminAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 404 not found error is written to
// the response if the user lacks admin access to the repository.
//
// TODO(bradrydzewski) we should consider returning a 403 forbidden
// if the user has read access.
func TestCheckAdminAccess_InsufficientPermissions(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
noAccess := &core.Perm{
Synced: time.Now().Unix(),
Read: true,
Write: true,
Admin: false,
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithPerm(
request.WithUser(
request.WithRepo(noContext, mockRepo),
mockUser,
),
noAccess,
),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckAdminAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies the the next handler in the middleware
// chain is processed if the authenticated user is a system
// administrator.
func TestCheckAdminAccess_SystemAdmin(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
user := &core.User{ID: 1, Admin: true}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithUser(r.Context(), user),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckAdminAccess())
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusTeapot; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
}
// this test verifies that a 401 unauthorized error is written to
// the response if the client is not authenticated and write
// access is required.
func TestCheckAccess_Guest_Write(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithRepo(noContext, mockRepo),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckAccess(true, true, false))
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrUnauthorized
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// this test verifies that a 401 unauthorized error is written to
// the response if the client is not authenticated and admin
// access is required.
func TestCheckAccess_Guest_Admin(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
r = r.WithContext(
request.WithRepo(noContext, mockRepo),
)
router := chi.NewRouter()
router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
router.Use(CheckAccess(true, false, true))
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
t.Errorf("Must not invoke next handler in middleware chain")
})
})
router.ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; got != want {
t.Errorf("Want status code %d, got %d", want, got)
}
got, want := new(errors.Error), errors.ErrUnauthorized
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// // this test verifies the the next handler in the middleware
// // chain is processed if the authenticated has read permissions
// // that are successfully synchronized with the source.
// func TestCheckAccess_RefreshPerms(t *testing.T) {
// controller := gomock.NewController(t)
// defer controller.Finish()
// expiredAccess := &core.Perm{
// Synced: 0,
// Read: false,
// Write: false,
// Admin: false,
// }
// updatedAccess := &core.Perm{
// Read: true,
// Write: true,
// Admin: true,
// }
// checkPermUpdate := func(ctx context.Context, perm *core.Perm) {
// if perm.Synced == 0 {
// t.Errorf("Expect synced timestamp updated")
// }
// if perm.Read == false {
// t.Errorf("Expect Read flag updated")
// }
// if perm.Write == false {
// t.Errorf("Expect Write flag updated")
// }
// if perm.Admin == false {
// t.Errorf("Expect Admin flag updated")
// }
// }
// repos := mock.NewMockRepositoryStore(controller)
// repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(mockRepo, nil)
// perms := mock.NewMockPermStore(controller)
// perms.EXPECT().Find(gomock.Any(), mockRepo.UID, mockUser.ID).Return(expiredAccess, nil)
// perms.EXPECT().Update(gomock.Any(), expiredAccess).Return(nil).Do(checkPermUpdate)
// service := mock.NewMockRepositoryService(controller)
// service.EXPECT().FindPerm(gomock.Any(), "octocat/hello-world").Return(updatedAccess, nil)
// factory := mock.NewMockRepositoryServiceFactory(controller)
// factory.EXPECT().Create(mockUser).Return(service)
// w := httptest.NewRecorder()
// r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
// r = r.WithContext(
// request.WithUser(r.Context(), mockUser),
// )
// router := chi.NewRouter()
// router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
// router.Use(CheckReadAccess(factory, repos, perms))
// router.Get("/", func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusTeapot)
// })
// })
// router.ServeHTTP(w, r)
// if got, want := w.Code, http.StatusTeapot; got != want {
// t.Errorf("Want status code %d, got %d", want, got)
// }
// }
// // this test verifies that a 404 not found error is written to
// // the response if the user permissions are expired and the
// // updated permissions cannot be fetched.
// func TestCheckAccess_RefreshPerms_Error(t *testing.T) {
// controller := gomock.NewController(t)
// defer controller.Finish()
// expiredAccess := &core.Perm{
// Synced: 0,
// Read: false,
// Write: false,
// Admin: false,
// }
// repos := mock.NewMockRepositoryStore(controller)
// repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(mockRepo, nil)
// perms := mock.NewMockPermStore(controller)
// perms.EXPECT().Find(gomock.Any(), mockRepo.UID, mockUser.ID).Return(expiredAccess, nil)
// service := mock.NewMockRepositoryService(controller)
// service.EXPECT().FindPerm(gomock.Any(), "octocat/hello-world").Return(nil, io.EOF)
// factory := mock.NewMockRepositoryServiceFactory(controller)
// factory.EXPECT().Create(mockUser).Return(service)
// w := httptest.NewRecorder()
// r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
// r = r.WithContext(
// request.WithUser(r.Context(), mockUser),
// )
// router := chi.NewRouter()
// router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
// router.Use(CheckReadAccess(factory, repos, perms))
// router.Get("/", func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusTeapot)
// })
// })
// router.ServeHTTP(w, r)
// if got, want := w.Code, 404; got != want {
// t.Errorf("Want status code %d, got %d", want, got)
// }
// }
// // this test verifies the the next handler in the middleware
// // chain is processed if the user permissions are expired,
// // updated permissions are fetched, but fail the changes fail
// // to persist to the database. We know the user has access,
// // so we allow them to proceed even in the event of a failure.
// func TestCheckAccess_RefreshPerms_CannotSave(t *testing.T) {
// controller := gomock.NewController(t)
// defer controller.Finish()
// expiredAccess := &core.Perm{
// Synced: 0,
// Read: false,
// Write: false,
// Admin: false,
// }
// updatedAccess := &core.Perm{
// Read: true,
// Write: true,
// Admin: true,
// }
// service := mock.NewMockRepositoryService(controller)
// service.EXPECT().FindPerm(gomock.Any(), "octocat/hello-world").Return(updatedAccess, nil)
// factory := mock.NewMockRepositoryServiceFactory(controller)
// factory.EXPECT().Create(mockUser).Return(service)
// repos := mock.NewMockRepositoryStore(controller)
// repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(mockRepo, nil)
// perms := mock.NewMockPermStore(controller)
// perms.EXPECT().Find(gomock.Any(), mockRepo.UID, mockUser.ID).Return(expiredAccess, nil)
// perms.EXPECT().Update(gomock.Any(), expiredAccess).Return(io.EOF)
// w := httptest.NewRecorder()
// r := httptest.NewRequest("GET", "/api/repos/octocat/hello-world", nil)
// r = r.WithContext(
// request.WithUser(r.Context(), mockUser),
// )
// router := chi.NewRouter()
// router.Route("/api/repos/{owner}/{name}", func(router chi.Router) {
// router.Use(CheckReadAccess(factory, repos, perms))
// router.Get("/", func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusTeapot)
// })
// })
// router.ServeHTTP(w, r)
// if got, want := w.Code, http.StatusTeapot; got != want {
// t.Errorf("Want status code %d, got %d", want, got)
// }
// }

126
handler/api/acl/repo.go Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"net/http"
"time"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/handler/api/render"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/logger"
"github.com/drone/drone/core"
"github.com/go-chi/chi"
"github.com/sirupsen/logrus"
)
// InjectRepository returns an http.Handler middleware that injects
// the repository and repository permissions into the context.
func InjectRepository(
repoz core.RepositoryService,
repos core.RepositoryStore,
perms core.PermStore,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
owner = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
log := logger.FromRequest(r).WithFields(
logrus.Fields{
"namespace": owner,
"name": name,
},
)
// the user is stored in the context and is
// provided by a an ancestor middleware in the
// chain.
user, sessionExists := request.UserFrom(ctx)
repo, err := repos.FindName(ctx, owner, name)
if err != nil {
if sessionExists {
render.NotFound(w, errors.ErrNotFound)
} else {
render.Unauthorized(w, errors.ErrUnauthorized)
}
log.WithError(err).Debugln("api: repository not found")
return
}
// the repository is stored in the request context
// and can be accessed by subsequent handlers in the
// request chain.
ctx = request.WithRepo(ctx, repo)
// if the user does not exist in the request context,
// this is a guest session, and there are no repository
// permissions to lookup.
if !sessionExists {
next.ServeHTTP(w, r.WithContext(ctx))
return
}
// else get the cached permissions from the database
// for the user and repository.
perm, err := perms.Find(ctx, repo.UID, user.ID)
if err != nil {
// if the permissions are not found we forward
// the request to the next handler in the chain
// with no permissions in the context.
//
// It is the responsibility to downstream
// middleware and handlers to decide if the
// request should be rejected.
next.ServeHTTP(w, r.WithContext(ctx))
return
}
log = log.WithFields(
logrus.Fields{
"read": perm.Read,
"write": perm.Write,
"admin": perm.Admin,
},
)
// because the permissions are synced with the remote
// system (e.g. github) they may be stale. If the permissions
// are stale they are refreshed below.
if perm.Synced == 0 || time.Unix(perm.Synced, 0).Add(time.Hour).Before(time.Now()) {
log.Debugln("api: sync repository permissions")
permv, err := repoz.FindPerm(ctx, user, repo.Slug)
if err != nil {
render.NotFound(w, errors.ErrNotFound)
log.WithError(err).
Warnln("api: cannot sync repository permissions")
return
}
perm.Synced = time.Now().Unix()
perm.Read = permv.Read
perm.Write = permv.Write
perm.Admin = permv.Admin
err = perms.Update(ctx, perm)
if err != nil {
log.WithError(err).Debugln("api: cannot cache repository permissions")
} else {
log.Debugln("api: repository permissions synchrnoized")
}
}
ctx = request.WithPerm(ctx, perm)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View file

@ -0,0 +1,205 @@
// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
package acl
import (
"context"
"database/sql"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/drone/drone/handler/api/request"
"github.com/drone/drone/mock"
"github.com/drone/drone/core"
"github.com/go-chi/chi"
"github.com/golang/mock/gomock"
)
// this unit test ensures that the http request returns a
// 401 unauthorized if the session does not exist, and the
// repository is not found.
func TestInjectRepository_RepoNotFound_Guest(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(nil, sql.ErrNoRows)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
context.WithValue(r.Context(), chi.RouteCtxKey, c),
)
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
t.Fail()
})
InjectRepository(nil, repos, nil)(next).ServeHTTP(w, r)
if got, want := w.Code, http.StatusUnauthorized; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
// this unit test ensures that the http request returns a
// 404 not found if the session does exist, but the
// repository is not found.
func TestInjectRepository_RepoNotFound_User(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(nil, sql.ErrNoRows)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
context.WithValue(
request.WithUser(r.Context(), &core.User{}),
chi.RouteCtxKey, c),
)
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
t.Fail()
})
InjectRepository(nil, repos, nil)(next).ServeHTTP(w, r)
if got, want := w.Code, 404; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
}
// this unit test ensures that the middleware function
// invokes the next handler in the chain if the repository
// is found, but no user session exists.
func TestInjectRepository_RepoFound_Guest(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(&core.Repository{}, nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
context.WithValue(
r.Context(),
chi.RouteCtxKey, c),
)
invoked := false
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
invoked = true
})
InjectRepository(nil, repos, nil)(next).ServeHTTP(w, r)
if !invoked {
t.Errorf("Expect middleware invoked")
}
}
// this unit test ensures that the middleware function
// invokes the next handler and stores the permissions
// in the context if found.
func TestInjectRepository_PermsFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &core.User{ID: 1}
mockRepo := &core.Repository{UID: "1"}
mockPerm := &core.Perm{Synced: time.Now().Unix()}
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(mockRepo, nil)
perms := mock.NewMockPermStore(controller)
perms.EXPECT().Find(gomock.Any(), mockRepo.UID, mockUser.ID).Return(mockPerm, nil)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
context.WithValue(
request.WithUser(r.Context(), mockUser),
chi.RouteCtxKey, c),
)
invoked := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
invoked = true
_, ok := request.PermFrom(r.Context())
if !ok {
t.Errorf("Expect perm from context")
}
})
InjectRepository(nil, repos, perms)(next).ServeHTTP(w, r)
if !invoked {
t.Errorf("Expect middleware invoked")
}
}
// this unit test ensures that the middleware function
// invokes the next handler even if the permissions are
// not found. It is the responsibility to downstream
// middleware and handlers to decide if the request
// should be rejected.
func TestInjectRepository_PermsNotFound(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &core.User{ID: 1}
mockRepo := &core.Repository{UID: "1"}
repos := mock.NewMockRepositoryStore(controller)
repos.EXPECT().FindName(gomock.Any(), "octocat", "hello-world").Return(mockRepo, nil)
perms := mock.NewMockPermStore(controller)
perms.EXPECT().Find(gomock.Any(), mockRepo.UID, mockUser.ID).Return(nil, sql.ErrNoRows)
c := new(chi.Context)
c.URLParams.Add("owner", "octocat")
c.URLParams.Add("name", "hello-world")
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
context.WithValue(
request.WithUser(r.Context(), mockUser),
chi.RouteCtxKey, c),
)
invoked := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
invoked = true
_, ok := request.PermFrom(r.Context())
if ok {
t.Errorf("Expect nil perm from context")
}
})
InjectRepository(nil, repos, perms)(next).ServeHTTP(w, r)
if !invoked {
t.Errorf("Expect middleware invoked")
}
}

Some files were not shown because too many files have changed in this diff Show more