mirror of
https://github.com/harness/drone.git
synced 2022-03-12 19:30:52 +03:00
squash and merge local branch
This commit is contained in:
112
.drone.jsonnet
Normal file
112
.drone.jsonnet
Normal 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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
28
.drone.sh
28
.drone.sh
@@ -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
|
|
||||||
419
.drone.yml
419
.drone.yml
@@ -1,143 +1,306 @@
|
|||||||
workspace:
|
---
|
||||||
base: /go
|
kind: pipeline
|
||||||
path: src/github.com/drone/drone
|
name: linux-amd64
|
||||||
|
|
||||||
pipeline:
|
platform:
|
||||||
test:
|
os: linux
|
||||||
image: golang:1.8
|
arch: amd64
|
||||||
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/)
|
|
||||||
|
|
||||||
test_postgres:
|
steps:
|
||||||
image: golang:1.8
|
- name: test
|
||||||
environment:
|
image: golang:1.11
|
||||||
- DATABASE_DRIVER=postgres
|
commands:
|
||||||
- DATABASE_CONFIG=host=postgres user=postgres dbname=postgres sslmode=disable
|
- go test -v ./...
|
||||||
commands:
|
volumes:
|
||||||
- go test github.com/drone/drone/store/datastore
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
test_mysql:
|
- name: build
|
||||||
image: golang:1.8
|
image: golang:1.11
|
||||||
environment:
|
commands:
|
||||||
- DATABASE_DRIVER=mysql
|
- go build -o release/linux/amd64/drone-server
|
||||||
- DATABASE_CONFIG=root@tcp(mysql:3306)/test?parseTime=true
|
- go build -o release/linux/amd64/drone-agent
|
||||||
commands:
|
- go build -o release/linux/amd64/drone-controller
|
||||||
- go test github.com/drone/drone/store/datastore
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- tag
|
||||||
|
|
||||||
build:
|
- name: publish_agent
|
||||||
image: golang:1.8
|
image: plugins/docker
|
||||||
commands: sh .drone.sh
|
settings:
|
||||||
secrets: [ ssh_key ]
|
auto_tag: true
|
||||||
when:
|
auto_tag_suffix: linux-amd64
|
||||||
event: [ push, tag ]
|
dockerfile: docker/Dockerfile.agent.linux.amd64
|
||||||
|
password:
|
||||||
publish_server_alpine:
|
from_secret: docker_password
|
||||||
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
|
|
||||||
repo: drone/agent
|
repo: drone/agent
|
||||||
dockerfile: Dockerfile.agent.alpine
|
username:
|
||||||
secrets: [ docker_username, docker_password ]
|
from_secret: docker_username
|
||||||
tag: [ alpine ]
|
when:
|
||||||
when:
|
event:
|
||||||
branch: master
|
- push
|
||||||
event: push
|
- tag
|
||||||
|
|
||||||
publish_agent_arm:
|
- name: publish_controller
|
||||||
image: plugins/docker
|
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
|
repo: drone/agent
|
||||||
dockerfile: Dockerfile.agent.linux.arm
|
username:
|
||||||
secrets: [ docker_username, docker_password ]
|
from_secret: docker_username
|
||||||
tag: [ linux-arm ]
|
when:
|
||||||
when:
|
event:
|
||||||
branch: master
|
- push
|
||||||
event: push
|
- tag
|
||||||
|
|
||||||
publish_agent_arm64:
|
- name: publish_controller
|
||||||
image: plugins/docker
|
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
|
repo: drone/agent
|
||||||
dockerfile: Dockerfile.agent.linux.arm64
|
username:
|
||||||
secrets: [ docker_username, docker_password ]
|
from_secret: docker_username
|
||||||
tag: [ linux-arm64 ]
|
when:
|
||||||
when:
|
event:
|
||||||
branch: master
|
- push
|
||||||
event: push
|
- tag
|
||||||
|
|
||||||
publish_agent_amd64:
|
- name: publish_controller
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
repo: drone/agent
|
settings:
|
||||||
dockerfile: Dockerfile.agent
|
auto_tag: true
|
||||||
secrets: [ docker_username, docker_password ]
|
auto_tag_suffix: linux-arm64
|
||||||
tag: [ latest ]
|
dockerfile: docker/Dockerfile.controller.linux.arm64
|
||||||
when:
|
password:
|
||||||
branch: master
|
from_secret: docker_password
|
||||||
event: push
|
repo: drone/controller
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- tag
|
||||||
|
|
||||||
release_server_alpine:
|
- name: publish_server
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
repo: drone/drone
|
settings:
|
||||||
dockerfile: Dockerfile.alpine
|
auto_tag: true
|
||||||
secrets: [ docker_username, docker_password ]
|
auto_tag_suffix: linux-arm64
|
||||||
tag: [ 0.8-alpine ]
|
dockerfile: docker/Dockerfile.server.linux.arm64
|
||||||
when:
|
password:
|
||||||
event: tag
|
from_secret: docker_password
|
||||||
|
repo: drone/server
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- tag
|
||||||
|
|
||||||
release_agent_alpine:
|
volumes:
|
||||||
image: plugins/docker
|
- name: gopath
|
||||||
repo: drone/agent
|
temp: {}
|
||||||
dockerfile: Dockerfile.agent.alpine
|
|
||||||
secrets: [ docker_username, docker_password ]
|
|
||||||
tag: [ 0.8-alpine ]
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
release_server:
|
---
|
||||||
image: plugins/docker
|
kind: pipeline
|
||||||
repo: drone/drone
|
name: manifest
|
||||||
secrets: [ docker_username, docker_password ]
|
|
||||||
tag: [ 0.8, 0.8.9 ]
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
release_agent:
|
platform:
|
||||||
image: plugins/docker
|
os: linux
|
||||||
repo: drone/agent
|
arch: amd64
|
||||||
dockerfile: Dockerfile.agent
|
|
||||||
secrets: [ docker_username, docker_password ]
|
|
||||||
tag: [ 0.8, 0.8.9 ]
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
services:
|
steps:
|
||||||
postgres:
|
- name: server
|
||||||
image: postgres:9.6
|
image: plugins/manifest:1
|
||||||
environment:
|
settings:
|
||||||
- POSTGRES_USER=postgres
|
ignore_missing: true
|
||||||
mysql:
|
password:
|
||||||
image: mysql:5.6.27
|
from_secret: docker_password
|
||||||
environment:
|
spec: docker/manifest.server.tmpl
|
||||||
- MYSQL_DATABASE=test
|
username:
|
||||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
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
|
||||||
|
|
||||||
|
...
|
||||||
|
|||||||
0
README.md → .github/readme.md
vendored
0
README.md → .github/readme.md
vendored
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,10 +1,8 @@
|
|||||||
drone/drone
|
.vscode
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.txt
|
*.txt
|
||||||
*.out
|
*.out
|
||||||
|
*.key
|
||||||
.env
|
.env
|
||||||
extras/
|
.env.*
|
||||||
release/
|
release/
|
||||||
|
|
||||||
server/swagger/files/*.json
|
|
||||||
.idea/
|
|
||||||
|
|||||||
18
BUILDING
18
BUILDING
@@ -1,13 +1,13 @@
|
|||||||
1. Install go 1.9 or later
|
1. Clone the repository
|
||||||
2. Install dependencies
|
2. Install go 1.11 or later with Go modules enabled
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
3. Install binaries to $GOPATH/bin
|
3. Install binaries to $GOPATH/bin
|
||||||
|
|
||||||
go install github.com/drone/drone/cmd/drone-agent
|
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
|
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
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@@ -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"]
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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"]
|
|
||||||
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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
111
LICENSE
@@ -1,80 +1,67 @@
|
|||||||
The Drone Community Edition (the "Community Edition") is licensed under the
|
Copyright 2019 Drone.IO, Inc.
|
||||||
Apache License, Version 2.0 (the "Apache License"). You may obtain a copy of
|
|
||||||
the Apache License at
|
|
||||||
|
|
||||||
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
|
Drone Non-Commercial License
|
||||||
they are under. The BUILDING file provides instructions for creating the
|
|
||||||
Community Edition distribution subject to the terms of the Apache License.
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
Contributor: Drone.IO, Inc.
|
||||||
|
|
||||||
Licensor: Drone.IO, Inc
|
Source Code: https://github.com/drone/drone
|
||||||
Licensed Work: Drone Enterprise Edition
|
|
||||||
|
|
||||||
Additional Use Grant: Usage of the software is free for entities with both:
|
This license lets you use and share this software for free,
|
||||||
(a) annual gross revenue under (USD) $1 million
|
with a trial-length time limit on commercial use. Specifically:
|
||||||
(according to GAAP, or the equivalent in its country
|
|
||||||
of domicile); and (b) less than (USD) $5 million in
|
|
||||||
aggregate debt and equity funding.
|
|
||||||
|
|
||||||
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
|
3. Do not make any legal claim against anyone for infringing any
|
||||||
Source license. However, the Licensed Work will eventually be made available
|
patent claim they would infringe by using this software alone,
|
||||||
under an Open Source License, as stated in this License.
|
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
|
Contributor waives the terms of rule 1 for companies meeting all
|
||||||
works, redistribute, and make non-production use of the Licensed Work. The
|
the following criteria, counting all subsidiaries and affiliated
|
||||||
Licensor may make an Additional Use Grant, above, permitting limited
|
entities as one:
|
||||||
production use.
|
|
||||||
|
|
||||||
Effective on the Change Date, or the fourth anniversary of the first publicly
|
1. worldwide annual gross revenue under $5 million US dollars,
|
||||||
available distribution of a specific version of the Licensed Work under this
|
per generally accepted accounting principles
|
||||||
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.
|
|
||||||
|
|
||||||
If your use of the Licensed Work does not comply with the requirements
|
2. less than $5 million US dollars in all-time aggregate debt and
|
||||||
currently in effect as described in this License, you must purchase a
|
equity financing
|
||||||
commercial license from the Licensor, its affiliated entities, or authorized
|
|
||||||
resellers, or you must refrain from using the Licensed Work.
|
|
||||||
|
|
||||||
All copies of the original and modified Licensed Work, and derivative works
|
3. less than 15,000 total pipelines executed using this software
|
||||||
of the Licensed Work, are subject to this License. This License applies
|
in the immediately preceding, year-long period
|
||||||
separately for each version of the Licensed Work and the Change Date may vary
|
|
||||||
for each version of the Licensed Work released by Licensor.
|
|
||||||
|
|
||||||
You must conspicuously display this License on each original or modified copy
|
Contributor will not revoke this waiver, but may change terms for
|
||||||
of the Licensed Work. If you receive the Licensed Work in original or
|
future versions of the software.
|
||||||
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.
|
|
||||||
|
|||||||
152
Taskfile.yml
Normal file
152
Taskfile.yml
Normal 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
|
||||||
@@ -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"]
|
|
||||||
}
|
|
||||||
206
cmd/drone-agent/config/config.go
Normal file
206
cmd/drone-agent/config/config.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +1,123 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
//
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// that can be found in the LICENSE file.
|
||||||
// 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/joho/godotenv/autoload"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
config, err := config.Environ()
|
||||||
app.Name = "drone-agent"
|
if err != nil {
|
||||||
app.Version = version.Version.String()
|
logrus.WithError(err).Fatalln("invalid configuration")
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
initLogging(config)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
ctx := signal.WithContext(
|
||||||
os.Exit(1)
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
cmd/drone-controller/config/config.go
Normal file
187
cmd/drone-controller/config/config.go
Normal 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
|
||||||
|
}
|
||||||
147
cmd/drone-controller/main.go
Normal file
147
cmd/drone-controller/main.go
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
113
cmd/drone-server/bootstrap/bootstrap.go
Normal file
113
cmd/drone-server/bootstrap/bootstrap.go
Normal 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
|
||||||
|
}
|
||||||
170
cmd/drone-server/bootstrap/bootstrap_test.go
Normal file
170
cmd/drone-server/bootstrap/bootstrap_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
485
cmd/drone-server/config/config.go
Normal file
485
cmd/drone-server/config/config.go
Normal 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
|
||||||
|
}
|
||||||
245
cmd/drone-server/inject_client.go
Normal file
245
cmd/drone-server/inject_client.go
Normal 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)
|
||||||
|
}
|
||||||
53
cmd/drone-server/inject_license.go
Normal file
53
cmd/drone-server/inject_license.go
Normal 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
|
||||||
|
}
|
||||||
164
cmd/drone-server/inject_login.go
Normal file
164
cmd/drone-server/inject_login.go
Normal 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
|
||||||
|
}
|
||||||
102
cmd/drone-server/inject_plugin.go
Normal file
102
cmd/drone-server/inject_plugin.go
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
78
cmd/drone-server/inject_runner.go
Normal file
78
cmd/drone-server/inject_runner.go
Normal 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
139
cmd/drone-server/inject_scheduler.go
Normal file
139
cmd/drone-server/inject_scheduler.go
Normal 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)
|
||||||
|
}
|
||||||
95
cmd/drone-server/inject_server.go
Normal file
95
cmd/drone-server/inject_server.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
136
cmd/drone-server/inject_service.go
Normal file
136
cmd/drone-server/inject_service.go
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
117
cmd/drone-server/inject_store.go
Normal file
117
cmd/drone-server/inject_store.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -15,26 +15,150 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"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/joho/godotenv"
|
||||||
"github.com/urfave/cli"
|
"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() {
|
func main() {
|
||||||
app := cli.NewApp()
|
var envfile string
|
||||||
app.Name = "drone-server"
|
flag.StringVar(&envfile, "env-file", ".env", "Read in a file of environment variables")
|
||||||
app.Version = version.Version.String()
|
flag.Parse()
|
||||||
app.Usage = "drone server"
|
|
||||||
app.Action = server
|
|
||||||
app.Flags = flags
|
|
||||||
app.Before = before
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
godotenv.Load(envfile)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
config, err := config.Environ()
|
||||||
os.Exit(1)
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package model
|
//+build wireinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"github.com/drone/drone/cmd/drone-server/config"
|
||||||
"fmt"
|
"github.com/google/wire"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildTrim(t *testing.T) {
|
func InitializeApplication(config config.Config) (application, error) {
|
||||||
d := make([]byte, 2000)
|
wire.Build(
|
||||||
rand.Read(d)
|
clientSet,
|
||||||
|
licenseSet,
|
||||||
b := Build{}
|
loginSet,
|
||||||
b.Message = fmt.Sprintf("%X", d)
|
pluginSet,
|
||||||
|
runnerSet,
|
||||||
if len(b.Message) != 4000 {
|
schedulerSet,
|
||||||
t.Errorf("Failed to generate 4000 byte test string")
|
serverSet,
|
||||||
}
|
serviceSet,
|
||||||
b.Trim()
|
storeSet,
|
||||||
if len(b.Message) != 2000 {
|
newApplication,
|
||||||
t.Errorf("Failed to trim text string to 2000 bytes")
|
)
|
||||||
}
|
return application{}, nil
|
||||||
}
|
}
|
||||||
98
cmd/drone-server/wire_gen.go
Normal file
98
cmd/drone-server/wire_gen.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package coding
|
package core
|
||||||
|
|
||||||
import (
|
import "context"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
// 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
|
||||||
func Test_util(t *testing.T) {
|
// system.
|
||||||
|
type AdmissionService interface {
|
||||||
g := goblin.Goblin(t)
|
Admit(context.Context, *User) error
|
||||||
g.Describe("Coding util", func() {
|
|
||||||
|
|
||||||
g.It("Should form project full name", func() {
|
|
||||||
g.Assert(projectFullName("gk", "prj")).Equal("gk/prj")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package client
|
package core
|
||||||
|
|
||||||
import (
|
import "context"
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
// Batch represents a Batch request to synchronize the local
|
||||||
currentUserUrl = "/user"
|
// repository and permission store for a user account.
|
||||||
)
|
type Batch struct {
|
||||||
|
Insert []*Repository `json:"insert"`
|
||||||
func (c *Client) CurrentUser() (User, error) {
|
Update []*Repository `json:"update"`
|
||||||
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
|
Revoke []*Repository `json:"revoke"`
|
||||||
var user User
|
}
|
||||||
|
|
||||||
contents, err := c.Do("GET", url, opaque, nil)
|
// Batcher batch updates the user account.
|
||||||
if err == nil {
|
type Batcher interface {
|
||||||
err = json.Unmarshal(contents, &user)
|
Batch(context.Context, *User, *Batch) error
|
||||||
}
|
|
||||||
|
|
||||||
return user, err
|
|
||||||
}
|
}
|
||||||
94
core/build.go
Normal file
94
core/build.go
Normal 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
5
core/build_test.go
Normal 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
59
core/commit.go
Normal 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
40
core/config.go
Normal 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
117
core/cron.go
Normal 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
5
core/cron_test.go
Normal 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// +build !cgo
|
package core
|
||||||
|
|
||||||
package datastore
|
// Hook event constants.
|
||||||
|
const (
|
||||||
import (
|
EventPush = "push"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
EventPullRequest = "pull_request"
|
||||||
_ "github.com/lib/pq"
|
EventTag = "tag"
|
||||||
|
EventPromote = "promote"
|
||||||
|
EventRollback = "rollback"
|
||||||
)
|
)
|
||||||
40
core/file.go
Normal file
40
core/file.go
Normal 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
66
core/hook.go
Normal 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
5
core/hook_test.go
Normal 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
72
core/license.go
Normal 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
5
core/license_test.go
Normal 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
70
core/logs.go
Normal 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
58
core/netrc.go
Normal 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
5
core/netrc_test.go
Normal 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package remote
|
package core
|
||||||
|
|
||||||
// AuthError represents remote authentication error.
|
import "context"
|
||||||
type AuthError struct {
|
|
||||||
Err string
|
// Organization represents an organization in the source
|
||||||
Description string
|
// code management system (e.g. GitHub).
|
||||||
URI string
|
type Organization struct {
|
||||||
|
Name string
|
||||||
|
Avatar string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements error interface.
|
// OrganizationService provides access to organization and
|
||||||
func (ae *AuthError) Error() string {
|
// team access in the external source code management system
|
||||||
err := ae.Err
|
// (e.g. GitHub).
|
||||||
if ae.Description != "" {
|
type OrganizationService interface {
|
||||||
err += " " + ae.Description
|
List(context.Context, *User) ([]*Organization, error)
|
||||||
}
|
|
||||||
if ae.URI != "" {
|
|
||||||
err += " " + ae.URI
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check interface
|
|
||||||
var _ error = new(AuthError)
|
|
||||||
68
core/perm.go
Normal file
68
core/perm.go
Normal 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
37
core/pubsub.go
Normal 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
60
core/registry.go
Normal 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)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package model
|
package core
|
||||||
|
|
||||||
// ResourceLimit is the resource limit to set on pipeline steps
|
import "context"
|
||||||
type ResourceLimit struct {
|
|
||||||
MemSwapLimit int64
|
// Renewer renews the user account authorization. If
|
||||||
MemLimit int64
|
// successful, the user token and token expiry attributes
|
||||||
ShmSize int64
|
// are updated, and persisted to the datastore.
|
||||||
CPUQuota int64
|
type Renewer interface {
|
||||||
CPUShares int64
|
Renew(ctx context.Context, user *User, force bool) error
|
||||||
CPUSet string
|
|
||||||
}
|
}
|
||||||
118
core/repo.go
Normal file
118
core/repo.go
Normal 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
50
core/sched.go
Normal 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
105
core/secret.go
Normal 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
71
core/secret_test.go
Normal 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
33
core/session.go
Normal 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
105
core/stage.go
Normal 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
71
core/stage_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package model
|
package core
|
||||||
|
|
||||||
const (
|
import "context"
|
||||||
EventPush = "push"
|
|
||||||
EventPull = "pull_request"
|
|
||||||
EventTag = "tag"
|
|
||||||
EventDeploy = "deployment"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// Status types.
|
||||||
const (
|
const (
|
||||||
StatusSkipped = "skipped"
|
StatusSkipped = "skipped"
|
||||||
StatusPending = "pending"
|
|
||||||
StatusRunning = "running"
|
|
||||||
StatusSuccess = "success"
|
|
||||||
StatusFailure = "failure"
|
|
||||||
StatusKilled = "killed"
|
|
||||||
StatusError = "error"
|
|
||||||
StatusBlocked = "blocked"
|
StatusBlocked = "blocked"
|
||||||
StatusDeclined = "declined"
|
StatusDeclined = "declined"
|
||||||
|
StatusWaiting = "waiting_on_dependencies"
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusRunning = "running"
|
||||||
|
StatusPassing = "success"
|
||||||
|
StatusFailing = "failure"
|
||||||
|
StatusKilled = "killed"
|
||||||
|
StatusError = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type (
|
||||||
RepoGit = "git"
|
// Status represents a commit status.
|
||||||
RepoHg = "hg"
|
Status struct {
|
||||||
RepoFossil = "fossil"
|
State string
|
||||||
RepoPerforce = "perforce"
|
Label string
|
||||||
)
|
Desc string
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
// StatusInput provides the necessary metadata to
|
||||||
VisibilityPublic = "public"
|
// set the commit or deployment status.
|
||||||
VisibilityPrivate = "private"
|
StatusInput struct {
|
||||||
VisibilityInternal = "internal"
|
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
65
core/step.go
Normal 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
23
core/step_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package session
|
package core
|
||||||
|
|
||||||
import (
|
import "context"
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetPerm(t *testing.T) {
|
|
||||||
|
|
||||||
|
// Syncer synchonrizes the account repository list.
|
||||||
|
type Syncer interface {
|
||||||
|
Sync(context.Context, *User) (*Batch, error)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package internal
|
package core
|
||||||
|
|
||||||
import (
|
// System stores system information.
|
||||||
"fmt"
|
type System struct {
|
||||||
)
|
Proto string `json:"proto,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
type APIClientErr struct {
|
Link string `json:"link,omitempty"`
|
||||||
Message string
|
Version string `json:"version,omitempty"`
|
||||||
URL string
|
|
||||||
Cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e APIClientErr) Error() string {
|
|
||||||
return fmt.Sprintf("%s (Requested %s): %v", e.Message, e.URL, e.Cause)
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 Drone.IO Inc.
|
// Copyright 2019 Drone IO, Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package client
|
package core
|
||||||
|
|
||||||
import (
|
import "context"
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// Trigger types
|
||||||
const (
|
const (
|
||||||
currentUserUrl = "/user"
|
TriggerHook = "@hook"
|
||||||
|
TriggerCron = "@cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) CurrentUser() (User, error) {
|
// Triggerer is responsible for triggering a Build from an
|
||||||
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
|
// incoming drone. If a build is skipped a nil value is
|
||||||
var user User
|
// returned.
|
||||||
|
type Triggerer interface {
|
||||||
contents, err := c.Do("GET", url, opaque, nil)
|
Trigger(context.Context, *Repository, *Hook) (*Build, error)
|
||||||
if err == nil {
|
|
||||||
err = json.Unmarshal(contents, &user)
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, err
|
|
||||||
}
|
}
|
||||||
5
core/trigger_test.go
Normal file
5
core/trigger_test.go
Normal 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
96
core/user.go
Normal 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
62
core/user_test.go
Normal 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
59
core/webhook.go
Normal 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
|
||||||
|
}
|
||||||
|
)
|
||||||
14
docker/Dockerfile.agent.linux.amd64
Normal file
14
docker/Dockerfile.agent.linux.amd64
Normal 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"]
|
||||||
10
docker/Dockerfile.agent.linux.arm
Normal file
10
docker/Dockerfile.agent.linux.arm
Normal 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"]
|
||||||
10
docker/Dockerfile.agent.linux.arm64
Normal file
10
docker/Dockerfile.agent.linux.arm64
Normal 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"]
|
||||||
12
docker/Dockerfile.agent.windows.1709
Normal file
12
docker/Dockerfile.agent.windows.1709
Normal 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" ]
|
||||||
12
docker/Dockerfile.agent.windows.1803
Normal file
12
docker/Dockerfile.agent.windows.1803
Normal 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" ]
|
||||||
14
docker/Dockerfile.controller.linux.amd64
Normal file
14
docker/Dockerfile.controller.linux.amd64
Normal 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"]
|
||||||
15
docker/Dockerfile.controller.linux.arm
Normal file
15
docker/Dockerfile.controller.linux.arm
Normal 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"]
|
||||||
15
docker/Dockerfile.controller.linux.arm64
Normal file
15
docker/Dockerfile.controller.linux.arm64
Normal 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"]
|
||||||
12
docker/Dockerfile.controller.windows.1803
Normal file
12
docker/Dockerfile.controller.windows.1803
Normal 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" ]
|
||||||
22
docker/Dockerfile.server.linux.amd64
Normal file
22
docker/Dockerfile.server.linux.amd64
Normal 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"]
|
||||||
22
docker/Dockerfile.server.linux.arm
Normal file
22
docker/Dockerfile.server.linux.arm
Normal 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"]
|
||||||
22
docker/Dockerfile.server.linux.arm64
Normal file
22
docker/Dockerfile.server.linux.arm64
Normal 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"]
|
||||||
31
docker/manifest.agent.tmpl
Normal file
31
docker/manifest.agent.tmpl
Normal 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
|
||||||
31
docker/manifest.controller.tmpl
Normal file
31
docker/manifest.controller.tmpl
Normal 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
|
||||||
25
docker/manifest.server.tmpl
Normal file
25
docker/manifest.server.tmpl
Normal 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
98
go.mod
Normal 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
194
go.sum
Normal 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
49
handler/api/acl/acl.go
Normal 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
129
handler/api/acl/acl_test.go
Normal 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
133
handler/api/acl/check.go
Normal 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),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
786
handler/api/acl/check_test.go
Normal file
786
handler/api/acl/check_test.go
Normal 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
126
handler/api/acl/repo.go
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
205
handler/api/acl/repo_test.go
Normal file
205
handler/api/acl/repo_test.go
Normal 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
Reference in New Issue
Block a user