squash and merge local branch
This commit is contained in:
parent
6d144de735
commit
5b6a3d8ff4
2348 changed files with 82051 additions and 618145 deletions
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
|
||||
path: src/github.com/drone/drone
|
||||
---
|
||||
kind: pipeline
|
||||
name: linux-amd64
|
||||
|
||||
pipeline:
|
||||
test:
|
||||
image: golang:1.8
|
||||
commands:
|
||||
- go get -u github.com/drone/drone-ui/dist
|
||||
- go get -u golang.org/x/tools/cmd/cover
|
||||
- go get -u golang.org/x/net/context
|
||||
- go get -u golang.org/x/net/context/ctxhttp
|
||||
- go get -u github.com/golang/protobuf/proto
|
||||
- go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
- go test -cover $(go list ./... | grep -v /vendor/)
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
test_postgres:
|
||||
image: golang:1.8
|
||||
environment:
|
||||
- DATABASE_DRIVER=postgres
|
||||
- DATABASE_CONFIG=host=postgres user=postgres dbname=postgres sslmode=disable
|
||||
commands:
|
||||
- go test github.com/drone/drone/store/datastore
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go test -v ./...
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
|
||||
test_mysql:
|
||||
image: golang:1.8
|
||||
environment:
|
||||
- DATABASE_DRIVER=mysql
|
||||
- DATABASE_CONFIG=root@tcp(mysql:3306)/test?parseTime=true
|
||||
commands:
|
||||
- go test github.com/drone/drone/store/datastore
|
||||
- name: build
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go build -o release/linux/amd64/drone-server
|
||||
- go build -o release/linux/amd64/drone-agent
|
||||
- go build -o release/linux/amd64/drone-controller
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
build:
|
||||
image: golang:1.8
|
||||
commands: sh .drone.sh
|
||||
secrets: [ ssh_key ]
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
publish_server_alpine:
|
||||
image: plugins/docker
|
||||
repo: drone/drone
|
||||
dockerfile: Dockerfile.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ alpine ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
|
||||
publish_server:
|
||||
image: plugins/docker
|
||||
repo: drone/drone
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ latest ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
|
||||
publish_agent_alpine:
|
||||
image: plugins/docker
|
||||
- name: publish_agent
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
dockerfile: docker/Dockerfile.agent.linux.amd64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ alpine ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
publish_agent_arm:
|
||||
image: plugins/docker
|
||||
- name: publish_controller
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
dockerfile: docker/Dockerfile.controller.linux.amd64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/controller
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: publish_server
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
dockerfile: docker/Dockerfile.server.linux.amd64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/server
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
volumes:
|
||||
- name: gopath
|
||||
temp: {}
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: linux-arm
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go test -v ./...
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
|
||||
- name: build
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go build -o release/linux/arm/drone-server
|
||||
- go build -o release/linux/arm/drone-agent
|
||||
- go build -o release/linux/arm/drone-controller
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: publish_agent
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
dockerfile: docker/Dockerfile.agent.linux.arm
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent.linux.arm
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ linux-arm ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
publish_agent_arm64:
|
||||
image: plugins/docker
|
||||
- name: publish_controller
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
dockerfile: docker/Dockerfile.controller.linux.arm
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/controller
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: publish_server
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
dockerfile: docker/Dockerfile.server.linux.arm
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/server
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
volumes:
|
||||
- name: gopath
|
||||
temp: {}
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: linux-arm64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go test -v ./...
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
|
||||
- name: build
|
||||
image: golang:1.11
|
||||
commands:
|
||||
- go build -o release/linux/arm64/drone-server
|
||||
- go build -o release/linux/arm64/drone-agent
|
||||
- go build -o release/linux/arm64/drone-controller
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: publish_agent
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
dockerfile: docker/Dockerfile.agent.linux.arm64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent.linux.arm64
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ linux-arm64 ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
publish_agent_amd64:
|
||||
image: plugins/docker
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ latest ]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
- name: publish_controller
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
dockerfile: docker/Dockerfile.controller.linux.arm64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/controller
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
release_server_alpine:
|
||||
image: plugins/docker
|
||||
repo: drone/drone
|
||||
dockerfile: Dockerfile.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8-alpine ]
|
||||
when:
|
||||
event: tag
|
||||
- name: publish_server
|
||||
image: plugins/docker
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
dockerfile: docker/Dockerfile.server.linux.arm64
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: drone/server
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
release_agent_alpine:
|
||||
image: plugins/docker
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8-alpine ]
|
||||
when:
|
||||
event: tag
|
||||
volumes:
|
||||
- name: gopath
|
||||
temp: {}
|
||||
|
||||
release_server:
|
||||
image: plugins/docker
|
||||
repo: drone/drone
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8, 0.8.9 ]
|
||||
when:
|
||||
event: tag
|
||||
---
|
||||
kind: pipeline
|
||||
name: manifest
|
||||
|
||||
release_agent:
|
||||
image: plugins/docker
|
||||
repo: drone/agent
|
||||
dockerfile: Dockerfile.agent
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8, 0.8.9 ]
|
||||
when:
|
||||
event: tag
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:9.6
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
mysql:
|
||||
image: mysql:5.6.27
|
||||
environment:
|
||||
- MYSQL_DATABASE=test
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
steps:
|
||||
- name: server
|
||||
image: plugins/manifest:1
|
||||
settings:
|
||||
ignore_missing: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
spec: docker/manifest.server.tmpl
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: agent
|
||||
image: plugins/manifest:1
|
||||
settings:
|
||||
ignore_missing: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
spec: docker/manifest.agent.tmpl
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: controller
|
||||
image: plugins/manifest:1
|
||||
settings:
|
||||
ignore_missing: true
|
||||
password:
|
||||
from_secret: docker_password
|
||||
spec: docker/manifest.controller.tmpl
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
depends_on:
|
||||
- linux-amd64
|
||||
- linux-arm
|
||||
- linux-arm64
|
||||
|
||||
...
|
||||
|
|
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
|
||||
*.txt
|
||||
*.out
|
||||
*.key
|
||||
.env
|
||||
extras/
|
||||
.env.*
|
||||
release/
|
||||
|
||||
server/swagger/files/*.json
|
||||
.idea/
|
||||
|
|
18
BUILDING
18
BUILDING
|
@ -1,13 +1,13 @@
|
|||
1. Install go 1.9 or later
|
||||
2. Install dependencies
|
||||
|
||||
go get -u github.com/drone/drone-ui/dist
|
||||
go get -u golang.org/x/net/context
|
||||
go get -u golang.org/x/net/context/ctxhttp
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
1. Clone the repository
|
||||
2. Install go 1.11 or later with Go modules enabled
|
||||
3. Install binaries to $GOPATH/bin
|
||||
|
||||
go install github.com/drone/drone/cmd/drone-agent
|
||||
go install github.com/drone/drone/cmd/drone-controller
|
||||
go install github.com/drone/drone/cmd/drone-server
|
||||
|
||||
4. Start the server at localhost:8080
|
||||
|
||||
export DRONE_GITHUB_CLIENT_ID=...
|
||||
export DRONE_GITHUB_CLIENT_SECRET=...
|
||||
drone-server
|
||||
|
|
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
|
||||
Apache License, Version 2.0 (the "Apache License"). You may obtain a copy of
|
||||
the Apache License at
|
||||
Copyright 2019 Drone.IO, Inc.
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Source code in this repository is variously licensed under the
|
||||
Apache License Version 2.0, an Apache compatible license, or the
|
||||
Drone Non-Commercial License. Source code in a given file is
|
||||
licensed under the Drone Non-Commercial License unless otherwise
|
||||
noted at the beginning of the file.
|
||||
|
||||
The Drone Enterprise Edition (the "Enterprise Edition") is licensed under
|
||||
the Drone Enterprise License, Version 1.1 (the "Enterprise License"). A copy
|
||||
of the Enterprise License is provided below.
|
||||
-----------------------------------------------------------------
|
||||
|
||||
The source files in this repository have a header indicating which license
|
||||
they are under. The BUILDING file provides instructions for creating the
|
||||
Community Edition distribution subject to the terms of the Apache License.
|
||||
Drone Non-Commercial License
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Contributor: Drone.IO, Inc.
|
||||
|
||||
Licensor: Drone.IO, Inc
|
||||
Licensed Work: Drone Enterprise Edition
|
||||
Source Code: https://github.com/drone/drone
|
||||
|
||||
Additional Use Grant: Usage of the software is free for entities with both:
|
||||
(a) annual gross revenue under (USD) $1 million
|
||||
(according to GAAP, or the equivalent in its country
|
||||
of domicile); and (b) less than (USD) $5 million in
|
||||
aggregate debt and equity funding.
|
||||
This license lets you use and share this software for free,
|
||||
with a trial-length time limit on commercial use. Specifically:
|
||||
|
||||
Change Date: 2022-01-01
|
||||
If you follow the rules below, you may do everything with this
|
||||
software that would otherwise infringe either the contributor's
|
||||
copyright in it, any patent claim the contributor can license
|
||||
that covers this software as of the contributor's latest
|
||||
contribution, or both.
|
||||
|
||||
Change License: Apache-2.0
|
||||
1. You must limit use of this software in any manner primarily
|
||||
intended for or directed toward commercial advantage or
|
||||
private monetary compensation to a trial period of 32
|
||||
consecutive calendar days. This limit does not apply to use in
|
||||
developing feedback, modifications, or extensions that you
|
||||
contribute back to those giving this license.
|
||||
|
||||
Notice
|
||||
2. Ensure everyone who gets a copy of this software from you, in
|
||||
source code or any other form, gets the text of this license
|
||||
and the contributor and source code lines above.
|
||||
|
||||
The Drone Enterprise License (this document, or the "License") is not an Open
|
||||
Source license. However, the Licensed Work will eventually be made available
|
||||
under an Open Source License, as stated in this License.
|
||||
3. Do not make any legal claim against anyone for infringing any
|
||||
patent claim they would infringe by using this software alone,
|
||||
accusing this software, with or without changes, alone or as
|
||||
part of a larger application.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
You are excused for unknowingly breaking rule 1 if you stop
|
||||
doing anything requiring this license within 30 days of
|
||||
learning you broke the rule.
|
||||
|
||||
Drone Enterprise License 1.1
|
||||
**This software comes as is, without any warranty at all. As far
|
||||
as the law allows, the contributor will not be liable for any
|
||||
damages related to this software or this license, for any kind of
|
||||
legal claim.**
|
||||
|
||||
Terms
|
||||
-----------------------------------------------------------------
|
||||
|
||||
The Licensor hereby grants you the right to copy, modify, create derivative
|
||||
works, redistribute, and make non-production use of the Licensed Work. The
|
||||
Licensor may make an Additional Use Grant, above, permitting limited
|
||||
production use.
|
||||
Contributor waives the terms of rule 1 for companies meeting all
|
||||
the following criteria, counting all subsidiaries and affiliated
|
||||
entities as one:
|
||||
|
||||
Effective on the Change Date, or the fourth anniversary of the first publicly
|
||||
available distribution of a specific version of the Licensed Work under this
|
||||
License, whichever comes first, the Licensor hereby grants you rights under
|
||||
the terms of the Change License, and the rights granted in the paragraph
|
||||
above terminate.
|
||||
1. worldwide annual gross revenue under $5 million US dollars,
|
||||
per generally accepted accounting principles
|
||||
|
||||
If your use of the Licensed Work does not comply with the requirements
|
||||
currently in effect as described in this License, you must purchase a
|
||||
commercial license from the Licensor, its affiliated entities, or authorized
|
||||
resellers, or you must refrain from using the Licensed Work.
|
||||
2. less than $5 million US dollars in all-time aggregate debt and
|
||||
equity financing
|
||||
|
||||
All copies of the original and modified Licensed Work, and derivative works
|
||||
of the Licensed Work, are subject to this License. This License applies
|
||||
separately for each version of the Licensed Work and the Change Date may vary
|
||||
for each version of the Licensed Work released by Licensor.
|
||||
3. less than 15,000 total pipelines executed using this software
|
||||
in the immediately preceding, year-long period
|
||||
|
||||
You must conspicuously display this License on each original or modified copy
|
||||
of the Licensed Work. If you receive the Licensed Work in original or
|
||||
modified form from a third party, the terms and conditions set forth in this
|
||||
License apply to your use of that work.
|
||||
|
||||
Any use of the Licensed Work in violation of this License will automatically
|
||||
terminate your rights under this License for the current and all other
|
||||
versions of the Licensed Work.
|
||||
|
||||
This License does not grant you any right in any trademark or logo of
|
||||
Licensor or its affiliates (provided that you may use a trademark or logo of
|
||||
Licensor as expressly required by this License).
|
||||
|
||||
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
||||
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
||||
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
||||
TITLE.
|
||||
Contributor will not revoke this waiver, but may change terms for
|
||||
future versions of the software.
|
||||
|
|
152
Taskfile.yml
Normal file
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.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Drone Non-Commercial License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/drone/drone/version"
|
||||
"github.com/drone/drone-runtime/engine/docker"
|
||||
"github.com/drone/drone/cmd/drone-agent/config"
|
||||
"github.com/drone/drone/operator/manager/rpc"
|
||||
"github.com/drone/drone/operator/runner"
|
||||
"github.com/drone/drone/plugin/registry"
|
||||
"github.com/drone/drone/plugin/secret"
|
||||
"github.com/drone/signal"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "drone-agent"
|
||||
app.Version = version.Version.String()
|
||||
app.Usage = "drone agent"
|
||||
app.Action = loop
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "ping",
|
||||
Usage: "ping the agent",
|
||||
Action: pinger,
|
||||
},
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_SERVER",
|
||||
Name: "server",
|
||||
Usage: "drone server address",
|
||||
Value: "localhost:9000",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_USERNAME",
|
||||
Name: "username",
|
||||
Usage: "drone auth username",
|
||||
Value: "x-oauth-basic",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_PASSWORD,DRONE_SECRET",
|
||||
Name: "password",
|
||||
Usage: "server-agent shared password",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
EnvVar: "DRONE_DEBUG",
|
||||
Name: "debug",
|
||||
Usage: "enable agent debug mode",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_DEBUG_PRETTY",
|
||||
Name: "pretty",
|
||||
Usage: "enable pretty-printed debug output",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
EnvVar: "DRONE_DEBUG_NOCOLOR",
|
||||
Name: "nocolor",
|
||||
Usage: "disable colored debug output",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_HOSTNAME,HOSTNAME",
|
||||
Name: "hostname",
|
||||
Usage: "agent hostname",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_PLATFORM",
|
||||
Name: "platform",
|
||||
Usage: "restrict builds by platform conditions",
|
||||
Value: "linux/amd64",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_FILTER",
|
||||
Name: "filter",
|
||||
Usage: "filter expression to restrict builds by label",
|
||||
},
|
||||
cli.IntFlag{
|
||||
EnvVar: "DRONE_MAX_PROCS",
|
||||
Name: "max-procs",
|
||||
Usage: "agent parallel builds",
|
||||
Value: 1,
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
EnvVar: "DRONE_HEALTHCHECK",
|
||||
Name: "healthcheck",
|
||||
Usage: "enable healthcheck endpoint",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
EnvVar: "DRONE_KEEPALIVE_TIME",
|
||||
Name: "keepalive-time",
|
||||
Usage: "after a duration of this time of no activity, the agent pings the server to check if the transport is still alive",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
EnvVar: "DRONE_KEEPALIVE_TIMEOUT",
|
||||
Name: "keepalive-timeout",
|
||||
Usage: "after pinging for a keepalive check, the agent waits for a duration of this time before closing the connection if no activity",
|
||||
Value: time.Second * 20,
|
||||
},
|
||||
config, err := config.Environ()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatalln("invalid configuration")
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
initLogging(config)
|
||||
ctx := signal.WithContext(
|
||||
context.Background(),
|
||||
)
|
||||
|
||||
secrets := secret.External(
|
||||
config.Secrets.Endpoint,
|
||||
config.Secrets.Password,
|
||||
config.Secrets.SkipVerify,
|
||||
)
|
||||
|
||||
auths := registry.Combine(
|
||||
registry.External(
|
||||
config.Secrets.Endpoint,
|
||||
config.Secrets.Password,
|
||||
config.Secrets.SkipVerify,
|
||||
),
|
||||
registry.FileSource(
|
||||
config.Docker.Config,
|
||||
),
|
||||
registry.EndpointSource(
|
||||
config.Registries.Endpoint,
|
||||
config.Registries.Password,
|
||||
config.Registries.SkipVerify,
|
||||
),
|
||||
)
|
||||
|
||||
manager := rpc.NewClient(
|
||||
config.RPC.Proto+"://"+config.RPC.Host,
|
||||
config.RPC.Secret,
|
||||
)
|
||||
if config.RPC.Debug {
|
||||
manager.SetDebug(true)
|
||||
}
|
||||
if config.Logging.Trace {
|
||||
manager.SetDebug(true)
|
||||
}
|
||||
|
||||
engine, err := docker.NewEnv()
|
||||
if err != nil {
|
||||
logrus.WithError(err).
|
||||
Fatalln("cannot load the docker engine")
|
||||
}
|
||||
|
||||
r := &runner.Runner{
|
||||
Platform: config.Runner.Platform,
|
||||
OS: config.Runner.OS,
|
||||
Arch: config.Runner.Arch,
|
||||
Kernel: config.Runner.Kernel,
|
||||
Variant: config.Runner.Variant,
|
||||
Engine: engine,
|
||||
Manager: manager,
|
||||
Registry: auths,
|
||||
Secrets: secrets,
|
||||
Volumes: config.Runner.Volumes,
|
||||
Networks: config.Runner.Networks,
|
||||
Devices: config.Runner.Devices,
|
||||
Privileged: config.Runner.Privileged,
|
||||
Machine: config.Runner.Machine,
|
||||
Labels: config.Runner.Labels,
|
||||
Environ: config.Runner.Environ,
|
||||
Limits: runner.Limits{
|
||||
MemSwapLimit: int64(config.Runner.Limits.MemSwapLimit),
|
||||
MemLimit: int64(config.Runner.Limits.MemLimit),
|
||||
ShmSize: int64(config.Runner.Limits.ShmSize),
|
||||
CPUQuota: config.Runner.Limits.CPUQuota,
|
||||
CPUShares: config.Runner.Limits.CPUShares,
|
||||
CPUSet: config.Runner.Limits.CPUSet,
|
||||
},
|
||||
}
|
||||
if err := r.Start(ctx, config.Runner.Capacity); err != nil {
|
||||
logrus.WithError(err).
|
||||
Warnln("program terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// helper funciton configures the logging.
|
||||
func initLogging(c config.Config) {
|
||||
if c.Logging.Debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
if c.Logging.Trace {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
if c.Logging.Text {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: c.Logging.Color,
|
||||
DisableColors: !c.Logging.Color,
|
||||
})
|
||||
} else {
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{
|
||||
PrettyPrint: c.Logging.Pretty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,26 +15,150 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone/version"
|
||||
"github.com/drone/drone/cmd/drone-server/bootstrap"
|
||||
"github.com/drone/drone/cmd/drone-server/config"
|
||||
"github.com/drone/drone/core"
|
||||
"github.com/drone/drone/operator/runner"
|
||||
"github.com/drone/drone/server"
|
||||
"github.com/drone/drone/trigger/cron"
|
||||
"github.com/drone/signal"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "drone-server"
|
||||
app.Version = version.Version.String()
|
||||
app.Usage = "drone server"
|
||||
app.Action = server
|
||||
app.Flags = flags
|
||||
app.Before = before
|
||||
var envfile string
|
||||
flag.StringVar(&envfile, "env-file", ".env", "Read in a file of environment variables")
|
||||
flag.Parse()
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
godotenv.Load(envfile)
|
||||
config, err := config.Environ()
|
||||
if err != nil {
|
||||
logger := logrus.WithError(err)
|
||||
logger.Fatalln("main: invalid configuration")
|
||||
}
|
||||
|
||||
initLogging(config)
|
||||
ctx := signal.WithContext(
|
||||
context.Background(),
|
||||
)
|
||||
|
||||
// if trace level logging is enabled, output the
|
||||
// configuration parameters.
|
||||
if logrus.IsLevelEnabled(logrus.TraceLevel) {
|
||||
fmt.Println(config.String())
|
||||
}
|
||||
|
||||
app, err := InitializeApplication(config)
|
||||
if err != nil {
|
||||
logger := logrus.WithError(err)
|
||||
logger.Fatalln("main: cannot initialize server")
|
||||
}
|
||||
|
||||
// optionally bootstrap the system with administrative or
|
||||
// machine users configured in the environment.
|
||||
err = bootstrap.New(app.users).Bootstrap(ctx, &core.User{
|
||||
Login: config.Users.Create.Username,
|
||||
Machine: config.Users.Create.Machine,
|
||||
Admin: config.Users.Create.Admin,
|
||||
Hash: config.Users.Create.Token,
|
||||
})
|
||||
if err != nil {
|
||||
logger := logrus.WithError(err)
|
||||
logger.Fatalln("cannot bootstrap user account")
|
||||
}
|
||||
|
||||
g := errgroup.Group{}
|
||||
g.Go(func() error {
|
||||
logrus.WithFields(
|
||||
logrus.Fields{
|
||||
"proto": config.Server.Proto,
|
||||
"host": config.Server.Host,
|
||||
"port": config.Server.Port,
|
||||
"url": config.Server.Addr,
|
||||
"acme": config.Server.Acme,
|
||||
},
|
||||
).Infoln("starting the http server")
|
||||
return app.server.ListenAndServe(ctx)
|
||||
})
|
||||
|
||||
// launches the cron runner in a goroutine. If the cron
|
||||
// runner is disabled, the goroutine exits immediately
|
||||
// without error.
|
||||
g.Go(func() (err error) {
|
||||
if config.Cron.Disabled {
|
||||
return nil
|
||||
}
|
||||
logrus.WithField("interval", config.Cron.Interval.String()).
|
||||
Infoln("starting the cron scheduler")
|
||||
return app.cron.Start(ctx, config.Cron.Interval)
|
||||
})
|
||||
|
||||
// launches the build runner in a goroutine. If the local
|
||||
// runner is disabled (because nomad or kubernetes is enabled)
|
||||
// then the goroutine exits immediately without error.
|
||||
g.Go(func() (err error) {
|
||||
if app.runner == nil {
|
||||
return nil
|
||||
}
|
||||
logrus.WithField("threads", config.Runner.Capacity).
|
||||
Infoln("main: starting the local build runner")
|
||||
return app.runner.Start(ctx, config.Runner.Capacity)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logrus.WithError(err).Fatalln("program terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// helper funciton configures the logging.
|
||||
func initLogging(c config.Config) {
|
||||
if c.Logging.Debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
if c.Logging.Trace {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
if c.Logging.Text {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: c.Logging.Color,
|
||||
DisableColors: !c.Logging.Color,
|
||||
})
|
||||
} else {
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{
|
||||
PrettyPrint: c.Logging.Pretty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// application is the main struct for the Drone server.
|
||||
type application struct {
|
||||
cron *cron.Scheduler
|
||||
runner *runner.Runner
|
||||
server *server.Server
|
||||
users core.UserStore
|
||||
}
|
||||
|
||||
// newApplication creates a new application struct.
|
||||
func newApplication(
|
||||
cron *cron.Scheduler,
|
||||
runner *runner.Runner,
|
||||
server *server.Server,
|
||||
users core.UserStore) application {
|
||||
return application{
|
||||
users: users,
|
||||
cron: cron,
|
||||
server: server,
|
||||
runner: runner,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,26 +12,27 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
//+build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
"github.com/drone/drone/cmd/drone-server/config"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func TestBuildTrim(t *testing.T) {
|
||||
d := make([]byte, 2000)
|
||||
rand.Read(d)
|
||||
|
||||
b := Build{}
|
||||
b.Message = fmt.Sprintf("%X", d)
|
||||
|
||||
if len(b.Message) != 4000 {
|
||||
t.Errorf("Failed to generate 4000 byte test string")
|
||||
}
|
||||
b.Trim()
|
||||
if len(b.Message) != 2000 {
|
||||
t.Errorf("Failed to trim text string to 2000 bytes")
|
||||
}
|
||||
func InitializeApplication(config config.Config) (application, error) {
|
||||
wire.Build(
|
||||
clientSet,
|
||||
licenseSet,
|
||||
loginSet,
|
||||
pluginSet,
|
||||
runnerSet,
|
||||
schedulerSet,
|
||||
serverSet,
|
||||
serviceSet,
|
||||
storeSet,
|
||||
newApplication,
|
||||
)
|
||||
return application{}, nil
|
||||
}
|
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,21 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package coding
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
import "context"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_util(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Coding util", func() {
|
||||
|
||||
g.It("Should form project full name", func() {
|
||||
g.Assert(projectFullName("gk", "prj")).Equal("gk/prj")
|
||||
})
|
||||
})
|
||||
// AdmissionService grants access to the system. The service can
|
||||
// be used to restrict access to authorized users, such as
|
||||
// members of an organiozation in your soruce control management
|
||||
// system.
|
||||
type AdmissionService interface {
|
||||
Admit(context.Context, *User) error
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
// Copyright 2019 Drone IO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,24 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
import "context"
|
||||
|
||||
const (
|
||||
currentUserUrl = "/user"
|
||||
)
|
||||
|
||||
func (c *Client) CurrentUser() (User, error) {
|
||||
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
|
||||
var user User
|
||||
|
||||
contents, err := c.Do("GET", url, opaque, nil)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(contents, &user)
|
||||
}
|
||||
|
||||
return user, err
|
||||
// Batch represents a Batch request to synchronize the local
|
||||
// repository and permission store for a user account.
|
||||
type Batch struct {
|
||||
Insert []*Repository `json:"insert"`
|
||||
Update []*Repository `json:"update"`
|
||||
Revoke []*Repository `json:"revoke"`
|
||||
}
|
||||
|
||||
// Batcher batch updates the user account.
|
||||
type Batcher interface {
|
||||
Batch(context.Context, *User, *Batch) error
|
||||
}
|
94
core/build.go
Normal file
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,11 +12,13 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !cgo
|
||||
package core
|
||||
|
||||
package datastore
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
// Hook event constants.
|
||||
const (
|
||||
EventPush = "push"
|
||||
EventPullRequest = "pull_request"
|
||||
EventTag = "tag"
|
||||
EventPromote = "promote"
|
||||
EventRollback = "rollback"
|
||||
)
|
40
core/file.go
Normal file
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,26 +12,20 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package remote
|
||||
package core
|
||||
|
||||
// AuthError represents remote authentication error.
|
||||
type AuthError struct {
|
||||
Err string
|
||||
Description string
|
||||
URI string
|
||||
import "context"
|
||||
|
||||
// Organization represents an organization in the source
|
||||
// code management system (e.g. GitHub).
|
||||
type Organization struct {
|
||||
Name string
|
||||
Avatar string
|
||||
}
|
||||
|
||||
// Error implements error interface.
|
||||
func (ae *AuthError) Error() string {
|
||||
err := ae.Err
|
||||
if ae.Description != "" {
|
||||
err += " " + ae.Description
|
||||
}
|
||||
if ae.URI != "" {
|
||||
err += " " + ae.URI
|
||||
}
|
||||
return err
|
||||
// OrganizationService provides access to organization and
|
||||
// team access in the external source code management system
|
||||
// (e.g. GitHub).
|
||||
type OrganizationService interface {
|
||||
List(context.Context, *User) ([]*Organization, error)
|
||||
}
|
||||
|
||||
// check interface
|
||||
var _ error = new(AuthError)
|
68
core/perm.go
Normal file
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,14 +12,13 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
package core
|
||||
|
||||
// ResourceLimit is the resource limit to set on pipeline steps
|
||||
type ResourceLimit struct {
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
ShmSize int64
|
||||
CPUQuota int64
|
||||
CPUShares int64
|
||||
CPUSet string
|
||||
import "context"
|
||||
|
||||
// Renewer renews the user account authorization. If
|
||||
// successful, the user token and token expiry attributes
|
||||
// are updated, and persisted to the datastore.
|
||||
type Renewer interface {
|
||||
Renew(ctx context.Context, user *User, force bool) error
|
||||
}
|
118
core/repo.go
Normal file
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,36 +12,43 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
package core
|
||||
|
||||
const (
|
||||
EventPush = "push"
|
||||
EventPull = "pull_request"
|
||||
EventTag = "tag"
|
||||
EventDeploy = "deployment"
|
||||
)
|
||||
import "context"
|
||||
|
||||
// Status types.
|
||||
const (
|
||||
StatusSkipped = "skipped"
|
||||
StatusPending = "pending"
|
||||
StatusRunning = "running"
|
||||
StatusSuccess = "success"
|
||||
StatusFailure = "failure"
|
||||
StatusKilled = "killed"
|
||||
StatusError = "error"
|
||||
StatusBlocked = "blocked"
|
||||
StatusDeclined = "declined"
|
||||
StatusWaiting = "waiting_on_dependencies"
|
||||
StatusPending = "pending"
|
||||
StatusRunning = "running"
|
||||
StatusPassing = "success"
|
||||
StatusFailing = "failure"
|
||||
StatusKilled = "killed"
|
||||
StatusError = "error"
|
||||
)
|
||||
|
||||
const (
|
||||
RepoGit = "git"
|
||||
RepoHg = "hg"
|
||||
RepoFossil = "fossil"
|
||||
RepoPerforce = "perforce"
|
||||
)
|
||||
type (
|
||||
// Status represents a commit status.
|
||||
Status struct {
|
||||
State string
|
||||
Label string
|
||||
Desc string
|
||||
Target string
|
||||
}
|
||||
|
||||
const (
|
||||
VisibilityPublic = "public"
|
||||
VisibilityPrivate = "private"
|
||||
VisibilityInternal = "internal"
|
||||
// StatusInput provides the necessary metadata to
|
||||
// set the commit or deployment status.
|
||||
StatusInput struct {
|
||||
Repo *Repository
|
||||
Build *Build
|
||||
}
|
||||
|
||||
// StatusService sends the commit status to an external
|
||||
// external source code management service (e.g. GitHub).
|
||||
StatusService interface {
|
||||
Send(ctx context.Context, user *User, req *StatusInput) error
|
||||
}
|
||||
)
|
65
core/step.go
Normal file
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,12 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package session
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetPerm(t *testing.T) {
|
||||
import "context"
|
||||
|
||||
// Syncer synchonrizes the account repository list.
|
||||
type Syncer interface {
|
||||
Sync(context.Context, *User) (*Batch, error)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
// Copyright 2019 Drone IO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,18 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type APIClientErr struct {
|
||||
Message string
|
||||
URL string
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e APIClientErr) Error() string {
|
||||
return fmt.Sprintf("%s (Requested %s): %v", e.Message, e.URL, e.Cause)
|
||||
// System stores system information.
|
||||
type System struct {
|
||||
Proto string `json:"proto,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
// Copyright 2019 Drone IO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,24 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
import "context"
|
||||
|
||||
// Trigger types
|
||||
const (
|
||||
currentUserUrl = "/user"
|
||||
TriggerHook = "@hook"
|
||||
TriggerCron = "@cron"
|
||||
)
|
||||
|
||||
func (c *Client) CurrentUser() (User, error) {
|
||||
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
|
||||
var user User
|
||||
|
||||
contents, err := c.Do("GET", url, opaque, nil)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(contents, &user)
|
||||
}
|
||||
|
||||
return user, err
|
||||
// Triggerer is responsible for triggering a Build from an
|
||||
// incoming drone. If a build is skipped a nil value is
|
||||
// returned.
|
||||
type Triggerer interface {
|
||||
Trigger(context.Context, *Repository, *Hook) (*Build, error)
|
||||
}
|
5
core/trigger_test.go
Normal file
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
Loading…
Reference in a new issue