woodpecker/pipeline/frontend/yaml/constraint/constraint.go

400 lines
9.5 KiB
Go
Raw Normal View History

// Copyright 2023 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
2017-03-05 07:56:08 +00:00
import (
2019-11-14 19:16:03 +00:00
"fmt"
"maps"
"path"
"strings"
2017-03-05 07:56:08 +00:00
"github.com/bmatcuk/doublestar/v4"
Update golang (packages) (#2904) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | code.gitea.io/sdk/gitea | require | minor | `v0.16.0` -> `v0.17.0` | | [github.com/antonmedv/expr](https://togithub.com/antonmedv/expr) | require | patch | `v1.15.5` -> `v1.15.6` | | [github.com/golang-jwt/jwt/v5](https://togithub.com/golang-jwt/jwt) | require | minor | `v5.1.0` -> `v5.2.0` | | [github.com/urfave/cli/v2](https://togithub.com/urfave/cli) | require | minor | `v2.25.7` -> `v2.26.0` | --- ### Release Notes <details> <summary>antonmedv/expr (github.com/antonmedv/expr)</summary> ### [`v1.15.6`](https://togithub.com/expr-lang/expr/releases/tag/v1.15.6) [Compare Source](https://togithub.com/antonmedv/expr/compare/v1.15.5...v1.15.6) - This is a new release for `expr-lang/epxr`. </details> <details> <summary>golang-jwt/jwt (github.com/golang-jwt/jwt/v5)</summary> ### [`v5.2.0`](https://togithub.com/golang-jwt/jwt/releases/tag/v5.2.0) [Compare Source](https://togithub.com/golang-jwt/jwt/compare/v5.1.0...v5.2.0) #### What's Changed - Exported `NewValidator` by [@&#8203;oxisto](https://togithub.com/oxisto) in [https://github.com/golang-jwt/jwt/pull/349](https://togithub.com/golang-jwt/jwt/pull/349) - Improve ErrInvalidKeyType error messages by [@&#8203;Laurin-Notemann](https://togithub.com/Laurin-Notemann) in [https://github.com/golang-jwt/jwt/pull/361](https://togithub.com/golang-jwt/jwt/pull/361) - Update MIGRATION_GUIDE.md by [@&#8203;jbarham](https://togithub.com/jbarham) in [https://github.com/golang-jwt/jwt/pull/363](https://togithub.com/golang-jwt/jwt/pull/363) #### New Contributors - [@&#8203;Laurin-Notemann](https://togithub.com/Laurin-Notemann) made their first contribution in [https://github.com/golang-jwt/jwt/pull/361](https://togithub.com/golang-jwt/jwt/pull/361) - [@&#8203;jbarham](https://togithub.com/jbarham) made their first contribution in [https://github.com/golang-jwt/jwt/pull/363](https://togithub.com/golang-jwt/jwt/pull/363) **Full Changelog**: https://github.com/golang-jwt/jwt/compare/v5.1.0...v5.2.0 </details> <details> <summary>urfave/cli (github.com/urfave/cli/v2)</summary> ### [`v2.26.0`](https://togithub.com/urfave/cli/releases/tag/v2.26.0) [Compare Source](https://togithub.com/urfave/cli/compare/v2.25.7...v2.26.0) #### What's Changed - Bash completion nits by [@&#8203;meatballhat](https://togithub.com/meatballhat) in [https://github.com/urfave/cli/pull/1762](https://togithub.com/urfave/cli/pull/1762) - Chore: Rename mkdocs requirements file name by [@&#8203;dearchap](https://togithub.com/dearchap) in [https://github.com/urfave/cli/pull/1776](https://togithub.com/urfave/cli/pull/1776) - Fix:(issue\_1787) Add fix for commands not listed when hide help comma… by [@&#8203;dearchap](https://togithub.com/dearchap) in [https://github.com/urfave/cli/pull/1788](https://togithub.com/urfave/cli/pull/1788) - Fix nil HelpFlag panic (v2) by [@&#8203;wxiaoguang](https://togithub.com/wxiaoguang) in [https://github.com/urfave/cli/pull/1795](https://togithub.com/urfave/cli/pull/1795) - Always get 0 for a nested int64 value in v2.25.7 by [@&#8203;stephenfire](https://togithub.com/stephenfire) in [https://github.com/urfave/cli/pull/1799](https://togithub.com/urfave/cli/pull/1799) - Helper messages for documenting build process by [@&#8203;abitrolly](https://togithub.com/abitrolly) in [https://github.com/urfave/cli/pull/1800](https://togithub.com/urfave/cli/pull/1800) - fix: check duplicated sub command name and alias by [@&#8203;linrl3](https://togithub.com/linrl3) in [https://github.com/urfave/cli/pull/1805](https://togithub.com/urfave/cli/pull/1805) - Fix:(issue\_1689) Have consistent behavior for default text in man and… by [@&#8203;dearchap](https://togithub.com/dearchap) in [https://github.com/urfave/cli/pull/1825](https://togithub.com/urfave/cli/pull/1825) - Fix linting issues by [@&#8203;skelouse](https://togithub.com/skelouse) in [https://github.com/urfave/cli/pull/1696](https://togithub.com/urfave/cli/pull/1696) #### New Contributors - [@&#8203;stephenfire](https://togithub.com/stephenfire) made their first contribution in [https://github.com/urfave/cli/pull/1799](https://togithub.com/urfave/cli/pull/1799) - [@&#8203;linrl3](https://togithub.com/linrl3) made their first contribution in [https://github.com/urfave/cli/pull/1805](https://togithub.com/urfave/cli/pull/1805) **Full Changelog**: https://github.com/urfave/cli/compare/v2.25.7...v2.26.0 </details> --- ### Configuration 📅 **Schedule**: Branch creation - "before 4am" (UTC), Automerge - "before 4am" (UTC). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/woodpecker-ci/woodpecker). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy44MS4zIiwidXBkYXRlZEluVmVyIjoiMzcuODEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: qwerty287 <ndev@web.de>
2023-12-04 19:53:46 +00:00
"github.com/expr-lang/expr"
"go.uber.org/multierr"
"gopkg.in/yaml.v3"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base"
2017-03-05 07:56:08 +00:00
)
type (
// When defines a set of runtime constraints.
When struct {
// If true then read from a list of constraint
Constraints []Constraint
}
Constraint struct {
Ref List
Repo List
Instance List
Platform List
Environment List
Event List
Branch List
Cron List
Status List
Matrix Map
Local yamlBaseTypes.BoolTrue
Path Path
Evaluate string `yaml:"evaluate,omitempty"`
2017-03-05 07:56:08 +00:00
}
// List defines a runtime constraint for exclude & include string slices.
List struct {
2017-03-05 07:56:08 +00:00
Include []string
Exclude []string
}
// Map defines a runtime constraint for exclude & include map strings.
Map struct {
2017-03-05 07:56:08 +00:00
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"`
}
2017-03-05 07:56:08 +00:00
)
func (when *When) IsEmpty() bool {
return len(when.Constraints) == 0
}
// Returns true if at least one of the internal constraints is true.
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, env)
if err != nil {
return false, err
}
if match {
return true, nil
}
}
if when.IsEmpty() {
// test against default Constraints
empty := &Constraint{}
return empty.Match(metadata, global, env)
}
return false, nil
}
func (when *When) IncludesStatusFailure() bool {
for _, c := range when.Constraints {
if c.Status.Includes("failure") {
return true
}
}
return false
}
func (when *When) IncludesStatusSuccess() bool {
// "success" acts differently than "failure" in that it's
// presumed to be included unless it's specifically not part
// of the list
if when.IsEmpty() {
return true
}
for _, c := range when.Constraints {
if len(c.Status.Include) == 0 || c.Status.Includes("success") {
return true
}
}
return false
}
// False if (any) non local
func (when *When) IsLocal() bool {
for _, c := range when.Constraints {
if !c.Local.Bool() {
return false
}
}
return true
}
func (when *When) UnmarshalYAML(value *yaml.Node) error {
switch value.Kind {
case yaml.SequenceNode:
if err := value.Decode(&when.Constraints); err != nil {
return err
}
case yaml.MappingNode:
c := Constraint{}
if err := value.Decode(&c); err != nil {
return err
}
when.Constraints = append(when.Constraints, c)
default:
return fmt.Errorf("not supported yaml kind: %v", value.Kind)
}
return nil
}
2017-03-05 07:56:08 +00:00
// 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) {
match := true
if !global {
// apply step only filters
match = c.Matrix.Match(m.Workflow.Matrix)
}
match = match && c.Platform.Match(m.Sys.Platform) &&
c.Environment.Match(m.Curr.Target) &&
c.Event.Match(m.Curr.Event) &&
c.Repo.Match(path.Join(m.Repo.Owner, m.Repo.Name)) &&
c.Ref.Match(m.Curr.Commit.Ref) &&
c.Instance.Match(m.Sys.Host)
// changed files filter apply only for pull-request and push events
if m.Curr.Event == metadata.EventPull || m.Curr.Event == metadata.EventPush {
match = match && c.Path.Match(m.Curr.Commit.ChangedFiles, m.Curr.Commit.Message)
}
if m.Curr.Event != metadata.EventTag {
match = match && c.Branch.Match(m.Curr.Commit.Branch)
}
if m.Curr.Event == metadata.EventCron {
match = match && c.Cron.Match(m.Curr.Cron)
}
if c.Evaluate != "" {
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
}
result, err := expr.Run(out, env)
if err != nil {
return false, err
}
bresult, ok := result.(bool)
if !ok {
return false, fmt.Errorf("could not parse result: %v", result)
}
match = match && bresult
}
return match, nil
2017-03-05 07:56:08 +00:00
}
// IsEmpty return true if a constraint has no conditions
func (c List) IsEmpty() bool {
return len(c.Include) == 0 && len(c.Exclude) == 0
}
2017-03-05 07:56:08 +00:00
// 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 {
2017-03-05 07:56:08 +00:00
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 {
2017-03-05 07:56:08 +00:00
for _, pattern := range c.Include {
if ok, _ := doublestar.Match(pattern, v); ok {
2017-03-05 07:56:08 +00:00
return true
}
}
return false
}
// Excludes returns true if the string matches the exclude patterns.
func (c *List) Excludes(v string) bool {
2017-03-05 07:56:08 +00:00
for _, pattern := range c.Exclude {
if ok, _ := doublestar.Match(pattern, v); ok {
2017-03-05 07:56:08 +00:00
return true
}
}
return false
}
// UnmarshalYAML unmarshals the constraint.
func (c *List) UnmarshalYAML(value *yaml.Node) error {
2022-01-05 20:50:23 +00:00
out1 := struct {
Include yamlBaseTypes.StringOrSlice
Exclude yamlBaseTypes.StringOrSlice
2017-03-05 07:56:08 +00:00
}{}
var out2 yamlBaseTypes.StringOrSlice
2017-03-05 07:56:08 +00:00
2019-11-14 19:16:03 +00:00
err1 := value.Decode(&out1)
err2 := value.Decode(&out2)
2017-03-05 07:56:08 +00:00
c.Exclude = out1.Exclude
c.Include = append( //nolint:gocritic
2017-03-05 07:56:08 +00:00
out1.Include,
out2...,
)
2019-11-14 19:16:03 +00:00
if err1 != nil && err2 != nil {
y, _ := yaml.Marshal(value)
return fmt.Errorf("could not parse condition: %s: %w", y, multierr.Append(err1, err2))
2019-11-14 19:16:03 +00:00
}
2017-03-05 07:56:08 +00:00
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 {
2017-03-05 07:56:08 +00:00
// 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
2017-03-05 07:56:08 +00:00
// 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 {
2017-03-05 07:56:08 +00:00
matches++
}
}
if matches == len(c.Exclude) {
return false
}
}
for key, val := range c.Include {
if ok, _ := doublestar.Match(val, params[key]); !ok {
2017-03-05 07:56:08 +00:00
return false
}
}
return true
}
// UnmarshalYAML unmarshal the constraint map.
func (c *Map) UnmarshalYAML(unmarshal func(any) error) error {
2017-03-05 07:56:08 +00:00
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
2017-03-05 07:56:08 +00:00
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 {
2022-01-05 20:50:23 +00:00
out1 := struct {
Include yamlBaseTypes.StringOrSlice `yaml:"include,omitempty"`
Exclude yamlBaseTypes.StringOrSlice `yaml:"exclude,omitempty"`
IgnoreMessage string `yaml:"ignore_message,omitempty"`
}{}
var out2 yamlBaseTypes.StringOrSlice
err1 := value.Decode(&out1)
err2 := value.Decode(&out2)
c.Exclude = out1.Exclude
c.IgnoreMessage = out1.IgnoreMessage
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
}
// always match if there are no commit files (empty commit)
if len(v) == 0 {
return true
}
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 the string matches any of the exclude patterns.
func (c *Path) Excludes(v []string) bool {
for _, pattern := range c.Exclude {
for _, file := range v {
if ok, _ := doublestar.Match(pattern, file); ok {
return true
}
}
}
return false
}