feat(linter): add GL046-GL049 rules, AND-group support, and GL032 fix
- GL046: validate image/service pull_policy values (always, if-not-present, never)
- GL047: error when a variables.options default value is not in the options list
- GL048: error on unrecognised trigger.forward keys
- GL049: validate rules[n].allow_failure (bool or {exit_codes:} map)
- Parse and evaluate workflow.rules/job.rules nested-array AND-groups; crash
on !!seq nodes is fixed; all members of a group must match for it to fire
- Add workflow.name and workflow.auto_cancel fields to Workflow struct
- Fix GL032 false positive: variables declared in any workflow rule's variables:
block no longer trigger an undeclared-variable warning in sibling workflow
rule if: expressions
- Add Windows ARM64 release build target (task build-windows-arm64)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,22 +42,18 @@ func EvalWorkflow(p *model.Pipeline, ctx *Context) (bool, map[string]string) {
|
||||
return true, nil
|
||||
}
|
||||
vars := ctx.Get
|
||||
for _, rule := range p.Workflow.Rules {
|
||||
// Workflow rules use strict evaluation: an unparseable condition is
|
||||
// treated as no-match so later rules (with valid conditions or a
|
||||
// bare when:) are reached. Permissive-true would cause an early rule
|
||||
// with a complex/invalid condition to block all subsequent rules.
|
||||
if !ruleIfMatchesStrict(rule.If, vars) {
|
||||
// Workflow rules use strict evaluation: an unparseable condition is treated
|
||||
// as no-match so later rules are reached. Permissive-true would cause an
|
||||
// early rule with a complex condition to block all subsequent rules.
|
||||
for _, group := range p.Workflow.Rules.Groups() {
|
||||
matched, when, groupVars := evalRuleGroup(group, vars, ctx, true)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
if !changesMatch(rule.Changes, ctx) {
|
||||
continue
|
||||
}
|
||||
when := rule.When
|
||||
if when == "" {
|
||||
when = "always"
|
||||
}
|
||||
return when != "never", ExtractStringVars(rule.Variables)
|
||||
return when != "never", groupVars
|
||||
}
|
||||
return false, nil // no rule matched → pipeline does not run
|
||||
}
|
||||
@@ -74,14 +70,12 @@ func EvalJob(job model.Job, ctx *Context) JobState {
|
||||
|
||||
// rules: takes priority over only/except.
|
||||
if len(job.Rules) > 0 {
|
||||
for _, rule := range job.Rules {
|
||||
if !ruleIfMatches(rule.If, vars) {
|
||||
for _, group := range job.Rules.Groups() {
|
||||
matched, when, _ := evalRuleGroup(group, vars, ctx, false)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
if !changesMatch(rule.Changes, ctx) {
|
||||
continue
|
||||
}
|
||||
return whenToState(rule.When)
|
||||
return whenToState(when)
|
||||
}
|
||||
return JobSkipped // no rule matched → job is excluded
|
||||
}
|
||||
@@ -98,6 +92,35 @@ func EvalJob(job model.Job, ctx *Context) JobState {
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// evalRuleGroup evaluates one rule group (one or more rules forming an AND-group).
|
||||
// All conditions in the group must match for the group to fire.
|
||||
// Returns (matched, effectiveWhen, mergedVars). When strict=true, unparseable
|
||||
// if: expressions count as no-match; when false, they count as match (permissive).
|
||||
func evalRuleGroup(group []model.Rule, vars func(string) string, ctx *Context, strict bool) (bool, string, map[string]string) {
|
||||
for _, rule := range group {
|
||||
var ifOk bool
|
||||
if strict {
|
||||
ifOk = ruleIfMatchesStrict(rule.If, vars)
|
||||
} else {
|
||||
ifOk = ruleIfMatches(rule.If, vars)
|
||||
}
|
||||
if !ifOk || !changesMatch(rule.Changes, ctx) {
|
||||
return false, "", nil
|
||||
}
|
||||
}
|
||||
merged := make(map[string]string)
|
||||
effectiveWhen := ""
|
||||
for _, rule := range group {
|
||||
for k, v := range ExtractStringVars(rule.Variables) {
|
||||
merged[k] = v
|
||||
}
|
||||
if rule.When != "" {
|
||||
effectiveWhen = rule.When
|
||||
}
|
||||
}
|
||||
return true, effectiveWhen, merged
|
||||
}
|
||||
|
||||
func ruleIfMatches(ifExpr string, vars func(string) string) bool {
|
||||
if ifExpr == "" {
|
||||
return true // no if: condition → rule always matches
|
||||
|
||||
Reference in New Issue
Block a user