*: initial implementation of when conditions
This commit is contained in:
parent
a4ad66ac2d
commit
6f38c48066
|
@ -16,9 +16,11 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sorintlab/agola/internal/common"
|
"github.com/sorintlab/agola/internal/common"
|
||||||
|
"github.com/sorintlab/agola/internal/services/types"
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -31,6 +33,10 @@ const (
|
||||||
maxStepNameLength = 100
|
maxStepNameLength = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regExpDelimiters = []string{"/", "#"}
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Runtimes map[string]*Runtime `yaml:"runtimes"`
|
Runtimes map[string]*Runtime `yaml:"runtimes"`
|
||||||
Tasks map[string]*Task `yaml:"tasks"`
|
Tasks map[string]*Task `yaml:"tasks"`
|
||||||
|
@ -72,11 +78,12 @@ type Pipeline struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Element struct {
|
type Element struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Task string `yaml:"task"`
|
Task string `yaml:"task"`
|
||||||
Depends []*Depend `yaml:"depends"`
|
Depends []*Depend `yaml:"depends"`
|
||||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
IgnoreFailure bool `yaml:"ignore_failure"`
|
||||||
Approval bool `yaml:"approval"`
|
Approval bool `yaml:"approval"`
|
||||||
|
When *types.When `yaml:"when"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DependCondition string
|
type DependCondition string
|
||||||
|
@ -88,7 +95,7 @@ const (
|
||||||
|
|
||||||
type Depend struct {
|
type Depend struct {
|
||||||
ElementName string `yaml:"name"`
|
ElementName string `yaml:"name"`
|
||||||
Conditions []DependCondition `yaml: "conditions"`
|
Conditions []DependCondition `yaml:"conditions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Step struct {
|
type Step struct {
|
||||||
|
@ -196,12 +203,20 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
type when struct {
|
||||||
|
Branch interface{} `yaml:"branch"`
|
||||||
|
Tag interface{} `yaml:"tag"`
|
||||||
|
Ref interface{} `yaml:"ref"`
|
||||||
|
}
|
||||||
|
|
||||||
type element struct {
|
type element struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Task string `yaml:"task"`
|
Task string `yaml:"task"`
|
||||||
Depends []interface{} `yaml:"depends"`
|
Depends []interface{} `yaml:"depends"`
|
||||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
IgnoreFailure bool `yaml:"ignore_failure"`
|
||||||
|
When *when `yaml:"when"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var te *element
|
var te *element
|
||||||
|
|
||||||
if err := unmarshal(&te); err != nil {
|
if err := unmarshal(&te); err != nil {
|
||||||
|
@ -248,9 +263,161 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
||||||
e.Depends = depends
|
e.Depends = depends
|
||||||
|
|
||||||
|
if te.When != nil {
|
||||||
|
w := &types.When{}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if te.When.Branch != nil {
|
||||||
|
w.Branch, err = parseWhenConditions(te.When.Branch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if te.When.Tag != nil {
|
||||||
|
w.Tag, err = parseWhenConditions(te.When.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if te.When.Ref != nil {
|
||||||
|
w.Ref, err = parseWhenConditions(te.When.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.When = w
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseWhenConditions(wi interface{}) (*types.WhenConditions, error) {
|
||||||
|
w := &types.WhenConditions{}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
include := []string{}
|
||||||
|
exclude := []string{}
|
||||||
|
|
||||||
|
switch c := wi.(type) {
|
||||||
|
case string:
|
||||||
|
include = []string{c}
|
||||||
|
case []interface{}:
|
||||||
|
ss, err := parseSliceString(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
include = ss
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
for k, v := range c {
|
||||||
|
ks, ok := k.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf(`expected one of "include" or "exclude", got %s`, ks)
|
||||||
|
}
|
||||||
|
switch ks {
|
||||||
|
case "include":
|
||||||
|
include, err = parseStringOrSlice(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "exclude":
|
||||||
|
exclude, err = parseStringOrSlice(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf(`expected one of "include" or "exclude", got %s`, ks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("wrong when format")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Include, err = parseWhenConditionSlice(include)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.Exclude, err = parseWhenConditionSlice(exclude)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWhenConditionSlice(conds []string) ([]types.WhenCondition, error) {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
wcs := []types.WhenCondition{}
|
||||||
|
for _, cond := range conds {
|
||||||
|
wc, err := parseWhenCondition(cond)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wcs = append(wcs, *wc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWhenCondition(s string) (*types.WhenCondition, error) {
|
||||||
|
isRegExp := false
|
||||||
|
if len(s) > 2 {
|
||||||
|
for _, d := range regExpDelimiters {
|
||||||
|
if strings.HasPrefix(s, d) && strings.HasSuffix(s, d) {
|
||||||
|
isRegExp = true
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wc := &types.WhenCondition{Match: s}
|
||||||
|
|
||||||
|
if isRegExp {
|
||||||
|
if _, err := regexp.Compile(s); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "wrong regular expression")
|
||||||
|
}
|
||||||
|
wc.Type = types.WhenConditionTypeRegExp
|
||||||
|
} else {
|
||||||
|
wc.Type = types.WhenConditionTypeSimple
|
||||||
|
}
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStringOrSlice(si interface{}) ([]string, error) {
|
||||||
|
ss := []string{}
|
||||||
|
switch c := si.(type) {
|
||||||
|
case string:
|
||||||
|
ss = []string{c}
|
||||||
|
case []interface{}:
|
||||||
|
var err error
|
||||||
|
ss, err = parseSliceString(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSliceString(si []interface{}) ([]string, error) {
|
||||||
|
ss := []string{}
|
||||||
|
for _, v := range si {
|
||||||
|
switch s := v.(type) {
|
||||||
|
case string:
|
||||||
|
ss = append(ss, s)
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("expected string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) Runtime(runtimeName string) *Runtime {
|
func (c *Config) Runtime(runtimeName string) *Runtime {
|
||||||
for n, r := range c.Runtimes {
|
for n, r := range c.Runtimes {
|
||||||
if n == runtimeName {
|
if n == runtimeName {
|
||||||
|
|
|
@ -18,8 +18,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/sorintlab/agola/internal/services/types"
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseConfig(t *testing.T) {
|
func TestParseConfig(t *testing.T) {
|
||||||
|
@ -110,10 +113,126 @@ func TestParseConfig(t *testing.T) {
|
||||||
t.Fatalf("got error: %v, want error: %v", err, tt.err)
|
t.Fatalf("got error: %v, want error: %v", err, tt.err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
} else {
|
||||||
}
|
if tt.err != nil {
|
||||||
if tt.err != nil {
|
t.Fatalf("got nil error, want error: %v", tt.err)
|
||||||
t.Fatalf("got nil error, want error: %v", tt.err)
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOutput(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in string
|
||||||
|
out *Config
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test element when conditions",
|
||||||
|
in: `
|
||||||
|
runtimes:
|
||||||
|
runtime01:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: image01
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
task01:
|
||||||
|
runtime: runtime01
|
||||||
|
environment:
|
||||||
|
ENV01: ENV01
|
||||||
|
|
||||||
|
pipelines:
|
||||||
|
pipeline01:
|
||||||
|
elements:
|
||||||
|
element01:
|
||||||
|
task: task01
|
||||||
|
when:
|
||||||
|
branch: master
|
||||||
|
tag:
|
||||||
|
- v1.x
|
||||||
|
- v2.x
|
||||||
|
ref:
|
||||||
|
include: master
|
||||||
|
exclude: [ /branch01/ , branch02 ]
|
||||||
|
`,
|
||||||
|
out: &Config{
|
||||||
|
Runtimes: map[string]*Runtime{
|
||||||
|
"runtime01": &Runtime{
|
||||||
|
Name: "runtime01",
|
||||||
|
Type: "pod",
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*Container{
|
||||||
|
&Container{
|
||||||
|
Image: "image01",
|
||||||
|
Environment: nil,
|
||||||
|
User: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tasks: map[string]*Task{
|
||||||
|
"task01": &Task{
|
||||||
|
Name: "task01",
|
||||||
|
Runtime: "runtime01",
|
||||||
|
Environment: map[string]string{
|
||||||
|
"ENV01": "ENV01",
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Shell: "",
|
||||||
|
User: "",
|
||||||
|
Steps: []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Pipelines: map[string]*Pipeline{
|
||||||
|
"pipeline01": &Pipeline{
|
||||||
|
Name: "pipeline01",
|
||||||
|
Elements: map[string]*Element{
|
||||||
|
"element01": &Element{
|
||||||
|
Name: "element01",
|
||||||
|
Task: "task01",
|
||||||
|
Depends: []*Depend{},
|
||||||
|
IgnoreFailure: false,
|
||||||
|
Approval: false,
|
||||||
|
When: &types.When{
|
||||||
|
Branch: &types.WhenConditions{
|
||||||
|
Include: []types.WhenCondition{
|
||||||
|
{Type: types.WhenConditionTypeSimple, Match: "master"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tag: &types.WhenConditions{
|
||||||
|
Include: []types.WhenCondition{
|
||||||
|
{Type: types.WhenConditionTypeSimple, Match: "v1.x"},
|
||||||
|
{Type: types.WhenConditionTypeSimple, Match: "v2.x"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ref: &types.WhenConditions{
|
||||||
|
Include: []types.WhenCondition{
|
||||||
|
{Type: types.WhenConditionTypeSimple, Match: "master"},
|
||||||
|
},
|
||||||
|
Exclude: []types.WhenCondition{
|
||||||
|
{Type: types.WhenConditionTypeRegExp, Match: "branch01"},
|
||||||
|
{Type: types.WhenConditionTypeSimple, Match: "branch02"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
out, err := ParseConfig([]byte(tt.in))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tt.out, out); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,25 +20,26 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sorintlab/agola/internal/config"
|
"github.com/sorintlab/agola/internal/config"
|
||||||
"github.com/sorintlab/agola/internal/services/runservice/types"
|
rstypes "github.com/sorintlab/agola/internal/services/runservice/types"
|
||||||
|
"github.com/sorintlab/agola/internal/services/types"
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genRuntime(c *config.Config, runtimeName string) *types.Runtime {
|
func genRuntime(c *config.Config, runtimeName string) *rstypes.Runtime {
|
||||||
ce := c.Runtime(runtimeName)
|
ce := c.Runtime(runtimeName)
|
||||||
|
|
||||||
containers := []*types.Container{}
|
containers := []*rstypes.Container{}
|
||||||
for _, cc := range ce.Containers {
|
for _, cc := range ce.Containers {
|
||||||
containers = append(containers, &types.Container{
|
containers = append(containers, &rstypes.Container{
|
||||||
Image: cc.Image,
|
Image: cc.Image,
|
||||||
Environment: cc.Environment,
|
Environment: cc.Environment,
|
||||||
User: cc.User,
|
User: cc.User,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return &types.Runtime{
|
return &rstypes.Runtime{
|
||||||
Type: types.RuntimeType(ce.Type),
|
Type: rstypes.RuntimeType(ce.Type),
|
||||||
Containers: containers,
|
Containers: containers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +90,7 @@ fi
|
||||||
return rs
|
return rs
|
||||||
|
|
||||||
case *config.RunStep:
|
case *config.RunStep:
|
||||||
rs := &types.RunStep{}
|
rs := &rstypes.RunStep{}
|
||||||
|
|
||||||
rs.Type = cs.Type
|
rs.Type = cs.Type
|
||||||
rs.Name = cs.Name
|
rs.Name = cs.Name
|
||||||
|
@ -101,14 +102,14 @@ fi
|
||||||
return rs
|
return rs
|
||||||
|
|
||||||
case *config.SaveToWorkspaceStep:
|
case *config.SaveToWorkspaceStep:
|
||||||
sws := &types.SaveToWorkspaceStep{}
|
sws := &rstypes.SaveToWorkspaceStep{}
|
||||||
|
|
||||||
sws.Type = cs.Type
|
sws.Type = cs.Type
|
||||||
sws.Name = cs.Name
|
sws.Name = cs.Name
|
||||||
|
|
||||||
sws.Contents = make([]types.SaveToWorkspaceContent, len(cs.Contents))
|
sws.Contents = make([]rstypes.SaveToWorkspaceContent, len(cs.Contents))
|
||||||
for i, csc := range cs.Contents {
|
for i, csc := range cs.Contents {
|
||||||
sc := types.SaveToWorkspaceContent{}
|
sc := rstypes.SaveToWorkspaceContent{}
|
||||||
sc.SourceDir = csc.SourceDir
|
sc.SourceDir = csc.SourceDir
|
||||||
sc.DestDir = csc.DestDir
|
sc.DestDir = csc.DestDir
|
||||||
sc.Paths = csc.Paths
|
sc.Paths = csc.Paths
|
||||||
|
@ -118,7 +119,7 @@ fi
|
||||||
return sws
|
return sws
|
||||||
|
|
||||||
case *config.RestoreWorkspaceStep:
|
case *config.RestoreWorkspaceStep:
|
||||||
rws := &types.RestoreWorkspaceStep{}
|
rws := &rstypes.RestoreWorkspaceStep{}
|
||||||
rws.Name = cs.Name
|
rws.Name = cs.Name
|
||||||
rws.Type = cs.Type
|
rws.Type = cs.Type
|
||||||
rws.DestDir = cs.DestDir
|
rws.DestDir = cs.DestDir
|
||||||
|
@ -132,32 +133,27 @@ fi
|
||||||
|
|
||||||
// GenRunConfig generates a run config from a pipeline in the config, expanding all the references to tasks
|
// GenRunConfig generates a run config from a pipeline in the config, expanding all the references to tasks
|
||||||
// this functions assumes that the config is already checked for possible errors (i.e referenced task must exits)
|
// this functions assumes that the config is already checked for possible errors (i.e referenced task must exits)
|
||||||
func GenRunConfig(c *config.Config, pipelineName string, env map[string]string) *types.RunConfig {
|
func GenRunConfig(c *config.Config, pipelineName string, env map[string]string, branch, tag, ref string) *rstypes.RunConfig {
|
||||||
cp := c.Pipeline(pipelineName)
|
cp := c.Pipeline(pipelineName)
|
||||||
|
|
||||||
rc := &types.RunConfig{
|
rc := &rstypes.RunConfig{
|
||||||
Name: cp.Name,
|
Name: cp.Name,
|
||||||
Tasks: make(map[string]*types.RunConfigTask),
|
Tasks: make(map[string]*rstypes.RunConfigTask),
|
||||||
Environment: env,
|
Environment: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cpe := range cp.Elements {
|
for _, cpe := range cp.Elements {
|
||||||
|
include := types.MatchWhen(cpe.When, branch, tag, ref)
|
||||||
|
|
||||||
// resolve referenced task
|
// resolve referenced task
|
||||||
cpt := c.Task(cpe.Task)
|
cpt := c.Task(cpe.Task)
|
||||||
|
|
||||||
//environment := map[string]string{}
|
|
||||||
//if ct.Environment != nil {
|
|
||||||
// environment = ct.Environment
|
|
||||||
//}
|
|
||||||
//mergeEnv(environment, rd.DynamicEnvironment)
|
|
||||||
//// StaticEnvironment variables ovverride every other environment variable
|
|
||||||
//mergeEnv(environment, rd.Environment)
|
|
||||||
steps := make([]interface{}, len(cpt.Steps))
|
steps := make([]interface{}, len(cpt.Steps))
|
||||||
for i, cpts := range cpt.Steps {
|
for i, cpts := range cpt.Steps {
|
||||||
steps[i] = stepFromConfigStep(cpts)
|
steps[i] = stepFromConfigStep(cpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &types.RunConfigTask{
|
t := &rstypes.RunConfigTask{
|
||||||
ID: uuid.NewV4().String(),
|
ID: uuid.NewV4().String(),
|
||||||
// use the element name from the config as the task name
|
// use the element name from the config as the task name
|
||||||
Name: cpe.Name,
|
Name: cpe.Name,
|
||||||
|
@ -168,6 +164,7 @@ func GenRunConfig(c *config.Config, pipelineName string, env map[string]string)
|
||||||
User: cpt.User,
|
User: cpt.User,
|
||||||
Steps: steps,
|
Steps: steps,
|
||||||
IgnoreFailure: cpe.IgnoreFailure,
|
IgnoreFailure: cpe.IgnoreFailure,
|
||||||
|
Skip: !include,
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.Tasks[t.ID] = t
|
rc.Tasks[t.ID] = t
|
||||||
|
@ -177,27 +174,27 @@ func GenRunConfig(c *config.Config, pipelineName string, env map[string]string)
|
||||||
for _, rct := range rc.Tasks {
|
for _, rct := range rc.Tasks {
|
||||||
cpe := cp.Elements[rct.Name]
|
cpe := cp.Elements[rct.Name]
|
||||||
|
|
||||||
depends := make([]*types.RunConfigTaskDepend, len(cpe.Depends))
|
depends := make([]*rstypes.RunConfigTaskDepend, len(cpe.Depends))
|
||||||
for id, d := range cpe.Depends {
|
for id, d := range cpe.Depends {
|
||||||
conditions := make([]types.RunConfigTaskDependCondition, len(d.Conditions))
|
conditions := make([]rstypes.RunConfigTaskDependCondition, len(d.Conditions))
|
||||||
// when no conditions are defined default to on_success
|
// when no conditions are defined default to on_success
|
||||||
if len(d.Conditions) == 0 {
|
if len(d.Conditions) == 0 {
|
||||||
conditions = append(conditions, types.RunConfigTaskDependConditionOnSuccess)
|
conditions = append(conditions, rstypes.RunConfigTaskDependConditionOnSuccess)
|
||||||
} else {
|
} else {
|
||||||
for ic, c := range d.Conditions {
|
for ic, c := range d.Conditions {
|
||||||
var condition types.RunConfigTaskDependCondition
|
var condition rstypes.RunConfigTaskDependCondition
|
||||||
switch c {
|
switch c {
|
||||||
case config.DependConditionOnSuccess:
|
case config.DependConditionOnSuccess:
|
||||||
condition = types.RunConfigTaskDependConditionOnSuccess
|
condition = rstypes.RunConfigTaskDependConditionOnSuccess
|
||||||
case config.DependConditionOnFailure:
|
case config.DependConditionOnFailure:
|
||||||
condition = types.RunConfigTaskDependConditionOnFailure
|
condition = rstypes.RunConfigTaskDependConditionOnFailure
|
||||||
}
|
}
|
||||||
conditions[ic] = condition
|
conditions[ic] = condition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drct := getRunConfigTaskByName(rc, d.ElementName)
|
drct := getRunConfigTaskByName(rc, d.ElementName)
|
||||||
depends[id] = &types.RunConfigTaskDepend{
|
depends[id] = &rstypes.RunConfigTaskDepend{
|
||||||
TaskID: drct.ID,
|
TaskID: drct.ID,
|
||||||
Conditions: conditions,
|
Conditions: conditions,
|
||||||
}
|
}
|
||||||
|
@ -209,7 +206,7 @@ func GenRunConfig(c *config.Config, pipelineName string, env map[string]string)
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRunConfigTaskByName(rc *types.RunConfig, name string) *types.RunConfigTask {
|
func getRunConfigTaskByName(rc *rstypes.RunConfig, name string) *rstypes.RunConfigTask {
|
||||||
for _, rct := range rc.Tasks {
|
for _, rct := range rc.Tasks {
|
||||||
if rct.Name == name {
|
if rct.Name == name {
|
||||||
return rct
|
return rct
|
||||||
|
@ -218,7 +215,7 @@ func getRunConfigTaskByName(rc *types.RunConfig, name string) *types.RunConfigTa
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRunConfig(rc *types.RunConfig) error {
|
func CheckRunConfig(rc *rstypes.RunConfig) error {
|
||||||
// check circular dependencies
|
// check circular dependencies
|
||||||
cerrs := &util.Errors{}
|
cerrs := &util.Errors{}
|
||||||
for _, t := range rc.Tasks {
|
for _, t := range rc.Tasks {
|
||||||
|
@ -262,7 +259,7 @@ func CheckRunConfig(rc *types.RunConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenTasksLevels(rc *types.RunConfig) error {
|
func GenTasksLevels(rc *rstypes.RunConfig) error {
|
||||||
// reset all task level
|
// reset all task level
|
||||||
for _, t := range rc.Tasks {
|
for _, t := range rc.Tasks {
|
||||||
t.Level = -1
|
t.Level = -1
|
||||||
|
@ -308,8 +305,8 @@ func GenTasksLevels(rc *types.RunConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetParents returns direct parents of task.
|
// GetParents returns direct parents of task.
|
||||||
func GetParents(rc *types.RunConfig, task *types.RunConfigTask) []*types.RunConfigTask {
|
func GetParents(rc *rstypes.RunConfig, task *rstypes.RunConfigTask) []*rstypes.RunConfigTask {
|
||||||
parents := []*types.RunConfigTask{}
|
parents := []*rstypes.RunConfigTask{}
|
||||||
for _, t := range rc.Tasks {
|
for _, t := range rc.Tasks {
|
||||||
isParent := false
|
isParent := false
|
||||||
for _, d := range task.Depends {
|
for _, d := range task.Depends {
|
||||||
|
@ -327,13 +324,13 @@ func GetParents(rc *types.RunConfig, task *types.RunConfigTask) []*types.RunConf
|
||||||
// GetAllParents returns all the parents (both direct and ancestors) of task.
|
// GetAllParents returns all the parents (both direct and ancestors) of task.
|
||||||
// In case of circular dependency it won't loop forever but will also return
|
// In case of circular dependency it won't loop forever but will also return
|
||||||
// task as parent of itself
|
// task as parent of itself
|
||||||
func GetAllParents(rc *types.RunConfig, task *types.RunConfigTask) []*types.RunConfigTask {
|
func GetAllParents(rc *rstypes.RunConfig, task *rstypes.RunConfigTask) []*rstypes.RunConfigTask {
|
||||||
pMap := map[string]*types.RunConfigTask{}
|
pMap := map[string]*rstypes.RunConfigTask{}
|
||||||
nextParents := GetParents(rc, task)
|
nextParents := GetParents(rc, task)
|
||||||
|
|
||||||
for len(nextParents) > 0 {
|
for len(nextParents) > 0 {
|
||||||
parents := nextParents
|
parents := nextParents
|
||||||
nextParents = []*types.RunConfigTask{}
|
nextParents = []*rstypes.RunConfigTask{}
|
||||||
for _, parent := range parents {
|
for _, parent := range parents {
|
||||||
if _, ok := pMap[parent.ID]; ok {
|
if _, ok := pMap[parent.ID]; ok {
|
||||||
continue
|
continue
|
||||||
|
@ -343,7 +340,7 @@ func GetAllParents(rc *types.RunConfig, task *types.RunConfigTask) []*types.RunC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parents := make([]*types.RunConfigTask, 0, len(pMap))
|
parents := make([]*rstypes.RunConfigTask, 0, len(pMap))
|
||||||
for _, v := range pMap {
|
for _, v := range pMap {
|
||||||
parents = append(parents, v)
|
parents = append(parents, v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
group = genGroup(userID, webhookData)
|
group = genGroup(userID, webhookData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.createRuns(ctx, data, group, annotations, env); err != nil {
|
if err := h.createRuns(ctx, data, group, annotations, env, webhookData); err != nil {
|
||||||
return http.StatusInternalServerError, "", errors.Wrapf(err, "failed to create run")
|
return http.StatusInternalServerError, "", errors.Wrapf(err, "failed to create run")
|
||||||
}
|
}
|
||||||
//if err := gitSource.CreateStatus(webhookData.Repo.Owner, webhookData.Repo.Name, webhookData.CommitSHA, gitsource.CommitStatusPending, "localhost:8080", "build %s", "agola"); err != nil {
|
//if err := gitSource.CreateStatus(webhookData.Repo.Owner, webhookData.Repo.Name, webhookData.CommitSHA, gitsource.CommitStatusPending, "localhost:8080", "build %s", "agola"); err != nil {
|
||||||
|
@ -261,7 +261,7 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) (int, string, error) {
|
||||||
return 0, "", nil
|
return 0, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *webhooksHandler) createRuns(ctx context.Context, configData []byte, group string, annotations, env map[string]string) error {
|
func (h *webhooksHandler) createRuns(ctx context.Context, configData []byte, group string, annotations, env map[string]string, webhookData *types.WebhookData) error {
|
||||||
config, err := config.ParseConfig([]byte(configData))
|
config, err := config.ParseConfig([]byte(configData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -270,7 +270,7 @@ func (h *webhooksHandler) createRuns(ctx context.Context, configData []byte, gro
|
||||||
|
|
||||||
//h.log.Debugf("pipeline: %s", createRunOpts.PipelineName)
|
//h.log.Debugf("pipeline: %s", createRunOpts.PipelineName)
|
||||||
for _, pipeline := range config.Pipelines {
|
for _, pipeline := range config.Pipelines {
|
||||||
rc := runconfig.GenRunConfig(config, pipeline.Name, env)
|
rc := runconfig.GenRunConfig(config, pipeline.Name, env, webhookData.Branch, webhookData.Tag, webhookData.Ref)
|
||||||
|
|
||||||
h.log.Debugf("rc: %s", util.Dump(rc))
|
h.log.Debugf("rc: %s", util.Dump(rc))
|
||||||
h.log.Infof("group: %s", group)
|
h.log.Infof("group: %s", group)
|
||||||
|
|
|
@ -287,9 +287,13 @@ func (s *CommandHandler) genRunTask(ctx context.Context, rct *types.RunConfigTas
|
||||||
rt := &types.RunTask{
|
rt := &types.RunTask{
|
||||||
ID: rct.ID,
|
ID: rct.ID,
|
||||||
Status: types.RunTaskStatusNotStarted,
|
Status: types.RunTaskStatusNotStarted,
|
||||||
|
Skip: rct.Skip,
|
||||||
Steps: make([]*types.RunTaskStep, len(rct.Steps)),
|
Steps: make([]*types.RunTaskStep, len(rct.Steps)),
|
||||||
WorkspaceArchives: []int{},
|
WorkspaceArchives: []int{},
|
||||||
}
|
}
|
||||||
|
if rt.Skip {
|
||||||
|
rt.Status = types.RunTaskStatusSkipped
|
||||||
|
}
|
||||||
for i := range rt.Steps {
|
for i := range rt.Steps {
|
||||||
s := &types.RunTaskStep{
|
s := &types.RunTaskStep{
|
||||||
Phase: types.ExecutorTaskPhaseNotStarted,
|
Phase: types.ExecutorTaskPhaseNotStarted,
|
||||||
|
|
|
@ -95,6 +95,9 @@ func (s *Scheduler) advanceRunTasks(ctx context.Context, r *types.Run) error {
|
||||||
// get tasks that can be executed
|
// get tasks that can be executed
|
||||||
for _, rt := range r.RunTasks {
|
for _, rt := range r.RunTasks {
|
||||||
log.Debugf("rt: %s", util.Dump(rt))
|
log.Debugf("rt: %s", util.Dump(rt))
|
||||||
|
if rt.Skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if rt.Status != types.RunTaskStatusNotStarted {
|
if rt.Status != types.RunTaskStatusNotStarted {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -105,6 +108,9 @@ func (s *Scheduler) advanceRunTasks(ctx context.Context, r *types.Run) error {
|
||||||
for _, p := range parents {
|
for _, p := range parents {
|
||||||
rp := r.RunTasks[p.ID]
|
rp := r.RunTasks[p.ID]
|
||||||
canRun = rp.Status.IsFinished() && rp.ArchivesFetchFinished()
|
canRun = rp.Status.IsFinished() && rp.ArchivesFetchFinished()
|
||||||
|
if rp.Status == types.RunTaskStatusSkipped {
|
||||||
|
rt.Status = types.RunTaskStatusSkipped
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if canRun {
|
if canRun {
|
||||||
|
@ -672,7 +678,9 @@ func (s *Scheduler) fetchLog(ctx context.Context, rt *types.RunTask, stepnum int
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if et == nil {
|
if et == nil {
|
||||||
log.Errorf("executor task with id %q doesn't exist. This shouldn't happen. Skipping fetching", rt.ID)
|
if !rt.Skip {
|
||||||
|
log.Errorf("executor task with id %q doesn't exist. This shouldn't happen. Skipping fetching", rt.ID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
executor, err := store.GetExecutor(ctx, s.e, et.Status.ExecutorID)
|
executor, err := store.GetExecutor(ctx, s.e, et.Status.ExecutorID)
|
||||||
|
|
|
@ -134,6 +134,7 @@ type RunTaskStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RunTaskStatusNotStarted RunTaskStatus = "notstarted"
|
RunTaskStatusNotStarted RunTaskStatus = "notstarted"
|
||||||
|
RunTaskStatusSkipped RunTaskStatus = "skipped"
|
||||||
RunTaskStatusCancelled RunTaskStatus = "cancelled"
|
RunTaskStatusCancelled RunTaskStatus = "cancelled"
|
||||||
RunTaskStatusRunning RunTaskStatus = "running"
|
RunTaskStatusRunning RunTaskStatus = "running"
|
||||||
RunTaskStatusStopped RunTaskStatus = "stopped"
|
RunTaskStatusStopped RunTaskStatus = "stopped"
|
||||||
|
@ -142,7 +143,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s RunTaskStatus) IsFinished() bool {
|
func (s RunTaskStatus) IsFinished() bool {
|
||||||
return s == RunTaskStatusCancelled || s == RunTaskStatusStopped || s == RunTaskStatusSuccess || s == RunTaskStatusFailed
|
return s == RunTaskStatusCancelled || s == RunTaskStatusSkipped || s == RunTaskStatusStopped || s == RunTaskStatusSuccess || s == RunTaskStatusFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunTaskFetchPhase string
|
type RunTaskFetchPhase string
|
||||||
|
@ -162,6 +163,8 @@ type RunTask struct {
|
||||||
// there're no executor tasks scheduled
|
// there're no executor tasks scheduled
|
||||||
Status RunTaskStatus `json:"status,omitempty"`
|
Status RunTaskStatus `json:"status,omitempty"`
|
||||||
|
|
||||||
|
Skip bool `json:"skip,omitempty"`
|
||||||
|
|
||||||
WaitingApproval bool `json:"waiting_approval,omitempty"`
|
WaitingApproval bool `json:"waiting_approval,omitempty"`
|
||||||
Approved bool `json:"approved,omitempty"`
|
Approved bool `json:"approved,omitempty"`
|
||||||
// ApprovalAnnotations stores data that the user can set on the approval. Useful
|
// ApprovalAnnotations stores data that the user can set on the approval. Useful
|
||||||
|
@ -252,6 +255,7 @@ type RunConfigTask struct {
|
||||||
Steps []interface{} `json:"steps,omitempty"`
|
Steps []interface{} `json:"steps,omitempty"`
|
||||||
IgnoreFailure bool `json:"ignore_failure,omitempty"`
|
IgnoreFailure bool `json:"ignore_failure,omitempty"`
|
||||||
NeedsApproval bool `json:"needs_approval,omitempty"`
|
NeedsApproval bool `json:"needs_approval,omitempty"`
|
||||||
|
Skip bool `json:"skip,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunConfigTaskDependCondition string
|
type RunConfigTaskDependCondition string
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,3 +137,82 @@ type Project struct {
|
||||||
|
|
||||||
SkipSSHHostKeyCheck bool `json:"skip_ssh_host_key_check,omitempty"`
|
SkipSSHHostKeyCheck bool `json:"skip_ssh_host_key_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type When struct {
|
||||||
|
Branch *WhenConditions `json:"branch,omitempty"`
|
||||||
|
Tag *WhenConditions `json:"tag,omitempty"`
|
||||||
|
Ref *WhenConditions `json:"ref,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WhenConditions struct {
|
||||||
|
Include []WhenCondition `json:"include,omitempty"`
|
||||||
|
Exclude []WhenCondition `json:"exclude,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WhenConditionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WhenConditionTypeSimple WhenConditionType = "simple"
|
||||||
|
WhenConditionTypeRegExp WhenConditionType = "regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WhenCondition struct {
|
||||||
|
Type WhenConditionType
|
||||||
|
Match string
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchWhen(when *When, branch, tag, ref string) bool {
|
||||||
|
include := true
|
||||||
|
if when != nil {
|
||||||
|
include = false
|
||||||
|
if when.Branch != nil {
|
||||||
|
// first check includes and override with excludes
|
||||||
|
if matchCondition(when.Branch.Include, branch) {
|
||||||
|
include = true
|
||||||
|
}
|
||||||
|
if matchCondition(when.Branch.Exclude, branch) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if when.Tag != nil {
|
||||||
|
// first check includes and override with excludes
|
||||||
|
if matchCondition(when.Tag.Include, tag) {
|
||||||
|
include = true
|
||||||
|
}
|
||||||
|
if matchCondition(when.Tag.Exclude, tag) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if when.Ref != nil {
|
||||||
|
// first check includes and override with excludes
|
||||||
|
if matchCondition(when.Ref.Include, ref) {
|
||||||
|
include = true
|
||||||
|
}
|
||||||
|
if matchCondition(when.Ref.Exclude, ref) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return include
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchCondition(conds []WhenCondition, s string) bool {
|
||||||
|
for _, cond := range conds {
|
||||||
|
switch cond.Type {
|
||||||
|
case WhenConditionTypeSimple:
|
||||||
|
if cond.Match == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case WhenConditionTypeRegExp:
|
||||||
|
re, err := regexp.Compile(cond.Match)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if re.MatchString(s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue