From a433591afa2280a9fbcc2172dd95f7989e5b82f5 Mon Sep 17 00:00:00 2001 From: Laszlo Fogas Date: Mon, 17 Jun 2019 09:06:36 +0200 Subject: [PATCH] Introducing runs_on to run pipelines on failure --- .../pipeline/pipeline/frontend/yaml/config.go | 1 + .../pipeline/frontend/yaml/config_test.go | 5 ++ cncd/queue/fifo_test.go | 74 ++++++++++++++++++- cncd/queue/queue.go | 35 +++++++-- server/hook.go | 1 + server/procBuilder.go | 2 + server/procBuilder_test.go | 34 +++++++++ 7 files changed, 143 insertions(+), 9 deletions(-) diff --git a/cncd/pipeline/pipeline/frontend/yaml/config.go b/cncd/pipeline/pipeline/frontend/yaml/config.go index 281e99908..afa8e0512 100644 --- a/cncd/pipeline/pipeline/frontend/yaml/config.go +++ b/cncd/pipeline/pipeline/frontend/yaml/config.go @@ -23,6 +23,7 @@ type ( Volumes Volumes Labels libcompose.SliceorMap DependsOn []string `yaml:"depends_on,omitempty"` + RunsOn []string `yaml:"runs_on,omitempty"` } // Workspace defines a pipeline workspace. diff --git a/cncd/pipeline/pipeline/frontend/yaml/config_test.go b/cncd/pipeline/pipeline/frontend/yaml/config_test.go index ce9cadfc6..afe9dcb3b 100644 --- a/cncd/pipeline/pipeline/frontend/yaml/config_test.go +++ b/cncd/pipeline/pipeline/frontend/yaml/config_test.go @@ -40,6 +40,8 @@ func TestParse(t *testing.T) { 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") }) // Check to make sure variable expansion works in yaml.MapSlice // g.It("Should unmarshal variables", func() { @@ -99,6 +101,9 @@ labels: depends_on: - lint - test +runs_on: + - success + - failure ` var sampleVarYaml = ` diff --git a/cncd/queue/fifo_test.go b/cncd/queue/fifo_test.go index 6f3e414a5..5aa8327ff 100644 --- a/cncd/queue/fifo_test.go +++ b/cncd/queue/fifo_test.go @@ -127,7 +127,7 @@ func TestFifoDependencies(t *testing.T) { task2 := &Task{ ID: "2", Dependencies: []string{"1"}, - DepStatus: make(map[string]bool), + DepStatus: make(map[string]bool), } q := New().(*fifo) @@ -157,14 +157,14 @@ func TestFifoErrors(t *testing.T) { task2 := &Task{ ID: "2", Dependencies: []string{"1"}, - DepStatus: make(map[string]bool), + DepStatus: make(map[string]bool), } task3 := &Task{ ID: "3", Dependencies: []string{"1"}, - DepStatus: make(map[string]bool), - RunOn: []string{"success", "failure"}, + DepStatus: make(map[string]bool), + RunOn: []string{"success", "failure"}, } q := New().(*fifo) @@ -202,3 +202,69 @@ func TestFifoErrors(t *testing.T) { 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 + } +} diff --git a/cncd/queue/queue.go b/cncd/queue/queue.go index cb0ae7530..2b70d3285 100644 --- a/cncd/queue/queue.go +++ b/cncd/queue/queue.go @@ -36,17 +36,29 @@ type Task struct { // ShouldRun tells if a task should be run or skipped, based on dependencies func (t *Task) ShouldRun() bool { - if runsOnFailure(t.RunOn) { + if runsOnFailure(t.RunOn) && runsOnSuccess(t.RunOn) { return true } - for _, success := range t.DepStatus { - if !success { - return false + if !runsOnFailure(t.RunOn) && runsOnSuccess(t.RunOn) { + for _, success := range t.DepStatus { + if !success { + return false + } } + return true } - 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 { @@ -58,6 +70,19 @@ func runsOnFailure(runsOn []string) bool { 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. type InfoT struct { Pending []*Task `json:"pending"` diff --git a/server/hook.go b/server/hook.go index 2f0de4785..8efa17218 100644 --- a/server/hook.go +++ b/server/hook.go @@ -321,6 +321,7 @@ 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{ diff --git a/server/procBuilder.go b/server/procBuilder.go index 391bf9f7b..5e0656d76 100644 --- a/server/procBuilder.go +++ b/server/procBuilder.go @@ -50,6 +50,7 @@ type buildItem struct { Platform string Labels map[string]string DependsOn []string + RunsOn []string Config *backend.Config } @@ -111,6 +112,7 @@ func (b *procBuilder) Build() ([]*buildItem, error) { Config: ir, Labels: parsed.Labels, DependsOn: parsed.DependsOn, + RunsOn: parsed.RunsOn, Platform: metadata.Sys.Arch, } if item.Labels == nil { diff --git a/server/procBuilder_test.go b/server/procBuilder_test.go index 177a960c5..1a885b6b6 100644 --- a/server/procBuilder_test.go +++ b/server/procBuilder_test.go @@ -124,3 +124,37 @@ depends_on: 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") + } +}