runservice: rework config format
The current config format was thought for future extensions for reusing runtimes and job definitions adding some parameters. After a lot of thoughts this looks like a complex approach: the final result will be a sort of templating without a lot of powers. Other approach like external templating should be an alternative but I really don't think templating yaml is the way to go. A much better approach will to just use jsonnet when we need to create matrix runs and a lot of other use cases. So just make the config a simple yaml/json. User can generate their config using any preferred tool and in future we'll leverage jsonnet automated parsing and provide a lot of jsonnet based examples for most use cases. Main changes: * Runs are now an array and not a map. The run name is in the Name field * Tasks are now an array and not a map. The task name is in the Name field * Use https://github.com/ghodss/yaml so we'll use json struct tags and unmarshall functions
This commit is contained in:
parent
6066221136
commit
03451535c8
|
@ -1,74 +1,60 @@
|
||||||
runtimes:
|
|
||||||
go1.12:
|
|
||||||
type: pod
|
|
||||||
arch: amd64
|
|
||||||
containers:
|
|
||||||
- image: golang:1.12-stretch
|
|
||||||
environment:
|
|
||||||
ENV01: envvalue01
|
|
||||||
debian:
|
|
||||||
type: pod
|
|
||||||
arch: amd64
|
|
||||||
containers:
|
|
||||||
- image: debian:stretch
|
|
||||||
dind:
|
|
||||||
type: pod
|
|
||||||
arch: amd64
|
|
||||||
containers:
|
|
||||||
- image: docker:stable-dind
|
|
||||||
privileged: true
|
|
||||||
entrypoint: dockerd
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build-go1.12:
|
|
||||||
runtime: go1.12
|
|
||||||
working_dir: /go/src/github.com/sorintlab/agola
|
|
||||||
environment:
|
|
||||||
GO111MODULE: "on"
|
|
||||||
VAR01:
|
|
||||||
from_variable: var01
|
|
||||||
steps:
|
|
||||||
- run: env
|
|
||||||
- clone:
|
|
||||||
- run: SKIP_DOCKER_TESTS=1 go test -v -count 1 ./...
|
|
||||||
|
|
||||||
build-docker-tests-go-1.12:
|
|
||||||
runtime: go1.12
|
|
||||||
working_dir: /go/src/github.com/sorintlab/agola
|
|
||||||
environment:
|
|
||||||
GO111MODULE: "on"
|
|
||||||
steps:
|
|
||||||
- run: env
|
|
||||||
- clone:
|
|
||||||
- run:
|
|
||||||
name: build docker tests binary
|
|
||||||
command: CGO_ENABLED=0 go test -c ./internal/services/runservice/executor/driver -o ./bin/docker-tests
|
|
||||||
environment:
|
|
||||||
ENV01: envvalue01
|
|
||||||
- save_to_workspace:
|
|
||||||
contents:
|
|
||||||
- source_dir: ./bin
|
|
||||||
dest_dir: /bin/
|
|
||||||
paths:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
test-docker-driver:
|
|
||||||
runtime: dind
|
|
||||||
steps:
|
|
||||||
- run: env
|
|
||||||
- restore_workspace:
|
|
||||||
dest_dir: .
|
|
||||||
- run: sleep 5
|
|
||||||
- run: ./bin/docker-tests -test.parallel 1 -test.v
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
agola build/test:
|
- name: agola build/test
|
||||||
elements:
|
tasks:
|
||||||
build go1.12:
|
- name: build go1.12
|
||||||
task: build-go1.12
|
runtime:
|
||||||
build docker tests go1.12:
|
type: pod
|
||||||
task: build-docker-tests-go-1.12
|
arch: amd64
|
||||||
test docker driver:
|
containers:
|
||||||
task: test-docker-driver
|
- image: golang:1.12-stretch
|
||||||
|
environment:
|
||||||
|
ENV01: envvalue01
|
||||||
|
working_dir: /go/src/github.com/sorintlab/agola
|
||||||
|
environment:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
VAR01:
|
||||||
|
from_variable: var01
|
||||||
|
steps:
|
||||||
|
- run: env
|
||||||
|
- clone:
|
||||||
|
- run: SKIP_DOCKER_TESTS=1 go test -v -count 1 ./...
|
||||||
|
- name: build docker tests go1.12
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
arch: amd64
|
||||||
|
containers:
|
||||||
|
- image: golang:1.12-stretch
|
||||||
|
environment:
|
||||||
|
ENV01: envvalue01
|
||||||
|
working_dir: /go/src/github.com/sorintlab/agola
|
||||||
|
environment:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
steps:
|
||||||
|
- run: env
|
||||||
|
- clone:
|
||||||
|
- run:
|
||||||
|
name: build docker tests binary
|
||||||
|
command: CGO_ENABLED=0 go test -c ./internal/services/runservice/executor/driver -o ./bin/docker-tests
|
||||||
|
environment:
|
||||||
|
ENV01: envvalue01
|
||||||
|
- save_to_workspace:
|
||||||
|
contents:
|
||||||
|
- source_dir: ./bin
|
||||||
|
dest_dir: /bin/
|
||||||
|
paths:
|
||||||
|
- "*"
|
||||||
|
- name: test docker driver
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
arch: amd64
|
||||||
|
containers:
|
||||||
|
- image: docker:stable-dind
|
||||||
|
privileged: true
|
||||||
|
entrypoint: dockerd
|
||||||
|
steps:
|
||||||
|
- run: env
|
||||||
|
- restore_workspace:
|
||||||
|
dest_dir: .
|
||||||
|
- run: ./bin/docker-tests -test.parallel 1 -test.v
|
||||||
depends:
|
depends:
|
||||||
- build docker tests go1.12
|
- build docker tests go1.12
|
1
go.mod
1
go.mod
|
@ -14,6 +14,7 @@ require (
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
github.com/docker/go-units v0.3.3 // indirect
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||||
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/go-bindata/go-bindata v1.0.0
|
github.com/go-bindata/go-bindata v1.0.0
|
||||||
github.com/go-ini/ini v1.42.0 // indirect
|
github.com/go-ini/ini v1.42.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -23,8 +24,8 @@ import (
|
||||||
"github.com/sorintlab/agola/internal/services/types"
|
"github.com/sorintlab/agola/internal/services/types"
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,19 +39,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Runtimes map[string]*Runtime `yaml:"runtimes"`
|
Runs []*Run `json:"runs"`
|
||||||
Tasks map[string]*Task `yaml:"tasks"`
|
|
||||||
Runs map[string]*Run `yaml:"runs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Runtime string `yaml:"runtime"`
|
|
||||||
Environment map[string]Value `yaml:"environment,omitempty"`
|
|
||||||
WorkingDir string `yaml:"working_dir"`
|
|
||||||
Shell string `yaml:"shell"`
|
|
||||||
User string `yaml:"user"`
|
|
||||||
Steps []interface{} `yaml:"steps"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuntimeType string
|
type RuntimeType string
|
||||||
|
@ -66,42 +55,46 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegistryAuth struct {
|
type RegistryAuth struct {
|
||||||
Type RegistryAuthType `yaml:"type"`
|
Type RegistryAuthType `json:"type"`
|
||||||
|
|
||||||
// default auth
|
// default auth
|
||||||
Username Value `yaml:"username"`
|
Username Value `json:"username"`
|
||||||
Password Value `yaml:"password"`
|
Password Value `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
Name string `yaml:"name"`
|
Type RuntimeType `json:"type,omitempty"`
|
||||||
Type RuntimeType `yaml:"type,omitempty"`
|
Auth *RegistryAuth `json:"auth"`
|
||||||
Auth *RegistryAuth `yaml:"auth"`
|
Arch common.Arch `json:"arch,omitempty"`
|
||||||
Arch common.Arch `yaml:"arch,omitempty"`
|
Containers []*Container `json:"containers,omitempty"`
|
||||||
Containers []*Container `yaml:"containers,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Image string `yaml:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
Auth *RegistryAuth `yaml:"auth"`
|
Auth *RegistryAuth `json:"auth"`
|
||||||
Environment map[string]Value `yaml:"environment,omitempty"`
|
Environment map[string]Value `json:"environment,omitempty"`
|
||||||
User string `yaml:"user"`
|
User string `json:"user"`
|
||||||
Privileged bool `yaml:"privileged"`
|
Privileged bool `json:"privileged"`
|
||||||
Entrypoint string `yaml:"entrypoint"`
|
Entrypoint string `json:"entrypoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Run struct {
|
type Run struct {
|
||||||
Name string `yaml:"name"`
|
Name string `json:"name"`
|
||||||
Elements map[string]*Element `yaml:"elements"`
|
Tasks []*Task `json:"tasks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Element struct {
|
type Task struct {
|
||||||
Name string `yaml:"name"`
|
Name string `json:"name"`
|
||||||
Task string `yaml:"task"`
|
Runtime *Runtime `json:"runtime"`
|
||||||
Depends []*Depend `yaml:"depends"`
|
Environment map[string]Value `json:"environment,omitempty"`
|
||||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
WorkingDir string `json:"working_dir"`
|
||||||
Approval bool `yaml:"approval"`
|
Shell string `json:"shell"`
|
||||||
When *types.When `yaml:"when"`
|
User string `json:"user"`
|
||||||
|
Steps []interface{} `json:"steps"`
|
||||||
|
Depends []*Depend `json:"depends"`
|
||||||
|
IgnoreFailure bool `json:"ignore_failure"`
|
||||||
|
Approval bool `json:"approval"`
|
||||||
|
When *types.When `json:"when"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DependCondition string
|
type DependCondition string
|
||||||
|
@ -113,26 +106,26 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Depend struct {
|
type Depend struct {
|
||||||
ElementName string `yaml:"name"`
|
TaskName string `json:"task"`
|
||||||
Conditions []DependCondition `yaml:"conditions"`
|
Conditions []DependCondition `json:"conditions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Step struct {
|
type Step struct {
|
||||||
Type string `yaml:"type"`
|
Type string `json:"type"`
|
||||||
Name string `yaml:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CloneStep struct {
|
type CloneStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunStep struct {
|
type RunStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
Command string `yaml:"command"`
|
Command string `json:"command"`
|
||||||
Environment map[string]Value `yaml:"environment,omitempty"`
|
Environment map[string]Value `json:"environment,omitempty"`
|
||||||
WorkingDir string `yaml:"working_dir"`
|
WorkingDir string `json:"working_dir"`
|
||||||
Shell string `yaml:"shell"`
|
Shell string `json:"shell"`
|
||||||
User string `yaml:"user"`
|
User string `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValueType int
|
type ValueType int
|
||||||
|
@ -148,63 +141,78 @@ type Value struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveContent struct {
|
type SaveContent struct {
|
||||||
SourceDir string `yaml:"source_dir"`
|
SourceDir string `json:"source_dir"`
|
||||||
DestDir string `yaml:"dest_dir"`
|
DestDir string `json:"dest_dir"`
|
||||||
Paths []string `yaml:"paths"`
|
Paths []string `json:"paths"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveToWorkspaceStep struct {
|
type SaveToWorkspaceStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
Contents []*SaveContent `yaml:"contents"`
|
Contents []*SaveContent `json:"contents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RestoreWorkspaceStep struct {
|
type RestoreWorkspaceStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
DestDir string `yaml:"dest_dir"`
|
DestDir string `json:"dest_dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveCacheStep struct {
|
type SaveCacheStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
Key string `yaml:"key"`
|
Key string `json:"key"`
|
||||||
Contents []*SaveContent `yaml:"contents"`
|
Contents []*SaveContent `json:"contents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RestoreCacheStep struct {
|
type RestoreCacheStep struct {
|
||||||
Step `yaml:",inline"`
|
Step `json:",inline"`
|
||||||
Keys []string `yaml:"keys"`
|
Keys []string `json:"keys"`
|
||||||
DestDir string `yaml:"dest_dir"`
|
DestDir string `json:"dest_dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (t *Task) UnmarshalJSON(b []byte) error {
|
||||||
type task struct {
|
type when struct {
|
||||||
Name string `yaml:"name"`
|
Branch interface{} `json:"branch"`
|
||||||
Runtime string `yaml:"runtime"`
|
Tag interface{} `json:"tag"`
|
||||||
Environment map[string]Value `yaml:"environment,omitempty"`
|
Ref interface{} `json:"ref"`
|
||||||
WorkingDir string `yaml:"working_dir"`
|
|
||||||
Shell string `yaml:"shell"`
|
|
||||||
User string `yaml:"user"`
|
|
||||||
Steps []map[string]interface{} `yaml:"steps"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tt *task
|
type runtask struct {
|
||||||
if err := unmarshal(&tt); err != nil {
|
Name string `json:"name"`
|
||||||
|
Runtime *Runtime `json:"runtime"`
|
||||||
|
Environment map[string]Value `json:"environment,omitempty"`
|
||||||
|
WorkingDir string `json:"working_dir"`
|
||||||
|
Shell string `json:"shell"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Steps []map[string]interface{} `json:"steps"`
|
||||||
|
Depends []interface{} `json:"depends"`
|
||||||
|
IgnoreFailure bool `json:"ignore_failure"`
|
||||||
|
Approval bool `json:"approval"`
|
||||||
|
When *when `json:"when"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var tr *runtask
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &tr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Name = tt.Name
|
t.Name = tr.Name
|
||||||
t.Runtime = tt.Runtime
|
t.Runtime = tr.Runtime
|
||||||
t.Environment = tt.Environment
|
t.Environment = tr.Environment
|
||||||
t.WorkingDir = tt.WorkingDir
|
t.WorkingDir = tr.WorkingDir
|
||||||
t.Shell = tt.Shell
|
t.Shell = tr.Shell
|
||||||
t.User = tt.User
|
t.User = tr.User
|
||||||
|
t.IgnoreFailure = tr.IgnoreFailure
|
||||||
|
t.Approval = tr.Approval
|
||||||
|
|
||||||
steps := make([]interface{}, len(tt.Steps))
|
steps := make([]interface{}, len(tr.Steps))
|
||||||
for i, stepEntry := range tt.Steps {
|
for i, stepEntry := range tr.Steps {
|
||||||
if len(stepEntry) > 1 {
|
if _, ok := stepEntry["type"]; ok {
|
||||||
return errors.Errorf("wrong steps description at index %d: more than one step name per list entry", i)
|
// handle default step definition using format { type: "steptype", other steps fields }
|
||||||
}
|
stepType, ok := stepEntry["type"].(string)
|
||||||
for stepType, stepSpec := range stepEntry {
|
if !ok {
|
||||||
o, err := yaml.Marshal(stepSpec)
|
return errors.Errorf("step type at index %d must be a string", i)
|
||||||
|
}
|
||||||
|
o, err := json.Marshal(stepEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -216,20 +224,15 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
||||||
case "run":
|
case "run":
|
||||||
var s RunStep
|
var s RunStep
|
||||||
switch stepSpec.(type) {
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
case string:
|
return err
|
||||||
s.Command = stepSpec.(string)
|
|
||||||
default:
|
|
||||||
if err := yaml.Unmarshal(o, &s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.Type = stepType
|
s.Type = stepType
|
||||||
steps[i] = &s
|
steps[i] = &s
|
||||||
|
|
||||||
case "save_to_workspace":
|
case "save_to_workspace":
|
||||||
var s SaveToWorkspaceStep
|
var s SaveToWorkspaceStep
|
||||||
if err := yaml.Unmarshal(o, &s); err != nil {
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Type = stepType
|
s.Type = stepType
|
||||||
|
@ -237,7 +240,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
||||||
case "restore_workspace":
|
case "restore_workspace":
|
||||||
var s RestoreWorkspaceStep
|
var s RestoreWorkspaceStep
|
||||||
if err := yaml.Unmarshal(o, &s); err != nil {
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Type = stepType
|
s.Type = stepType
|
||||||
|
@ -245,7 +248,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
||||||
case "save_cache":
|
case "save_cache":
|
||||||
var s SaveCacheStep
|
var s SaveCacheStep
|
||||||
if err := yaml.Unmarshal(o, &s); err != nil {
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Type = stepType
|
s.Type = stepType
|
||||||
|
@ -253,7 +256,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
||||||
case "restore_cache":
|
case "restore_cache":
|
||||||
var s RestoreCacheStep
|
var s RestoreCacheStep
|
||||||
if err := yaml.Unmarshal(o, &s); err != nil {
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Type = stepType
|
s.Type = stepType
|
||||||
|
@ -261,119 +264,181 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unknown step type: %s", stepType)
|
return errors.Errorf("unknown step type: %s", stepType)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// handle simpler (for yaml not for json) steps definition using format "steptype": { stepSpecification }
|
||||||
|
if len(stepEntry) > 1 {
|
||||||
|
return errors.Errorf("wrong steps description at index %d: more than one step name per list entry", i)
|
||||||
|
}
|
||||||
|
for stepType, stepSpec := range stepEntry {
|
||||||
|
o, err := json.Marshal(stepSpec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch stepType {
|
||||||
|
case "clone":
|
||||||
|
var s CloneStep
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
|
||||||
|
case "run":
|
||||||
|
var s RunStep
|
||||||
|
switch stepSpec.(type) {
|
||||||
|
case string:
|
||||||
|
s.Command = stepSpec.(string)
|
||||||
|
default:
|
||||||
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
|
||||||
|
case "save_to_workspace":
|
||||||
|
var s SaveToWorkspaceStep
|
||||||
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
|
||||||
|
case "restore_workspace":
|
||||||
|
var s RestoreWorkspaceStep
|
||||||
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
|
||||||
|
case "save_cache":
|
||||||
|
var s SaveCacheStep
|
||||||
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
|
||||||
|
case "restore_cache":
|
||||||
|
var s RestoreCacheStep
|
||||||
|
if err := json.Unmarshal(o, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Type = stepType
|
||||||
|
steps[i] = &s
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unknown step type: %s", stepType)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Steps = steps
|
t.Steps = steps
|
||||||
|
|
||||||
return nil
|
depends := make([]*Depend, len(tr.Depends))
|
||||||
}
|
for i, dependEntry := range tr.Depends {
|
||||||
|
|
||||||
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 {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Task string `yaml:"task"`
|
|
||||||
Depends []interface{} `yaml:"depends"`
|
|
||||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
|
||||||
Approval bool `yaml:"approval"`
|
|
||||||
When *when `yaml:"when"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var te *element
|
|
||||||
|
|
||||||
if err := unmarshal(&te); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Name = te.Name
|
|
||||||
e.Task = te.Task
|
|
||||||
e.IgnoreFailure = te.IgnoreFailure
|
|
||||||
e.Approval = te.Approval
|
|
||||||
|
|
||||||
depends := make([]*Depend, len(te.Depends))
|
|
||||||
for i, dependEntry := range te.Depends {
|
|
||||||
var depend *Depend
|
var depend *Depend
|
||||||
switch dependEntry.(type) {
|
isSimpler := false
|
||||||
|
switch de := dependEntry.(type) {
|
||||||
|
// handle simpler (for yaml) depends definition using format "taskname":
|
||||||
case string:
|
case string:
|
||||||
depend = &Depend{
|
depend = &Depend{
|
||||||
ElementName: dependEntry.(string),
|
TaskName: dependEntry.(string),
|
||||||
}
|
}
|
||||||
case map[interface{}]interface{}:
|
case map[string]interface{}:
|
||||||
type deplist map[string][]DependCondition
|
if len(de) == 1 {
|
||||||
var dl deplist
|
for _, v := range de {
|
||||||
o, err := yaml.Marshal(dependEntry)
|
switch v.(type) {
|
||||||
if err != nil {
|
case []interface{}:
|
||||||
return err
|
isSimpler = true
|
||||||
|
case string:
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported depend entry format")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal(o, &dl); err != nil {
|
if !isSimpler {
|
||||||
return err
|
// handle default depends definition using format "task": "taskname", conditions: [ list of conditions ]
|
||||||
}
|
o, err := json.Marshal(dependEntry)
|
||||||
if len(dl) != 1 {
|
if err != nil {
|
||||||
return errors.Errorf("unsupported depend format. Must be a string or a list")
|
return err
|
||||||
}
|
}
|
||||||
for k, v := range dl {
|
if err := json.Unmarshal(o, &depend); err != nil {
|
||||||
depend = &Depend{
|
return err
|
||||||
ElementName: k,
|
}
|
||||||
Conditions: v,
|
} else {
|
||||||
|
// handle simpler (for yaml) depends definition using format "taskname": [ list of conditions ]
|
||||||
|
if len(de) != 1 {
|
||||||
|
return errors.Errorf("unsupported depend entry format")
|
||||||
|
}
|
||||||
|
type deplist map[string][]DependCondition
|
||||||
|
var dl deplist
|
||||||
|
o, err := json.Marshal(dependEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(o, &dl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(dl) != 1 {
|
||||||
|
return errors.Errorf("unsupported depend entry format")
|
||||||
|
}
|
||||||
|
for k, v := range dl {
|
||||||
|
depend = &Depend{
|
||||||
|
TaskName: k,
|
||||||
|
Conditions: v,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unsupported depend format. Must be a string or a list")
|
return errors.Errorf("unsupported depend entry format")
|
||||||
}
|
}
|
||||||
depends[i] = depend
|
depends[i] = depend
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Depends = depends
|
t.Depends = depends
|
||||||
|
|
||||||
if te.When != nil {
|
if tr.When != nil {
|
||||||
w := &types.When{}
|
w := &types.When{}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if te.When.Branch != nil {
|
if tr.When.Branch != nil {
|
||||||
w.Branch, err = parseWhenConditions(te.When.Branch)
|
w.Branch, err = parseWhenConditions(tr.When.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if te.When.Tag != nil {
|
if tr.When.Tag != nil {
|
||||||
w.Tag, err = parseWhenConditions(te.When.Tag)
|
w.Tag, err = parseWhenConditions(tr.When.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if te.When.Ref != nil {
|
if tr.When.Ref != nil {
|
||||||
w.Ref, err = parseWhenConditions(te.When.Ref)
|
w.Ref, err = parseWhenConditions(tr.When.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.When = w
|
t.When = w
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (val *Value) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (val *Value) UnmarshalJSON(b []byte) error {
|
||||||
var ival interface{}
|
var ival interface{}
|
||||||
if err := unmarshal(&ival); err != nil {
|
if err := json.Unmarshal(b, &ival); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch valValue := ival.(type) {
|
switch valValue := ival.(type) {
|
||||||
case string:
|
case string:
|
||||||
val.Type = ValueTypeString
|
val.Type = ValueTypeString
|
||||||
val.Value = valValue
|
val.Value = valValue
|
||||||
case map[interface{}]interface{}:
|
case map[string]interface{}:
|
||||||
for k, v := range valValue {
|
for k, v := range valValue {
|
||||||
if k == "from_variable" {
|
if k == "from_variable" {
|
||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
|
@ -407,13 +472,9 @@ func parseWhenConditions(wi interface{}) (*types.WhenConditions, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
include = ss
|
include = ss
|
||||||
case map[interface{}]interface{}:
|
case map[string]interface{}:
|
||||||
for k, v := range c {
|
for k, v := range c {
|
||||||
ks, ok := k.(string)
|
switch k {
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf(`expected one of "include" or "exclude", got %s`, ks)
|
|
||||||
}
|
|
||||||
switch ks {
|
|
||||||
case "include":
|
case "include":
|
||||||
include, err = parseStringOrSlice(v)
|
include, err = parseStringOrSlice(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -425,7 +486,7 @@ func parseWhenConditions(wi interface{}) (*types.WhenConditions, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf(`expected one of "include" or "exclude", got %s`, ks)
|
return nil, errors.Errorf(`expected one of "include" or "exclude", got %s`, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -514,31 +575,22 @@ func parseSliceString(si []interface{}) ([]string, error) {
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Runtime(runtimeName string) *Runtime {
|
func (c *Config) Run(runName string) *Run {
|
||||||
for n, r := range c.Runtimes {
|
for _, r := range c.Runs {
|
||||||
if n == runtimeName {
|
if r.Name == runName {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("runtime %q doesn't exists", runtimeName))
|
panic(fmt.Sprintf("run %q doesn't exists", runName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Task(taskName string) *Task {
|
func (r *Run) Task(taskName string) *Task {
|
||||||
for n, t := range c.Tasks {
|
for _, t := range r.Tasks {
|
||||||
if n == taskName {
|
if t.Name == taskName {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("task %q doesn't exists", taskName))
|
panic(fmt.Sprintf("task %q for run %q doesn't exists", taskName, r.Name))
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Run(runName string) *Run {
|
|
||||||
for n, p := range c.Runs {
|
|
||||||
if n == runName {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("run %q doesn't exists", runName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultConfig = Config{}
|
var DefaultConfig = Config{}
|
||||||
|
@ -549,92 +601,79 @@ func ParseConfig(configData []byte) (*Config, error) {
|
||||||
return nil, errors.Wrapf(err, "failed to unmarshal config")
|
return nil, errors.Wrapf(err, "failed to unmarshal config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Runs) == 0 {
|
|
||||||
return nil, errors.Errorf("no runs defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set names from maps keys
|
|
||||||
for n, runtime := range config.Runtimes {
|
|
||||||
if runtime == nil {
|
|
||||||
return nil, errors.Errorf("runtime %q is empty", n)
|
|
||||||
}
|
|
||||||
runtime.Name = n
|
|
||||||
}
|
|
||||||
|
|
||||||
for n, task := range config.Tasks {
|
|
||||||
if task == nil {
|
|
||||||
return nil, errors.Errorf("task %q is empty", n)
|
|
||||||
}
|
|
||||||
task.Name = n
|
|
||||||
}
|
|
||||||
|
|
||||||
for n, run := range config.Runs {
|
|
||||||
if run == nil {
|
|
||||||
return nil, errors.Errorf("run %q is empty", n)
|
|
||||||
}
|
|
||||||
run.Name = n
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, run := range config.Runs {
|
|
||||||
for n, element := range run.Elements {
|
|
||||||
if element == nil {
|
|
||||||
return nil, errors.Errorf("run %q: element %q is empty", run.Name, n)
|
|
||||||
}
|
|
||||||
element.Name = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set auth type to default if not specified
|
|
||||||
for _, runtime := range config.Runtimes {
|
|
||||||
if runtime.Auth != nil {
|
|
||||||
if runtime.Auth.Type == "" {
|
|
||||||
runtime.Auth.Type = RegistryAuthTypeDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, container := range runtime.Containers {
|
|
||||||
if container.Auth != nil {
|
|
||||||
if container.Auth.Type == "" {
|
|
||||||
container.Auth.Type = RegistryAuthTypeDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set steps defaults
|
|
||||||
for _, t := range config.Tasks {
|
|
||||||
for _, s := range t.Steps {
|
|
||||||
switch step := s.(type) {
|
|
||||||
// TODO(sgotti) we could use the run step command as step name but when the
|
|
||||||
// command is very long or multi line it doesn't makes sense and will
|
|
||||||
// probably be quite unuseful/confusing from an UI point of view
|
|
||||||
case *SaveCacheStep:
|
|
||||||
for _, content := range step.Contents {
|
|
||||||
if len(content.Paths) == 0 {
|
|
||||||
// default to all files inside the sourceDir
|
|
||||||
content.Paths = []string{"**"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Infof("s: %s", util.Dump(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config, checkConfig(&config)
|
return &config, checkConfig(&config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkConfig(config *Config) error {
|
func checkConfig(config *Config) error {
|
||||||
|
if len(config.Runs) == 0 {
|
||||||
|
return errors.Errorf("no runs defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
seenRuns := map[string]struct{}{}
|
||||||
|
for ri, run := range config.Runs {
|
||||||
|
if run == nil {
|
||||||
|
return errors.Errorf("run at index %d is empty", ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if run.Name == "" {
|
||||||
|
return errors.Errorf("run at index %d has empty name", ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(run.Name) > maxRunNameLength {
|
||||||
|
return errors.Errorf("run name %q too long", run.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := seenRuns[run.Name]; ok {
|
||||||
|
return errors.Errorf("duplicate run name: %s", run.Name)
|
||||||
|
}
|
||||||
|
seenRuns[run.Name] = struct{}{}
|
||||||
|
|
||||||
|
seenTasks := map[string]struct{}{}
|
||||||
|
for ti, task := range run.Tasks {
|
||||||
|
if task == nil {
|
||||||
|
return errors.Errorf("run %q: task at index %d is empty", run.Name, ti)
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.Name == "" {
|
||||||
|
return errors.Errorf("run %q: task at index %d has empty name", run.Name, ti)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(task.Name) > maxTaskNameLength {
|
||||||
|
return errors.Errorf("task name %q too long", task.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := seenTasks[task.Name]; ok {
|
||||||
|
return errors.Errorf("duplicate task name: %s", task.Name)
|
||||||
|
}
|
||||||
|
seenTasks[task.Name] = struct{}{}
|
||||||
|
|
||||||
|
// check tasks runtime
|
||||||
|
if task.Runtime == nil {
|
||||||
|
return errors.Errorf("task %q: runtime is not defined", task.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := task.Runtime
|
||||||
|
if r.Type != RuntimeTypePod {
|
||||||
|
return errors.Errorf("task %q runtime: wrong type %q", task.Name, r.Type)
|
||||||
|
}
|
||||||
|
if len(r.Containers) == 0 {
|
||||||
|
return errors.Errorf("task %q runtime: at least one container must be defined", task.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check broken dependencies
|
// check broken dependencies
|
||||||
for _, run := range config.Runs {
|
for _, run := range config.Runs {
|
||||||
// collect all task names
|
// collect all task names
|
||||||
allElements := map[string]struct{}{}
|
allTasks := map[string]struct{}{}
|
||||||
for _, element := range run.Elements {
|
for _, task := range run.Tasks {
|
||||||
allElements[element.Name] = struct{}{}
|
allTasks[task.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, element := range run.Elements {
|
for _, task := range run.Tasks {
|
||||||
for _, dep := range element.Depends {
|
for _, dep := range task.Depends {
|
||||||
if _, ok := allElements[dep.ElementName]; !ok {
|
if _, ok := allTasks[dep.TaskName]; !ok {
|
||||||
return errors.Errorf("run element %q needed by element %q doesn't exist", dep.ElementName, element.Name)
|
return errors.Errorf("run task %q needed by task %q doesn't exist", dep.TaskName, task.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,21 +682,21 @@ func checkConfig(config *Config) error {
|
||||||
// check circular dependencies
|
// check circular dependencies
|
||||||
for _, run := range config.Runs {
|
for _, run := range config.Runs {
|
||||||
cerrs := &util.Errors{}
|
cerrs := &util.Errors{}
|
||||||
for _, element := range run.Elements {
|
for _, task := range run.Tasks {
|
||||||
allParents := getAllElementParents(run, element)
|
allParents := getAllTaskParents(run, task)
|
||||||
for _, parent := range allParents {
|
for _, parent := range allParents {
|
||||||
if parent.Name == element.Name {
|
if parent.Name == task.Name {
|
||||||
// TODO(sgotti) get the parent that depends on task to report it
|
// TODO(sgotti) get the parent that depends on task to report it
|
||||||
dep := []string{}
|
dep := []string{}
|
||||||
for _, parent := range allParents {
|
for _, parent := range allParents {
|
||||||
pparents := getElementParents(run, parent)
|
pparents := getTaskParents(run, parent)
|
||||||
for _, pparent := range pparents {
|
for _, pparent := range pparents {
|
||||||
if pparent.Name == element.Name {
|
if pparent.Name == task.Name {
|
||||||
dep = append(dep, fmt.Sprintf("%q", parent.Name))
|
dep = append(dep, fmt.Sprintf("%q", parent.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cerrs.Append(errors.Errorf("circular dependency between element %q and elements %s", element.Name, strings.Join(dep, " ")))
|
cerrs.Append(errors.Errorf("circular dependency between task %q and tasks %s", task.Name, strings.Join(dep, " ")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,15 +707,15 @@ func checkConfig(config *Config) error {
|
||||||
|
|
||||||
// check that the task and its parent don't have a common dependency
|
// check that the task and its parent don't have a common dependency
|
||||||
for _, run := range config.Runs {
|
for _, run := range config.Runs {
|
||||||
for _, element := range run.Elements {
|
for _, task := range run.Tasks {
|
||||||
parents := getElementParents(run, element)
|
parents := getTaskParents(run, task)
|
||||||
for _, parent := range parents {
|
for _, parent := range parents {
|
||||||
allParents := getAllElementParents(run, element)
|
allParents := getAllTaskParents(run, task)
|
||||||
allParentParents := getAllElementParents(run, parent)
|
allParentParents := getAllTaskParents(run, parent)
|
||||||
for _, p := range allParents {
|
for _, p := range allParents {
|
||||||
for _, pp := range allParentParents {
|
for _, pp := range allParentParents {
|
||||||
if p.Name == pp.Name {
|
if p.Name == pp.Name {
|
||||||
return errors.Errorf("element %s and its dependency %s have both a dependency on element %s", element.Name, parent.Name, p.Name)
|
return errors.Errorf("task %s and its dependency %s have both a dependency on task %s", task.Name, parent.Name, p.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,66 +723,66 @@ func checkConfig(config *Config) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range config.Runtimes {
|
// check duplicate task dependencies
|
||||||
if r.Type != RuntimeTypePod {
|
|
||||||
return errors.Errorf("runtime %q: wrong type %q", r.Name, r.Type)
|
|
||||||
}
|
|
||||||
if len(r.Containers) == 0 {
|
|
||||||
return errors.Errorf("runtime %q: at least one container must be defined", r.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range config.Tasks {
|
|
||||||
if len(t.Name) > maxTaskNameLength {
|
|
||||||
return errors.Errorf("task name %q too long", t.Name)
|
|
||||||
}
|
|
||||||
if t.Runtime == "" {
|
|
||||||
return errors.Errorf("task %q: undefined runtime", t.Name)
|
|
||||||
}
|
|
||||||
if _, ok := config.Runtimes[t.Runtime]; !ok {
|
|
||||||
return errors.Errorf("runtime %q needed by task %q doesn't exist", t.Runtime, t.Name)
|
|
||||||
}
|
|
||||||
for i, s := range t.Steps {
|
|
||||||
switch step := s.(type) {
|
|
||||||
// TODO(sgotti) we could use the run step command as step name but when the
|
|
||||||
// command is very long or multi line it doesn't makes sense and will
|
|
||||||
// probably be quite unuseful/confusing from an UI point of view
|
|
||||||
case *RunStep:
|
|
||||||
if step.Name == "" {
|
|
||||||
lines, err := util.CountLines(step.Command)
|
|
||||||
// if we failed to count the lines (shouldn't happen) or the number of lines is > 1 then a name is requred
|
|
||||||
if err != nil || lines > 1 {
|
|
||||||
return errors.Errorf("missing step name for step %d in task %q, required since command is more than one line", i, t.Name)
|
|
||||||
}
|
|
||||||
len := len(step.Command)
|
|
||||||
if len > maxStepNameLength {
|
|
||||||
len = maxStepNameLength
|
|
||||||
}
|
|
||||||
step.Name = step.Command[:len]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, run := range config.Runs {
|
for _, run := range config.Runs {
|
||||||
if len(run.Name) > maxRunNameLength {
|
for _, task := range run.Tasks {
|
||||||
return errors.Errorf("run name %q too long", run.Name)
|
|
||||||
}
|
|
||||||
for _, element := range run.Elements {
|
|
||||||
// check missing tasks reference
|
|
||||||
if element.Task == "" {
|
|
||||||
return errors.Errorf("no task defined for run element %q", element.Name)
|
|
||||||
}
|
|
||||||
if _, ok := config.Tasks[element.Task]; !ok {
|
|
||||||
return errors.Errorf("task %q needed by run element %q doesn't exist", element.Task, element.Name)
|
|
||||||
}
|
|
||||||
// check duplicate dependencies in task
|
// check duplicate dependencies in task
|
||||||
seenDependencies := map[string]struct{}{}
|
seenDependencies := map[string]struct{}{}
|
||||||
for _, dep := range element.Depends {
|
for _, dep := range task.Depends {
|
||||||
if _, ok := seenDependencies[dep.ElementName]; ok {
|
if _, ok := seenDependencies[dep.TaskName]; ok {
|
||||||
return errors.Errorf("duplicate task dependency: %s", element.Name)
|
return errors.Errorf("duplicate task dependency: %s", task.Name)
|
||||||
|
}
|
||||||
|
seenDependencies[dep.TaskName] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults
|
||||||
|
for _, run := range config.Runs {
|
||||||
|
for _, task := range run.Tasks {
|
||||||
|
// Set auth type to default if not specified
|
||||||
|
runtime := task.Runtime
|
||||||
|
if runtime.Auth != nil {
|
||||||
|
if runtime.Auth.Type == "" {
|
||||||
|
runtime.Auth.Type = RegistryAuthTypeDefault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, container := range runtime.Containers {
|
||||||
|
if container.Auth != nil {
|
||||||
|
if container.Auth.Type == "" {
|
||||||
|
container.Auth.Type = RegistryAuthTypeDefault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set steps defaults
|
||||||
|
for i, s := range task.Steps {
|
||||||
|
switch step := s.(type) {
|
||||||
|
// TODO(sgotti) we could use the run step command as step name but when the
|
||||||
|
// command is very long or multi line it doesn't makes sense and will
|
||||||
|
// probably be quite unuseful/confusing from an UI point of view
|
||||||
|
case *RunStep:
|
||||||
|
if step.Name == "" {
|
||||||
|
lines, err := util.CountLines(step.Command)
|
||||||
|
// if we failed to count the lines (shouldn't happen) or the number of lines is > 1 then a name is requred
|
||||||
|
if err != nil || lines > 1 {
|
||||||
|
return errors.Errorf("missing step name for step %d (run) in task %q, required since command is more than one line", i, task.Name)
|
||||||
|
}
|
||||||
|
len := len(step.Command)
|
||||||
|
if len > maxStepNameLength {
|
||||||
|
len = maxStepNameLength
|
||||||
|
}
|
||||||
|
step.Name = step.Command[:len]
|
||||||
|
}
|
||||||
|
|
||||||
|
case *SaveCacheStep:
|
||||||
|
for _, content := range step.Contents {
|
||||||
|
if len(content.Paths) == 0 {
|
||||||
|
// default to all files inside the sourceDir
|
||||||
|
content.Paths = []string{"**"}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
seenDependencies[dep.ElementName] = struct{}{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -751,13 +790,13 @@ func checkConfig(config *Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getElementParents returns direct parents of element.
|
// getTaskParents returns direct parents of task.
|
||||||
func getElementParents(run *Run, element *Element) []*Element {
|
func getTaskParents(run *Run, task *Task) []*Task {
|
||||||
parents := []*Element{}
|
parents := []*Task{}
|
||||||
for _, el := range run.Elements {
|
for _, el := range run.Tasks {
|
||||||
isParent := false
|
isParent := false
|
||||||
for _, d := range element.Depends {
|
for _, d := range task.Depends {
|
||||||
if d.ElementName == el.Name {
|
if d.TaskName == el.Name {
|
||||||
isParent = true
|
isParent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -768,26 +807,26 @@ func getElementParents(run *Run, element *Element) []*Element {
|
||||||
return parents
|
return parents
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAllElementParents returns all the parents (both direct and ancestors) of an element.
|
// getAllTaskParents returns all the parents (both direct and ancestors) of a 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
|
||||||
// the element as parent of itself
|
// the task as parent of itself
|
||||||
func getAllElementParents(run *Run, element *Element) []*Element {
|
func getAllTaskParents(run *Run, task *Task) []*Task {
|
||||||
pMap := map[string]*Element{}
|
pMap := map[string]*Task{}
|
||||||
nextParents := getElementParents(run, element)
|
nextParents := getTaskParents(run, task)
|
||||||
|
|
||||||
for len(nextParents) > 0 {
|
for len(nextParents) > 0 {
|
||||||
parents := nextParents
|
parents := nextParents
|
||||||
nextParents = []*Element{}
|
nextParents = []*Task{}
|
||||||
for _, parent := range parents {
|
for _, parent := range parents {
|
||||||
if _, ok := pMap[parent.Name]; ok {
|
if _, ok := pMap[parent.Name]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pMap[parent.Name] = parent
|
pMap[parent.Name] = parent
|
||||||
nextParents = append(nextParents, getElementParents(run, parent)...)
|
nextParents = append(nextParents, getTaskParents(run, parent)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parents := make([]*Element, 0, len(pMap))
|
parents := make([]*Task, 0, len(pMap))
|
||||||
for _, v := range pMap {
|
for _, v := range pMap {
|
||||||
parents = append(parents, v)
|
parents = append(parents, v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,52 +47,61 @@ func TestParseConfig(t *testing.T) {
|
||||||
name: "test empty run",
|
name: "test empty run",
|
||||||
in: `
|
in: `
|
||||||
runs:
|
runs:
|
||||||
run01:
|
-
|
||||||
`,
|
`,
|
||||||
err: fmt.Errorf(`run "run01" is empty`),
|
err: fmt.Errorf(`run at index 0 is empty`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test missing element dependency",
|
name: "test empty task",
|
||||||
in: `
|
in: `
|
||||||
tasks:
|
|
||||||
task0k1:
|
|
||||||
environment:
|
|
||||||
ENV01: ENV01
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
run01:
|
- name: run01
|
||||||
elements:
|
tasks:
|
||||||
element01:
|
-
|
||||||
task: task01
|
|
||||||
depends:
|
|
||||||
- element02
|
|
||||||
`,
|
`,
|
||||||
err: fmt.Errorf(`run element "element02" needed by element "element01" doesn't exist`),
|
err: fmt.Errorf(`run "run01": task at index 0 is empty`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test circular dependency between 2 elements a -> b -> a",
|
name: "test missing task dependency",
|
||||||
in: `
|
in: `
|
||||||
tasks:
|
|
||||||
task01:
|
|
||||||
environment:
|
|
||||||
ENV01: ENV01
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
run01:
|
- name: run01
|
||||||
elements:
|
tasks:
|
||||||
element01:
|
- name: task01
|
||||||
task: task01
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: busybox
|
||||||
depends:
|
depends:
|
||||||
- element02
|
- task02
|
||||||
element02:
|
`,
|
||||||
task: task01
|
err: fmt.Errorf(`run task "task02" needed by task "task01" doesn't exist`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test circular dependency between 2 tasks a -> b -> a",
|
||||||
|
in: `
|
||||||
|
runs:
|
||||||
|
- name: run01
|
||||||
|
tasks:
|
||||||
|
- name: task01
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: busybox
|
||||||
depends:
|
depends:
|
||||||
- element01
|
- task02
|
||||||
|
- name: task02
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: busybox
|
||||||
|
depends:
|
||||||
|
- task01
|
||||||
`,
|
`,
|
||||||
err: &util.Errors{
|
err: &util.Errors{
|
||||||
Errs: []error{
|
Errs: []error{
|
||||||
errors.Errorf("circular dependency between element %q and elements %q", "element01", "element02"),
|
errors.Errorf("circular dependency between task %q and tasks %q", "task01", "task02"),
|
||||||
errors.Errorf("circular dependency between element %q and elements %q", "element02", "element01"),
|
errors.Errorf("circular dependency between task %q and tasks %q", "task02", "task01"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -129,54 +138,67 @@ func TestParseOutput(t *testing.T) {
|
||||||
out *Config
|
out *Config
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test element when conditions",
|
name: "test task all options",
|
||||||
in: `
|
in: `
|
||||||
runtimes:
|
runs:
|
||||||
runtime01:
|
- name: run01
|
||||||
type: pod
|
tasks:
|
||||||
auth:
|
- name: task01
|
||||||
username: username
|
runtime:
|
||||||
password:
|
type: pod
|
||||||
from_variable: password
|
auth:
|
||||||
containers:
|
username: username
|
||||||
- image: image01
|
password:
|
||||||
auth:
|
from_variable: password
|
||||||
username:
|
containers:
|
||||||
from_variable: username2
|
- image: image01
|
||||||
password: password2
|
auth:
|
||||||
|
username:
|
||||||
|
from_variable: username2
|
||||||
|
password: password2
|
||||||
|
environment:
|
||||||
|
ENV01: ENV01
|
||||||
|
ENVFROMVARIABLE01:
|
||||||
|
from_variable: variable01
|
||||||
environment:
|
environment:
|
||||||
ENV01: ENV01
|
ENV01: ENV01
|
||||||
ENVFROMVARIABLE01:
|
ENVFROMVARIABLE01:
|
||||||
from_variable: variable01
|
from_variable: variable01
|
||||||
|
steps:
|
||||||
|
# normal step definition
|
||||||
|
- type: clone
|
||||||
|
- type: run
|
||||||
|
command: command01
|
||||||
|
- type: run
|
||||||
|
name: name different than command
|
||||||
|
command: command02
|
||||||
|
- type: run
|
||||||
|
command: command03
|
||||||
|
environment:
|
||||||
|
ENV01: ENV01
|
||||||
|
ENVFROMVARIABLE01:
|
||||||
|
from_variable: variable01
|
||||||
|
- type: save_cache
|
||||||
|
key: cache-{{ arch }}
|
||||||
|
contents:
|
||||||
|
- source_dir: /go/pkg/mod/cache
|
||||||
|
|
||||||
tasks:
|
# simpler (for yaml not for json) steps definition
|
||||||
task01:
|
- clone:
|
||||||
runtime: runtime01
|
- run: command01
|
||||||
environment:
|
- run:
|
||||||
ENV01: ENV01
|
name: name different than command
|
||||||
ENVFROMVARIABLE01:
|
command: command02
|
||||||
from_variable: variable01
|
- run:
|
||||||
steps:
|
command: command03
|
||||||
- run: command01
|
environment:
|
||||||
- run:
|
ENV01: ENV01
|
||||||
name: name different than command
|
ENVFROMVARIABLE01:
|
||||||
command: command02
|
from_variable: variable01
|
||||||
- run:
|
- save_cache:
|
||||||
command: command03
|
key: cache-{{ arch }}
|
||||||
environment:
|
contents:
|
||||||
ENV01: ENV01
|
- source_dir: /go/pkg/mod/cache
|
||||||
ENVFROMVARIABLE01:
|
|
||||||
from_variable: variable01
|
|
||||||
- save_cache:
|
|
||||||
key: cache-{{ arch }}
|
|
||||||
contents:
|
|
||||||
- source_dir: /go/pkg/mod/cache
|
|
||||||
|
|
||||||
runs:
|
|
||||||
run01:
|
|
||||||
elements:
|
|
||||||
element01:
|
|
||||||
task: task01
|
|
||||||
when:
|
when:
|
||||||
branch: master
|
branch: master
|
||||||
tag:
|
tag:
|
||||||
|
@ -185,88 +207,132 @@ func TestParseOutput(t *testing.T) {
|
||||||
ref:
|
ref:
|
||||||
include: master
|
include: master
|
||||||
exclude: [ /branch01/ , branch02 ]
|
exclude: [ /branch01/ , branch02 ]
|
||||||
|
depends:
|
||||||
|
- task: task02
|
||||||
|
conditions:
|
||||||
|
- on_success
|
||||||
|
- on_failure
|
||||||
|
- task03
|
||||||
|
- task04:
|
||||||
|
- on_success
|
||||||
|
- name: task02
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: image01
|
||||||
|
- name: task03
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: image01
|
||||||
|
- name: task04
|
||||||
|
runtime:
|
||||||
|
type: pod
|
||||||
|
containers:
|
||||||
|
- image: image01
|
||||||
`,
|
`,
|
||||||
out: &Config{
|
out: &Config{
|
||||||
Runtimes: map[string]*Runtime{
|
Runs: []*Run{
|
||||||
"runtime01": &Runtime{
|
&Run{
|
||||||
Name: "runtime01",
|
|
||||||
Type: "pod",
|
|
||||||
Auth: &RegistryAuth{
|
|
||||||
Type: RegistryAuthTypeDefault,
|
|
||||||
Username: Value{Type: ValueTypeString, Value: "username"},
|
|
||||||
Password: Value{Type: ValueTypeFromVariable, Value: "password"},
|
|
||||||
},
|
|
||||||
Arch: "",
|
|
||||||
Containers: []*Container{
|
|
||||||
&Container{
|
|
||||||
Image: "image01",
|
|
||||||
Auth: &RegistryAuth{
|
|
||||||
Type: RegistryAuthTypeDefault,
|
|
||||||
Username: Value{Type: ValueTypeFromVariable, Value: "username2"},
|
|
||||||
Password: Value{Type: ValueTypeString, Value: "password2"},
|
|
||||||
},
|
|
||||||
Environment: map[string]Value{
|
|
||||||
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
User: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tasks: map[string]*Task{
|
|
||||||
"task01": &Task{
|
|
||||||
Name: "task01",
|
|
||||||
Runtime: "runtime01",
|
|
||||||
Environment: map[string]Value{
|
|
||||||
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
WorkingDir: "",
|
|
||||||
Shell: "",
|
|
||||||
User: "",
|
|
||||||
Steps: []interface{}{
|
|
||||||
&RunStep{
|
|
||||||
Step: Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command01",
|
|
||||||
},
|
|
||||||
Command: "command01",
|
|
||||||
},
|
|
||||||
&RunStep{
|
|
||||||
Step: Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "name different than command",
|
|
||||||
},
|
|
||||||
Command: "command02",
|
|
||||||
},
|
|
||||||
&RunStep{
|
|
||||||
Step: Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command03",
|
|
||||||
},
|
|
||||||
Command: "command03",
|
|
||||||
Environment: map[string]Value{
|
|
||||||
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&SaveCacheStep{
|
|
||||||
Step: Step{Type: "save_cache"},
|
|
||||||
Key: "cache-{{ arch }}",
|
|
||||||
Contents: []*SaveContent{&SaveContent{SourceDir: "/go/pkg/mod/cache", Paths: []string{"**"}}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Runs: map[string]*Run{
|
|
||||||
"run01": &Run{
|
|
||||||
Name: "run01",
|
Name: "run01",
|
||||||
Elements: map[string]*Element{
|
Tasks: []*Task{
|
||||||
"element01": &Element{
|
&Task{
|
||||||
Name: "element01",
|
Name: "task01",
|
||||||
Task: "task01",
|
Runtime: &Runtime{
|
||||||
Depends: []*Depend{},
|
Type: "pod",
|
||||||
|
Auth: &RegistryAuth{
|
||||||
|
Type: RegistryAuthTypeDefault,
|
||||||
|
Username: Value{Type: ValueTypeString, Value: "username"},
|
||||||
|
Password: Value{Type: ValueTypeFromVariable, Value: "password"},
|
||||||
|
},
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*Container{
|
||||||
|
&Container{
|
||||||
|
Image: "image01",
|
||||||
|
Auth: &RegistryAuth{
|
||||||
|
Type: RegistryAuthTypeDefault,
|
||||||
|
Username: Value{Type: ValueTypeFromVariable, Value: "username2"},
|
||||||
|
Password: Value{Type: ValueTypeString, Value: "password2"},
|
||||||
|
},
|
||||||
|
Environment: map[string]Value{
|
||||||
|
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
User: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Environment: map[string]Value{
|
||||||
|
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Shell: "",
|
||||||
|
User: "",
|
||||||
|
Steps: []interface{}{
|
||||||
|
&CloneStep{Step: Step{Type: "clone"}},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command01",
|
||||||
|
},
|
||||||
|
Command: "command01",
|
||||||
|
},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "name different than command",
|
||||||
|
},
|
||||||
|
Command: "command02",
|
||||||
|
},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command03",
|
||||||
|
},
|
||||||
|
Command: "command03",
|
||||||
|
Environment: map[string]Value{
|
||||||
|
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&SaveCacheStep{
|
||||||
|
Step: Step{Type: "save_cache"},
|
||||||
|
Key: "cache-{{ arch }}",
|
||||||
|
Contents: []*SaveContent{&SaveContent{SourceDir: "/go/pkg/mod/cache", Paths: []string{"**"}}},
|
||||||
|
},
|
||||||
|
&CloneStep{Step: Step{Type: "clone"}},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command01",
|
||||||
|
},
|
||||||
|
Command: "command01",
|
||||||
|
},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "name different than command",
|
||||||
|
},
|
||||||
|
Command: "command02",
|
||||||
|
},
|
||||||
|
&RunStep{
|
||||||
|
Step: Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command03",
|
||||||
|
},
|
||||||
|
Command: "command03",
|
||||||
|
Environment: map[string]Value{
|
||||||
|
"ENV01": Value{Type: ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": Value{Type: ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&SaveCacheStep{
|
||||||
|
Step: Step{Type: "save_cache"},
|
||||||
|
Key: "cache-{{ arch }}",
|
||||||
|
Contents: []*SaveContent{&SaveContent{SourceDir: "/go/pkg/mod/cache", Paths: []string{"**"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
IgnoreFailure: false,
|
IgnoreFailure: false,
|
||||||
Approval: false,
|
Approval: false,
|
||||||
When: &types.When{
|
When: &types.When{
|
||||||
|
@ -291,6 +357,56 @@ func TestParseOutput(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Depends: []*Depend{
|
||||||
|
&Depend{TaskName: "task02", Conditions: []DependCondition{DependConditionOnSuccess, DependConditionOnFailure}},
|
||||||
|
&Depend{TaskName: "task03", Conditions: nil},
|
||||||
|
&Depend{TaskName: "task04", Conditions: []DependCondition{DependConditionOnSuccess}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&Task{
|
||||||
|
Name: "task02",
|
||||||
|
Runtime: &Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*Container{
|
||||||
|
&Container{
|
||||||
|
Image: "image01",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Steps: []interface{}{},
|
||||||
|
Depends: []*Depend{},
|
||||||
|
},
|
||||||
|
&Task{
|
||||||
|
Name: "task03",
|
||||||
|
Runtime: &Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*Container{
|
||||||
|
&Container{
|
||||||
|
Image: "image01",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Steps: []interface{}{},
|
||||||
|
Depends: []*Depend{},
|
||||||
|
},
|
||||||
|
&Task{
|
||||||
|
Name: "task04",
|
||||||
|
Runtime: &Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*Container{
|
||||||
|
&Container{
|
||||||
|
Image: "image01",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Steps: []interface{}{},
|
||||||
|
Depends: []*Depend{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,9 +25,7 @@ import (
|
||||||
"github.com/sorintlab/agola/internal/util"
|
"github.com/sorintlab/agola/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genRuntime(c *config.Config, runtimeName string, variables map[string]string) *rstypes.Runtime {
|
func genRuntime(c *config.Config, ce *config.Runtime, variables map[string]string) *rstypes.Runtime {
|
||||||
ce := c.Runtime(runtimeName)
|
|
||||||
|
|
||||||
containers := []*rstypes.Container{}
|
containers := []*rstypes.Container{}
|
||||||
for _, cc := range ce.Containers {
|
for _, cc := range ce.Containers {
|
||||||
env := genEnv(cc.Environment, variables)
|
env := genEnv(cc.Environment, variables)
|
||||||
|
@ -189,11 +187,8 @@ func GenRunConfigTasks(uuid util.UUIDGenerator, c *config.Config, runName string
|
||||||
|
|
||||||
rcts := map[string]*rstypes.RunConfigTask{}
|
rcts := map[string]*rstypes.RunConfigTask{}
|
||||||
|
|
||||||
for _, cre := range cr.Elements {
|
for _, ct := range cr.Tasks {
|
||||||
include := types.MatchWhen(cre.When, branch, tag, ref)
|
include := types.MatchWhen(ct.When, branch, tag, ref)
|
||||||
|
|
||||||
// resolve referenced task
|
|
||||||
ct := c.Task(cre.Task)
|
|
||||||
|
|
||||||
steps := make([]interface{}, len(ct.Steps))
|
steps := make([]interface{}, len(ct.Steps))
|
||||||
for i, cpts := range ct.Steps {
|
for i, cpts := range ct.Steps {
|
||||||
|
@ -203,18 +198,17 @@ func GenRunConfigTasks(uuid util.UUIDGenerator, c *config.Config, runName string
|
||||||
tEnv := genEnv(ct.Environment, variables)
|
tEnv := genEnv(ct.Environment, variables)
|
||||||
|
|
||||||
t := &rstypes.RunConfigTask{
|
t := &rstypes.RunConfigTask{
|
||||||
ID: uuid.New(cre.Name).String(),
|
ID: uuid.New(ct.Name).String(),
|
||||||
// use the element name from the config as the task name
|
Name: ct.Name,
|
||||||
Name: cre.Name,
|
|
||||||
Runtime: genRuntime(c, ct.Runtime, variables),
|
Runtime: genRuntime(c, ct.Runtime, variables),
|
||||||
Environment: tEnv,
|
Environment: tEnv,
|
||||||
WorkingDir: ct.WorkingDir,
|
WorkingDir: ct.WorkingDir,
|
||||||
Shell: ct.Shell,
|
Shell: ct.Shell,
|
||||||
User: ct.User,
|
User: ct.User,
|
||||||
Steps: steps,
|
Steps: steps,
|
||||||
IgnoreFailure: cre.IgnoreFailure,
|
IgnoreFailure: ct.IgnoreFailure,
|
||||||
Skip: !include,
|
Skip: !include,
|
||||||
NeedsApproval: cre.Approval,
|
NeedsApproval: ct.Approval,
|
||||||
}
|
}
|
||||||
|
|
||||||
rcts[t.ID] = t
|
rcts[t.ID] = t
|
||||||
|
@ -222,10 +216,10 @@ func GenRunConfigTasks(uuid util.UUIDGenerator, c *config.Config, runName string
|
||||||
|
|
||||||
// populate depends, needs to be done after having created all the tasks so we can resolve their id
|
// populate depends, needs to be done after having created all the tasks so we can resolve their id
|
||||||
for _, rct := range rcts {
|
for _, rct := range rcts {
|
||||||
cre := cr.Elements[rct.Name]
|
ct := cr.Task(rct.Name)
|
||||||
|
|
||||||
depends := make(map[string]*rstypes.RunConfigTaskDepend, len(cre.Depends))
|
depends := make(map[string]*rstypes.RunConfigTaskDepend, len(ct.Depends))
|
||||||
for _, d := range cre.Depends {
|
for _, d := range ct.Depends {
|
||||||
conditions := make([]rstypes.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 {
|
||||||
|
@ -243,7 +237,7 @@ func GenRunConfigTasks(uuid util.UUIDGenerator, c *config.Config, runName string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drct := getRunConfigTaskByName(rcts, d.ElementName)
|
drct := getRunConfigTaskByName(rcts, d.TaskName)
|
||||||
depends[drct.ID] = &rstypes.RunConfigTaskDepend{
|
depends[drct.ID] = &rstypes.RunConfigTaskDepend{
|
||||||
TaskID: drct.ID,
|
TaskID: drct.ID,
|
||||||
Conditions: conditions,
|
Conditions: conditions,
|
||||||
|
|
|
@ -632,80 +632,71 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test runconfig generation",
|
name: "test runconfig generation",
|
||||||
in: &config.Config{
|
in: &config.Config{
|
||||||
Runtimes: map[string]*config.Runtime{
|
Runs: []*config.Run{
|
||||||
"runtime01": &config.Runtime{
|
&config.Run{
|
||||||
Name: "runtime01",
|
|
||||||
Type: "pod",
|
|
||||||
Auth: &config.RegistryAuth{
|
|
||||||
Type: config.RegistryAuthTypeDefault,
|
|
||||||
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
|
||||||
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
|
||||||
},
|
|
||||||
Arch: "",
|
|
||||||
Containers: []*config.Container{
|
|
||||||
&config.Container{
|
|
||||||
Image: "image01",
|
|
||||||
Auth: &config.RegistryAuth{
|
|
||||||
Type: config.RegistryAuthTypeDefault,
|
|
||||||
Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"},
|
|
||||||
Password: config.Value{Type: config.ValueTypeString, Value: "password2"},
|
|
||||||
},
|
|
||||||
Environment: map[string]config.Value{
|
|
||||||
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
User: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tasks: map[string]*config.Task{
|
|
||||||
"task01": &config.Task{
|
|
||||||
Name: "task01",
|
|
||||||
Runtime: "runtime01",
|
|
||||||
Environment: map[string]config.Value{
|
|
||||||
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
WorkingDir: "",
|
|
||||||
Shell: "",
|
|
||||||
User: "",
|
|
||||||
Steps: []interface{}{
|
|
||||||
&config.RunStep{
|
|
||||||
Step: config.Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command01",
|
|
||||||
},
|
|
||||||
Command: "command01",
|
|
||||||
},
|
|
||||||
&config.RunStep{
|
|
||||||
Step: config.Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "name different than command",
|
|
||||||
},
|
|
||||||
Command: "command02",
|
|
||||||
},
|
|
||||||
&config.RunStep{
|
|
||||||
Step: config.Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command03",
|
|
||||||
},
|
|
||||||
Command: "command03",
|
|
||||||
Environment: map[string]config.Value{
|
|
||||||
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
|
||||||
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Runs: map[string]*config.Run{
|
|
||||||
"run01": &config.Run{
|
|
||||||
Name: "run01",
|
Name: "run01",
|
||||||
Elements: map[string]*config.Element{
|
Tasks: []*config.Task{
|
||||||
"element01": &config.Element{
|
&config.Task{
|
||||||
Name: "element01",
|
Name: "task01",
|
||||||
Task: "task01",
|
Runtime: &config.Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Auth: &config.RegistryAuth{
|
||||||
|
Type: config.RegistryAuthTypeDefault,
|
||||||
|
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
||||||
|
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
||||||
|
},
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*config.Container{
|
||||||
|
&config.Container{
|
||||||
|
Image: "image01",
|
||||||
|
Auth: &config.RegistryAuth{
|
||||||
|
Type: config.RegistryAuthTypeDefault,
|
||||||
|
Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"},
|
||||||
|
Password: config.Value{Type: config.ValueTypeString, Value: "password2"},
|
||||||
|
},
|
||||||
|
Environment: map[string]config.Value{
|
||||||
|
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
User: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Environment: map[string]config.Value{
|
||||||
|
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
WorkingDir: "",
|
||||||
|
Shell: "",
|
||||||
|
User: "",
|
||||||
|
Steps: []interface{}{
|
||||||
|
&config.RunStep{
|
||||||
|
Step: config.Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command01",
|
||||||
|
},
|
||||||
|
Command: "command01",
|
||||||
|
},
|
||||||
|
&config.RunStep{
|
||||||
|
Step: config.Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "name different than command",
|
||||||
|
},
|
||||||
|
Command: "command02",
|
||||||
|
},
|
||||||
|
&config.RunStep{
|
||||||
|
Step: config.Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command03",
|
||||||
|
},
|
||||||
|
Command: "command03",
|
||||||
|
Environment: map[string]config.Value{
|
||||||
|
"ENV01": config.Value{Type: config.ValueTypeString, Value: "ENV01"},
|
||||||
|
"ENVFROMVARIABLE01": config.Value{Type: config.ValueTypeFromVariable, Value: "variable01"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Depends: []*config.Depend{},
|
Depends: []*config.Depend{},
|
||||||
IgnoreFailure: false,
|
IgnoreFailure: false,
|
||||||
Approval: false,
|
Approval: false,
|
||||||
|
@ -727,9 +718,9 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
"registry_username": "yourregistryusername",
|
"registry_username": "yourregistryusername",
|
||||||
},
|
},
|
||||||
out: map[string]*rstypes.RunConfigTask{
|
out: map[string]*rstypes.RunConfigTask{
|
||||||
uuid.New("element01").String(): &rstypes.RunConfigTask{
|
uuid.New("task01").String(): &rstypes.RunConfigTask{
|
||||||
ID: uuid.New("element01").String(),
|
ID: uuid.New("task01").String(),
|
||||||
Name: "element01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
||||||
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
||||||
Containers: []*rstypes.Container{
|
Containers: []*rstypes.Container{
|
||||||
{
|
{
|
||||||
|
@ -762,45 +753,35 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test runtime auth used for container nil auth",
|
name: "test runtime auth used for container nil auth",
|
||||||
in: &config.Config{
|
in: &config.Config{
|
||||||
Runtimes: map[string]*config.Runtime{
|
Runs: []*config.Run{
|
||||||
"runtime01": &config.Runtime{
|
&config.Run{
|
||||||
Name: "runtime01",
|
|
||||||
Type: "pod",
|
|
||||||
Auth: &config.RegistryAuth{
|
|
||||||
Type: config.RegistryAuthTypeDefault,
|
|
||||||
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
|
||||||
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
|
||||||
},
|
|
||||||
Arch: "",
|
|
||||||
Containers: []*config.Container{
|
|
||||||
&config.Container{
|
|
||||||
Image: "image01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tasks: map[string]*config.Task{
|
|
||||||
"task01": &config.Task{
|
|
||||||
Name: "task01",
|
|
||||||
Runtime: "runtime01",
|
|
||||||
Steps: []interface{}{
|
|
||||||
&config.RunStep{
|
|
||||||
Step: config.Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command01",
|
|
||||||
},
|
|
||||||
Command: "command01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Runs: map[string]*config.Run{
|
|
||||||
"run01": &config.Run{
|
|
||||||
Name: "run01",
|
Name: "run01",
|
||||||
Elements: map[string]*config.Element{
|
Tasks: []*config.Task{
|
||||||
"element01": &config.Element{
|
&config.Task{
|
||||||
Name: "element01",
|
Name: "task01",
|
||||||
Task: "task01",
|
Runtime: &config.Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Auth: &config.RegistryAuth{
|
||||||
|
Type: config.RegistryAuthTypeDefault,
|
||||||
|
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
||||||
|
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
||||||
|
},
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*config.Container{
|
||||||
|
&config.Container{
|
||||||
|
Image: "image01",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Steps: []interface{}{
|
||||||
|
&config.RunStep{
|
||||||
|
Step: config.Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command01",
|
||||||
|
},
|
||||||
|
Command: "command01",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -811,9 +792,9 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
"password": "yourregistrypassword",
|
"password": "yourregistrypassword",
|
||||||
},
|
},
|
||||||
out: map[string]*rstypes.RunConfigTask{
|
out: map[string]*rstypes.RunConfigTask{
|
||||||
uuid.New("element01").String(): &rstypes.RunConfigTask{
|
uuid.New("task01").String(): &rstypes.RunConfigTask{
|
||||||
ID: uuid.New("element01").String(),
|
ID: uuid.New("task01").String(),
|
||||||
Name: "element01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
||||||
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
||||||
Containers: []*rstypes.Container{
|
Containers: []*rstypes.Container{
|
||||||
{
|
{
|
||||||
|
@ -837,50 +818,40 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test runtime auth not used for container with auth",
|
name: "test runtime auth not used for container with auth",
|
||||||
in: &config.Config{
|
in: &config.Config{
|
||||||
Runtimes: map[string]*config.Runtime{
|
Runs: []*config.Run{
|
||||||
"runtime01": &config.Runtime{
|
&config.Run{
|
||||||
Name: "runtime01",
|
|
||||||
Type: "pod",
|
|
||||||
Auth: &config.RegistryAuth{
|
|
||||||
Type: config.RegistryAuthTypeDefault,
|
|
||||||
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
|
||||||
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
|
||||||
},
|
|
||||||
Arch: "",
|
|
||||||
Containers: []*config.Container{
|
|
||||||
&config.Container{
|
|
||||||
Image: "image01",
|
|
||||||
Auth: &config.RegistryAuth{
|
|
||||||
Type: config.RegistryAuthTypeDefault,
|
|
||||||
Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"},
|
|
||||||
Password: config.Value{Type: config.ValueTypeString, Value: "password2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tasks: map[string]*config.Task{
|
|
||||||
"task01": &config.Task{
|
|
||||||
Name: "task01",
|
|
||||||
Runtime: "runtime01",
|
|
||||||
Steps: []interface{}{
|
|
||||||
&config.RunStep{
|
|
||||||
Step: config.Step{
|
|
||||||
Type: "run",
|
|
||||||
Name: "command01",
|
|
||||||
},
|
|
||||||
Command: "command01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Runs: map[string]*config.Run{
|
|
||||||
"run01": &config.Run{
|
|
||||||
Name: "run01",
|
Name: "run01",
|
||||||
Elements: map[string]*config.Element{
|
Tasks: []*config.Task{
|
||||||
"element01": &config.Element{
|
&config.Task{
|
||||||
Name: "element01",
|
Name: "task01",
|
||||||
Task: "task01",
|
Runtime: &config.Runtime{
|
||||||
|
Type: "pod",
|
||||||
|
Auth: &config.RegistryAuth{
|
||||||
|
Type: config.RegistryAuthTypeDefault,
|
||||||
|
Username: config.Value{Type: config.ValueTypeString, Value: "username"},
|
||||||
|
Password: config.Value{Type: config.ValueTypeFromVariable, Value: "password"},
|
||||||
|
},
|
||||||
|
Arch: "",
|
||||||
|
Containers: []*config.Container{
|
||||||
|
&config.Container{
|
||||||
|
Image: "image01",
|
||||||
|
Auth: &config.RegistryAuth{
|
||||||
|
Type: config.RegistryAuthTypeDefault,
|
||||||
|
Username: config.Value{Type: config.ValueTypeFromVariable, Value: "registry_username"},
|
||||||
|
Password: config.Value{Type: config.ValueTypeString, Value: "password2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Steps: []interface{}{
|
||||||
|
&config.RunStep{
|
||||||
|
Step: config.Step{
|
||||||
|
Type: "run",
|
||||||
|
Name: "command01",
|
||||||
|
},
|
||||||
|
Command: "command01",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -891,9 +862,9 @@ func TestGenRunConfig(t *testing.T) {
|
||||||
"registry_username": "yourregistryusername",
|
"registry_username": "yourregistryusername",
|
||||||
},
|
},
|
||||||
out: map[string]*rstypes.RunConfigTask{
|
out: map[string]*rstypes.RunConfigTask{
|
||||||
uuid.New("element01").String(): &rstypes.RunConfigTask{
|
uuid.New("task01").String(): &rstypes.RunConfigTask{
|
||||||
ID: uuid.New("element01").String(),
|
ID: uuid.New("task01").String(),
|
||||||
Name: "element01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
Name: "task01", Depends: map[string]*rstypes.RunConfigTaskDepend{},
|
||||||
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
Runtime: &rstypes.Runtime{Type: rstypes.RuntimeType("pod"),
|
||||||
Containers: []*rstypes.Container{
|
Containers: []*rstypes.Container{
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue