mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-09-23 22:43:17 +00: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:
@@ -444,7 +444,7 @@ when:
|
|||||||
|
|
||||||
#### `evaluate`
|
#### `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.
|
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"'
|
- 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
|
### `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.
|
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,6 +1,6 @@
|
|||||||
# Environment variables
|
# 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
|
```diff
|
||||||
steps:
|
steps:
|
||||||
|
@@ -104,7 +104,7 @@ func New(opts ...Option) *Compiler {
|
|||||||
func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, error) {
|
func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, error) {
|
||||||
config := new(backend_types.Config)
|
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.
|
// 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.
|
// An empty pipeline will just be skipped completely.
|
||||||
return config, nil
|
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)
|
config.Stages = append(config.Stages, stage)
|
||||||
} else if !c.local && !conf.SkipClone {
|
} else if !c.local && !conf.SkipClone {
|
||||||
for i, container := range conf.Clone.ContainerList {
|
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
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -212,7 +212,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||||||
stage.Alias = nameServices
|
stage.Alias = nameServices
|
||||||
|
|
||||||
for i, container := range conf.Services.ContainerList {
|
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
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -234,7 +234,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||||||
continue
|
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
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/antonmedv/expr"
|
"github.com/antonmedv/expr"
|
||||||
"github.com/bmatcuk/doublestar/v4"
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/metadata"
|
"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.
|
// 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 {
|
for _, c := range when.Constraints {
|
||||||
match, err := c.Match(metadata, global)
|
match, err := c.Match(metadata, global, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ func (when *When) Match(metadata metadata.Metadata, global bool) (bool, error) {
|
|||||||
if when.IsEmpty() {
|
if when.IsEmpty() {
|
||||||
// test against default Constraints
|
// test against default Constraints
|
||||||
empty := &Constraint{}
|
empty := &Constraint{}
|
||||||
return empty.Match(metadata, global)
|
return empty.Match(metadata, global, env)
|
||||||
}
|
}
|
||||||
return false, nil
|
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
|
// Match returns true if all constraints match the given input. If a single
|
||||||
// constraint fails a false value is returned.
|
// 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
|
match := true
|
||||||
if !global {
|
if !global {
|
||||||
// apply step only filters
|
// apply step only filters
|
||||||
@@ -167,8 +168,12 @@ func (c *Constraint) Match(m metadata.Metadata, global bool) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Evaluate != "" {
|
if c.Evaluate != "" {
|
||||||
env := m.Environ()
|
if env == nil {
|
||||||
out, err := expr.Compile(c.Evaluate, expr.Env(env), expr.AsBool())
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@@ -405,6 +405,7 @@ func TestConstraints(t *testing.T) {
|
|||||||
desc string
|
desc string
|
||||||
conf string
|
conf string
|
||||||
with metadata.Metadata
|
with metadata.Metadata
|
||||||
|
env map[string]string
|
||||||
want bool
|
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"}},
|
with: metadata.Metadata{Curr: metadata.Pipeline{Event: metadata.EventPush}, Repo: metadata.Repo{Owner: "owner", Name: "repo"}},
|
||||||
want: true,
|
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 {
|
for _, test := range testdata {
|
||||||
@@ -517,7 +532,7 @@ func TestConstraints(t *testing.T) {
|
|||||||
conf, err := frontend.EnvVarSubst(test.conf, test.with.Environ())
|
conf, err := frontend.EnvVarSubst(test.conf, test.with.Environ())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
c := parseConstraints(t, conf)
|
c := parseConstraints(t, conf)
|
||||||
got, err := c.Match(test.with, false)
|
got, err := c.Match(test.with, false, test.env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Match returned error: %v", err)
|
t.Errorf("Match returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -84,7 +84,7 @@ func TestParse(t *testing.T) {
|
|||||||
Curr: metadata.Pipeline{
|
Curr: metadata.Pipeline{
|
||||||
Event: "tester",
|
Event: "tester",
|
||||||
},
|
},
|
||||||
}, false)
|
}, false, nil)
|
||||||
g.Assert(match).Equal(true)
|
g.Assert(match).Equal(true)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
})
|
})
|
||||||
@@ -94,7 +94,7 @@ func TestParse(t *testing.T) {
|
|||||||
Curr: metadata.Pipeline{
|
Curr: metadata.Pipeline{
|
||||||
Event: "tester2",
|
Event: "tester2",
|
||||||
},
|
},
|
||||||
}, false)
|
}, false, nil)
|
||||||
g.Assert(match).Equal(true)
|
g.Assert(match).Equal(true)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
})
|
})
|
||||||
@@ -106,7 +106,7 @@ func TestParse(t *testing.T) {
|
|||||||
Branch: "tester",
|
Branch: "tester",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, true)
|
}, true, nil)
|
||||||
g.Assert(match).Equal(true)
|
g.Assert(match).Equal(true)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
})
|
})
|
||||||
@@ -116,7 +116,7 @@ func TestParse(t *testing.T) {
|
|||||||
Curr: metadata.Pipeline{
|
Curr: metadata.Pipeline{
|
||||||
Event: "push",
|
Event: "push",
|
||||||
},
|
},
|
||||||
}, false)
|
}, false, nil)
|
||||||
g.Assert(match).Equal(false)
|
g.Assert(match).Equal(false)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
})
|
})
|
||||||
|
@@ -118,7 +118,7 @@ func (b *StepBuilder) Build() ([]*Item, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checking if filtered.
|
// 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(
|
log.Debug().Str("pipeline", workflow.Name).Msg(
|
||||||
"Marked as skipped, dose not match metadata",
|
"Marked as skipped, dose not match metadata",
|
||||||
)
|
)
|
||||||
|
@@ -72,7 +72,7 @@ func checkIfFiltered(repo *model.Repo, p *model.Pipeline, forgeYamlConfigs []*fo
|
|||||||
log.Trace().Msgf("config '%s': %#v", forgeYamlConfig.Name, parsedPipelineConfig)
|
log.Trace().Msgf("config '%s': %#v", forgeYamlConfig.Name, parsedPipelineConfig)
|
||||||
|
|
||||||
// ignore if the pipeline was filtered by matched constraints
|
// 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
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
Reference in New Issue
Block a user