mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-30 10:11:23 +02:00
commit
245d7bee06
12
.drone.yml
12
.drone.yml
@ -1,3 +1,7 @@
|
||||
clone:
|
||||
git:
|
||||
image: plugins/git:next
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/github.com/laszlocph/drone-oss-08
|
||||
@ -109,7 +113,7 @@ pipeline:
|
||||
repo: laszlocloud/drone-oss-08-server
|
||||
dockerfile: Dockerfile.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8.96-alpine ]
|
||||
tag: [ 0.8.96-multi-pipeline-alpine ]
|
||||
when:
|
||||
event: tag
|
||||
|
||||
@ -118,7 +122,7 @@ pipeline:
|
||||
repo: laszlocloud/drone-oss-08-agent
|
||||
dockerfile: Dockerfile.agent.alpine
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8.96-alpine ]
|
||||
tag: [ 0.8.96-multi-pipeline-alpine ]
|
||||
when:
|
||||
event: tag
|
||||
|
||||
@ -126,7 +130,7 @@ pipeline:
|
||||
image: plugins/docker
|
||||
repo: laszlocloud/drone-oss-08-server
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8.96 ]
|
||||
tag: [ 0.8.96-multi-pipeline ]
|
||||
when:
|
||||
event: tag
|
||||
|
||||
@ -135,7 +139,7 @@ pipeline:
|
||||
repo: laszlocloud/drone-oss-08-agent
|
||||
dockerfile: Dockerfile.agent
|
||||
secrets: [ docker_username, docker_password ]
|
||||
tag: [ 0.8.96 ]
|
||||
tag: [ 0.8.96-multi-pipeline ]
|
||||
when:
|
||||
event: tag
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,4 +8,5 @@ release/
|
||||
cli/release/
|
||||
|
||||
server/swagger/files/*.json
|
||||
server/swagger/swagger_gen.go
|
||||
.idea/
|
||||
|
17
BUILDING
17
BUILDING
@ -10,3 +10,20 @@
|
||||
|
||||
go install github.com/laszlocph/drone-oss-08/cmd/drone-agent
|
||||
go install github.com/laszlocph/drone-oss-08/cmd/drone-server
|
||||
|
||||
---
|
||||
|
||||
0. To generate SQL files
|
||||
|
||||
go get github.com/vektra/mockery/.../
|
||||
|
||||
export download_url=$(curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/latest | \
|
||||
jq -r '.assets[] | select(.name | contains("'"$(uname | tr '[:upper:]' '[:lower:]')"'_amd64")) | .browser_download_url')
|
||||
curl -o swagger -L'#' "$download_url"
|
||||
chmod +x swagger
|
||||
sudo mv swagger /usr/local/bin
|
||||
|
||||
go get github.com/laszlocph/togo
|
||||
|
||||
go generate
|
||||
|
||||
|
@ -30,7 +30,7 @@ func TestLogging(t *testing.T) {
|
||||
logger.Tail(ctx, testPath, func(entry ...*Entry) { wg.Done() })
|
||||
}()
|
||||
|
||||
<-time.After(time.Millisecond)
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
wg.Add(4)
|
||||
go func() {
|
||||
@ -45,7 +45,7 @@ func TestLogging(t *testing.T) {
|
||||
logger.Tail(ctx, testPath, func(entry ...*Entry) { wg.Done() })
|
||||
}()
|
||||
|
||||
<-time.After(time.Millisecond)
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
wg.Wait()
|
||||
cancel()
|
||||
|
@ -222,3 +222,10 @@ func (m *Metadata) EnvironDrone() map[string]string {
|
||||
}
|
||||
|
||||
var pullRegexp = regexp.MustCompile("\\d+")
|
||||
|
||||
func (m *Metadata) SetPlatform(platform string) {
|
||||
if platform == "" {
|
||||
platform = "linux/amd64"
|
||||
}
|
||||
m.Sys.Arch = platform
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
|
||||
}
|
||||
|
||||
// add default clone step
|
||||
if c.local == false && len(conf.Clone.Containers) == 0 {
|
||||
if c.local == false && len(conf.Clone.Containers) == 0 && !conf.SkipClone {
|
||||
container := &yaml.Container{
|
||||
Name: "clone",
|
||||
Image: "plugins/git:latest",
|
||||
@ -118,7 +118,7 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
config.Stages = append(config.Stages, stage)
|
||||
} else if c.local == false {
|
||||
} else if c.local == false && !conf.SkipClone {
|
||||
for i, container := range conf.Clone.Containers {
|
||||
if !container.Constraints.Match(c.metadata) {
|
||||
continue
|
||||
|
@ -22,6 +22,9 @@ type (
|
||||
Networks Networks
|
||||
Volumes Volumes
|
||||
Labels libcompose.SliceorMap
|
||||
DependsOn []string `yaml:"depends_on,omitempty"`
|
||||
RunsOn []string `yaml:"runs_on,omitempty"`
|
||||
SkipClone bool `yaml:"skip_clone"`
|
||||
}
|
||||
|
||||
// Workspace defines a pipeline workspace.
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func xTestParse(t *testing.T) {
|
||||
func TestParse(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Parser", func() {
|
||||
@ -35,9 +35,14 @@ func xTestParse(t *testing.T) {
|
||||
g.Assert(out.Pipeline.Containers[1].Commands).Equal(yaml.Stringorslice{"go build"})
|
||||
g.Assert(out.Pipeline.Containers[2].Name).Equal("notify")
|
||||
g.Assert(out.Pipeline.Containers[2].Image).Equal("slack")
|
||||
g.Assert(out.Pipeline.Containers[2].NetworkMode).Equal("container:name")
|
||||
// g.Assert(out.Pipeline.Containers[2].NetworkMode).Equal("container:name")
|
||||
g.Assert(out.Labels["com.example.team"]).Equal("frontend")
|
||||
g.Assert(out.Labels["com.example.type"]).Equal("build")
|
||||
g.Assert(out.DependsOn[0]).Equal("lint")
|
||||
g.Assert(out.DependsOn[1]).Equal("test")
|
||||
g.Assert(out.RunsOn[0]).Equal("success")
|
||||
g.Assert(out.RunsOn[1]).Equal("failure")
|
||||
g.Assert(out.SkipClone).Equal(false)
|
||||
})
|
||||
// Check to make sure variable expansion works in yaml.MapSlice
|
||||
// g.It("Should unmarshal variables", func() {
|
||||
@ -94,6 +99,12 @@ volumes:
|
||||
labels:
|
||||
com.example.type: "build"
|
||||
com.example.team: "frontend"
|
||||
depends_on:
|
||||
- lint
|
||||
- test
|
||||
runs_on:
|
||||
- success
|
||||
- failure
|
||||
`
|
||||
|
||||
var sampleVarYaml = `
|
||||
|
@ -40,9 +40,8 @@ func Parse(data []byte) ([]Axis, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if not a matrix build return an array with just the single axis.
|
||||
if len(matrix) == 0 {
|
||||
return nil, nil
|
||||
return []Axis{}, nil
|
||||
}
|
||||
|
||||
return calc(matrix), nil
|
||||
|
@ -25,10 +25,10 @@ func TestMatrix(t *testing.T) {
|
||||
g.Assert(len(set)).Equal(24)
|
||||
})
|
||||
|
||||
g.It("Should return nil if no matrix", func() {
|
||||
g.It("Should return empty array if no matrix", func() {
|
||||
axis, err := ParseString("")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(axis == nil).IsTrue()
|
||||
g.Assert(len(axis) == 0).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return included axis", func() {
|
||||
|
@ -30,7 +30,7 @@ func TestPubsub(t *testing.T) {
|
||||
broker.Subscribe(ctx, testTopic, func(message Message) { wg.Done() })
|
||||
}()
|
||||
|
||||
<-time.After(time.Millisecond)
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
if _, ok := broker.(*publisher).topics[testTopic]; !ok {
|
||||
t.Errorf("Expect topic registered with publisher")
|
||||
@ -86,7 +86,7 @@ func TestSubscriptionClosed(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
<-time.After(time.Millisecond)
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
if _, ok := broker.(*publisher).topics[testTopic]; !ok {
|
||||
t.Errorf("Expect topic registered with publisher")
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
@ -50,6 +52,17 @@ func (q *fifo) Push(c context.Context, task *Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Push pushes an item to the tail of this queue.
|
||||
func (q *fifo) PushAtOnce(c context.Context, tasks []*Task) error {
|
||||
q.Lock()
|
||||
for _, task := range tasks {
|
||||
q.pending.PushBack(task)
|
||||
}
|
||||
q.Unlock()
|
||||
go q.process()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Poll retrieves and removes the head of this queue.
|
||||
func (q *fifo) Poll(c context.Context, f Filter) (*Task, error) {
|
||||
q.Lock()
|
||||
@ -82,11 +95,14 @@ func (q *fifo) Done(c context.Context, id string) error {
|
||||
// Error signals that the item is done executing with error.
|
||||
func (q *fifo) Error(c context.Context, id string, err error) error {
|
||||
q.Lock()
|
||||
state, ok := q.running[id]
|
||||
taskEntry, ok := q.running[id]
|
||||
if ok {
|
||||
state.error = err
|
||||
close(state.done)
|
||||
q.updateDepStatusInQueue(id, err == nil)
|
||||
taskEntry.error = err
|
||||
close(taskEntry.done)
|
||||
delete(q.running, id)
|
||||
} else {
|
||||
q.removeFromPending(id)
|
||||
}
|
||||
q.Unlock()
|
||||
return nil
|
||||
@ -173,8 +189,44 @@ func (q *fifo) process() {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
||||
// TODO(bradrydzewski) move this to a helper function
|
||||
// push items to the front of the queue if the item expires.
|
||||
q.resubmitExpiredBuilds()
|
||||
|
||||
for pending, worker := q.assignToWorker(); pending != nil && worker != nil; pending, worker = q.assignToWorker() {
|
||||
task := pending.Value.(*Task)
|
||||
delete(q.workers, worker)
|
||||
q.pending.Remove(pending)
|
||||
q.running[task.ID] = &entry{
|
||||
item: task,
|
||||
done: make(chan bool),
|
||||
deadline: time.Now().Add(q.extension),
|
||||
}
|
||||
worker.channel <- task
|
||||
}
|
||||
}
|
||||
|
||||
func (q *fifo) assignToWorker() (*list.Element, *worker) {
|
||||
var next *list.Element
|
||||
for e := q.pending.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
task := e.Value.(*Task)
|
||||
logrus.Debugf("queue: trying to assign task: %v with deps %v", task.ID, task.Dependencies)
|
||||
if q.depsInQueue(task) {
|
||||
logrus.Debugf("queue: skipping due to unmet dependencies %v", task.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
for w := range q.workers {
|
||||
if w.filter(task) {
|
||||
logrus.Debugf("queue: assigned task: %v with deps %v", task.ID, task.Dependencies)
|
||||
return e, w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (q *fifo) resubmitExpiredBuilds() {
|
||||
for id, state := range q.running {
|
||||
if time.Now().After(state.deadline) {
|
||||
q.pending.PushFront(state.item)
|
||||
@ -182,26 +234,61 @@ func (q *fifo) process() {
|
||||
close(state.done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *fifo) depsInQueue(task *Task) bool {
|
||||
var next *list.Element
|
||||
loop:
|
||||
for e := q.pending.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
item := e.Value.(*Task)
|
||||
for w := range q.workers {
|
||||
if w.filter(item) {
|
||||
delete(q.workers, w)
|
||||
q.pending.Remove(e)
|
||||
possibleDep, ok := e.Value.(*Task)
|
||||
logrus.Debugf("queue: pending right now: %v", possibleDep.ID)
|
||||
for _, dep := range task.Dependencies {
|
||||
if ok && possibleDep.ID == dep {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for possibleDepID := range q.running {
|
||||
logrus.Debugf("queue: running right now: %v", possibleDepID)
|
||||
for _, dep := range task.Dependencies {
|
||||
if possibleDepID == dep {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
q.running[item.ID] = &entry{
|
||||
item: item,
|
||||
done: make(chan bool),
|
||||
deadline: time.Now().Add(q.extension),
|
||||
}
|
||||
|
||||
w.channel <- item
|
||||
break loop
|
||||
func (q *fifo) updateDepStatusInQueue(taskID string, success bool) {
|
||||
var next *list.Element
|
||||
for e := q.pending.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
pending, ok := e.Value.(*Task)
|
||||
for _, dep := range pending.Dependencies {
|
||||
if ok && taskID == dep {
|
||||
pending.DepStatus[dep] = success
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, running := range q.running {
|
||||
for _, dep := range running.item.Dependencies {
|
||||
if taskID == dep {
|
||||
running.item.DepStatus[dep] = success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *fifo) removeFromPending(taskID string) {
|
||||
logrus.Debugf("queue: trying to remove %s", taskID)
|
||||
var next *list.Element
|
||||
for e := q.pending.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
task := e.Value.(*Task)
|
||||
if task.ID == taskID {
|
||||
logrus.Debugf("queue: %s is removed from pending", taskID)
|
||||
q.pending.Remove(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -117,3 +118,188 @@ func TestFifoEvict(t *testing.T) {
|
||||
t.Errorf("expect not found error when evicting item not in queue, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFifoDependencies(t *testing.T) {
|
||||
task1 := &Task{
|
||||
ID: "1",
|
||||
}
|
||||
|
||||
task2 := &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]bool),
|
||||
}
|
||||
|
||||
q := New().(*fifo)
|
||||
q.Push(noContext, task2)
|
||||
q.Push(noContext, task1)
|
||||
|
||||
got, _ := q.Poll(noContext, func(*Task) bool { return true })
|
||||
if got != task1 {
|
||||
t.Errorf("expect task1 returned from queue as task2 depends on it")
|
||||
return
|
||||
}
|
||||
|
||||
q.Done(noContext, got.ID)
|
||||
|
||||
got, _ = q.Poll(noContext, func(*Task) bool { return true })
|
||||
if got != task2 {
|
||||
t.Errorf("expect task2 returned from queue")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestFifoErrors(t *testing.T) {
|
||||
task1 := &Task{
|
||||
ID: "1",
|
||||
}
|
||||
|
||||
task2 := &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]bool),
|
||||
}
|
||||
|
||||
task3 := &Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]bool),
|
||||
RunOn: []string{"success", "failure"},
|
||||
}
|
||||
|
||||
q := New().(*fifo)
|
||||
q.Push(noContext, task2)
|
||||
q.Push(noContext, task3)
|
||||
q.Push(noContext, task1)
|
||||
|
||||
got, _ := q.Poll(noContext, func(*Task) bool { return true })
|
||||
if got != task1 {
|
||||
t.Errorf("expect task1 returned from queue as task2 depends on it")
|
||||
return
|
||||
}
|
||||
|
||||
q.Error(noContext, got.ID, fmt.Errorf("exitcode 1, there was an error"))
|
||||
|
||||
got, _ = q.Poll(noContext, func(*Task) bool { return true })
|
||||
if got != task2 {
|
||||
t.Errorf("expect task2 returned from queue")
|
||||
return
|
||||
}
|
||||
|
||||
if got.ShouldRun() {
|
||||
t.Errorf("expect task2 should not run, since task1 failed")
|
||||
return
|
||||
}
|
||||
|
||||
got, _ = q.Poll(noContext, func(*Task) bool { return true })
|
||||
if got != task3 {
|
||||
t.Errorf("expect task3 returned from queue")
|
||||
return
|
||||
}
|
||||
|
||||
if !got.ShouldRun() {
|
||||
t.Errorf("expect task3 should run, task1 failed, but task3 runs on failure too")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestFifoCancel(t *testing.T) {
|
||||
task1 := &Task{
|
||||
ID: "1",
|
||||
}
|
||||
|
||||
task2 := &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]bool),
|
||||
}
|
||||
|
||||
task3 := &Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]bool),
|
||||
RunOn: []string{"success", "failure"},
|
||||
}
|
||||
|
||||
q := New().(*fifo)
|
||||
q.Push(noContext, task2)
|
||||
q.Push(noContext, task3)
|
||||
q.Push(noContext, task1)
|
||||
|
||||
_, _ = q.Poll(noContext, func(*Task) bool { return true })
|
||||
q.Error(noContext, task1.ID, fmt.Errorf("cancelled"))
|
||||
q.Error(noContext, task2.ID, fmt.Errorf("cancelled"))
|
||||
q.Error(noContext, task3.ID, fmt.Errorf("cancelled"))
|
||||
|
||||
info := q.Info(noContext)
|
||||
if len(info.Pending) != 0 {
|
||||
t.Errorf("All pipelines should be cancelled")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldRun(t *testing.T) {
|
||||
task := &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: map[string]bool{
|
||||
"1": true,
|
||||
},
|
||||
RunOn: []string{"failure"},
|
||||
}
|
||||
if task.ShouldRun() {
|
||||
t.Errorf("expect task to not run, it runs on failure only")
|
||||
return
|
||||
}
|
||||
|
||||
task = &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: map[string]bool{
|
||||
"1": true,
|
||||
},
|
||||
RunOn: []string{"failure", "success"},
|
||||
}
|
||||
if !task.ShouldRun() {
|
||||
t.Errorf("expect task to run")
|
||||
return
|
||||
}
|
||||
|
||||
task = &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: map[string]bool{
|
||||
"1": false,
|
||||
},
|
||||
}
|
||||
if task.ShouldRun() {
|
||||
t.Errorf("expect task to not run")
|
||||
return
|
||||
}
|
||||
|
||||
task = &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: map[string]bool{
|
||||
"1": true,
|
||||
},
|
||||
RunOn: []string{"success"},
|
||||
}
|
||||
if !task.ShouldRun() {
|
||||
t.Errorf("expect task to run")
|
||||
return
|
||||
}
|
||||
|
||||
task = &Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: map[string]bool{
|
||||
"1": false,
|
||||
},
|
||||
RunOn: []string{"failure"},
|
||||
}
|
||||
if !task.ShouldRun() {
|
||||
t.Errorf("expect task to run")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,64 @@ type Task struct {
|
||||
|
||||
// Labels represents the key-value pairs the entry is lebeled with.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
// Task IDs this task depend
|
||||
Dependencies []string
|
||||
|
||||
// If dep finished sucessfully
|
||||
DepStatus map[string]bool
|
||||
|
||||
// RunOn failure or success
|
||||
RunOn []string
|
||||
}
|
||||
|
||||
// ShouldRun tells if a task should be run or skipped, based on dependencies
|
||||
func (t *Task) ShouldRun() bool {
|
||||
if runsOnFailure(t.RunOn) && runsOnSuccess(t.RunOn) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !runsOnFailure(t.RunOn) && runsOnSuccess(t.RunOn) {
|
||||
for _, success := range t.DepStatus {
|
||||
if !success {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if runsOnFailure(t.RunOn) && !runsOnSuccess(t.RunOn) {
|
||||
for _, success := range t.DepStatus {
|
||||
if success {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func runsOnFailure(runsOn []string) bool {
|
||||
for _, status := range runsOn {
|
||||
if status == "failure" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runsOnSuccess(runsOn []string) bool {
|
||||
if len(runsOn) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, status := range runsOn {
|
||||
if status == "success" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InfoT provides runtime information.
|
||||
@ -44,9 +102,12 @@ type Filter func(*Task) bool
|
||||
// Queue defines a task queue for scheduling tasks among
|
||||
// a pool of workers.
|
||||
type Queue interface {
|
||||
// Push pushes an task to the tail of this queue.
|
||||
// Push pushes a task to the tail of this queue.
|
||||
Push(c context.Context, task *Task) error
|
||||
|
||||
// Push pushes a task to the tail of this queue.
|
||||
PushAtOnce(c context.Context, tasks []*Task) error
|
||||
|
||||
// Poll retrieves and removes a task head of this queue.
|
||||
Poll(c context.Context, f Filter) (*Task, error)
|
||||
|
||||
@ -68,46 +129,3 @@ type Queue interface {
|
||||
// Info returns internal queue information.
|
||||
Info(c context.Context) InfoT
|
||||
}
|
||||
|
||||
// // global instance of the queue.
|
||||
// var global = New()
|
||||
//
|
||||
// // Set sets the global queue.
|
||||
// func Set(queue Queue) {
|
||||
// global = queue
|
||||
// }
|
||||
//
|
||||
// // Push pushes an task to the tail of the global queue.
|
||||
// func Push(c context.Context, task *Task) error {
|
||||
// return global.Push(c, task)
|
||||
// }
|
||||
//
|
||||
// // Poll retrieves and removes a task head of the global queue.
|
||||
// func Poll(c context.Context, f Filter) (*Task, error) {
|
||||
// return global.Poll(c, f)
|
||||
// }
|
||||
//
|
||||
// // Extend extends the deadline for a task.
|
||||
// func Extend(c context.Context, id string) error {
|
||||
// return global.Extend(c, id)
|
||||
// }
|
||||
//
|
||||
// // Done signals the task is complete.
|
||||
// func Done(c context.Context, id string) error {
|
||||
// return global.Done(c, id)
|
||||
// }
|
||||
//
|
||||
// // Error signals the task is complete with errors.
|
||||
// func Error(c context.Context, id string, err error) {
|
||||
// global.Error(c, id, err)
|
||||
// }
|
||||
//
|
||||
// // Wait waits until the task is complete.
|
||||
// func Wait(c context.Context, id string) error {
|
||||
// return global.Wait(c, id)
|
||||
// }
|
||||
//
|
||||
// // Info returns internal queue information.
|
||||
// func Info(c context.Context) InfoT {
|
||||
// return global.Info(c)
|
||||
// }
|
||||
|
@ -28,4 +28,4 @@ services:
|
||||
environment:
|
||||
- DRONE_SERVER=drone-server:9000
|
||||
- DRONE_SECRET=${DRONE_SECRET}
|
||||
- DRONE_MAX_PROCS=1
|
||||
- DRONE_MAX_PROCS=2
|
@ -16,10 +16,11 @@ package model
|
||||
|
||||
// ConfigStore persists pipeline configuration to storage.
|
||||
type ConfigStore interface {
|
||||
ConfigLoad(int64) (*Config, error)
|
||||
ConfigFind(*Repo, string) (*Config, error)
|
||||
ConfigsForBuild(buildID int64) ([]*Config, error)
|
||||
ConfigFindIdentical(repoID int64, sha string) (*Config, error)
|
||||
ConfigFindApproved(*Config) (bool, error)
|
||||
ConfigCreate(*Config) error
|
||||
BuildConfigCreate(*BuildConfig) error
|
||||
}
|
||||
|
||||
// Config represents a pipeline configuration.
|
||||
@ -28,4 +29,11 @@ type Config struct {
|
||||
RepoID int64 `json:"-" meddler:"config_repo_id"`
|
||||
Data string `json:"data" meddler:"config_data"`
|
||||
Hash string `json:"hash" meddler:"config_hash"`
|
||||
Name string `json:"name" meddler:"config_name"`
|
||||
}
|
||||
|
||||
// BuildConfig is the n:n relation between Build and Config
|
||||
type BuildConfig struct {
|
||||
ConfigID int64 `json:"-" meddler:"config_id"`
|
||||
BuildID int64 `json:"-" meddler:"build_id"`
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ProcStore persists process information to storage.
|
||||
type ProcStore interface {
|
||||
ProcLoad(int64) (*Proc, error)
|
||||
@ -57,18 +59,24 @@ func (p *Proc) Failing() bool {
|
||||
|
||||
// Tree creates a process tree from a flat process list.
|
||||
func Tree(procs []*Proc) []*Proc {
|
||||
var (
|
||||
nodes []*Proc
|
||||
parent *Proc
|
||||
)
|
||||
var nodes []*Proc
|
||||
for _, proc := range procs {
|
||||
if proc.PPID == 0 {
|
||||
nodes = append(nodes, proc)
|
||||
parent = proc
|
||||
continue
|
||||
} else {
|
||||
parent, _ := findNode(nodes, proc.PPID)
|
||||
parent.Children = append(parent.Children, proc)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func findNode(nodes []*Proc, pid int) (*Proc, error) {
|
||||
for _, node := range nodes {
|
||||
if node.PID == pid {
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Corrupt proc structure")
|
||||
}
|
||||
|
@ -23,9 +23,11 @@ import (
|
||||
|
||||
// Task defines scheduled pipeline Task.
|
||||
type Task struct {
|
||||
ID string `meddler:"task_id"`
|
||||
Data []byte `meddler:"task_data"`
|
||||
Labels map[string]string `meddler:"task_labels,json"`
|
||||
ID string `meddler:"task_id"`
|
||||
Data []byte `meddler:"task_data"`
|
||||
Labels map[string]string `meddler:"task_labels,json"`
|
||||
Dependencies []string `meddler:"task_dependencies,json"`
|
||||
RunOn []string `meddler:"task_run_on,json"`
|
||||
}
|
||||
|
||||
// TaskStore defines storage for scheduled Tasks.
|
||||
@ -39,13 +41,18 @@ type TaskStore interface {
|
||||
// ensures the task Queue can be restored when the system starts.
|
||||
func WithTaskStore(q queue.Queue, s TaskStore) queue.Queue {
|
||||
tasks, _ := s.TaskList()
|
||||
toEnqueue := []*queue.Task{}
|
||||
for _, task := range tasks {
|
||||
q.Push(context.Background(), &queue.Task{
|
||||
ID: task.ID,
|
||||
Data: task.Data,
|
||||
Labels: task.Labels,
|
||||
toEnqueue = append(toEnqueue, &queue.Task{
|
||||
ID: task.ID,
|
||||
Data: task.Data,
|
||||
Labels: task.Labels,
|
||||
Dependencies: task.Dependencies,
|
||||
RunOn: task.RunOn,
|
||||
DepStatus: make(map[string]bool),
|
||||
})
|
||||
}
|
||||
q.PushAtOnce(context.Background(), toEnqueue)
|
||||
return &persistentQueue{q, s}
|
||||
}
|
||||
|
||||
@ -54,12 +61,14 @@ type persistentQueue struct {
|
||||
store TaskStore
|
||||
}
|
||||
|
||||
// Push pushes an task to the tail of this queue.
|
||||
// Push pushes a task to the tail of this queue.
|
||||
func (q *persistentQueue) Push(c context.Context, task *queue.Task) error {
|
||||
q.store.TaskInsert(&Task{
|
||||
ID: task.ID,
|
||||
Data: task.Data,
|
||||
Labels: task.Labels,
|
||||
ID: task.ID,
|
||||
Data: task.Data,
|
||||
Labels: task.Labels,
|
||||
Dependencies: task.Dependencies,
|
||||
RunOn: task.RunOn,
|
||||
})
|
||||
err := q.Queue.Push(c, task)
|
||||
if err != nil {
|
||||
@ -68,6 +77,26 @@ func (q *persistentQueue) Push(c context.Context, task *queue.Task) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Push pushes multiple tasks to the tail of this queue.
|
||||
func (q *persistentQueue) PushAtOnce(c context.Context, tasks []*queue.Task) error {
|
||||
for _, task := range tasks {
|
||||
q.store.TaskInsert(&Task{
|
||||
ID: task.ID,
|
||||
Data: task.Data,
|
||||
Labels: task.Labels,
|
||||
Dependencies: task.Dependencies,
|
||||
RunOn: task.RunOn,
|
||||
})
|
||||
}
|
||||
err := q.Queue.PushAtOnce(c, tasks)
|
||||
if err != nil {
|
||||
for _, task := range tasks {
|
||||
q.store.TaskDelete(task.ID)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Poll retrieves and removes a task head of this queue.
|
||||
func (q *persistentQueue) Poll(c context.Context, f queue.Filter) (*queue.Task, error) {
|
||||
task, err := q.Queue.Poll(c, f)
|
||||
|
@ -55,6 +55,7 @@ type Repo struct {
|
||||
Config string `json:"config_file" meddler:"repo_config_path"`
|
||||
Hash string `json:"-" meddler:"repo_hash"`
|
||||
Perm *Perm `json:"-" meddler:"-"`
|
||||
Fallback bool `json:"fallback" meddler:"repo_fallback"`
|
||||
}
|
||||
|
||||
func (r *Repo) ResetVisibility() {
|
||||
@ -105,4 +106,5 @@ type RepoPatch struct {
|
||||
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
||||
AllowTag *bool `json:"allow_tag,omitempty"`
|
||||
BuildCounter *int `json:"build_counter,omitempty"`
|
||||
Fallback *bool `json:"fallback,omitempty"`
|
||||
}
|
||||
|
@ -202,20 +202,19 @@ func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
|
||||
// File fetches the file from the Bitbucket repository and returns its contents.
|
||||
func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
return c.FileRef(u, r, b.Commit, f)
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the Bitbucket repository and returns its contents.
|
||||
func (c *config) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
config, err := c.newClient(u).FindSource(r.Owner, r.Name, ref, f)
|
||||
config, err := c.newClient(u).FindSource(r.Owner, r.Name, b.Commit, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(config.Data), err
|
||||
}
|
||||
|
||||
func (c *config) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status creates a build status for the Bitbucket commit.
|
||||
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
status := internal.BuildStatus{
|
||||
State: convertStatus(b.Status),
|
||||
Desc: convertDesc(b.Status),
|
||||
|
@ -283,7 +283,7 @@ func Test_bitbucket(t *testing.T) {
|
||||
})
|
||||
|
||||
g.It("Should update the status", func() {
|
||||
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://127.0.0.1")
|
||||
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://127.0.0.1", nil)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
|
@ -179,14 +179,12 @@ func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
return client.FindFileForRepo(r.Owner, r.Name, f, b.Ref)
|
||||
}
|
||||
|
||||
func (c *Config) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token)
|
||||
|
||||
return client.FindFileForRepo(r.Owner, r.Name, f, ref)
|
||||
func (c *Config) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status is not supported by the bitbucketserver driver.
|
||||
func (c *Config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *Config) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
status := internal.BuildStatus{
|
||||
State: convertStatus(b.Status),
|
||||
Desc: convertDesc(b.Status),
|
||||
|
@ -238,18 +238,12 @@ func (c *Coding) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// FileRef fetches a file from the remote repository for the given ref
|
||||
// and returns in string format.
|
||||
func (c *Coding) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
data, err := c.newClient(u).GetFile(r.Owner, r.Name, ref, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
func (c *Coding) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
func (c *Coding) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *Coding) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
// EMPTY: not implemented in Coding OAuth API
|
||||
return nil
|
||||
}
|
||||
|
@ -184,11 +184,6 @@ func Test_coding(t *testing.T) {
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(string(data)).Equal("pipeline:\n test:\n image: golang:1.6\n commands:\n - go test\n")
|
||||
})
|
||||
g.It("Should return file for specified ref", func() {
|
||||
data, err := c.FileRef(fakeUser, fakeRepo, "master", ".drone.yml")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(string(data)).Equal("pipeline:\n test:\n image: golang:1.6\n commands:\n - go test\n")
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When requesting a netrc config", func() {
|
||||
|
@ -103,13 +103,12 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// File is not supported by the Gerrit driver.
|
||||
func (c *client) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
return nil, nil
|
||||
func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status is not supported by the Gogs driver.
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -249,13 +249,12 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the Gitea repository and returns its contents.
|
||||
func (c *client) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
return c.newClientToken(u.Token).GetFile(r.Owner, r.Name, ref, f)
|
||||
func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status is supported by the Gitea driver.
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
status := getStatus(b.Status)
|
||||
|
@ -18,10 +18,10 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote/gitea/fixtures"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote/gitea/fixtures"
|
||||
)
|
||||
|
||||
func Test_gitea(t *testing.T) {
|
||||
@ -149,7 +149,7 @@ func Test_gitea(t *testing.T) {
|
||||
})
|
||||
|
||||
g.It("Should return nil from send build status", func() {
|
||||
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://gitea.io")
|
||||
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://gitea.io", nil)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
|
@ -51,7 +51,7 @@ const (
|
||||
// GitHub commit status.
|
||||
func convertStatus(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning, model.StatusBlocked:
|
||||
case model.StatusPending, model.StatusRunning, model.StatusBlocked, model.StatusSkipped:
|
||||
return statusPending
|
||||
case model.StatusFailure, model.StatusDeclined:
|
||||
return statusFailure
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote"
|
||||
@ -225,22 +226,77 @@ func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
|
||||
// File fetches the file from the GitHub repository and returns its contents.
|
||||
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
return c.FileRef(u, r, b.Commit, f)
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the GitHub repository and returns its contents.
|
||||
func (c *client) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
opts := new(github.RepositoryContentGetOptions)
|
||||
opts.Ref = ref
|
||||
opts.Ref = b.Commit
|
||||
data, _, _, err := client.Repositories.GetContents(r.Owner, r.Name, f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data == nil {
|
||||
return nil, fmt.Errorf("%s is a folder not a file use Dir(..)", f)
|
||||
}
|
||||
return data.Decode()
|
||||
}
|
||||
|
||||
func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
opts := new(github.RepositoryContentGetOptions)
|
||||
opts.Ref = b.Commit
|
||||
_, data, _, err := client.Repositories.GetContents(r.Owner, r.Name, f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fc := make(chan *remote.FileMeta)
|
||||
errc := make(chan error)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(data))
|
||||
|
||||
for _, file := range data {
|
||||
go func(path string) {
|
||||
content, err := c.File(u, r, b, path)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
} else {
|
||||
fc <- &remote.FileMeta{
|
||||
Name: path,
|
||||
Data: content,
|
||||
}
|
||||
}
|
||||
}(f + "/" + *file.Name)
|
||||
}
|
||||
|
||||
var files []*remote.FileMeta
|
||||
var errors []error
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case err, open := <-errc:
|
||||
if open {
|
||||
errors = append(errors, err)
|
||||
wg.Done()
|
||||
}
|
||||
case fileMeta, open := <-fc:
|
||||
if open {
|
||||
files = append(files, fileMeta)
|
||||
wg.Done()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
close(fc)
|
||||
close(errc)
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Netrc returns a netrc file capable of authenticating GitHub requests and
|
||||
// cloning GitHub repositories. The netrc will use the global machine account
|
||||
// when configured.
|
||||
@ -374,17 +430,17 @@ func matchingHooks(hooks []github.Hook, rawurl string) *github.Hook {
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
// An example would be the GitHub pull request status.
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
client := c.newClientToken(u.Token)
|
||||
switch b.Event {
|
||||
case "deployment":
|
||||
return deploymentStatus(client, r, b, link)
|
||||
default:
|
||||
return repoStatus(client, r, b, link, c.Context)
|
||||
return repoStatus(client, r, b, link, c.Context, proc)
|
||||
}
|
||||
}
|
||||
|
||||
func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link, ctx string) error {
|
||||
func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link, ctx string, proc *model.Proc) error {
|
||||
context := ctx
|
||||
switch b.Event {
|
||||
case model.EventPull:
|
||||
@ -395,10 +451,19 @@ func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link, ctx
|
||||
}
|
||||
}
|
||||
|
||||
status := github.String(convertStatus(b.Status))
|
||||
desc := github.String(convertDesc(b.Status))
|
||||
|
||||
if proc != nil {
|
||||
context += "/" + proc.Name
|
||||
status = github.String(convertStatus(proc.State))
|
||||
desc = github.String(convertDesc(proc.State))
|
||||
}
|
||||
|
||||
data := github.RepoStatus{
|
||||
Context: github.String(context),
|
||||
State: github.String(convertStatus(b.Status)),
|
||||
Description: github.String(convertDesc(b.Status)),
|
||||
State: status,
|
||||
Description: desc,
|
||||
TargetURL: github.String(link),
|
||||
}
|
||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, b.Commit, &data)
|
||||
|
@ -325,28 +325,27 @@ func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
|
||||
// File fetches a file from the remote repository and returns in string format.
|
||||
func (g *Gitlab) File(user *model.User, repo *model.Repo, build *model.Build, f string) ([]byte, error) {
|
||||
return g.FileRef(user, repo, build.Commit, f)
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the GitHub repository and returns its contents.
|
||||
func (g *Gitlab) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
var client = NewClient(g.URL, u.Token, g.SkipVerify)
|
||||
id, err := GetProjectId(g, client, r.Owner, r.Name)
|
||||
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := client.RepoRawFileRef(id, ref, f)
|
||||
out, err := client.RepoRawFileRef(id, build.Commit, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Gitlab) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// NOTE Currently gitlab doesn't support status for commits and events,
|
||||
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
|
||||
// gitlab uses API to fetch build status on client side. But for now we skip this.
|
||||
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string) error {
|
||||
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||
|
||||
status := getStatus(b.Status)
|
||||
|
@ -338,25 +338,14 @@ func (g *Gitlab) File(user *model.User, repo *model.Repo, build *model.Build, f
|
||||
return out, err
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the GitHub repository and returns its contents.
|
||||
func (g *Gitlab) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
var client = NewClient(g.URL, u.Token, g.SkipVerify)
|
||||
id, err := GetProjectId(g, client, r.Owner, r.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := client.RepoRawFileRef(id, ref, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, err
|
||||
func (c *Gitlab) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// NOTE Currently gitlab doesn't support status for commits and events,
|
||||
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
|
||||
// gitlab uses API to fetch build status on client side. But for now we skip this.
|
||||
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string) error {
|
||||
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
||||
|
||||
status := getStatus(b.Status)
|
||||
|
@ -22,9 +22,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/go-gogs-client"
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote"
|
||||
"github.com/gogits/go-gogs-client"
|
||||
)
|
||||
|
||||
// Opts defines configuration options.
|
||||
@ -202,13 +202,12 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// FileRef fetches the file from the Gogs repository and returns its contents.
|
||||
func (c *client) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) {
|
||||
return c.newClientToken(u.Token).GetFile(r.Owner, r.Name, ref, f)
|
||||
func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
// Status is not supported by the Gogs driver.
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ func Test_gogs(t *testing.T) {
|
||||
|
||||
g.It("Should return no-op for usupporeted features", func() {
|
||||
_, err1 := c.Auth("octocat", "4vyW6b49Z")
|
||||
err2 := c.Status(nil, nil, nil, "")
|
||||
err2 := c.Status(nil, nil, nil, "", nil)
|
||||
err3 := c.Deactivate(nil, nil, "")
|
||||
g.Assert(err1 != nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
|
@ -1,25 +1,11 @@
|
||||
// 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.
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mock
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
import http "net/http"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/laszlocph/drone-oss-08/model"
|
||||
import remote "github.com/laszlocph/drone-oss-08/remote"
|
||||
|
||||
// Remote is an autogenerated mock type for the Remote type
|
||||
type Remote struct {
|
||||
@ -75,6 +61,29 @@ func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Dir provides a mock function with given fields: u, r, b, f
|
||||
func (_m *Remote) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) {
|
||||
ret := _m.Called(u, r, b, f)
|
||||
|
||||
var r0 []*remote.FileMeta
|
||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) []*remote.FileMeta); ok {
|
||||
r0 = rf(u, r, b, f)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*remote.FileMeta)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
|
||||
r1 = rf(u, r, b, f)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// File provides a mock function with given fields: u, r, b, f
|
||||
func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
ret := _m.Called(u, r, b, f)
|
||||
@ -98,29 +107,6 @@ func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FileRef provides a mock function with given fields: u, r, ref, f
|
||||
func (_m *Remote) FileRef(u *model.User, r *model.Repo, ref string, f string) ([]byte, error) {
|
||||
ret := _m.Called(u, r, ref, f)
|
||||
|
||||
var r0 []byte
|
||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string, string) []byte); ok {
|
||||
r0 = rf(u, r, ref, f)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo, string, string) error); ok {
|
||||
r1 = rf(u, r, ref, f)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Hook provides a mock function with given fields: r
|
||||
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
ret := _m.Called(r)
|
||||
@ -246,15 +232,15 @@ func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, e
|
||||
}
|
||||
|
||||
// Repos provides a mock function with given fields: u
|
||||
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||
func (_m *Remote) Repos(u *model.User) ([]*model.Repo, error) {
|
||||
ret := _m.Called(u)
|
||||
|
||||
var r0 []*model.RepoLite
|
||||
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
|
||||
var r0 []*model.Repo
|
||||
if rf, ok := ret.Get(0).(func(*model.User) []*model.Repo); ok {
|
||||
r0 = rf(u)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.RepoLite)
|
||||
r0 = ret.Get(0).([]*model.Repo)
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,29 +268,6 @@ func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link stri
|
||||
return r0
|
||||
}
|
||||
|
||||
// TeamPerm provides a mock function with given fields: u, org
|
||||
func (_m *Remote) TeamPerm(u *model.User, org string) (*model.Perm, error) {
|
||||
ret := _m.Called(u, org)
|
||||
|
||||
var r0 *model.Perm
|
||||
if rf, ok := ret.Get(0).(func(*model.User, string) *model.Perm); ok {
|
||||
r0 = rf(u, org)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Perm)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.User, string) error); ok {
|
||||
r1 = rf(u, org)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Teams provides a mock function with given fields: u
|
||||
func (_m *Remote) Teams(u *model.User) ([]*model.Team, error) {
|
||||
ret := _m.Called(u)
|
||||
|
@ -18,7 +18,6 @@ package remote
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
|
||||
@ -51,13 +50,12 @@ type Remote interface {
|
||||
// format.
|
||||
File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error)
|
||||
|
||||
// FileRef fetches a file from the remote repository for the given ref
|
||||
// and returns in string format.
|
||||
FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error)
|
||||
// Dir fetches a folder from the remote repository
|
||||
Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*FileMeta, error)
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
// An example would be the GitHub pull request status.
|
||||
Status(u *model.User, r *model.Repo, b *model.Build, link string) error
|
||||
Status(u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error
|
||||
|
||||
// Netrc returns a .netrc file that can be used to clone
|
||||
// private repositories from a remote system.
|
||||
@ -75,6 +73,18 @@ type Remote interface {
|
||||
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
||||
}
|
||||
|
||||
// FileMeta represents a file in version control
|
||||
type FileMeta struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ByName []*FileMeta
|
||||
|
||||
func (a ByName) Len() int { return len(a) }
|
||||
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// Refresher refreshes an oauth token and expiration for the given user. It
|
||||
// returns true if the token was refreshed, false if the token was not refreshed,
|
||||
// and error if it failed to refersh.
|
||||
@ -115,22 +125,10 @@ func Perm(c context.Context, u *model.User, owner, repo string) (*model.Perm, er
|
||||
return FromContext(c).Perm(u, owner, repo)
|
||||
}
|
||||
|
||||
// File fetches a file from the remote repository and returns in string format.
|
||||
func File(c context.Context, u *model.User, r *model.Repo, b *model.Build, f string) (out []byte, err error) {
|
||||
for i := 0; i < 12; i++ {
|
||||
out, err = FromContext(c).File(u, r, b, f)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
// An example would be the GitHub pull request status.
|
||||
func Status(c context.Context, u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
return FromContext(c).Status(u, r, b, link)
|
||||
func Status(c context.Context, u *model.User, r *model.Repo, b *model.Build, link string, proc *model.Proc) error {
|
||||
return FromContext(c).Status(u, r, b, link, proc)
|
||||
}
|
||||
|
||||
// Netrc returns a .netrc file that can be used to clone
|
||||
@ -168,18 +166,3 @@ func Refresh(c context.Context, u *model.User) (bool, error) {
|
||||
}
|
||||
return refresher.Refresh(u)
|
||||
}
|
||||
|
||||
// FileBackoff fetches the file using an exponential backoff.
|
||||
// TODO replace this with a proper backoff
|
||||
func FileBackoff(remote Remote, u *model.User, r *model.Repo, b *model.Build, f string) (out []byte, err error) {
|
||||
for i := 0; i < 5; i++ {
|
||||
select {
|
||||
case <-time.After(time.Second * time.Duration(i)):
|
||||
out, err = remote.File(u, r, b, f)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
106
server/build.go
106
server/build.go
@ -156,13 +156,12 @@ func GetProcLogs(c *gin.Context) {
|
||||
io.Copy(c.Writer, rc)
|
||||
}
|
||||
|
||||
// DeleteBuild cancels a build
|
||||
func DeleteBuild(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
|
||||
// parse the build number and job sequence number from
|
||||
// the repquest parameter.
|
||||
// parse the build number from the request parameter.
|
||||
num, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||
seq, _ := strconv.Atoi(c.Params.ByName("job"))
|
||||
|
||||
build, err := store.GetBuildNumber(c, repo, num)
|
||||
if err != nil {
|
||||
@ -170,27 +169,40 @@ func DeleteBuild(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
proc, err := store.FromContext(c).ProcFind(build, seq)
|
||||
procs, err := store.FromContext(c).ProcList(build)
|
||||
if err != nil {
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
if proc.State != model.StatusRunning {
|
||||
cancelled := false
|
||||
for _, proc := range procs {
|
||||
if proc.PPID != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if proc.State != model.StatusRunning && proc.State != model.StatusPending {
|
||||
continue
|
||||
}
|
||||
|
||||
proc.State = model.StatusKilled
|
||||
proc.Stopped = time.Now().Unix()
|
||||
if proc.Started == 0 {
|
||||
proc.Started = proc.Stopped
|
||||
}
|
||||
proc.ExitCode = 137
|
||||
// TODO cancel child procs
|
||||
store.FromContext(c).ProcUpdate(proc)
|
||||
|
||||
Config.Services.Queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel)
|
||||
cancelled = true
|
||||
}
|
||||
|
||||
if !cancelled {
|
||||
c.String(400, "Cannot cancel a non-running build")
|
||||
return
|
||||
}
|
||||
|
||||
proc.State = model.StatusKilled
|
||||
proc.Stopped = time.Now().Unix()
|
||||
if proc.Started == 0 {
|
||||
proc.Started = proc.Stopped
|
||||
}
|
||||
proc.ExitCode = 137
|
||||
// TODO cancel child procs
|
||||
store.FromContext(c).ProcUpdate(proc)
|
||||
|
||||
Config.Services.Queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel)
|
||||
c.String(204, "")
|
||||
}
|
||||
|
||||
@ -268,7 +280,7 @@ func PostApproval(c *gin.Context) {
|
||||
build.Reviewer = user.Login
|
||||
|
||||
// fetch the build file from the database
|
||||
conf, err := Config.Storage.Config.ConfigLoad(build.ConfigID)
|
||||
configs, err := Config.Storage.Config.ConfigsForBuild(build.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
@ -307,13 +319,10 @@ func PostApproval(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, uri)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
}()
|
||||
var yamls []*remote.FileMeta
|
||||
for _, y := range configs {
|
||||
yamls = append(yamls, &remote.FileMeta{Data: []byte(y.Data), Name: y.Name})
|
||||
}
|
||||
|
||||
b := procBuilder{
|
||||
Repo: repo,
|
||||
@ -323,7 +332,7 @@ func PostApproval(c *gin.Context) {
|
||||
Secs: secs,
|
||||
Regs: regs,
|
||||
Link: httputil.GetURL(c.Request),
|
||||
Yaml: conf.Data,
|
||||
Yamls: yamls,
|
||||
Envs: envs,
|
||||
}
|
||||
buildItems, err := b.Build()
|
||||
@ -336,12 +345,25 @@ func PostApproval(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
setBuildProcs(build, buildItems)
|
||||
err = store.FromContext(c).ProcCreate(build.Procs)
|
||||
if err != nil {
|
||||
logrus.Errorf("error persisting procs %s/%d: %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, item := range buildItems {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
if len(buildItems) > 1 {
|
||||
err = remote_.Status(user, repo, build, uri, item.Proc)
|
||||
} else {
|
||||
err = remote_.Status(user, repo, build, uri, nil)
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
publishToTopic(c, build, repo)
|
||||
queueBuild(build, repo, buildItems)
|
||||
}
|
||||
@ -376,7 +398,7 @@ func PostDecline(c *gin.Context) {
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, uri)
|
||||
err = remote_.Status(user, repo, build, uri, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
@ -436,7 +458,7 @@ func PostBuild(c *gin.Context) {
|
||||
}
|
||||
|
||||
// fetch the .drone.yml file from the database
|
||||
conf, err := Config.Storage.Config.ConfigLoad(build.ConfigID)
|
||||
configs, err := Config.Storage.Config.ConfigsForBuild(build.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
@ -474,6 +496,13 @@ func PostBuild(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = persistBuildConfigs(configs, build.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to persist build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read query string parameters into buildParams, exclude reserved params
|
||||
var buildParams = map[string]string{}
|
||||
for key, val := range c.Request.URL.Query() {
|
||||
@ -504,6 +533,11 @@ func PostBuild(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var yamls []*remote.FileMeta
|
||||
for _, y := range configs {
|
||||
yamls = append(yamls, &remote.FileMeta{Data: []byte(y.Data), Name: y.Name})
|
||||
}
|
||||
|
||||
b := procBuilder{
|
||||
Repo: repo,
|
||||
Curr: build,
|
||||
@ -512,7 +546,7 @@ func PostBuild(c *gin.Context) {
|
||||
Secs: secs,
|
||||
Regs: regs,
|
||||
Link: httputil.GetURL(c.Request),
|
||||
Yaml: conf.Data,
|
||||
Yamls: yamls,
|
||||
Envs: buildParams,
|
||||
}
|
||||
buildItems, err := b.Build()
|
||||
@ -525,8 +559,6 @@ func PostBuild(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
setBuildProcs(build, buildItems)
|
||||
|
||||
err = store.FromContext(c).ProcCreate(build.Procs)
|
||||
if err != nil {
|
||||
logrus.Errorf("cannot restart %s#%d: %s", repo.FullName, build.Number, err)
|
||||
@ -582,6 +614,20 @@ func DeleteBuildLogs(c *gin.Context) {
|
||||
c.String(204, "")
|
||||
}
|
||||
|
||||
func persistBuildConfigs(configs []*model.Config, buildID int64) error {
|
||||
for _, conf := range configs {
|
||||
buildConfig := &model.BuildConfig{
|
||||
ConfigID: conf.ID,
|
||||
BuildID: buildID,
|
||||
}
|
||||
err := Config.Storage.Config.BuildConfigCreate(buildConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var deleteStr = `[
|
||||
{
|
||||
"proc": %q,
|
||||
|
53
server/configFetcher.go
Normal file
53
server/configFetcher.go
Normal file
@ -0,0 +1,53 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote"
|
||||
)
|
||||
|
||||
type configFetcher struct {
|
||||
remote_ remote.Remote
|
||||
user *model.User
|
||||
repo *model.Repo
|
||||
build *model.Build
|
||||
}
|
||||
|
||||
func (cf *configFetcher) Fetch() ([]*remote.FileMeta, error) {
|
||||
for i := 0; i < 5; i++ {
|
||||
select {
|
||||
case <-time.After(time.Second * time.Duration(i)):
|
||||
// either a file
|
||||
file, fileerr := cf.remote_.File(cf.user, cf.repo, cf.build, cf.repo.Config)
|
||||
if fileerr == nil {
|
||||
return []*remote.FileMeta{&remote.FileMeta{
|
||||
Name: cf.repo.Config,
|
||||
Data: file,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// or a folder
|
||||
dir, direrr := cf.remote_.Dir(cf.user, cf.repo, cf.build, strings.TrimSuffix(cf.repo.Config, "/"))
|
||||
|
||||
if direrr == nil {
|
||||
return dir, nil
|
||||
} else if !cf.repo.Fallback {
|
||||
return nil, direrr
|
||||
}
|
||||
|
||||
// or fallback
|
||||
file, fileerr = cf.remote_.File(cf.user, cf.repo, cf.build, ".drone.yml")
|
||||
if fileerr != nil {
|
||||
return nil, fileerr
|
||||
}
|
||||
|
||||
return []*remote.FileMeta{&remote.FileMeta{
|
||||
Name: cf.repo.Config,
|
||||
Data: file,
|
||||
}}, nil
|
||||
}
|
||||
}
|
||||
return []*remote.FileMeta{}, nil
|
||||
}
|
22
server/configFetcher_test.go
Normal file
22
server/configFetcher_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote/github"
|
||||
)
|
||||
|
||||
func TestFetchGithub(t *testing.T) {
|
||||
github, err := github.New(github.Opts{URL: "https://github.com"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
configFetcher := &configFetcher{
|
||||
remote_: github,
|
||||
user: &model.User{Token: "xxx"},
|
||||
repo: &model.Repo{Owner: "laszlocph", Name: "drone-multipipeline", Config: ".drone"},
|
||||
build: &model.Build{Commit: "89ab7b2d6bfb347144ac7c557e638ab402848fee"},
|
||||
}
|
||||
configFetcher.Fetch()
|
||||
}
|
148
server/hook.go
148
server/hook.go
@ -143,34 +143,21 @@ func PostHook(c *gin.Context) {
|
||||
}
|
||||
|
||||
// fetch the build file from the remote
|
||||
remoteYamlConfig, err := remote.FileBackoff(remote_, user, repo, build, repo.Config)
|
||||
configFetcher := &configFetcher{remote_: remote_, user: user, repo: repo, build: build}
|
||||
remoteYamlConfigs, err := configFetcher.Fetch()
|
||||
if err != nil {
|
||||
logrus.Errorf("error: %s: cannot find %s in %s: %s", repo.FullName, repo.Config, build.Ref, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
conf, err := findOrPersistPipelineConfig(repo, remoteYamlConfig)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to find or persist build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
|
||||
if branchFiltered(build, remoteYamlConfigs) {
|
||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
||||
return
|
||||
}
|
||||
build.ConfigID = conf.ID
|
||||
|
||||
// verify that pipeline can be built at all
|
||||
parsedPipelineConfig, err := yaml.ParseString(conf.Data)
|
||||
if err == nil {
|
||||
if !parsedPipelineConfig.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if repo.IsGated {
|
||||
allowed, _ := Config.Services.Senders.SenderAllowed(user, repo, build, conf)
|
||||
if !allowed {
|
||||
build.Status = model.StatusBlocked
|
||||
}
|
||||
if repo.IsGated { // This feature is not clear to me. Reenabling once better understood
|
||||
build.Status = model.StatusBlocked
|
||||
}
|
||||
|
||||
// update some build fields
|
||||
@ -185,6 +172,16 @@ func PostHook(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// persist the build config for historical correctness, restarts, etc
|
||||
for _, remoteYamlConfig := range remoteYamlConfigs {
|
||||
_, err := findOrPersistPipelineConfig(build, remoteYamlConfig)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to find or persist build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, build)
|
||||
|
||||
if build.Status == model.StatusBlocked {
|
||||
@ -218,14 +215,6 @@ func PostHook(c *gin.Context) {
|
||||
// get the previous build so that we can send status change notifications
|
||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||
|
||||
defer func() {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, uri)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
}()
|
||||
|
||||
b := procBuilder{
|
||||
Repo: repo,
|
||||
Curr: build,
|
||||
@ -235,7 +224,7 @@ func PostHook(c *gin.Context) {
|
||||
Regs: regs,
|
||||
Envs: envs,
|
||||
Link: httputil.GetURL(c.Request),
|
||||
Yaml: conf.Data,
|
||||
Yamls: remoteYamlConfigs,
|
||||
}
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
@ -247,66 +236,75 @@ func PostHook(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
setBuildProcs(build, buildItems)
|
||||
|
||||
err = store.FromContext(c).ProcCreate(build.Procs)
|
||||
if err != nil {
|
||||
logrus.Errorf("error persisting procs %s/%d: %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, item := range buildItems {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
if len(buildItems) > 1 {
|
||||
err = remote_.Status(user, repo, build, uri, item.Proc)
|
||||
} else {
|
||||
err = remote_.Status(user, repo, build, uri, nil)
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
publishToTopic(c, build, repo)
|
||||
queueBuild(build, repo, buildItems)
|
||||
}
|
||||
|
||||
func findOrPersistPipelineConfig(repo *model.Repo, remoteYamlConfig []byte) (*model.Config, error) {
|
||||
sha := shasum(remoteYamlConfig)
|
||||
conf, err := Config.Storage.Config.ConfigFind(repo, sha)
|
||||
func branchFiltered(build *model.Build, remoteYamlConfigs []*remote.FileMeta) bool {
|
||||
for _, remoteYamlConfig := range remoteYamlConfigs {
|
||||
parsedPipelineConfig, err := yaml.ParseString(string(remoteYamlConfig.Data))
|
||||
if err == nil {
|
||||
if !parsedPipelineConfig.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func findOrPersistPipelineConfig(build *model.Build, remoteYamlConfig *remote.FileMeta) (*model.Config, error) {
|
||||
sha := shasum(remoteYamlConfig.Data)
|
||||
conf, err := Config.Storage.Config.ConfigFindIdentical(build.RepoID, sha)
|
||||
if err != nil {
|
||||
conf = &model.Config{
|
||||
RepoID: repo.ID,
|
||||
Data: string(remoteYamlConfig),
|
||||
RepoID: build.RepoID,
|
||||
Data: string(remoteYamlConfig.Data),
|
||||
Hash: sha,
|
||||
Name: sanitizePath(remoteYamlConfig.Name),
|
||||
}
|
||||
err = Config.Storage.Config.ConfigCreate(conf)
|
||||
if err != nil {
|
||||
// retry in case we receive two hooks at the same time
|
||||
conf, err = Config.Storage.Config.ConfigFind(repo, sha)
|
||||
conf, err = Config.Storage.Config.ConfigFindIdentical(build.RepoID, sha)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildConfig := &model.BuildConfig{
|
||||
ConfigID: conf.ID,
|
||||
BuildID: build.ID,
|
||||
}
|
||||
err = Config.Storage.Config.BuildConfigCreate(buildConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func setBuildProcs(build *model.Build, buildItems []*buildItem) {
|
||||
pcounter := len(buildItems)
|
||||
for _, item := range buildItems {
|
||||
build.Procs = append(build.Procs, item.Proc)
|
||||
item.Proc.BuildID = build.ID
|
||||
|
||||
for _, stage := range item.Config.Stages {
|
||||
var gid int
|
||||
for _, step := range stage.Steps {
|
||||
pcounter++
|
||||
if gid == 0 {
|
||||
gid = pcounter
|
||||
}
|
||||
proc := &model.Proc{
|
||||
BuildID: build.ID,
|
||||
Name: step.Alias,
|
||||
PID: pcounter,
|
||||
PPID: item.Proc.PID,
|
||||
PGID: gid,
|
||||
State: model.StatusPending,
|
||||
}
|
||||
build.Procs = append(build.Procs, proc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// publishes message to UI clients
|
||||
func publishToTopic(c *gin.Context, build *model.Build, repo *model.Repo) {
|
||||
message := pubsub.Message{
|
||||
Labels: map[string]string{
|
||||
@ -325,7 +323,11 @@ func publishToTopic(c *gin.Context, build *model.Build, repo *model.Repo) {
|
||||
}
|
||||
|
||||
func queueBuild(build *model.Build, repo *model.Repo, buildItems []*buildItem) {
|
||||
var tasks []*queue.Task
|
||||
for _, item := range buildItems {
|
||||
if item.Proc.State == model.StatusSkipped {
|
||||
continue
|
||||
}
|
||||
task := new(queue.Task)
|
||||
task.ID = fmt.Sprint(item.Proc.ID)
|
||||
task.Labels = map[string]string{}
|
||||
@ -334,6 +336,9 @@ func queueBuild(build *model.Build, repo *model.Repo, buildItems []*buildItem) {
|
||||
}
|
||||
task.Labels["platform"] = item.Platform
|
||||
task.Labels["repo"] = repo.FullName
|
||||
task.Dependencies = taskIds(item.DependsOn, buildItems)
|
||||
task.RunOn = item.RunsOn
|
||||
task.DepStatus = make(map[string]bool)
|
||||
|
||||
task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||
ID: fmt.Sprint(item.Proc.ID),
|
||||
@ -342,8 +347,21 @@ func queueBuild(build *model.Build, repo *model.Repo, buildItems []*buildItem) {
|
||||
})
|
||||
|
||||
Config.Services.Logs.Open(context.Background(), task.ID)
|
||||
Config.Services.Queue.Push(context.Background(), task)
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
Config.Services.Queue.PushAtOnce(context.Background(), tasks)
|
||||
}
|
||||
|
||||
func taskIds(dependsOn []string, buildItems []*buildItem) []string {
|
||||
taskIds := []string{}
|
||||
for _, dep := range dependsOn {
|
||||
for _, buildItem := range buildItems {
|
||||
if buildItem.Proc.Name == dep {
|
||||
taskIds = append(taskIds, fmt.Sprint(buildItem.Proc.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
return taskIds
|
||||
}
|
||||
|
||||
func shasum(raw []byte) string {
|
||||
|
@ -1,45 +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 server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
)
|
||||
|
||||
func TestMultilineEnvsubst(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{
|
||||
Message: `aaa
|
||||
bbb`,
|
||||
},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yaml: `pipeline:
|
||||
xxx:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`,
|
||||
}
|
||||
|
||||
if _, err := b.Build(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/envsubst"
|
||||
@ -28,6 +29,7 @@ import (
|
||||
"github.com/laszlocph/drone-oss-08/cncd/pipeline/pipeline/frontend/yaml/linter"
|
||||
"github.com/laszlocph/drone-oss-08/cncd/pipeline/pipeline/frontend/yaml/matrix"
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote"
|
||||
)
|
||||
|
||||
// Takes the hook data and the yaml and returns in internal data model
|
||||
@ -39,156 +41,198 @@ type procBuilder struct {
|
||||
Secs []*model.Secret
|
||||
Regs []*model.Registry
|
||||
Link string
|
||||
Yaml string
|
||||
Yamls []*remote.FileMeta
|
||||
Envs map[string]string
|
||||
}
|
||||
|
||||
type buildItem struct {
|
||||
Proc *model.Proc
|
||||
Platform string
|
||||
Labels map[string]string
|
||||
Config *backend.Config
|
||||
Proc *model.Proc
|
||||
Platform string
|
||||
Labels map[string]string
|
||||
DependsOn []string
|
||||
RunsOn []string
|
||||
Config *backend.Config
|
||||
}
|
||||
|
||||
func (b *procBuilder) Build() ([]*buildItem, error) {
|
||||
|
||||
axes, err := matrix.ParseString(b.Yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
|
||||
var items []*buildItem
|
||||
for i, axis := range axes {
|
||||
proc := &model.Proc{
|
||||
BuildID: b.Curr.ID,
|
||||
PID: i + 1,
|
||||
PGID: i + 1,
|
||||
State: model.StatusPending,
|
||||
Environ: axis,
|
||||
}
|
||||
|
||||
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, proc, b.Link)
|
||||
environ := metadata.Environ()
|
||||
for k, v := range metadata.EnvironDrone() {
|
||||
environ[k] = v
|
||||
}
|
||||
for k, v := range axis {
|
||||
environ[k] = v
|
||||
}
|
||||
sort.Sort(remote.ByName(b.Yamls))
|
||||
|
||||
var secrets []compiler.Secret
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.Match(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
secrets = append(secrets, compiler.Secret{
|
||||
Name: sec.Name,
|
||||
Value: sec.Value,
|
||||
Match: sec.Images,
|
||||
})
|
||||
}
|
||||
|
||||
y := b.Yaml
|
||||
s, err := envsubst.Eval(y, func(name string) string {
|
||||
env := environ[name]
|
||||
if strings.Contains(env, "\n") {
|
||||
env = fmt.Sprintf("%q", env)
|
||||
}
|
||||
return env
|
||||
})
|
||||
for j, y := range b.Yamls {
|
||||
// matrix axes
|
||||
axes, err := matrix.ParseString(string(y.Data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y = s
|
||||
|
||||
parsed, err := yaml.ParseString(y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata.Sys.Arch = parsed.Platform
|
||||
if metadata.Sys.Arch == "" {
|
||||
metadata.Sys.Arch = "linux/amd64"
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
|
||||
lerr := linter.New(
|
||||
linter.WithTrusted(b.Repo.IsTrusted),
|
||||
).Lint(parsed)
|
||||
if lerr != nil {
|
||||
return nil, lerr
|
||||
}
|
||||
for i, axis := range axes {
|
||||
proc := &model.Proc{
|
||||
BuildID: b.Curr.ID,
|
||||
PID: j + i + 1,
|
||||
PGID: j + i + 1,
|
||||
State: model.StatusPending,
|
||||
Environ: axis,
|
||||
Name: sanitizePath(y.Name),
|
||||
}
|
||||
b.Curr.Procs = append(b.Curr.Procs, proc)
|
||||
|
||||
var registries []compiler.Registry
|
||||
for _, reg := range b.Regs {
|
||||
registries = append(registries, compiler.Registry{
|
||||
Hostname: reg.Address,
|
||||
Username: reg.Username,
|
||||
Password: reg.Password,
|
||||
Email: reg.Email,
|
||||
})
|
||||
}
|
||||
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, proc, b.Link)
|
||||
environ := b.environmentVariables(metadata, axis)
|
||||
|
||||
ir := compiler.New(
|
||||
compiler.WithEnviron(environ),
|
||||
compiler.WithEnviron(b.Envs),
|
||||
compiler.WithEscalated(Config.Pipeline.Privileged...),
|
||||
compiler.WithResourceLimit(Config.Pipeline.Limits.MemSwapLimit, Config.Pipeline.Limits.MemLimit, Config.Pipeline.Limits.ShmSize, Config.Pipeline.Limits.CPUQuota, Config.Pipeline.Limits.CPUShares, Config.Pipeline.Limits.CPUSet),
|
||||
compiler.WithVolumes(Config.Pipeline.Volumes...),
|
||||
compiler.WithNetworks(Config.Pipeline.Networks...),
|
||||
compiler.WithLocal(false),
|
||||
compiler.WithOption(
|
||||
compiler.WithNetrc(
|
||||
b.Netrc.Login,
|
||||
b.Netrc.Password,
|
||||
b.Netrc.Machine,
|
||||
),
|
||||
b.Repo.IsPrivate,
|
||||
),
|
||||
compiler.WithRegistry(registries...),
|
||||
compiler.WithSecret(secrets...),
|
||||
compiler.WithPrefix(
|
||||
fmt.Sprintf(
|
||||
"%d_%d",
|
||||
proc.ID,
|
||||
rand.Int(),
|
||||
),
|
||||
),
|
||||
compiler.WithEnviron(proc.Environ),
|
||||
compiler.WithProxy(),
|
||||
compiler.WithWorkspaceFromURL("/drone", b.Repo.Link),
|
||||
compiler.WithMetadata(metadata),
|
||||
).Compile(parsed)
|
||||
// substitute vars
|
||||
substituted, err := b.envsubst_(string(y.Data), environ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for _, sec := range b.Secs {
|
||||
// if !sec.MatchEvent(b.Curr.Event) {
|
||||
// continue
|
||||
// }
|
||||
// if b.Curr.Verified || sec.SkipVerify {
|
||||
// ir.Secrets = append(ir.Secrets, &backend.Secret{
|
||||
// Mask: sec.Conceal,
|
||||
// Name: sec.Name,
|
||||
// Value: sec.Value,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// parse yaml pipeline
|
||||
parsed, err := yaml.ParseString(substituted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item := &buildItem{
|
||||
Proc: proc,
|
||||
Config: ir,
|
||||
Labels: parsed.Labels,
|
||||
Platform: metadata.Sys.Arch,
|
||||
// lint pipeline
|
||||
lerr := linter.New(
|
||||
linter.WithTrusted(b.Repo.IsTrusted),
|
||||
).Lint(parsed)
|
||||
if lerr != nil {
|
||||
return nil, lerr
|
||||
}
|
||||
|
||||
if !parsed.Branches.Match(b.Curr.Branch) {
|
||||
proc.State = model.StatusSkipped
|
||||
}
|
||||
|
||||
metadata.SetPlatform(parsed.Platform)
|
||||
|
||||
ir := b.toInternalRepresentation(parsed, environ, metadata, proc.ID)
|
||||
|
||||
item := &buildItem{
|
||||
Proc: proc,
|
||||
Config: ir,
|
||||
Labels: parsed.Labels,
|
||||
DependsOn: parsed.DependsOn,
|
||||
RunsOn: parsed.RunsOn,
|
||||
Platform: metadata.Sys.Arch,
|
||||
}
|
||||
if item.Labels == nil {
|
||||
item.Labels = map[string]string{}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
if item.Labels == nil {
|
||||
item.Labels = map[string]string{}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
setBuildSteps(b.Curr, items)
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (b *procBuilder) envsubst_(y string, environ map[string]string) (string, error) {
|
||||
return envsubst.Eval(y, func(name string) string {
|
||||
env := environ[name]
|
||||
if strings.Contains(env, "\n") {
|
||||
env = fmt.Sprintf("%q", env)
|
||||
}
|
||||
return env
|
||||
})
|
||||
}
|
||||
|
||||
func (b *procBuilder) environmentVariables(metadata frontend.Metadata, axis matrix.Axis) map[string]string {
|
||||
environ := metadata.Environ()
|
||||
for k, v := range metadata.EnvironDrone() {
|
||||
environ[k] = v
|
||||
}
|
||||
for k, v := range axis {
|
||||
environ[k] = v
|
||||
}
|
||||
return environ
|
||||
}
|
||||
|
||||
func (b *procBuilder) toInternalRepresentation(parsed *yaml.Config, environ map[string]string, metadata frontend.Metadata, procID int64) *backend.Config {
|
||||
var secrets []compiler.Secret
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.Match(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
secrets = append(secrets, compiler.Secret{
|
||||
Name: sec.Name,
|
||||
Value: sec.Value,
|
||||
Match: sec.Images,
|
||||
})
|
||||
}
|
||||
|
||||
var registries []compiler.Registry
|
||||
for _, reg := range b.Regs {
|
||||
registries = append(registries, compiler.Registry{
|
||||
Hostname: reg.Address,
|
||||
Username: reg.Username,
|
||||
Password: reg.Password,
|
||||
Email: reg.Email,
|
||||
})
|
||||
}
|
||||
|
||||
return compiler.New(
|
||||
compiler.WithEnviron(environ),
|
||||
compiler.WithEnviron(b.Envs),
|
||||
compiler.WithEscalated(Config.Pipeline.Privileged...),
|
||||
compiler.WithResourceLimit(Config.Pipeline.Limits.MemSwapLimit, Config.Pipeline.Limits.MemLimit, Config.Pipeline.Limits.ShmSize, Config.Pipeline.Limits.CPUQuota, Config.Pipeline.Limits.CPUShares, Config.Pipeline.Limits.CPUSet),
|
||||
compiler.WithVolumes(Config.Pipeline.Volumes...),
|
||||
compiler.WithNetworks(Config.Pipeline.Networks...),
|
||||
compiler.WithLocal(false),
|
||||
compiler.WithOption(
|
||||
compiler.WithNetrc(
|
||||
b.Netrc.Login,
|
||||
b.Netrc.Password,
|
||||
b.Netrc.Machine,
|
||||
),
|
||||
b.Repo.IsPrivate,
|
||||
),
|
||||
compiler.WithRegistry(registries...),
|
||||
compiler.WithSecret(secrets...),
|
||||
compiler.WithPrefix(
|
||||
fmt.Sprintf(
|
||||
"%d_%d",
|
||||
procID,
|
||||
rand.Int(),
|
||||
),
|
||||
),
|
||||
compiler.WithProxy(),
|
||||
compiler.WithWorkspaceFromURL("/drone", b.Repo.Link),
|
||||
compiler.WithMetadata(metadata),
|
||||
).Compile(parsed)
|
||||
}
|
||||
|
||||
func setBuildSteps(build *model.Build, buildItems []*buildItem) {
|
||||
pcounter := len(buildItems)
|
||||
for _, item := range buildItems {
|
||||
for _, stage := range item.Config.Stages {
|
||||
var gid int
|
||||
for _, step := range stage.Steps {
|
||||
pcounter++
|
||||
if gid == 0 {
|
||||
gid = pcounter
|
||||
}
|
||||
proc := &model.Proc{
|
||||
BuildID: build.ID,
|
||||
Name: step.Alias,
|
||||
PID: pcounter,
|
||||
PPID: item.Proc.PID,
|
||||
PGID: gid,
|
||||
State: model.StatusPending,
|
||||
}
|
||||
if item.Proc.State == model.StatusSkipped {
|
||||
proc.State = model.StatusSkipped
|
||||
}
|
||||
build.Procs = append(build.Procs, proc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromStruct(repo *model.Repo, build, last *model.Build, proc *model.Proc, link string) frontend.Metadata {
|
||||
host := link
|
||||
@ -261,3 +305,10 @@ func metadataFromStruct(repo *model.Repo, build, last *model.Build, proc *model.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizePath(path string) string {
|
||||
path = strings.TrimSuffix(path, ".yml")
|
||||
path = strings.TrimPrefix(path, ".drone/")
|
||||
path = strings.TrimPrefix(path, ".")
|
||||
return path
|
||||
}
|
||||
|
206
server/procBuilder_test.go
Normal file
206
server/procBuilder_test.go
Normal file
@ -0,0 +1,206 @@
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/laszlocph/drone-oss-08/remote"
|
||||
)
|
||||
|
||||
func TestMultilineEnvsubst(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{
|
||||
Message: `aaa
|
||||
bbb`,
|
||||
},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yamls: []*remote.FileMeta{
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
xxx:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`)},
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
build:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`)},
|
||||
}}
|
||||
|
||||
if buildItems, err := b.Build(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
fmt.Println(buildItems)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPipeline(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yamls: []*remote.FileMeta{
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
xxx:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`)},
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
build:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`)},
|
||||
},
|
||||
}
|
||||
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(buildItems) != 2 {
|
||||
t.Fatal("Should have generated 2 buildItems")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependsOn(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yamls: []*remote.FileMeta{
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
deploy:
|
||||
image: scratch
|
||||
|
||||
depends_on:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
`)},
|
||||
},
|
||||
}
|
||||
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(buildItems[0].DependsOn) != 3 {
|
||||
t.Fatal("Should have 3 dependencies")
|
||||
}
|
||||
if buildItems[0].DependsOn[1] != "test" {
|
||||
t.Fatal("Should depend on test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunsOn(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yamls: []*remote.FileMeta{
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
deploy:
|
||||
image: scratch
|
||||
|
||||
runs_on:
|
||||
- success
|
||||
- failure
|
||||
`)},
|
||||
},
|
||||
}
|
||||
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(buildItems[0].RunsOn) != 2 {
|
||||
t.Fatal("Should run on success and failure")
|
||||
}
|
||||
if buildItems[0].RunsOn[1] != "failure" {
|
||||
t.Fatal("Should run on failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBranchFilter(t *testing.T) {
|
||||
b := procBuilder{
|
||||
Repo: &model.Repo{},
|
||||
Curr: &model.Build{Branch: "dev"},
|
||||
Last: &model.Build{},
|
||||
Netrc: &model.Netrc{},
|
||||
Secs: []*model.Secret{},
|
||||
Regs: []*model.Registry{},
|
||||
Link: "",
|
||||
Yamls: []*remote.FileMeta{
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
xxx:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
branches: master
|
||||
`)},
|
||||
&remote.FileMeta{Data: []byte(`
|
||||
pipeline:
|
||||
build:
|
||||
image: scratch
|
||||
yyy: ${DRONE_COMMIT_MESSAGE}
|
||||
`)},
|
||||
},
|
||||
}
|
||||
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(buildItems) != 2 {
|
||||
t.Fatal("Should have generated 2 buildItems")
|
||||
}
|
||||
if buildItems[0].Proc.State != model.StatusSkipped {
|
||||
t.Fatal("Should not run on dev branch")
|
||||
}
|
||||
for _, child := range buildItems[0].Proc.Children {
|
||||
if child.State != model.StatusSkipped {
|
||||
t.Fatal("Children should skipped status too")
|
||||
}
|
||||
}
|
||||
if buildItems[1].Proc.State != model.StatusPending {
|
||||
t.Fatal("Should not run on dev branch")
|
||||
}
|
||||
}
|
@ -150,6 +150,9 @@ func PatchRepo(c *gin.Context) {
|
||||
if in.BuildCounter != nil {
|
||||
repo.Counter = *in.BuildCounter
|
||||
}
|
||||
if in.Fallback != nil {
|
||||
repo.Fallback = *in.Fallback
|
||||
}
|
||||
|
||||
err := store.UpdateRepo(c, repo)
|
||||
if err != nil {
|
||||
|
262
server/rpc.go
262
server/rpc.go
@ -113,26 +113,22 @@ func (s *RPC) Next(c context.Context, filter rpc.Filter) (*rpc.Pipeline, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task, err := s.queue.Poll(c, fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if task == nil {
|
||||
return nil, nil
|
||||
for {
|
||||
task, err := s.queue.Poll(c, fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if task == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if task.ShouldRun() {
|
||||
pipeline := new(rpc.Pipeline)
|
||||
err = json.Unmarshal(task.Data, pipeline)
|
||||
return pipeline, err
|
||||
} else {
|
||||
s.Done(c, task.ID, rpc.State{})
|
||||
}
|
||||
}
|
||||
pipeline := new(rpc.Pipeline)
|
||||
|
||||
// check if the process was previously cancelled
|
||||
// cancelled, _ := s.checkCancelled(pipeline)
|
||||
// if cancelled {
|
||||
// logrus.Debugf("ignore pid %v: cancelled by user", pipeline.ID)
|
||||
// if derr := s.queue.Done(c, pipeline.ID); derr != nil {
|
||||
// logrus.Errorf("error: done: cannot ack proc_id %v: %s", pipeline.ID, err)
|
||||
// }
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
err = json.Unmarshal(task.Data, pipeline)
|
||||
return pipeline, err
|
||||
}
|
||||
|
||||
// Wait implements the rpc.Wait function
|
||||
@ -383,76 +379,139 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
|
||||
return err
|
||||
}
|
||||
|
||||
proc.Stopped = state.Finished
|
||||
proc.Error = state.Error
|
||||
proc.ExitCode = state.ExitCode
|
||||
proc.State = model.StatusSuccess
|
||||
if proc.ExitCode != 0 || proc.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
if err := s.store.ProcUpdate(proc); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", procID, err)
|
||||
}
|
||||
s.updateProcState(proc, state)
|
||||
|
||||
if err := s.queue.Done(c, id); err != nil {
|
||||
var queueErr error
|
||||
if proc.Failing() {
|
||||
queueErr = s.queue.Error(c, id, fmt.Errorf("Proc finished with exitcode %d, %s", state.ExitCode, state.Error))
|
||||
} else {
|
||||
queueErr = s.queue.Done(c, id)
|
||||
}
|
||||
if queueErr != nil {
|
||||
log.Printf("error: done: cannot ack proc_id %d: %s", procID, err)
|
||||
}
|
||||
|
||||
// TODO handle this error
|
||||
procs, _ := s.store.ProcList(build)
|
||||
for _, p := range procs {
|
||||
if p.Running() && p.PPID == proc.PID {
|
||||
p.State = model.StatusSkipped
|
||||
if p.Started != 0 {
|
||||
p.State = model.StatusSuccess // for deamons that are killed
|
||||
p.Stopped = proc.Stopped
|
||||
}
|
||||
if err := s.store.ProcUpdate(p); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d child state: %s", p.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.completeChildrenIfParentCompleted(procs, proc)
|
||||
|
||||
running := false
|
||||
status := model.StatusSuccess
|
||||
for _, p := range procs {
|
||||
if p.PPID == 0 {
|
||||
if p.Running() {
|
||||
running = true
|
||||
}
|
||||
if p.Failing() {
|
||||
status = p.State
|
||||
}
|
||||
}
|
||||
}
|
||||
if !running {
|
||||
build.Status = status
|
||||
if !isThereRunningStage(procs) {
|
||||
build.Status = buildStatus(procs)
|
||||
build.Finished = proc.Stopped
|
||||
if err := s.store.UpdateBuild(build); err != nil {
|
||||
log.Printf("error: done: cannot update build_id %d final state: %s", build.ID, err)
|
||||
}
|
||||
|
||||
// update the status
|
||||
user, err := s.store.GetUser(repo.UserID)
|
||||
if err == nil {
|
||||
if refresher, ok := s.remote.(remote.Refresher); ok {
|
||||
ok, _ := refresher.Refresh(user)
|
||||
if ok {
|
||||
s.store.UpdateUser(user)
|
||||
}
|
||||
}
|
||||
uri := fmt.Sprintf("%s/%s/%d", s.host, repo.FullName, build.Number)
|
||||
err = s.remote.Status(user, repo, build, uri)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
if !isMultiPipeline(procs) {
|
||||
s.updateRemoteStatus(repo, build, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if isMultiPipeline(procs) {
|
||||
s.updateRemoteStatus(repo, build, proc)
|
||||
}
|
||||
|
||||
if err := s.logger.Close(c, id); err != nil {
|
||||
log.Printf("error: done: cannot close build_id %d logger: %s", proc.ID, err)
|
||||
}
|
||||
|
||||
s.notify(c, repo, build, procs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isMultiPipeline(procs []*model.Proc) bool {
|
||||
countPPIDZero := 0
|
||||
for _, proc := range procs {
|
||||
if proc.PPID == 0 {
|
||||
countPPIDZero++
|
||||
}
|
||||
}
|
||||
return countPPIDZero > 1
|
||||
}
|
||||
|
||||
// Log implements the rpc.Log function
|
||||
func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error {
|
||||
entry := new(logging.Entry)
|
||||
entry.Data, _ = json.Marshal(line)
|
||||
s.logger.Write(c, id, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPC) updateProcState(proc *model.Proc, state rpc.State) {
|
||||
proc.Stopped = state.Finished
|
||||
proc.Error = state.Error
|
||||
proc.ExitCode = state.ExitCode
|
||||
if state.Started == 0 {
|
||||
proc.State = model.StatusSkipped
|
||||
} else {
|
||||
proc.State = model.StatusSuccess
|
||||
}
|
||||
if proc.ExitCode != 0 || proc.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
if err := s.store.ProcUpdate(proc); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RPC) completeChildrenIfParentCompleted(procs []*model.Proc, completedProc *model.Proc) {
|
||||
for _, p := range procs {
|
||||
if p.Running() && p.PPID == completedProc.PID {
|
||||
p.State = model.StatusSkipped
|
||||
if p.Started != 0 {
|
||||
p.State = model.StatusSuccess // for deamons that are killed
|
||||
p.Stopped = completedProc.Stopped
|
||||
}
|
||||
if err := s.store.ProcUpdate(p); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d child state: %s", p.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isThereRunningStage(procs []*model.Proc) bool {
|
||||
for _, p := range procs {
|
||||
if p.PPID == 0 {
|
||||
if p.Running() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildStatus(procs []*model.Proc) string {
|
||||
status := model.StatusSuccess
|
||||
|
||||
for _, p := range procs {
|
||||
if p.PPID == 0 {
|
||||
if p.Failing() {
|
||||
status = p.State
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (s *RPC) updateRemoteStatus(repo *model.Repo, build *model.Build, proc *model.Proc) {
|
||||
user, err := s.store.GetUser(repo.UserID)
|
||||
if err == nil {
|
||||
if refresher, ok := s.remote.(remote.Refresher); ok {
|
||||
ok, _ := refresher.Refresh(user)
|
||||
if ok {
|
||||
s.store.UpdateUser(user)
|
||||
}
|
||||
}
|
||||
uri := fmt.Sprintf("%s/%s/%d", s.host, repo.FullName, build.Number)
|
||||
err = s.remote.Status(user, repo, build, uri, proc)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d: %v", repo.FullName, build.Number, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RPC) notify(c context.Context, repo *model.Repo, build *model.Build, procs []*model.Proc) {
|
||||
build.Procs = model.Tree(procs)
|
||||
message := pubsub.Message{
|
||||
Labels: map[string]string{
|
||||
@ -465,31 +524,6 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
|
||||
Build: *build,
|
||||
})
|
||||
s.pubsub.Publish(c, "topic/events", message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log implements the rpc.Log function
|
||||
func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error {
|
||||
entry := new(logging.Entry)
|
||||
entry.Data, _ = json.Marshal(line)
|
||||
s.logger.Write(c, id, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPC) checkCancelled(pipeline *rpc.Pipeline) (bool, error) {
|
||||
pid, err := strconv.ParseInt(pipeline.ID, 10, 64)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
proc, err := s.store.ProcLoad(pid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if proc.State == model.StatusKilled {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func createFilterFunc(filter rpc.Filter) (queue.Filter, error) {
|
||||
@ -561,42 +595,6 @@ func (s *DroneServer) Next(c oldcontext.Context, req *proto.NextRequest) (*proto
|
||||
res.Pipeline.Payload, _ = json.Marshal(pipeline.Config)
|
||||
|
||||
return res, err
|
||||
|
||||
// fn := func(task *queue.Task) bool {
|
||||
// for k, v := range req.GetFilter().Labels {
|
||||
// if task.Labels[k] != v {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
// task, err := s.Queue.Poll(c, fn)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// } else if task == nil {
|
||||
// return nil, nil
|
||||
// }
|
||||
//
|
||||
// pipeline := new(rpc.Pipeline)
|
||||
// json.Unmarshal(task.Data, pipeline)
|
||||
//
|
||||
// res := new(proto.NextReply)
|
||||
// res.Pipeline = new(proto.Pipeline)
|
||||
// res.Pipeline.Id = pipeline.ID
|
||||
// res.Pipeline.Timeout = pipeline.Timeout
|
||||
// res.Pipeline.Payload, _ = json.Marshal(pipeline.Config)
|
||||
//
|
||||
// // check if the process was previously cancelled
|
||||
// // cancelled, _ := s.checkCancelled(pipeline)
|
||||
// // if cancelled {
|
||||
// // logrus.Debugf("ignore pid %v: cancelled by user", pipeline.ID)
|
||||
// // if derr := s.queue.Done(c, pipeline.ID); derr != nil {
|
||||
// // logrus.Errorf("error: done: cannot ack proc_id %v: %s", pipeline.ID, err)
|
||||
// // }
|
||||
// // return nil, nil
|
||||
// // }
|
||||
//
|
||||
// return res, nil
|
||||
}
|
||||
|
||||
func (s *DroneServer) Init(c oldcontext.Context, req *proto.InitRequest) (*proto.Empty, error) {
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/drone-oss-08/model"
|
||||
)
|
||||
|
||||
func TestBuilds(t *testing.T) {
|
||||
|
@ -22,17 +22,17 @@ import (
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
func (db *datastore) ConfigLoad(id int64) (*model.Config, error) {
|
||||
func (db *datastore) ConfigsForBuild(buildID int64) ([]*model.Config, error) {
|
||||
stmt := sql.Lookup(db.driver, "config-find-id")
|
||||
conf := new(model.Config)
|
||||
err := meddler.QueryRow(db, conf, stmt, id)
|
||||
return conf, err
|
||||
var configs = []*model.Config{}
|
||||
err := meddler.QueryAll(db, &configs, stmt, buildID)
|
||||
return configs, err
|
||||
}
|
||||
|
||||
func (db *datastore) ConfigFind(repo *model.Repo, hash string) (*model.Config, error) {
|
||||
func (db *datastore) ConfigFindIdentical(repoID int64, hash string) (*model.Config, error) {
|
||||
stmt := sql.Lookup(db.driver, "config-find-repo-hash")
|
||||
conf := new(model.Config)
|
||||
err := meddler.QueryRow(db, conf, stmt, repo.ID, hash)
|
||||
err := meddler.QueryRow(db, conf, stmt, repoID, hash)
|
||||
return conf, err
|
||||
}
|
||||
|
||||
@ -51,3 +51,7 @@ func (db *datastore) ConfigFindApproved(config *model.Config) (bool, error) {
|
||||
func (db *datastore) ConfigCreate(config *model.Config) error {
|
||||
return meddler.Insert(db, "config", config)
|
||||
}
|
||||
|
||||
func (db *datastore) BuildConfigCreate(buildConfig *model.BuildConfig) error {
|
||||
return meddler.Insert(db, "build_config", buildConfig)
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ import (
|
||||
func TestConfig(t *testing.T) {
|
||||
s := newTest()
|
||||
defer func() {
|
||||
s.Exec("delete from repos")
|
||||
s.Exec("delete from builds")
|
||||
s.Exec("delete from procs")
|
||||
s.Exec("delete from config")
|
||||
s.Close()
|
||||
}()
|
||||
@ -32,18 +35,49 @@ func TestConfig(t *testing.T) {
|
||||
hash = "8d8647c9aa90d893bfb79dddbe901f03e258588121e5202632f8ae5738590b26"
|
||||
)
|
||||
|
||||
if err := s.ConfigCreate(
|
||||
&model.Config{
|
||||
RepoID: 2,
|
||||
Data: data,
|
||||
Hash: hash,
|
||||
},
|
||||
); err != nil {
|
||||
repo := &model.Repo{
|
||||
UserID: 1,
|
||||
FullName: "bradrydzewski/drone",
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
if err := s.CreateRepo(repo); err != nil {
|
||||
t.Errorf("Unexpected error: insert repo: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
config := &model.Config{
|
||||
RepoID: repo.ID,
|
||||
Data: data,
|
||||
Hash: hash,
|
||||
Name: "default",
|
||||
}
|
||||
if err := s.ConfigCreate(config); err != nil {
|
||||
t.Errorf("Unexpected error: insert config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := s.ConfigFind(&model.Repo{ID: 2}, hash)
|
||||
build := &model.Build{
|
||||
RepoID: repo.ID,
|
||||
Status: model.StatusRunning,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
if err := s.CreateBuild(build); err != nil {
|
||||
t.Errorf("Unexpected error: insert build: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.BuildConfigCreate(
|
||||
&model.BuildConfig{
|
||||
ConfigID: config.ID,
|
||||
BuildID: build.ID,
|
||||
},
|
||||
); err != nil {
|
||||
t.Errorf("Unexpected error: insert build config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := s.ConfigFindIdentical(repo.ID, hash)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@ -51,7 +85,7 @@ func TestConfig(t *testing.T) {
|
||||
if got, want := config.ID, int64(1); got != want {
|
||||
t.Errorf("Want config id %d, got %d", want, got)
|
||||
}
|
||||
if got, want := config.RepoID, int64(2); got != want {
|
||||
if got, want := config.RepoID, repo.ID; got != want {
|
||||
t.Errorf("Want config repo id %d, got %d", want, got)
|
||||
}
|
||||
if got, want := config.Data, data; got != want {
|
||||
@ -60,13 +94,16 @@ func TestConfig(t *testing.T) {
|
||||
if got, want := config.Hash, hash; got != want {
|
||||
t.Errorf("Want config hash %s, got %s", want, got)
|
||||
}
|
||||
if got, want := config.Name, "default"; got != want {
|
||||
t.Errorf("Want config name %s, got %s", want, got)
|
||||
}
|
||||
|
||||
loaded, err := s.ConfigLoad(config.ID)
|
||||
loaded, err := s.ConfigsForBuild(build.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Want config by id, got error %q", err)
|
||||
return
|
||||
}
|
||||
if got, want := loaded.ID, config.ID; got != want {
|
||||
if got, want := loaded[0].ID, config.ID; got != want {
|
||||
t.Errorf("Want config by id %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
@ -74,9 +111,10 @@ func TestConfig(t *testing.T) {
|
||||
func TestConfigApproved(t *testing.T) {
|
||||
s := newTest()
|
||||
defer func() {
|
||||
s.Exec("delete from config")
|
||||
s.Exec("delete from builds")
|
||||
s.Exec("delete from repos")
|
||||
s.Exec("delete from builds")
|
||||
s.Exec("delete from procs")
|
||||
s.Exec("delete from config")
|
||||
s.Close()
|
||||
}()
|
||||
|
||||
@ -86,49 +124,83 @@ func TestConfigApproved(t *testing.T) {
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
s.CreateRepo(repo)
|
||||
if err := s.CreateRepo(repo); err != nil {
|
||||
t.Errorf("Unexpected error: insert repo: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
data = "pipeline: [ { image: golang, commands: [ go build, go test ] } ]"
|
||||
hash = "8d8647c9aa90d893bfb79dddbe901f03e258588121e5202632f8ae5738590b26"
|
||||
conf = &model.Config{
|
||||
data = "pipeline: [ { image: golang, commands: [ go build, go test ] } ]"
|
||||
hash = "8d8647c9aa90d893bfb79dddbe901f03e258588121e5202632f8ae5738590b26"
|
||||
buildBlocked = &model.Build{
|
||||
RepoID: repo.ID,
|
||||
Data: data,
|
||||
Hash: hash,
|
||||
Status: model.StatusBlocked,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
buildPending = &model.Build{
|
||||
RepoID: repo.ID,
|
||||
Status: model.StatusPending,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
buildRunning = &model.Build{
|
||||
RepoID: repo.ID,
|
||||
Status: model.StatusRunning,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
)
|
||||
|
||||
if err := s.CreateBuild(buildBlocked); err != nil {
|
||||
t.Errorf("Unexpected error: insert build: %s", err)
|
||||
return
|
||||
}
|
||||
if err := s.CreateBuild(buildPending); err != nil {
|
||||
t.Errorf("Unexpected error: insert build: %s", err)
|
||||
return
|
||||
}
|
||||
conf := &model.Config{
|
||||
RepoID: repo.ID,
|
||||
Data: data,
|
||||
Hash: hash,
|
||||
}
|
||||
if err := s.ConfigCreate(conf); err != nil {
|
||||
t.Errorf("Unexpected error: insert config: %s", err)
|
||||
return
|
||||
}
|
||||
s.CreateBuild(&model.Build{
|
||||
RepoID: repo.ID,
|
||||
buildConfig := &model.BuildConfig{
|
||||
ConfigID: conf.ID,
|
||||
Status: model.StatusBlocked,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
})
|
||||
s.CreateBuild(&model.Build{
|
||||
RepoID: repo.ID,
|
||||
ConfigID: conf.ID,
|
||||
Status: model.StatusPending,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
})
|
||||
|
||||
if ok, _ := s.ConfigFindApproved(conf); ok == true {
|
||||
t.Errorf("Want config not approved, when blocked or pending")
|
||||
BuildID: buildBlocked.ID,
|
||||
}
|
||||
if err := s.BuildConfigCreate(buildConfig); err != nil {
|
||||
t.Errorf("Unexpected error: insert build_config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.CreateBuild(&model.Build{
|
||||
RepoID: repo.ID,
|
||||
ConfigID: conf.ID,
|
||||
Status: model.StatusRunning,
|
||||
Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
})
|
||||
if approved, err := s.ConfigFindApproved(conf); approved != false || err != nil {
|
||||
t.Errorf("Want config not approved, when blocked or pending. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok, _ := s.ConfigFindApproved(conf); ok == false {
|
||||
t.Errorf("Want config approved, when running.")
|
||||
s.CreateBuild(buildRunning)
|
||||
conf2 := &model.Config{
|
||||
RepoID: repo.ID,
|
||||
Data: data,
|
||||
Hash: "xxx",
|
||||
}
|
||||
if err := s.ConfigCreate(conf2); err != nil {
|
||||
t.Errorf("Unexpected error: insert config: %s", err)
|
||||
return
|
||||
}
|
||||
buildConfig2 := &model.BuildConfig{
|
||||
ConfigID: conf2.ID,
|
||||
BuildID: buildRunning.ID,
|
||||
}
|
||||
if err := s.BuildConfigCreate(buildConfig2); err != nil {
|
||||
t.Errorf("Unexpected error: insert config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if approved, err := s.ConfigFindApproved(conf2); approved != true || err != nil {
|
||||
t.Errorf("Want config approved, when running. %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -136,6 +208,9 @@ func TestConfigApproved(t *testing.T) {
|
||||
func TestConfigIndexes(t *testing.T) {
|
||||
s := newTest()
|
||||
defer func() {
|
||||
s.Exec("delete from repos")
|
||||
s.Exec("delete from builds")
|
||||
s.Exec("delete from procs")
|
||||
s.Exec("delete from config")
|
||||
s.Close()
|
||||
}()
|
||||
|
@ -1,17 +1,3 @@
|
||||
// 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 mysql
|
||||
|
||||
import (
|
||||
@ -170,6 +156,38 @@ var migrations = []struct {
|
||||
name: "alter-table-update-file-meta",
|
||||
stmt: alterTableUpdateFileMeta,
|
||||
},
|
||||
{
|
||||
name: "create-table-build-config",
|
||||
stmt: createTableBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-config-name",
|
||||
stmt: alterTableAddConfigName,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-config-name",
|
||||
stmt: updateTableSetConfigName,
|
||||
},
|
||||
{
|
||||
name: "populate-build-config",
|
||||
stmt: populateBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-dependencies",
|
||||
stmt: alterTableAddTaskDependencies,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-run-on",
|
||||
stmt: alterTableAddTaskRunOn,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-repo-fallback",
|
||||
stmt: alterTableAddRepoFallback,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-repo-fallback",
|
||||
stmt: updateTableSetRepoFallback,
|
||||
},
|
||||
}
|
||||
|
||||
// Migrate performs the database migration. If the migration fails
|
||||
@ -636,3 +654,62 @@ UPDATE files SET
|
||||
,file_meta_failed=0
|
||||
,file_meta_skipped=0
|
||||
`
|
||||
|
||||
//
|
||||
// 019_create_table_build_config.sql
|
||||
//
|
||||
|
||||
var createTableBuildConfig = `
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
||||
`
|
||||
|
||||
//
|
||||
// 020_add_column_config_name.sql
|
||||
//
|
||||
|
||||
var alterTableAddConfigName = `
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
`
|
||||
|
||||
var updateTableSetConfigName = `
|
||||
UPDATE config SET config_name = "drone"
|
||||
`
|
||||
|
||||
//
|
||||
// 021_populate_build_config.sql
|
||||
//
|
||||
|
||||
var populateBuildConfig = `
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
||||
`
|
||||
|
||||
//
|
||||
// 022_add_task_columns.sql
|
||||
//
|
||||
|
||||
var alterTableAddTaskDependencies = `
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies MEDIUMBLOB
|
||||
`
|
||||
|
||||
var alterTableAddTaskRunOn = `
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on MEDIUMBLOB
|
||||
`
|
||||
|
||||
//
|
||||
// 023_add_repo_fallback_column.sql
|
||||
//
|
||||
|
||||
var alterTableAddRepoFallback = `
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
`
|
||||
|
||||
var updateTableSetRepoFallback = `
|
||||
UPDATE repos SET repo_fallback='false'
|
||||
`
|
||||
|
@ -0,0 +1,9 @@
|
||||
-- name: create-table-build-config
|
||||
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
@ -0,0 +1,7 @@
|
||||
-- name: alter-table-add-config-name
|
||||
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
|
||||
-- name: update-table-set-config-name
|
||||
|
||||
UPDATE config SET config_name = "drone"
|
@ -0,0 +1,4 @@
|
||||
-- name: populate-build-config
|
||||
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
6
store/datastore/ddl/mysql/files/022_add_task_columns.sql
Normal file
6
store/datastore/ddl/mysql/files/022_add_task_columns.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- name: alter-table-add-task-dependencies
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies MEDIUMBLOB
|
||||
|
||||
-- name: alter-table-add-task-run-on
|
||||
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on MEDIUMBLOB
|
@ -0,0 +1,5 @@
|
||||
-- name: alter-table-add-repo-fallback
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
|
||||
-- name: update-table-set-repo-fallback
|
||||
UPDATE repos SET repo_fallback='false'
|
@ -1,17 +1,3 @@
|
||||
// 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 postgres
|
||||
|
||||
import (
|
||||
@ -170,6 +156,38 @@ var migrations = []struct {
|
||||
name: "alter-table-update-file-meta",
|
||||
stmt: alterTableUpdateFileMeta,
|
||||
},
|
||||
{
|
||||
name: "create-table-build-config",
|
||||
stmt: createTableBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-config-name",
|
||||
stmt: alterTableAddConfigName,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-config-name",
|
||||
stmt: updateTableSetConfigName,
|
||||
},
|
||||
{
|
||||
name: "populate-build-config",
|
||||
stmt: populateBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-dependencies",
|
||||
stmt: alterTableAddTaskDependencies,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-run-on",
|
||||
stmt: alterTableAddTaskRunOn,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-repo-fallback",
|
||||
stmt: alterTableAddRepoFallback,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-repo-fallback",
|
||||
stmt: updateTableSetRepoFallback,
|
||||
},
|
||||
}
|
||||
|
||||
// Migrate performs the database migration. If the migration fails
|
||||
@ -530,7 +548,7 @@ CREATE INDEX IF NOT EXISTS sender_repo_ix ON senders (sender_repo_id);
|
||||
//
|
||||
|
||||
var alterTableAddRepoVisibility = `
|
||||
ALTER TABLE repos ADD COLUMN repo_visibility VARCHAR(50)
|
||||
ALTER TABLE repos ADD COLUMN repo_visibility VARCHAR(50);
|
||||
`
|
||||
|
||||
var updateTableSetRepoVisibility = `
|
||||
@ -538,7 +556,7 @@ UPDATE repos
|
||||
SET repo_visibility = (CASE
|
||||
WHEN repo_private = false THEN 'public'
|
||||
ELSE 'private'
|
||||
END)
|
||||
END);
|
||||
`
|
||||
|
||||
//
|
||||
@ -554,12 +572,13 @@ UPDATE repos SET repo_counter = (
|
||||
SELECT max(build_number)
|
||||
FROM builds
|
||||
WHERE builds.build_repo_id = repos.repo_id
|
||||
)
|
||||
);
|
||||
`
|
||||
|
||||
var updateTableSetRepoSeqDefault = `
|
||||
UPDATE repos SET repo_counter = 0
|
||||
WHERE repo_counter IS NULL
|
||||
;
|
||||
`
|
||||
|
||||
//
|
||||
@ -567,11 +586,11 @@ WHERE repo_counter IS NULL
|
||||
//
|
||||
|
||||
var alterTableAddRepoActive = `
|
||||
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
|
||||
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN;
|
||||
`
|
||||
|
||||
var updateTableSetRepoActive = `
|
||||
UPDATE repos SET repo_active = true
|
||||
UPDATE repos SET repo_active = true;
|
||||
`
|
||||
|
||||
//
|
||||
@ -583,7 +602,7 @@ ALTER TABLE users ADD COLUMN user_synced INTEGER;
|
||||
`
|
||||
|
||||
var updateTableSetUserSynced = `
|
||||
UPDATE users SET user_synced = 0
|
||||
UPDATE users SET user_synced = 0;
|
||||
`
|
||||
|
||||
//
|
||||
@ -615,19 +634,19 @@ CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);
|
||||
//
|
||||
|
||||
var alterTableAddFilePid = `
|
||||
ALTER TABLE files ADD COLUMN file_pid INTEGER
|
||||
ALTER TABLE files ADD COLUMN file_pid INTEGER;
|
||||
`
|
||||
|
||||
var alterTableAddFileMetaPassed = `
|
||||
ALTER TABLE files ADD COLUMN file_meta_passed INTEGER
|
||||
ALTER TABLE files ADD COLUMN file_meta_passed INTEGER;
|
||||
`
|
||||
|
||||
var alterTableAddFileMetaFailed = `
|
||||
ALTER TABLE files ADD COLUMN file_meta_failed INTEGER
|
||||
ALTER TABLE files ADD COLUMN file_meta_failed INTEGER;
|
||||
`
|
||||
|
||||
var alterTableAddFileMetaSkipped = `
|
||||
ALTER TABLE files ADD COLUMN file_meta_skipped INTEGER
|
||||
ALTER TABLE files ADD COLUMN file_meta_skipped INTEGER;
|
||||
`
|
||||
|
||||
var alterTableUpdateFileMeta = `
|
||||
@ -635,4 +654,64 @@ UPDATE files SET
|
||||
file_meta_passed=0
|
||||
,file_meta_failed=0
|
||||
,file_meta_skipped=0
|
||||
;
|
||||
`
|
||||
|
||||
//
|
||||
// 019_create_table_build_config.sql
|
||||
//
|
||||
|
||||
var createTableBuildConfig = `
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
||||
`
|
||||
|
||||
//
|
||||
// 020_add_column_config_name.sql
|
||||
//
|
||||
|
||||
var alterTableAddConfigName = `
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
`
|
||||
|
||||
var updateTableSetConfigName = `
|
||||
UPDATE config SET config_name = 'drone'
|
||||
`
|
||||
|
||||
//
|
||||
// 021_populate_build_config.sql
|
||||
//
|
||||
|
||||
var populateBuildConfig = `
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
||||
`
|
||||
|
||||
//
|
||||
// 022_add_task_columns.sql
|
||||
//
|
||||
|
||||
var alterTableAddTaskDependencies = `
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies BYTEA
|
||||
`
|
||||
|
||||
var alterTableAddTaskRunOn = `
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on BYTEA
|
||||
`
|
||||
|
||||
//
|
||||
// 023_add_repo_fallback_column.sql
|
||||
//
|
||||
|
||||
var alterTableAddRepoFallback = `
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
`
|
||||
|
||||
var updateTableSetRepoFallback = `
|
||||
UPDATE repos SET repo_fallback='false'
|
||||
`
|
||||
|
@ -0,0 +1,9 @@
|
||||
-- name: create-table-build-config
|
||||
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
@ -0,0 +1,7 @@
|
||||
-- name: alter-table-add-config-name
|
||||
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
|
||||
-- name: update-table-set-config-name
|
||||
|
||||
UPDATE config SET config_name = 'drone'
|
@ -0,0 +1,4 @@
|
||||
-- name: populate-build-config
|
||||
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
@ -0,0 +1,6 @@
|
||||
-- name: alter-table-add-task-dependencies
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies BYTEA
|
||||
|
||||
-- name: alter-table-add-task-run-on
|
||||
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on BYTEA
|
@ -0,0 +1,5 @@
|
||||
-- name: alter-table-add-repo-fallback
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
|
||||
-- name: update-table-set-repo-fallback
|
||||
UPDATE repos SET repo_fallback='false'
|
@ -1,17 +1,3 @@
|
||||
// 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 sqlite
|
||||
|
||||
import (
|
||||
@ -174,6 +160,38 @@ var migrations = []struct {
|
||||
name: "alter-table-update-file-meta",
|
||||
stmt: alterTableUpdateFileMeta,
|
||||
},
|
||||
{
|
||||
name: "create-table-build-config",
|
||||
stmt: createTableBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-config-name",
|
||||
stmt: alterTableAddConfigName,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-config-name",
|
||||
stmt: updateTableSetConfigName,
|
||||
},
|
||||
{
|
||||
name: "populate-build-config",
|
||||
stmt: populateBuildConfig,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-dependencies",
|
||||
stmt: alterTableAddTaskDependencies,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-task-run-on",
|
||||
stmt: alterTableAddTaskRunOn,
|
||||
},
|
||||
{
|
||||
name: "alter-table-add-repo-fallback",
|
||||
stmt: alterTableAddRepoFallback,
|
||||
},
|
||||
{
|
||||
name: "update-table-set-repo-fallback",
|
||||
stmt: updateTableSetRepoFallback,
|
||||
},
|
||||
}
|
||||
|
||||
// Migrate performs the database migration. If the migration fails
|
||||
@ -637,3 +655,62 @@ UPDATE files SET
|
||||
,file_meta_failed=0
|
||||
,file_meta_skipped=0
|
||||
`
|
||||
|
||||
//
|
||||
// 019_create_table_build_config.sql
|
||||
//
|
||||
|
||||
var createTableBuildConfig = `
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
||||
`
|
||||
|
||||
//
|
||||
// 020_add_column_config_name.sql
|
||||
//
|
||||
|
||||
var alterTableAddConfigName = `
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
`
|
||||
|
||||
var updateTableSetConfigName = `
|
||||
UPDATE config SET config_name = "drone"
|
||||
`
|
||||
|
||||
//
|
||||
// 021_populate_build_config.sql
|
||||
//
|
||||
|
||||
var populateBuildConfig = `
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
||||
`
|
||||
|
||||
//
|
||||
// 022_add_task_columns.sql
|
||||
//
|
||||
|
||||
var alterTableAddTaskDependencies = `
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies BLOB
|
||||
`
|
||||
|
||||
var alterTableAddTaskRunOn = `
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on BLOB
|
||||
`
|
||||
|
||||
//
|
||||
// 023_add_repo_fallback_column.sql
|
||||
//
|
||||
|
||||
var alterTableAddRepoFallback = `
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
`
|
||||
|
||||
var updateTableSetRepoFallback = `
|
||||
UPDATE repos SET repo_fallback='false'
|
||||
`
|
||||
|
@ -0,0 +1,9 @@
|
||||
-- name: create-table-build-config
|
||||
|
||||
CREATE TABLE IF NOT EXISTS build_config (
|
||||
config_id INTEGER NOT NULL
|
||||
,build_id INTEGER NOT NULL
|
||||
,PRIMARY KEY (config_id, build_id)
|
||||
,FOREIGN KEY (config_id) REFERENCES config (config_id)
|
||||
,FOREIGN KEY (build_id) REFERENCES builds (build_id)
|
||||
);
|
@ -0,0 +1,7 @@
|
||||
-- name: alter-table-add-config-name
|
||||
|
||||
ALTER TABLE config ADD COLUMN config_name TEXT
|
||||
|
||||
-- name: update-table-set-config-name
|
||||
|
||||
UPDATE config SET config_name = "drone"
|
@ -0,0 +1,4 @@
|
||||
-- name: populate-build-config
|
||||
|
||||
INSERT INTO build_config (config_id, build_id)
|
||||
SELECT build_config_id, build_id FROM builds
|
@ -0,0 +1,6 @@
|
||||
-- name: alter-table-add-task-dependencies
|
||||
ALTER TABLE tasks ADD COLUMN task_dependencies BLOB
|
||||
|
||||
-- name: alter-table-add-task-run-on
|
||||
|
||||
ALTER TABLE tasks ADD COLUMN task_run_on BLOB
|
@ -0,0 +1,5 @@
|
||||
-- name: alter-table-add-repo-fallback
|
||||
ALTER TABLE repos ADD COLUMN repo_fallback BOOLEAN
|
||||
|
||||
-- name: update-table-set-repo-fallback
|
||||
UPDATE repos SET repo_fallback='false'
|
@ -1,12 +1,14 @@
|
||||
-- name: config-find-id
|
||||
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = ?
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = ?
|
||||
|
||||
-- name: config-find-repo-hash
|
||||
|
||||
@ -15,6 +17,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = ?
|
||||
AND config_hash = ?
|
||||
@ -23,6 +26,10 @@ WHERE config_repo_id = ?
|
||||
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = ?
|
||||
AND build_config_id = ?
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = ?
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
|
@ -4,6 +4,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
|
||||
-- name: task-delete
|
||||
|
@ -55,12 +55,14 @@ var index = map[string]string{
|
||||
|
||||
var configFindId = `
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = ?
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = ?
|
||||
`
|
||||
|
||||
var configFindRepoHash = `
|
||||
@ -69,6 +71,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = ?
|
||||
AND config_hash = ?
|
||||
@ -77,7 +80,11 @@ WHERE config_repo_id = ?
|
||||
var configFindApproved = `
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = ?
|
||||
AND build_config_id = ?
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = ?
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
`
|
||||
@ -547,6 +554,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
`
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
-- name: config-find-id
|
||||
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = $1
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = $1
|
||||
|
||||
-- name: config-find-repo-hash
|
||||
|
||||
@ -15,6 +17,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = $1
|
||||
AND config_hash = $2
|
||||
@ -23,6 +26,10 @@ WHERE config_repo_id = $1
|
||||
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = $1
|
||||
AND build_config_id = $2
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = $2
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
|
@ -4,6 +4,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
|
||||
-- name: task-delete
|
||||
|
@ -55,12 +55,14 @@ var index = map[string]string{
|
||||
|
||||
var configFindId = `
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = $1
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = $1
|
||||
`
|
||||
|
||||
var configFindRepoHash = `
|
||||
@ -69,6 +71,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = $1
|
||||
AND config_hash = $2
|
||||
@ -77,7 +80,11 @@ WHERE config_repo_id = $1
|
||||
var configFindApproved = `
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = $1
|
||||
AND build_config_id = $2
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = $2
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
`
|
||||
@ -95,7 +102,7 @@ WHERE repo_active = true
|
||||
|
||||
var countBuilds = `
|
||||
SELECT count(1)
|
||||
FROM builds;
|
||||
FROM builds
|
||||
`
|
||||
|
||||
var feedLatestBuild = `
|
||||
@ -552,6 +559,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
`
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
-- name: config-find-id
|
||||
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = ?
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = ?
|
||||
|
||||
-- name: config-find-repo-hash
|
||||
|
||||
@ -15,6 +17,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = ?
|
||||
AND config_hash = ?
|
||||
@ -23,6 +26,10 @@ WHERE config_repo_id = ?
|
||||
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = ?
|
||||
AND build_config_id = ?
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = ?
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
|
@ -4,6 +4,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
|
||||
-- name: task-delete
|
||||
|
@ -55,12 +55,14 @@ var index = map[string]string{
|
||||
|
||||
var configFindId = `
|
||||
SELECT
|
||||
config_id
|
||||
config.config_id
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_id = ?
|
||||
LEFT JOIN build_config ON config.config_id = build_config.config_id
|
||||
WHERE build_config.build_id = ?
|
||||
`
|
||||
|
||||
var configFindRepoHash = `
|
||||
@ -69,6 +71,7 @@ SELECT
|
||||
,config_repo_id
|
||||
,config_hash
|
||||
,config_data
|
||||
,config_name
|
||||
FROM config
|
||||
WHERE config_repo_id = ?
|
||||
AND config_hash = ?
|
||||
@ -77,7 +80,11 @@ WHERE config_repo_id = ?
|
||||
var configFindApproved = `
|
||||
SELECT build_id FROM builds
|
||||
WHERE build_repo_id = ?
|
||||
AND build_config_id = ?
|
||||
AND build_id in (
|
||||
SELECT build_id
|
||||
FROM build_config
|
||||
WHERE build_config.config_id = ?
|
||||
)
|
||||
AND build_status NOT IN ('blocked', 'pending')
|
||||
LIMIT 1
|
||||
`
|
||||
@ -547,6 +554,8 @@ SELECT
|
||||
task_id
|
||||
,task_data
|
||||
,task_labels
|
||||
,task_dependencies
|
||||
,task_run_on
|
||||
FROM tasks
|
||||
`
|
||||
|
||||
|
@ -111,10 +111,11 @@ type Store interface {
|
||||
PermDelete(perm *model.Perm) error
|
||||
PermFlush(user *model.User, before int64) error
|
||||
|
||||
ConfigLoad(int64) (*model.Config, error)
|
||||
ConfigFind(*model.Repo, string) (*model.Config, error)
|
||||
ConfigsForBuild(buildID int64) ([]*model.Config, error)
|
||||
ConfigFindIdentical(repoID int64, sha string) (*model.Config, error)
|
||||
ConfigFindApproved(*model.Config) (bool, error)
|
||||
ConfigCreate(*model.Config) error
|
||||
BuildConfigCreate(*model.BuildConfig) error
|
||||
|
||||
SenderFind(*model.Repo, string) (*model.Sender, error)
|
||||
SenderList(*model.Repo) ([]*model.Sender, error)
|
||||
|
47
vendor/github.com/laszlocph/drone-ui/dist/dist_gen.go
generated
vendored
47
vendor/github.com/laszlocph/drone-ui/dist/dist_gen.go
generated
vendored
File diff suppressed because one or more lines are too long
8
vendor/vendor.json
vendored
8
vendor/vendor.json
vendored
@ -425,10 +425,12 @@
|
||||
"revisionTime": "2016-05-04T02:26:26Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "i3dVVpc0/It5nJIt/LEOHNuvqQY=",
|
||||
"checksumSHA1": "R/gRUF6hXEFbDGSIOKt6VdCPwHE=",
|
||||
"path": "github.com/laszlocph/drone-ui/dist",
|
||||
"revision": "106788432c8e9f19ee7a73fd977ef15d32cca79d",
|
||||
"revisionTime": "2019-06-24T07:03:37Z"
|
||||
"revision": "1c55c6bab89440efc658a708ba7bb3424dbd5d0d",
|
||||
"revisionTime": "2019-06-25T11:41:53Z",
|
||||
"version": "fallback-config",
|
||||
"versionExact": "fallback-config"
|
||||
},
|
||||
{
|
||||
"path": "github.com/lib/pq",
|
||||
|
Loading…
Reference in New Issue
Block a user