Add when evaluate filter (#1213)

closes #312 
closes #224
closes #963

Have a look for

https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md
This commit is contained in:
Anbraten 2022-10-06 01:49:23 +02:00 committed by GitHub
parent f1339412eb
commit 287800ac62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 159 additions and 39 deletions

View file

@ -157,7 +157,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
}
// compiles the yaml file
compiled := compiler.New(
compiled, err := compiler.New(
compiler.WithEscalated(
c.StringSlice("privileged")...,
),
@ -185,6 +185,9 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
compiler.WithSecret(secrets...),
compiler.WithEnviron(droneEnv),
).Compile(conf)
if err != nil {
return err
}
backend.Init(context.WithValue(c.Context, types.CliContext, c))

View file

@ -435,6 +435,33 @@ when:
**Hint:** Passing a defined ignore-message like `[ALL]` inside the commit message will ignore all path conditions.
#### `evaluate`
Execute a step only if the provided evaluate expression is equal to true. Each [`CI_` variable](./50-environment.md#built-in-environment-variables) can be used inside the expression.
The expression syntax can be found in [the docs](https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md) of the underlying library.
Run on pushes to the default branch for the repository `owner/repo`:
```yaml
when:
- evaluate: 'CI_BUILD_EVENT == "push" && CI_REPO == "owner/repo" && CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
```
Run on commits created by user `woodpecker-ci`:
```yaml
when:
- evaluate: 'CI_COMMIT_AUTHOR == "woodpecker-ci"'
```
Skip all commits containing `please ignore me` in the commit message:
```yaml
when:
- evaluate: 'not (CI_COMMIT_MESSAGE contains "please ignore me")'
```
### `group` - Parallel execution
Woodpecker supports parallel step execution for same-machine fan-in and fan-out. Parallel steps are configured using the `group` attribute. This instructs the pipeline runner to execute the named group in parallel.

1
go.mod
View file

@ -5,6 +5,7 @@ go 1.18
require (
code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe
codeberg.org/6543/go-yaml2json v0.2.1
github.com/antonmedv/expr v1.9.0
github.com/bmatcuk/doublestar/v4 v4.2.0
github.com/caddyserver/certmagic v0.17.1-0.20220901172127-2e22c6fa8c47
github.com/docker/cli v20.10.17+incompatible

16
go.sum
View file

@ -66,6 +66,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
@ -85,6 +86,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -143,6 +146,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -195,6 +199,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@ -506,6 +512,8 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -532,6 +540,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -624,6 +634,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@ -663,6 +674,8 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -683,6 +696,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
@ -716,6 +730,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -962,6 +977,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -93,13 +93,15 @@ func New(opts ...Option) *Compiler {
// Compile compiles the YAML configuration to the pipeline intermediate
// representation configuration format.
func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
func (c *Compiler) Compile(conf *yaml.Config) (*backend.Config, error) {
config := new(backend.Config)
if !conf.When.Match(c.metadata, true) {
if match, err := conf.When.Match(c.metadata, true); !match && err == nil {
// This pipeline does not match the configured filter so return an empty config and stop further compilation.
// An empty pipeline will just be skipped completely.
return config
return config, nil
} else if err != nil {
return nil, err
}
// create a default volume
@ -166,9 +168,12 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
config.Stages = append(config.Stages, stage)
} else if !c.local && !conf.SkipClone {
for i, container := range conf.Clone.Containers {
if !container.When.Match(c.metadata, false) {
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
continue
} else if err != nil {
return nil, err
}
stage := new(backend.Stage)
stage.Name = fmt.Sprintf("%s_clone_%v", c.prefix, i)
stage.Alias = container.Name
@ -193,8 +198,10 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
stage.Alias = nameServices
for i, container := range conf.Services.Containers {
if !container.When.Match(c.metadata, false) {
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
continue
} else if err != nil {
return nil, err
}
name := fmt.Sprintf("%s_%s_%d", c.prefix, nameServices, i)
@ -213,8 +220,10 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
continue
}
if !container.When.Match(c.metadata, false) {
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
continue
} else if err != nil {
return nil, err
}
if stage == nil || group != container.Group || container.Group == "" {
@ -233,7 +242,7 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
c.setupCacheRebuild(conf, config)
return config
return config, nil
}
func (c *Compiler) setupCache(conf *yaml.Config, ir *backend.Config) {

View file

@ -79,37 +79,45 @@ func TestParse(t *testing.T) {
}
g.It("Should match event tester", func() {
g.Assert(matchConfig.When.Match(frontend.Metadata{
match, err := matchConfig.When.Match(frontend.Metadata{
Curr: frontend.Build{
Event: "tester",
},
}, false)).Equal(true)
}, false)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
g.It("Should match event tester2", func() {
g.Assert(matchConfig.When.Match(frontend.Metadata{
match, err := matchConfig.When.Match(frontend.Metadata{
Curr: frontend.Build{
Event: "tester2",
},
}, false)).Equal(true)
}, false)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
g.It("Should match branch tester", func() {
g.Assert(matchConfig.When.Match(frontend.Metadata{
match, err := matchConfig.When.Match(frontend.Metadata{
Curr: frontend.Build{
Commit: frontend.Commit{
Branch: "tester",
},
},
}, true)).Equal(true)
}, true)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
g.It("Should not match event push", func() {
g.Assert(matchConfig.When.Match(frontend.Metadata{
match, err := matchConfig.When.Match(frontend.Metadata{
Curr: frontend.Build{
Event: "push",
},
}, false)).Equal(false)
}, false)
g.Assert(match).Equal(false)
g.Assert(err).IsNil()
})
})
})

View file

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/antonmedv/expr"
"github.com/bmatcuk/doublestar/v4"
"gopkg.in/yaml.v3"
@ -31,6 +32,7 @@ type (
Matrix Map
Local types.BoolTrue
Path Path
Evaluate string `yaml:"evaluate,omitempty"`
}
// List defines a runtime constraint for exclude & include string slices.
@ -58,10 +60,14 @@ func (when *When) IsEmpty() bool {
}
// Returns true if at least one of the internal constraints is true.
func (when *When) Match(metadata frontend.Metadata, global bool) bool {
func (when *When) Match(metadata frontend.Metadata, global bool) (bool, error) {
for _, c := range when.Constraints {
if c.Match(metadata, global) {
return true
match, err := c.Match(metadata, global)
if err != nil {
return false, err
}
if match {
return true, nil
}
}
@ -70,7 +76,7 @@ func (when *When) Match(metadata frontend.Metadata, global bool) bool {
empty := &Constraint{}
return empty.Match(metadata, global)
}
return false
return false, nil
}
func (when *When) IncludesStatus(status string) bool {
@ -126,7 +132,7 @@ func (when *When) UnmarshalYAML(value *yaml.Node) error {
// Match returns true if all constraints match the given input. If a single
// constraint fails a false value is returned.
func (c *Constraint) Match(metadata frontend.Metadata, global bool) bool {
func (c *Constraint) Match(metadata frontend.Metadata, global bool) (bool, error) {
match := true
if !global {
c.SetDefaultEventFilter()
@ -155,7 +161,20 @@ func (c *Constraint) Match(metadata frontend.Metadata, global bool) bool {
match = match && c.Cron.Match(metadata.Curr.Cron)
}
return match
if c.Evaluate != "" {
env := metadata.Environ()
out, err := expr.Compile(c.Evaluate, expr.Env(env), expr.AsBool())
if err != nil {
return false, err
}
result, err := expr.Run(out, env)
if err != nil {
return false, err
}
match = match && result.(bool)
}
return match, nil
}
// SetDefaultEventFilter set default e event filter if not event filter is already set

View file

@ -484,14 +484,29 @@ func TestConstraints(t *testing.T) {
},
want: false,
},
{
desc: "filter by eval based on event",
conf: `{ evaluate: 'CI_BUILD_EVENT == "push"' }`,
with: frontend.Metadata{Curr: frontend.Build{Event: frontend.EventPush}},
want: true,
},
{
desc: "filter by eval based on event and repo",
conf: `{ evaluate: 'CI_BUILD_EVENT == "push" && CI_REPO == "owner/repo"' }`,
with: frontend.Metadata{Curr: frontend.Build{Event: frontend.EventPush}, Repo: frontend.Repo{Name: "owner/repo"}},
want: true,
},
}
for _, test := range testdata {
t.Run(test.desc, func(t *testing.T) {
c := parseConstraints(t, test.conf)
got, want := c.Match(test.with, false), test.want
if got != want {
t.Errorf("Expect %+v matches %q is %v", test.with, test.conf, want)
got, err := c.Match(test.with, false)
if err != nil {
t.Errorf("Match returned error: %v", err)
}
if got != test.want {
t.Errorf("Expect %+v matches %q is %v", test.with, test.conf, test.want)
}
})
}

View file

@ -134,9 +134,16 @@ pipeline:
image: alpine
commands: echo "test"
when:
event: cron
cron:
include:
- test
- hello
exclude: hi
- event: cron
cron:
include:
- test
- hello
exclude: hi
when-evaluate:
image: alpine
commands: echo "test"
when:
- event: push
evaluate: 'CI_BUILD_EVENT == "push" && CI_REPO == "owner/repo"'

View file

@ -189,6 +189,10 @@
"additionalProperties": false
}
]
},
"evaluate": {
"description": "Execute a step only if the expression evaluates to true. Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#evaluate",
"type": "string"
}
}
},
@ -344,6 +348,10 @@
"additionalProperties": false
}
]
},
"evaluate": {
"description": "Execute a step only if the expression evaluates to true. Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#evaluate",
"type": "string"
}
}
},
@ -499,10 +507,7 @@
"description": "expose ports to which other steps can connect to",
"type": "array",
"items": {
"oneOf": [
{ "type": "number" },
{ "type": "string" }
]
"oneOf": [{ "type": "number" }, { "type": "string" }]
},
"minLength": 1
}

View file

@ -72,8 +72,10 @@ func checkIfFiltered(build *model.Build, remoteYamlConfigs []*remote.FileMeta) (
log.Trace().Msgf("config '%s': %#v", remoteYamlConfig.Name, parsedPipelineConfig)
// ignore if the pipeline was filtered by matched constraints
if !parsedPipelineConfig.When.Match(matchMetadata, true) {
if match, err := parsedPipelineConfig.When.Match(matchMetadata, true); !match && err == nil {
continue
} else if err != nil {
return false, err
}
// ignore if the pipeline was filtered by the branch (legacy)

View file

@ -119,11 +119,16 @@ func (b *ProcBuilder) Build() ([]*BuildItem, error) {
}
// checking if filtered.
if !parsed.When.Match(metadata, true) {
if match, err := parsed.When.Match(metadata, true); !match && err == nil {
log.Debug().Str("pipeline", proc.Name).Msg(
"Marked as skipped, dose not match metadata",
)
proc.State = model.StatusSkipped
} else if err != nil {
log.Debug().Str("pipeline", proc.Name).Msg(
"Pipeline config could not be parsed",
)
return nil, err
}
// TODO: deprecated branches filter => remove after some time
@ -134,7 +139,10 @@ func (b *ProcBuilder) Build() ([]*BuildItem, error) {
proc.State = model.StatusSkipped
}
ir := b.toInternalRepresentation(parsed, environ, metadata, proc.ID)
ir, err := b.toInternalRepresentation(parsed, environ, metadata, proc.ID)
if err != nil {
return nil, err
}
if len(ir.Stages) == 0 {
continue
@ -229,7 +237,7 @@ func (b *ProcBuilder) environmentVariables(metadata frontend.Metadata, axis matr
return environ
}
func (b *ProcBuilder) toInternalRepresentation(parsed *yaml.Config, environ map[string]string, metadata frontend.Metadata, procID int64) *backend.Config {
func (b *ProcBuilder) toInternalRepresentation(parsed *yaml.Config, environ map[string]string, metadata frontend.Metadata, procID int64) (*backend.Config, error) {
var secrets []compiler.Secret
for _, sec := range b.Secs {
if !sec.Match(b.Curr.Event) {