mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-02-13 21:00:00 +00:00
Make pipeline/frontend/yaml/* types able to be marshaled back to YAML (#1835)
This commit is contained in:
@@ -280,7 +280,7 @@ func TestCompilerCompile(t *testing.T) {
|
||||
Name: "step",
|
||||
Image: "bash",
|
||||
Commands: []string{"env"},
|
||||
Environment: yaml_base_types.EnvironmentMap{
|
||||
Environment: map[string]any{
|
||||
"MISSING": map[string]any{"from_secret": "missing"},
|
||||
},
|
||||
}}}},
|
||||
@@ -375,7 +375,7 @@ func TestCompilerCompileWithFromSecret(t *testing.T) {
|
||||
Name: "step",
|
||||
Image: "bash",
|
||||
Commands: []string{"env"},
|
||||
Environment: yaml_base_types.EnvironmentMap{
|
||||
Environment: map[string]any{
|
||||
"SECRET": map[string]any{"from_secret": "secret_name"},
|
||||
},
|
||||
}}}},
|
||||
|
||||
@@ -19,11 +19,8 @@ import (
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/expr-lang/expr"
|
||||
"go.uber.org/multierr"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
|
||||
@@ -38,38 +35,18 @@ type (
|
||||
}
|
||||
|
||||
Constraint struct {
|
||||
Ref List
|
||||
Repo List
|
||||
Instance List
|
||||
Platform List
|
||||
Branch List
|
||||
Cron List
|
||||
Status List
|
||||
Matrix Map
|
||||
Local yamlBaseTypes.BoolTrue
|
||||
Path Path
|
||||
Evaluate string `yaml:"evaluate,omitempty"`
|
||||
Event yamlBaseTypes.StringOrSlice
|
||||
}
|
||||
|
||||
// List defines a runtime constraint for exclude & include string slices.
|
||||
List struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
// Map defines a runtime constraint for exclude & include map strings.
|
||||
Map struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}
|
||||
|
||||
// Path defines a runtime constrain for exclude & include paths.
|
||||
Path struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
IgnoreMessage string `yaml:"ignore_message,omitempty"`
|
||||
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
|
||||
Ref List `yaml:"ref,omitempty"`
|
||||
Repo List `yaml:"repo,omitempty"`
|
||||
Instance List `yaml:"instance,omitempty"`
|
||||
Platform List `yaml:"platform,omitempty"`
|
||||
Branch List `yaml:"branch,omitempty"`
|
||||
Cron List `yaml:"cron,omitempty"`
|
||||
Status List `yaml:"status,omitempty"`
|
||||
Matrix Map `yaml:"matrix,omitempty"`
|
||||
Local yamlBaseTypes.BoolTrue `yaml:"local,omitempty"`
|
||||
Path Path `yaml:"path,omitempty"`
|
||||
Evaluate string `yaml:"evaluate,omitempty"`
|
||||
Event yamlBaseTypes.StringOrSlice `yaml:"event,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -153,6 +130,18 @@ func (when *When) UnmarshalYAML(value *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (when When) MarshalYAML() (any, error) {
|
||||
switch len(when.Constraints) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
return when.Constraints[0], nil
|
||||
default:
|
||||
return when.Constraints, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns true if all constraints match the given input. If a single
|
||||
// constraint fails a false value is returned.
|
||||
func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]string) (bool, error) {
|
||||
@@ -204,203 +193,3 @@ func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]stri
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
// IsEmpty return true if a constraint has no conditions.
|
||||
func (c List) IsEmpty() bool {
|
||||
return len(c.Include) == 0 && len(c.Exclude) == 0
|
||||
}
|
||||
|
||||
// Match returns true if the string matches the include patterns and does not
|
||||
// match any of the exclude patterns.
|
||||
func (c *List) Match(v string) bool {
|
||||
if c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
if c.Includes(v) {
|
||||
return true
|
||||
}
|
||||
if len(c.Include) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Includes returns true if the string matches the include patterns.
|
||||
func (c *List) Includes(v string) bool {
|
||||
for _, pattern := range c.Include {
|
||||
if ok, _ := doublestar.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Excludes returns true if the string matches the exclude patterns.
|
||||
func (c *List) Excludes(v string) bool {
|
||||
for _, pattern := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint.
|
||||
func (c *List) UnmarshalYAML(value *yaml.Node) error {
|
||||
out1 := struct {
|
||||
Include yamlBaseTypes.StringOrSlice
|
||||
Exclude yamlBaseTypes.StringOrSlice
|
||||
}{}
|
||||
|
||||
var out2 yamlBaseTypes.StringOrSlice
|
||||
|
||||
err1 := value.Decode(&out1)
|
||||
err2 := value.Decode(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude
|
||||
c.Include = append( //nolint:gocritic
|
||||
out1.Include,
|
||||
out2...,
|
||||
)
|
||||
|
||||
if err1 != nil && err2 != nil {
|
||||
y, _ := yaml.Marshal(value)
|
||||
return fmt.Errorf("could not parse condition: %s: %w", y, multierr.Append(err1, err2))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match returns true if the params matches the include key values and does not
|
||||
// match any of the exclude key values.
|
||||
func (c *Map) Match(params map[string]string) bool {
|
||||
// when no includes or excludes automatically match
|
||||
if len(c.Include) == 0 && len(c.Exclude) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Exclusions are processed first. So we can include everything and then
|
||||
// selectively include others.
|
||||
if len(c.Exclude) != 0 {
|
||||
var matches int
|
||||
|
||||
for key, val := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(val, params[key]); ok {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
if matches == len(c.Exclude) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for key, val := range c.Include {
|
||||
if ok, _ := doublestar.Match(val, params[key]); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint map.
|
||||
func (c *Map) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
out1 := struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}{
|
||||
Include: map[string]string{},
|
||||
Exclude: map[string]string{},
|
||||
}
|
||||
|
||||
out2 := map[string]string{}
|
||||
|
||||
_ = unmarshal(&out1) // it contains include and exclude statement
|
||||
_ = unmarshal(&out2) // it contains no include/exclude statement, assume include as default
|
||||
|
||||
c.Include = out1.Include
|
||||
c.Exclude = out1.Exclude
|
||||
for k, v := range out2 {
|
||||
c.Include[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint.
|
||||
func (c *Path) UnmarshalYAML(value *yaml.Node) error {
|
||||
out1 := struct {
|
||||
Include yamlBaseTypes.StringOrSlice `yaml:"include,omitempty"`
|
||||
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude,omitempty"`
|
||||
IgnoreMessage string `yaml:"ignore_message,omitempty"`
|
||||
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
|
||||
}{}
|
||||
|
||||
var out2 yamlBaseTypes.StringOrSlice
|
||||
|
||||
err1 := value.Decode(&out1)
|
||||
err2 := value.Decode(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude
|
||||
c.IgnoreMessage = out1.IgnoreMessage
|
||||
c.OnEmpty = out1.OnEmpty
|
||||
c.Include = append( //nolint:gocritic
|
||||
out1.Include,
|
||||
out2...,
|
||||
)
|
||||
|
||||
if err1 != nil && err2 != nil {
|
||||
y, _ := yaml.Marshal(value)
|
||||
return fmt.Errorf("could not parse condition: %s", y)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match returns true if file paths in string slice matches the include and not exclude patterns
|
||||
// or if commit message contains ignore message.
|
||||
func (c *Path) Match(v []string, message string) bool {
|
||||
// ignore file pattern matches if the commit message contains a pattern
|
||||
if len(c.IgnoreMessage) > 0 && strings.Contains(strings.ToLower(message), strings.ToLower(c.IgnoreMessage)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// return value based on 'on_empty', if there are no commit files (empty commit)
|
||||
if len(v) == 0 {
|
||||
return c.OnEmpty.Bool()
|
||||
}
|
||||
|
||||
if len(c.Exclude) > 0 && c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
if len(c.Include) > 0 && !c.Includes(v) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Includes returns true if the string matches any of the include patterns.
|
||||
func (c *Path) Includes(v []string) bool {
|
||||
for _, pattern := range c.Include {
|
||||
for _, file := range v {
|
||||
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Excludes returns true if all of the strings match any of the exclude patterns.
|
||||
func (c *Path) Excludes(v []string) bool {
|
||||
for _, file := range v {
|
||||
matched := false
|
||||
for _, pattern := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -23,397 +23,6 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
|
||||
)
|
||||
|
||||
func TestConstraint(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with string
|
||||
want bool
|
||||
}{
|
||||
// string value
|
||||
{
|
||||
conf: "main",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "main",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "feature/*",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// slice value
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// includes block
|
||||
{
|
||||
conf: "include: main",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: main",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: feature/*",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: feature/*",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// excludes block
|
||||
{
|
||||
conf: "exclude: main",
|
||||
with: "develop",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: main",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: feature/*",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: feature/*",
|
||||
with: "feature/foo",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ main, develop ]",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ feature/*, bar ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ feature/*, bar ]",
|
||||
with: "feature/foo",
|
||||
want: false,
|
||||
},
|
||||
// include and exclude blocks
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ develop ] }",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ feature/bar ] }",
|
||||
with: "feature/bar",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ main, develop ] }",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
// empty blocks
|
||||
{
|
||||
conf: "",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraint(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstraintList(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with []string
|
||||
message string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
conf: "",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "CHANGELOG.md",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'*.md'",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "['*.md']",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/*'",
|
||||
with: []string{"docs/README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/*'",
|
||||
with: []string{"docs/sub/README.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "'docs/**'",
|
||||
with: []string{"docs/README.md", "docs/sub/README.md", "docs/sub-sub/README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/**'",
|
||||
with: []string{"README.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ] }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ README.md ] }",
|
||||
with: []string{"design.md"},
|
||||
want: true,
|
||||
},
|
||||
// include and exclude blocks
|
||||
{
|
||||
conf: "{ include: [ '*.md', '*.ini' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md", "CHANGELOG.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md", "CHANGELOG.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md, docs/**/*.md ] }",
|
||||
with: []string{"docs/main.md", "CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md, docs/**/*.md ] }",
|
||||
with: []string{"docs/main.md", "CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
// commit message ignore matches
|
||||
{
|
||||
conf: "{ include: [ README.md ], ignore_message: '[ALL]' }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ '*.php' ], ignore_message: '[ALL]' }",
|
||||
with: []string{"myfile.php"},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ ignore_message: '[ALL]' }",
|
||||
with: []string{},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
// empty commit
|
||||
{
|
||||
conf: "{ include: [ README.md ] }",
|
||||
with: []string{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ], on_empty: false }",
|
||||
with: []string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ], on_empty: true }",
|
||||
with: []string{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraintPath(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with, test.message))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstraintMap(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with map[string]string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
conf: "GOLANG: 1.7",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "GOLANG: tip",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.* }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, BRANCH: release/**/test }",
|
||||
with: map[string]string{"GOLANG": "1.7", "BRANCH": "release/v1.12.1//test"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, BRANCH: release/**/test }",
|
||||
with: map[string]string{"GOLANG": "1.7", "BRANCH": "release/v1.12.1/qest"},
|
||||
want: false,
|
||||
},
|
||||
// include syntax
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7 }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: tip }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: false,
|
||||
},
|
||||
// exclude syntax
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7 }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: tip }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
// exclude AND include values
|
||||
{
|
||||
conf: "{ include: { GOLANG: 1.7 }, exclude: { GOLANG: 1.7 } }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
// blanks
|
||||
{
|
||||
conf: "",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "GOLANG: 1.7",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.0 }",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraintMap(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with), "config: '%s', with: '%s'", test.conf, test.with)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstraintStatusSuccess(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
@@ -576,21 +185,3 @@ func parseConstraints(t *testing.T, s string) *When {
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
|
||||
func parseConstraint(t *testing.T, s string) *List {
|
||||
c := &List{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
|
||||
func parseConstraintMap(t *testing.T, s string) *Map {
|
||||
c := &Map{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
|
||||
func parseConstraintPath(t *testing.T, s string) *Path {
|
||||
c := &Path{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
|
||||
119
pipeline/frontend/yaml/constraint/list.go
Normal file
119
pipeline/frontend/yaml/constraint/list.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"go.uber.org/multierr"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base"
|
||||
)
|
||||
|
||||
// List defines a runtime constraint for exclude & include string slices.
|
||||
type List struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
// IsEmpty return true if a constraint has no conditions.
|
||||
func (c List) IsEmpty() bool {
|
||||
return len(c.Include) == 0 && len(c.Exclude) == 0
|
||||
}
|
||||
|
||||
// Match returns true if the string matches the include patterns and does not
|
||||
// match any of the exclude patterns.
|
||||
func (c *List) Match(v string) bool {
|
||||
if c == nil {
|
||||
return true
|
||||
}
|
||||
if c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
if c.Includes(v) {
|
||||
return true
|
||||
}
|
||||
if len(c.Include) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Includes returns true if the string matches the include patterns.
|
||||
func (c *List) Includes(v string) bool {
|
||||
for _, pattern := range c.Include {
|
||||
if ok, _ := doublestar.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Excludes returns true if the string matches the exclude patterns.
|
||||
func (c *List) Excludes(v string) bool {
|
||||
for _, pattern := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint.
|
||||
func (c *List) UnmarshalYAML(value *yaml.Node) error {
|
||||
out1 := struct {
|
||||
Include yamlBaseTypes.StringOrSlice
|
||||
Exclude yamlBaseTypes.StringOrSlice
|
||||
}{}
|
||||
|
||||
var out2 yamlBaseTypes.StringOrSlice
|
||||
|
||||
err1 := value.Decode(&out1)
|
||||
err2 := value.Decode(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude
|
||||
c.Include = append( //nolint:gocritic
|
||||
out1.Include,
|
||||
out2...,
|
||||
)
|
||||
|
||||
if err1 != nil && err2 != nil {
|
||||
y, _ := yaml.Marshal(value)
|
||||
return fmt.Errorf("could not parse condition: %s: %w", y, multierr.Append(err1, err2))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (c List) MarshalYAML() (any, error) {
|
||||
switch {
|
||||
case len(c.Include) == 0 && len(c.Exclude) == 0:
|
||||
return nil, nil
|
||||
case len(c.Exclude) == 0:
|
||||
return yamlBaseTypes.StringOrSlice(c.Include), nil
|
||||
default:
|
||||
// we can not return type List as it would lead to infinite recursion :/
|
||||
return struct {
|
||||
Include yamlBaseTypes.StringOrSlice `yaml:"include,omitempty"`
|
||||
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude,omitempty"`
|
||||
}{
|
||||
Include: c.Include,
|
||||
Exclude: c.Exclude,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
167
pipeline/frontend/yaml/constraint/list_test.go
Normal file
167
pipeline/frontend/yaml/constraint/list_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConstraintList(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with string
|
||||
want bool
|
||||
}{
|
||||
// string value
|
||||
{
|
||||
conf: "main",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "main",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "feature/*",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// slice value
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "[ main, feature/* ]",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// includes block
|
||||
{
|
||||
conf: "include: main",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: main",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: feature/*",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: feature/*",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "develop",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: [ main, feature/* ]",
|
||||
with: "feature/foo",
|
||||
want: true,
|
||||
},
|
||||
// excludes block
|
||||
{
|
||||
conf: "exclude: main",
|
||||
with: "develop",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: main",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: feature/*",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: feature/*",
|
||||
with: "feature/foo",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ main, develop ]",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ feature/*, bar ]",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: [ feature/*, bar ]",
|
||||
with: "feature/foo",
|
||||
want: false,
|
||||
},
|
||||
// include and exclude blocks
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ develop ] }",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ feature/bar ] }",
|
||||
with: "feature/bar",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ main, feature/* ], exclude: [ main, develop ] }",
|
||||
with: "main",
|
||||
want: false,
|
||||
},
|
||||
// empty blocks
|
||||
{
|
||||
conf: "",
|
||||
with: "main",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraintList(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with))
|
||||
}
|
||||
}
|
||||
|
||||
func parseConstraintList(t *testing.T, s string) *List {
|
||||
c := &List{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
99
pipeline/frontend/yaml/constraint/map.go
Normal file
99
pipeline/frontend/yaml/constraint/map.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import "github.com/bmatcuk/doublestar/v4"
|
||||
|
||||
// Map defines a runtime constraint for exclude & include map strings.
|
||||
type Map struct {
|
||||
Include map[string]string `yaml:"include,omitempty"`
|
||||
Exclude map[string]string `yaml:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// Match returns true if the params matches the include key values and does not
|
||||
// match any of the exclude key values.
|
||||
func (c *Map) Match(params map[string]string) bool {
|
||||
// when no includes or excludes automatically match
|
||||
if c == nil || len(c.Include) == 0 && len(c.Exclude) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Exclusions are processed first. So we can include everything and then
|
||||
// selectively include others.
|
||||
if len(c.Exclude) != 0 {
|
||||
var matches int
|
||||
|
||||
for key, val := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(val, params[key]); ok {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
if matches == len(c.Exclude) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for key, val := range c.Include {
|
||||
if ok, _ := doublestar.Match(val, params[key]); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint map.
|
||||
func (c *Map) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
out1 := struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}{
|
||||
Include: map[string]string{},
|
||||
Exclude: map[string]string{},
|
||||
}
|
||||
|
||||
out2 := map[string]string{}
|
||||
|
||||
_ = unmarshal(&out1) // it contains include and exclude statement
|
||||
_ = unmarshal(&out2) // it contains no include/exclude statement, assume include as default
|
||||
|
||||
c.Include = out1.Include
|
||||
c.Exclude = out1.Exclude
|
||||
for k, v := range out2 {
|
||||
c.Include[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (c Map) MarshalYAML() (any, error) {
|
||||
switch {
|
||||
case len(c.Include) == 0 && len(c.Exclude) == 0:
|
||||
return nil, nil
|
||||
case len(c.Exclude) == 0:
|
||||
return c.Include, nil
|
||||
case len(c.Include) == 0 && len(c.Exclude) != 0:
|
||||
return struct {
|
||||
Exclude map[string]string
|
||||
}{Exclude: c.Exclude}, nil
|
||||
default:
|
||||
// we can not return type Map as it would lead to infinite recursion :/
|
||||
return struct {
|
||||
Include map[string]string `yaml:"include,omitempty"`
|
||||
Exclude map[string]string `yaml:"exclude,omitempty"`
|
||||
}{
|
||||
Include: c.Include,
|
||||
Exclude: c.Exclude,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
150
pipeline/frontend/yaml/constraint/map_test.go
Normal file
150
pipeline/frontend/yaml/constraint/map_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConstraintMap(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with map[string]string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
conf: "GOLANG: 1.7",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "GOLANG: tip",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.* }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, BRANCH: release/**/test }",
|
||||
with: map[string]string{"GOLANG": "1.7", "BRANCH": "release/v1.12.1//test"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, BRANCH: release/**/test }",
|
||||
with: map[string]string{"GOLANG": "1.7", "BRANCH": "release/v1.12.1/qest"},
|
||||
want: false,
|
||||
},
|
||||
// include syntax
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7 }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: tip }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: false,
|
||||
},
|
||||
// exclude syntax
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7 }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: tip }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.1", "MYSQL": "5.6"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
// exclude AND include values
|
||||
{
|
||||
conf: "{ include: { GOLANG: 1.7 }, exclude: { GOLANG: 1.7 } }",
|
||||
with: map[string]string{"GOLANG": "1.7"},
|
||||
want: false,
|
||||
},
|
||||
// blanks
|
||||
{
|
||||
conf: "",
|
||||
with: map[string]string{"GOLANG": "1.7", "REDIS": "3.0"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "GOLANG: 1.7",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ GOLANG: 1.7, REDIS: 3.0 }",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "include: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "exclude: { GOLANG: 1.7, REDIS: 3.1 }",
|
||||
with: map[string]string{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraintMap(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with), "config: '%s', with: '%s'", test.conf, test.with)
|
||||
}
|
||||
}
|
||||
|
||||
func parseConstraintMap(t *testing.T, s string) *Map {
|
||||
c := &Map{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
139
pipeline/frontend/yaml/constraint/path.go
Normal file
139
pipeline/frontend/yaml/constraint/path.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base"
|
||||
)
|
||||
|
||||
// Path defines a runtime constrain for exclude & include paths.
|
||||
type Path struct {
|
||||
Include []string `yaml:"include,omitempty"`
|
||||
Exclude []string `yaml:"exclude,omitempty"`
|
||||
IgnoreMessage string `yaml:"ignore_message,omitempty"`
|
||||
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal the constraint.
|
||||
func (c *Path) UnmarshalYAML(value *yaml.Node) error {
|
||||
out1 := struct {
|
||||
Include yamlBaseTypes.StringOrSlice `yaml:"include"`
|
||||
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude"`
|
||||
IgnoreMessage string `yaml:"ignore_message"`
|
||||
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty"`
|
||||
}{}
|
||||
|
||||
var out2 yamlBaseTypes.StringOrSlice
|
||||
|
||||
err1 := value.Decode(&out1)
|
||||
err2 := value.Decode(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude
|
||||
c.IgnoreMessage = out1.IgnoreMessage
|
||||
c.OnEmpty = out1.OnEmpty
|
||||
c.Include = append( //nolint:gocritic
|
||||
out1.Include,
|
||||
out2...,
|
||||
)
|
||||
|
||||
if err1 != nil && err2 != nil {
|
||||
y, _ := yaml.Marshal(value)
|
||||
return fmt.Errorf("could not parse condition: %s", y)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (c Path) MarshalYAML() (any, error) {
|
||||
// if only Include is set return simple syntax
|
||||
if len(c.Exclude) == 0 &&
|
||||
len(c.IgnoreMessage) == 0 &&
|
||||
c.OnEmpty.Bool() {
|
||||
if len(c.Include) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return yamlBaseTypes.StringOrSlice(c.Include), nil
|
||||
}
|
||||
// we can not return type Path as it would lead to infinite recursion :/
|
||||
return struct {
|
||||
Include yamlBaseTypes.StringOrSlice `yaml:"include,omitempty"`
|
||||
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude,omitempty"`
|
||||
IgnoreMessage string `yaml:"ignore_message,omitempty"`
|
||||
OnEmpty yamlBaseTypes.BoolTrue `yaml:"on_empty,omitempty"`
|
||||
}{
|
||||
Include: c.Include,
|
||||
Exclude: c.Exclude,
|
||||
IgnoreMessage: c.IgnoreMessage,
|
||||
OnEmpty: c.OnEmpty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Match returns true if file paths in string slice matches the include and not exclude patterns
|
||||
// or if commit message contains ignore message.
|
||||
func (c *Path) Match(v []string, message string) bool {
|
||||
// ignore file pattern matches if the commit message contains a pattern
|
||||
if len(c.IgnoreMessage) > 0 && strings.Contains(strings.ToLower(message), strings.ToLower(c.IgnoreMessage)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// return value based on 'on_empty', if there are no commit files (empty commit)
|
||||
if len(v) == 0 {
|
||||
return c.OnEmpty.Bool()
|
||||
}
|
||||
|
||||
if len(c.Exclude) > 0 && c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
if len(c.Include) > 0 && !c.Includes(v) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Includes returns true if the string matches any of the include patterns.
|
||||
func (c *Path) Includes(v []string) bool {
|
||||
for _, pattern := range c.Include {
|
||||
for _, file := range v {
|
||||
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Excludes returns true if all of the strings match any of the exclude patterns.
|
||||
func (c *Path) Excludes(v []string) bool {
|
||||
for _, file := range v {
|
||||
matched := false
|
||||
for _, pattern := range c.Exclude {
|
||||
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
158
pipeline/frontend/yaml/constraint/path_test.go
Normal file
158
pipeline/frontend/yaml/constraint/path_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2025 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constraint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConstraintPath(t *testing.T) {
|
||||
testdata := []struct {
|
||||
conf string
|
||||
with []string
|
||||
message string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
conf: "",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "CHANGELOG.md",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'*.md'",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "['*.md']",
|
||||
with: []string{"CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/*'",
|
||||
with: []string{"docs/README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/*'",
|
||||
with: []string{"docs/sub/README.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "'docs/**'",
|
||||
with: []string{"docs/README.md", "docs/sub/README.md", "docs/sub-sub/README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "'docs/**'",
|
||||
with: []string{"README.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ] }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ README.md ] }",
|
||||
with: []string{"design.md"},
|
||||
want: true,
|
||||
},
|
||||
// include and exclude blocks
|
||||
{
|
||||
conf: "{ include: [ '*.md', '*.ini' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ '*.md' ], exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md", "CHANGELOG.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md ] }",
|
||||
with: []string{"README.md", "CHANGELOG.md"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md, docs/**/*.md ] }",
|
||||
with: []string{"docs/main.md", "CHANGELOG.md"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ CHANGELOG.md, docs/**/*.md ] }",
|
||||
with: []string{"docs/main.md", "CHANGELOG.md", "README.md"},
|
||||
want: true,
|
||||
},
|
||||
// commit message ignore matches
|
||||
{
|
||||
conf: "{ include: [ README.md ], ignore_message: '[ALL]' }",
|
||||
with: []string{"CHANGELOG.md"},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ exclude: [ '*.php' ], ignore_message: '[ALL]' }",
|
||||
with: []string{"myfile.php"},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ ignore_message: '[ALL]' }",
|
||||
with: []string{},
|
||||
message: "Build them [ALL]",
|
||||
want: true,
|
||||
},
|
||||
// empty commit
|
||||
{
|
||||
conf: "{ include: [ README.md ] }",
|
||||
with: []string{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ], on_empty: false }",
|
||||
with: []string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
conf: "{ include: [ README.md ], on_empty: true }",
|
||||
with: []string{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testdata {
|
||||
c := parseConstraintPath(t, test.conf)
|
||||
assert.Equal(t, test.want, c.Match(test.with, test.message))
|
||||
}
|
||||
}
|
||||
|
||||
func parseConstraintPath(t *testing.T, s string) *Path {
|
||||
c := &Path{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(s), c))
|
||||
return c
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
|
||||
yaml_base_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base"
|
||||
@@ -69,7 +71,7 @@ func TestParse(t *testing.T) {
|
||||
assert.Empty(t, out.Steps.ContainerList[0].When.Constraints)
|
||||
assert.Equal(t, "notify_success", out.Steps.ContainerList[1].Name)
|
||||
assert.Equal(t, "plugins/slack", out.Steps.ContainerList[1].Image)
|
||||
assert.Equal(t, yaml_base_types.StringOrSlice{"success"}, out.Steps.ContainerList[1].When.Constraints[0].Event)
|
||||
assert.Equal(t, yaml_base_types.StringOrSlice{"push"}, out.Steps.ContainerList[1].When.Constraints[0].Event)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -170,9 +172,6 @@ when:
|
||||
- tester2
|
||||
- branch:
|
||||
- tester
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
workspace:
|
||||
path: src/github.com/octocat/hello-world
|
||||
base: /go
|
||||
@@ -189,6 +188,7 @@ steps:
|
||||
- go build
|
||||
when:
|
||||
event: push
|
||||
depends_on: []
|
||||
notify:
|
||||
image: slack
|
||||
channel: dev
|
||||
@@ -217,38 +217,86 @@ steps:
|
||||
`
|
||||
|
||||
var sampleVarYaml = `
|
||||
_slack: &SLACK
|
||||
variables: &SLACK
|
||||
image: plugins/slack
|
||||
steps:
|
||||
notify_fail: *SLACK
|
||||
notify_success:
|
||||
<< : *SLACK
|
||||
when:
|
||||
event: success
|
||||
event: push
|
||||
echo:
|
||||
when:
|
||||
- path: wow.sh
|
||||
repo: "test"
|
||||
branch:
|
||||
exclude: main
|
||||
- path:
|
||||
- test.yaml
|
||||
- test.zig
|
||||
- path:
|
||||
exclude: a
|
||||
on_empty: true
|
||||
- ref: ref/tags/v1
|
||||
path:
|
||||
env:
|
||||
image: print
|
||||
environment:
|
||||
DRIVER: next
|
||||
PLATFORM: linux
|
||||
`
|
||||
|
||||
var sampleSliceYaml = `
|
||||
steps:
|
||||
nil_slice:
|
||||
image: plugins/slack
|
||||
empty_slice:
|
||||
image: plugins/slack
|
||||
depends_on: []
|
||||
`
|
||||
func TestReSerialize(t *testing.T) {
|
||||
work1, err := ParseString(sampleVarYaml)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
workBin, err := yaml.Marshal(work1)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
assert.EqualValues(t, `steps:
|
||||
- name: notify_fail
|
||||
image: plugins/slack
|
||||
- name: notify_success
|
||||
image: plugins/slack
|
||||
when:
|
||||
event: push
|
||||
- name: echo
|
||||
when:
|
||||
- repo: test
|
||||
branch:
|
||||
exclude: main
|
||||
path: wow.sh
|
||||
- path:
|
||||
- test.yaml
|
||||
- test.zig
|
||||
- path:
|
||||
exclude: a
|
||||
- ref: ref/tags/v1
|
||||
- name: env
|
||||
image: print
|
||||
environment:
|
||||
DRIVER: next
|
||||
PLATFORM: linux
|
||||
skip_clone: false
|
||||
`, string(workBin))
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
t.Run("should marshal a not set slice to nil", func(t *testing.T) {
|
||||
out, err := ParseString(sampleSliceYaml)
|
||||
assert.NoError(t, err)
|
||||
out, err := ParseString(sampleYaml)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("should marshal a not set slice to nil", func(t *testing.T) {
|
||||
assert.Equal(t, "test", out.Steps.ContainerList[0].Name)
|
||||
assert.Nil(t, out.Steps.ContainerList[0].DependsOn)
|
||||
assert.Empty(t, out.Steps.ContainerList[0].DependsOn)
|
||||
})
|
||||
|
||||
t.Run("should marshal an empty slice", func(t *testing.T) {
|
||||
out, err := ParseString(sampleSliceYaml)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "build", out.Steps.ContainerList[1].Name)
|
||||
assert.NotNil(t, out.Steps.ContainerList[1].DependsOn)
|
||||
assert.Empty(t, (out.Steps.ContainerList[1].DependsOn))
|
||||
})
|
||||
|
||||
@@ -42,7 +42,16 @@ func (b *BoolTrue) UnmarshalYAML(value *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (b BoolTrue) MarshalYAML() (any, error) {
|
||||
return b.Bool(), nil
|
||||
}
|
||||
|
||||
// Bool returns the bool value.
|
||||
func (b BoolTrue) Bool() bool {
|
||||
return !b.value
|
||||
}
|
||||
|
||||
func ToBoolTrue(v bool) BoolTrue {
|
||||
return BoolTrue{value: !v}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,27 @@ func TestBoolTrue(t *testing.T) {
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Run("marshal empty", func(t *testing.T) {
|
||||
in := &BoolTrue{}
|
||||
out, err := yaml.Marshal(&in)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "true\n", string(out))
|
||||
})
|
||||
|
||||
t.Run("marshal true", func(t *testing.T) {
|
||||
in := ToBoolTrue(true)
|
||||
out, err := yaml.Marshal(&in)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "true\n", string(out))
|
||||
})
|
||||
|
||||
t.Run("marshal false", func(t *testing.T) {
|
||||
in := ToBoolTrue(false)
|
||||
out, err := yaml.Marshal(&in)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "false\n", string(out))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// TODO: delete file after v3.0.0 release
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EnvironmentMap map[string]any
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaler interface.
|
||||
func (s *EnvironmentMap) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var mapType map[string]any
|
||||
err := unmarshal(&mapType)
|
||||
if err == nil {
|
||||
*s = mapType
|
||||
return nil
|
||||
}
|
||||
|
||||
var sliceType []any
|
||||
if err := unmarshal(&sliceType); err == nil {
|
||||
return fmt.Errorf("list syntax for 'environment' has been removed, use map syntax instead (https://woodpecker-ci.org/docs/usage/environment)")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// TODO: delete file after v3.0.0 release
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type StructMap struct {
|
||||
Foos EnvironmentMap `yaml:"foos,omitempty"`
|
||||
}
|
||||
|
||||
func TestEnvironmentMapYaml(t *testing.T) {
|
||||
str := `{foos: [bar=baz, far=faz]}`
|
||||
s := StructMap{}
|
||||
err := yaml.Unmarshal([]byte(str), &s)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualValues(t, "list syntax for 'environment' has been removed, use map syntax instead (https://woodpecker-ci.org/docs/usage/environment)", err.Error())
|
||||
}
|
||||
|
||||
s.Foos = EnvironmentMap{"bar": "baz", "far": "faz"}
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.NoError(t, err)
|
||||
str = `foos:
|
||||
bar: baz
|
||||
far: faz
|
||||
`
|
||||
assert.EqualValues(t, str, string(d))
|
||||
|
||||
s2 := StructMap{}
|
||||
assert.NoError(t, yaml.Unmarshal(d, &s2))
|
||||
|
||||
assert.Equal(t, EnvironmentMap{"bar": "baz", "far": "faz"}, s2.Foos)
|
||||
}
|
||||
@@ -44,6 +44,16 @@ func (s *StringOrSlice) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return errors.New("failed to unmarshal StringOrSlice")
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (s StringOrSlice) MarshalYAML() (any, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, nil
|
||||
} else if len(s) == 1 {
|
||||
return s[0], nil
|
||||
}
|
||||
return []string(s), nil
|
||||
}
|
||||
|
||||
func toStrings(s []any) ([]string, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
|
||||
@@ -22,10 +22,49 @@ import (
|
||||
)
|
||||
|
||||
type StructStringOrSlice struct {
|
||||
Foo StringOrSlice
|
||||
Foo StringOrSlice `yaml:"foo"`
|
||||
Bar StringOrSlice `yaml:"bar,omitempty"`
|
||||
}
|
||||
|
||||
func TestStringOrSliceYaml(t *testing.T) {
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
str := `{foo: [bar, baz]}`
|
||||
|
||||
s := StructStringOrSlice{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(str), &s))
|
||||
|
||||
assert.Equal(t, StringOrSlice{"bar", "baz"}, s.Foo)
|
||||
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
s2 := StructStringOrSlice{}
|
||||
assert.NoError(t, yaml.Unmarshal(d, &s2))
|
||||
|
||||
assert.Equal(t, StringOrSlice{"bar", "baz"}, s2.Foo)
|
||||
})
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
str := StructStringOrSlice{}
|
||||
out, err := yaml.Marshal(str)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "foo: null\n", string(out))
|
||||
|
||||
str = StructStringOrSlice{Foo: []string{"a\""}}
|
||||
out, err = yaml.Marshal(str)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "foo: a\"\n", string(out))
|
||||
|
||||
str = StructStringOrSlice{Foo: []string{"a", "b", "c"}}
|
||||
out, err = yaml.Marshal(str)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, `foo:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
`, string(out))
|
||||
})
|
||||
|
||||
str := `{foo: [bar, "baz"]}`
|
||||
s := StructStringOrSlice{}
|
||||
assert.NoError(t, yaml.Unmarshal([]byte(str), &s))
|
||||
|
||||
@@ -33,13 +33,14 @@ type (
|
||||
// Container defines a container.
|
||||
Container struct {
|
||||
// common
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Image string `yaml:"image,omitempty"`
|
||||
Pull bool `yaml:"pull,omitempty"`
|
||||
Commands base.StringOrSlice `yaml:"commands,omitempty"`
|
||||
Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"`
|
||||
Directory string `yaml:"directory,omitempty"`
|
||||
Settings map[string]any `yaml:"settings"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Image string `yaml:"image,omitempty"`
|
||||
Pull bool `yaml:"pull,omitempty"`
|
||||
Commands base.StringOrSlice `yaml:"commands,omitempty"`
|
||||
Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"`
|
||||
Directory string `yaml:"directory,omitempty"`
|
||||
Settings map[string]any `yaml:"settings,omitempty"`
|
||||
Environment map[string]any `yaml:"environment,omitempty"`
|
||||
// flow control
|
||||
DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"`
|
||||
When constraint.When `yaml:"when,omitempty"`
|
||||
@@ -56,9 +57,6 @@ type (
|
||||
|
||||
// ACTIVE DEVELOPMENT BELOW
|
||||
|
||||
// TODO: remove base.EnvironmentMap and use map[string]any after v3.0.0 release
|
||||
Environment base.EnvironmentMap `yaml:"environment,omitempty"`
|
||||
|
||||
// Remove after v3.1.0
|
||||
Secrets []any `yaml:"secrets,omitempty"`
|
||||
|
||||
@@ -121,6 +119,11 @@ func (c *ContainerList) UnmarshalYAML(value *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements custom Yaml marshaling.
|
||||
func (c ContainerList) MarshalYAML() (any, error) {
|
||||
return c.ContainerList, nil
|
||||
}
|
||||
|
||||
func (c *Container) IsPlugin() bool {
|
||||
return len(c.Commands) == 0 &&
|
||||
len(c.Entrypoint) == 0 &&
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// Supported database drivers
|
||||
// Supported database drivers.
|
||||
const (
|
||||
DriverMysql = "mysql"
|
||||
DriverPostgres = "postgres"
|
||||
|
||||
Reference in New Issue
Block a user