1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2025-10-30 23:27:39 +02:00

Make pipeline/frontend/yaml/* types able to be marshaled back to YAML (#1835)

This commit is contained in:
6543
2025-10-25 10:27:22 +02:00
committed by GitHub
parent 5439a97be8
commit 3d43a3fce6
18 changed files with 1022 additions and 769 deletions

View File

@@ -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"},
},
}}}},

View File

@@ -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
}

View File

@@ -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
}

View 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
}
}

View 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
}

View 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
}
}

View 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
}

View 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
}

View 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
}

View File

@@ -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))
})

View File

@@ -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}
}

View File

@@ -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))
})
})
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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))

View File

@@ -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 &&

View File

@@ -22,7 +22,7 @@ import (
_ "github.com/lib/pq"
)
// Supported database drivers
// Supported database drivers.
const (
DriverMysql = "mysql"
DriverPostgres = "postgres"