Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkg/runner/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import (
type ExpressionEvaluator interface {
evaluate(context.Context, string, exprparser.DefaultStatusCheck) (interface{}, error)
EvaluateYamlNode(context.Context, *yaml.Node) error
// EvaluateYamlNodeGetResult evaluates the yaml node and returns the result without modifying the original node.
// This is useful when multiple goroutines need to evaluate the same shared yaml.Node concurrently.
EvaluateYamlNodeGetResult(context.Context, *yaml.Node) (*yaml.Node, error)
Interpolate(context.Context, string) string
}

Expand Down Expand Up @@ -377,6 +380,13 @@ func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.N
return nil
}

// EvaluateYamlNodeGetResult evaluates the yaml node and returns the result without modifying the original node.
// This is useful when multiple goroutines need to evaluate the same shared yaml.Node concurrently,
// such as when evaluating matrix jobs in parallel.
func (ee expressionEvaluator) EvaluateYamlNodeGetResult(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
return ee.evaluateYamlNodeInternal(ctx, node)
}

func (ee expressionEvaluator) Interpolate(ctx context.Context, in string) string {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return in
Expand Down
59 changes: 57 additions & 2 deletions pkg/runner/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model"
"github.com/opencontainers/selinux/go-selinux"
"gopkg.in/yaml.v3"
)

// RunContext contains info about current job
Expand Down Expand Up @@ -764,12 +765,66 @@ func (rc *RunContext) runsOnPlatformNames(ctx context.Context) []string {
return []string{}
}

if err := rc.ExprEval.EvaluateYamlNode(ctx, &job.RawRunsOn); err != nil {
// Use EvaluateYamlNodeGetResult to avoid modifying the shared yaml.Node.
// This is important when multiple matrix jobs run in parallel - they share
// the same Job object, and modifying RawRunsOn would cause race conditions.
evaluatedNode, err := rc.ExprEval.EvaluateYamlNodeGetResult(ctx, &job.RawRunsOn)
if err != nil {
common.Logger(ctx).Errorf("Error while evaluating runs-on: %v", err)
return []string{}
}

return job.RunsOn()
if evaluatedNode == nil {
return []string{}
}

return extractRunsOnFromNode(*evaluatedNode)
}

// extractRunsOnFromNode extracts the runs-on labels from a yaml.Node.
// This mirrors the logic in (*Job).RunsOn() but works on any yaml.Node.
func extractRunsOnFromNode(node yaml.Node) []string {
switch node.Kind {
case yaml.MappingNode:
var val struct {
Group string
Labels yaml.Node
}

if err := node.Decode(&val); err != nil {
return nil
}

labels := extractRunsOnFromNode(val.Labels)

if val.Group != "" {
labels = append(labels, val.Group)
}

return labels
default:
return nodeAsStringSlice(node)
}
}

// nodeAsStringSlice converts a yaml.Node to a []string.
// This is a copy of the function from model/workflow.go to avoid export issues.
func nodeAsStringSlice(node yaml.Node) []string {
switch node.Kind {
case yaml.ScalarNode:
var val string
if err := node.Decode(&val); err != nil {
return nil
}
return []string{val}
case yaml.SequenceNode:
var val []string
if err := node.Decode(&val); err != nil {
return nil
}
return val
}
return nil
}

func (rc *RunContext) platformImage(ctx context.Context) string {
Expand Down