1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-12-06 08:16:19 +02:00

Fixed when:evaluate on non-standard (non-CI*) env vars (#1907)

Makes it possible to evaluate `when` constraint on custom environment
variables.
This commit is contained in:
Thomas Anderson 2023-07-03 00:45:22 +03:00 committed by GitHub
parent 2ba64dcb7d
commit b616a822a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 46 additions and 19 deletions

View File

@ -444,7 +444,7 @@ when:
#### `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.
Execute a step only if the provided evaluate expression is equal to true. Both built-in [`CI_`](./50-environment.md#built-in-environment-variables) and custom 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.
@ -476,6 +476,13 @@ when:
- evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "deploy"'
```
Skip step only if `SKIP=true`, run otherwise or if undefined:
```yaml
when:
- evaluate: 'SKIP != "true"'
```
### `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.

View File

@ -1,6 +1,6 @@
# Environment variables
Woodpecker provides the ability to pass environment variables to individual pipeline steps. Example pipeline step with custom environment variables:
Woodpecker provides the ability to pass environment variables to individual pipeline steps. Note that these can't overwrite any existing, built-in variables. Example pipeline step with custom environment variables:
```diff
steps:

View File

@ -104,7 +104,7 @@ func New(opts ...Option) *Compiler {
func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, error) {
config := new(backend_types.Config)
if match, err := conf.When.Match(c.metadata, true); !match && err == nil {
if match, err := conf.When.Match(c.metadata, true, c.env); !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, nil
@ -177,7 +177,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
config.Stages = append(config.Stages, stage)
} else if !c.local && !conf.SkipClone {
for i, container := range conf.Clone.ContainerList {
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
continue
} else if err != nil {
return nil, err
@ -212,7 +212,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
stage.Alias = nameServices
for i, container := range conf.Services.ContainerList {
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
continue
} else if err != nil {
return nil, err
@ -234,7 +234,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
continue
}
if match, err := container.When.Match(c.metadata, false); !match && err == nil {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
continue
} else if err != nil {
return nil, err

View File

@ -8,6 +8,7 @@ import (
"github.com/antonmedv/expr"
"github.com/bmatcuk/doublestar/v4"
"golang.org/x/exp/maps"
"gopkg.in/yaml.v3"
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/metadata"
@ -62,9 +63,9 @@ func (when *When) IsEmpty() bool {
}
// Returns true if at least one of the internal constraints is true.
func (when *When) Match(metadata metadata.Metadata, global bool) (bool, error) {
func (when *When) Match(metadata metadata.Metadata, global bool, env map[string]string) (bool, error) {
for _, c := range when.Constraints {
match, err := c.Match(metadata, global)
match, err := c.Match(metadata, global, env)
if err != nil {
return false, err
}
@ -76,7 +77,7 @@ func (when *When) Match(metadata metadata.Metadata, global bool) (bool, error) {
if when.IsEmpty() {
// test against default Constraints
empty := &Constraint{}
return empty.Match(metadata, global)
return empty.Match(metadata, global, env)
}
return false, nil
}
@ -139,7 +140,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(m metadata.Metadata, global bool) (bool, error) {
func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]string) (bool, error) {
match := true
if !global {
// apply step only filters
@ -167,8 +168,12 @@ func (c *Constraint) Match(m metadata.Metadata, global bool) (bool, error) {
}
if c.Evaluate != "" {
env := m.Environ()
out, err := expr.Compile(c.Evaluate, expr.Env(env), expr.AsBool())
if env == nil {
env = m.Environ()
} else {
maps.Copy(env, m.Environ())
}
out, err := expr.Compile(c.Evaluate, expr.Env(env), expr.AllowUndefinedVariables(), expr.AsBool())
if err != nil {
return false, err
}

View File

@ -405,6 +405,7 @@ func TestConstraints(t *testing.T) {
desc string
conf string
with metadata.Metadata
env map[string]string
want bool
}{
{
@ -510,6 +511,20 @@ func TestConstraints(t *testing.T) {
with: metadata.Metadata{Curr: metadata.Pipeline{Event: metadata.EventPush}, Repo: metadata.Repo{Owner: "owner", Name: "repo"}},
want: true,
},
{
desc: "filter by eval based on custom variable",
conf: `{ evaluate: 'TESTVAR == "testval"' }`,
with: metadata.Metadata{Curr: metadata.Pipeline{Event: metadata.EventManual}},
env: map[string]string{"TESTVAR": "testval"},
want: true,
},
{
desc: "filter by eval based on custom variable",
conf: `{ evaluate: 'TESTVAR == "testval"' }`,
with: metadata.Metadata{Curr: metadata.Pipeline{Event: metadata.EventManual}},
env: map[string]string{"TESTVAR": "qwe"},
want: false,
},
}
for _, test := range testdata {
@ -517,7 +532,7 @@ func TestConstraints(t *testing.T) {
conf, err := frontend.EnvVarSubst(test.conf, test.with.Environ())
assert.NoError(t, err)
c := parseConstraints(t, conf)
got, err := c.Match(test.with, false)
got, err := c.Match(test.with, false, test.env)
if err != nil {
t.Errorf("Match returned error: %v", err)
}

View File

@ -84,7 +84,7 @@ func TestParse(t *testing.T) {
Curr: metadata.Pipeline{
Event: "tester",
},
}, false)
}, false, nil)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
@ -94,7 +94,7 @@ func TestParse(t *testing.T) {
Curr: metadata.Pipeline{
Event: "tester2",
},
}, false)
}, false, nil)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
@ -106,7 +106,7 @@ func TestParse(t *testing.T) {
Branch: "tester",
},
},
}, true)
}, true, nil)
g.Assert(match).Equal(true)
g.Assert(err).IsNil()
})
@ -116,7 +116,7 @@ func TestParse(t *testing.T) {
Curr: metadata.Pipeline{
Event: "push",
},
}, false)
}, false, nil)
g.Assert(match).Equal(false)
g.Assert(err).IsNil()
})

View File

@ -118,7 +118,7 @@ func (b *StepBuilder) Build() ([]*Item, error) {
}
// checking if filtered.
if match, err := parsed.When.Match(workflowMetadata, true); !match && err == nil {
if match, err := parsed.When.Match(workflowMetadata, true, environ); !match && err == nil {
log.Debug().Str("pipeline", workflow.Name).Msg(
"Marked as skipped, dose not match metadata",
)

View File

@ -72,7 +72,7 @@ func checkIfFiltered(repo *model.Repo, p *model.Pipeline, forgeYamlConfigs []*fo
log.Trace().Msgf("config '%s': %#v", forgeYamlConfig.Name, parsedPipelineConfig)
// ignore if the pipeline was filtered by matched constraints
if match, err := parsedPipelineConfig.When.Match(matchMetadata, true); !match && err == nil {
if match, err := parsedPipelineConfig.When.Match(matchMetadata, true, p.AdditionalVariables); !match && err == nil {
continue
} else if err != nil {
return false, err