mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-22 06:45:03 +01:00
Replace expressions engine (#133)
This commit is contained in:
242
internal/model/strategy_utils.go
Normal file
242
internal/model/strategy_utils.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// TraceWriter is an interface for logging trace information.
|
||||
// Implementations can write to console, file, or any other sink.
|
||||
type TraceWriter interface {
|
||||
Info(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// StrategyResult holds the result of expanding a strategy.
|
||||
// FlatMatrix contains the expanded matrix entries.
|
||||
// IncludeMatrix contains entries that were added via include.
|
||||
// FailFast indicates whether the job should fail fast.
|
||||
// MaxParallel is the maximum parallelism allowed.
|
||||
// MatrixKeys is the set of keys present in the matrix.
|
||||
type StrategyResult struct {
|
||||
FlatMatrix []map[string]yaml.Node
|
||||
IncludeMatrix []map[string]yaml.Node
|
||||
FailFast bool
|
||||
MaxParallel *float64
|
||||
MatrixKeys map[string]struct{}
|
||||
}
|
||||
|
||||
type strategyContext struct {
|
||||
jobTraceWriter TraceWriter
|
||||
failFast bool
|
||||
maxParallel float64
|
||||
matrix map[string][]yaml.Node
|
||||
|
||||
flatMatrix []map[string]yaml.Node
|
||||
includeMatrix []map[string]yaml.Node
|
||||
|
||||
include []yaml.Node
|
||||
exclude []yaml.Node
|
||||
}
|
||||
|
||||
func (strategyContext *strategyContext) handleInclude() error {
|
||||
// Handle include logic
|
||||
if len(strategyContext.include) > 0 {
|
||||
for _, incNode := range strategyContext.include {
|
||||
if incNode.Kind != yaml.MappingNode {
|
||||
return fmt.Errorf("include entry is not a mapping node")
|
||||
}
|
||||
incMap := make(map[string]yaml.Node)
|
||||
for i := 0; i < len(incNode.Content); i += 2 {
|
||||
keyNode := incNode.Content[i]
|
||||
valNode := incNode.Content[i+1]
|
||||
if keyNode.Kind != yaml.ScalarNode {
|
||||
return fmt.Errorf("include key is not scalar")
|
||||
}
|
||||
incMap[keyNode.Value] = *valNode
|
||||
}
|
||||
matched := false
|
||||
for _, row := range strategyContext.flatMatrix {
|
||||
match := true
|
||||
for k, v := range incMap {
|
||||
if rv, ok := row[k]; ok && !nodesEqual(rv, v) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
matched = true
|
||||
// Add missing keys
|
||||
strategyContext.jobTraceWriter.Info("Add missing keys %v", incMap)
|
||||
for k, v := range incMap {
|
||||
if _, ok := row[k]; !ok {
|
||||
row[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
if strategyContext.jobTraceWriter != nil {
|
||||
strategyContext.jobTraceWriter.Info("Append include entry %v", incMap)
|
||||
}
|
||||
strategyContext.includeMatrix = append(strategyContext.includeMatrix, incMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (strategyContext *strategyContext) handleExclude() error {
|
||||
// Handle exclude logic
|
||||
if len(strategyContext.exclude) > 0 {
|
||||
for _, exNode := range strategyContext.exclude {
|
||||
// exNode is expected to be a mapping node
|
||||
if exNode.Kind != yaml.MappingNode {
|
||||
return fmt.Errorf("exclude entry is not a mapping node")
|
||||
}
|
||||
// Convert mapping to map[string]yaml.Node
|
||||
exMap := make(map[string]yaml.Node)
|
||||
for i := 0; i < len(exNode.Content); i += 2 {
|
||||
keyNode := exNode.Content[i]
|
||||
valNode := exNode.Content[i+1]
|
||||
if keyNode.Kind != yaml.ScalarNode {
|
||||
return fmt.Errorf("exclude key is not scalar")
|
||||
}
|
||||
exMap[keyNode.Value] = *valNode
|
||||
}
|
||||
// Remove matching rows
|
||||
filtered := []map[string]yaml.Node{}
|
||||
for _, row := range strategyContext.flatMatrix {
|
||||
match := true
|
||||
for k, v := range exMap {
|
||||
if rv, ok := row[k]; !ok || !nodesEqual(rv, v) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
filtered = append(filtered, row)
|
||||
} else if strategyContext.jobTraceWriter != nil {
|
||||
strategyContext.jobTraceWriter.Info("Removing %v from matrix due to exclude entry %v", row, exMap)
|
||||
}
|
||||
}
|
||||
strategyContext.flatMatrix = filtered
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandStrategy expands the given strategy into a flat matrix and include matrix.
|
||||
// It mimics the behavior of the C# StrategyUtils. The strategy parameter is expected
|
||||
// to be populated from a YAML mapping that follows the GitHub Actions strategy schema.
|
||||
func ExpandStrategy(strategy *Strategy, jobTraceWriter TraceWriter) (*StrategyResult, error) {
|
||||
if strategy == nil {
|
||||
return &StrategyResult{FlatMatrix: []map[string]yaml.Node{{}}, IncludeMatrix: []map[string]yaml.Node{}, FailFast: true}, nil
|
||||
}
|
||||
|
||||
// Initialize defaults
|
||||
strategyContext := &strategyContext{
|
||||
jobTraceWriter: jobTraceWriter,
|
||||
failFast: strategy.FailFast,
|
||||
maxParallel: strategy.MaxParallel,
|
||||
matrix: strategy.Matrix,
|
||||
flatMatrix: []map[string]yaml.Node{{}},
|
||||
}
|
||||
// Process matrix entries
|
||||
for key, values := range strategyContext.matrix {
|
||||
switch key {
|
||||
case "include":
|
||||
strategyContext.include = values
|
||||
case "exclude":
|
||||
strategyContext.exclude = values
|
||||
default:
|
||||
// Other keys are treated as matrix dimensions
|
||||
// Expand each existing row with the new key/value pairs
|
||||
next := []map[string]yaml.Node{}
|
||||
for _, row := range strategyContext.flatMatrix {
|
||||
for _, val := range values {
|
||||
newRow := make(map[string]yaml.Node)
|
||||
for k, v := range row {
|
||||
newRow[k] = v
|
||||
}
|
||||
newRow[key] = val
|
||||
next = append(next, newRow)
|
||||
}
|
||||
}
|
||||
strategyContext.flatMatrix = next
|
||||
}
|
||||
}
|
||||
|
||||
if err := strategyContext.handleExclude(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(strategyContext.flatMatrix) == 0 {
|
||||
if jobTraceWriter != nil {
|
||||
jobTraceWriter.Info("Matrix is empty, adding an empty entry")
|
||||
}
|
||||
strategyContext.flatMatrix = []map[string]yaml.Node{{}}
|
||||
}
|
||||
|
||||
// Enforce job matrix limit of github
|
||||
if len(strategyContext.flatMatrix) > 256 {
|
||||
if jobTraceWriter != nil {
|
||||
jobTraceWriter.Info("Failure: Matrix contains more than 256 entries after exclude")
|
||||
}
|
||||
return nil, errors.New("matrix contains more than 256 entries")
|
||||
}
|
||||
|
||||
// Build matrix keys set
|
||||
matrixKeys := make(map[string]struct{})
|
||||
if len(strategyContext.flatMatrix) > 0 {
|
||||
for k := range strategyContext.flatMatrix[0] {
|
||||
matrixKeys[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if err := strategyContext.handleInclude(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StrategyResult{
|
||||
FlatMatrix: strategyContext.flatMatrix,
|
||||
IncludeMatrix: strategyContext.includeMatrix,
|
||||
FailFast: strategyContext.failFast,
|
||||
MaxParallel: &strategyContext.maxParallel,
|
||||
MatrixKeys: matrixKeys,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nodesEqual compares two yaml.Node values for equality.
|
||||
func nodesEqual(a, b yaml.Node) bool {
|
||||
return DeepEquals(a, b, true)
|
||||
}
|
||||
|
||||
// GetDefaultDisplaySuffix returns a string like "(foo, bar, baz)".
|
||||
// Empty items are ignored. If all items are empty the result is "".
|
||||
func GetDefaultDisplaySuffix(items []string) string {
|
||||
var b strings.Builder // efficient string concatenation
|
||||
|
||||
first := true // true until we write the first non‑empty item
|
||||
|
||||
for _, mk := range items {
|
||||
if mk == "" { // Go has no null string, so we only need to check for empty
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
b.WriteString("(")
|
||||
first = false
|
||||
} else {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(mk)
|
||||
}
|
||||
|
||||
if !first { // we wrote at least one item
|
||||
b.WriteString(")")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user