refactor: use the shared workflow model from actionslib (#38768)

Pairs with https://gitea.com/gitea/runner/pulls/1143.

Gitea depends on `gitea.com/gitea/runner` for exactly two packages:
`act/model` and `act/exprparser`, the workflow model and the expression
evaluator it needs to parse workflows and to build the task payload the
runner consumes. Pulling the whole runner module in for that is heavy
and puts shared code in the repository of one of the two consumers.

Both packages now live in `gitea.dev/actionslib` (`pkg/model`,
`pkg/exprparser`), the module Gitea and the runner already share for the
runner API, so the dependency on the runner repository is dropped here.

### Changes

- `gitea.com/gitea/runner/act/model` ->
`gitea.dev/actionslib/pkg/model`, `.../act/exprparser` ->
`gitea.dev/actionslib/pkg/exprparser` (22 files, import paths only).
- `routers/api/actions/runner/interceptor.go` takes the `x-runner-uuid`
/ `x-runner-token` names from `gitea.dev/actionslib/pkg/protocol`
instead of repeating the literals the runner also has.
- `go.mod`: `gitea.com/gitea/runner` removed.

---------

Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
Lunny Xiao
2026-08-07 20:56:40 -07:00
committed by GitHub
parent c14edf3313
commit 2087d4a1a5
52 changed files with 99 additions and 452 deletions
+2 -52
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,14 +6,13 @@ toolchain go1.26.5
require (
connectrpc.com/connect v1.20.0
gitea.com/gitea/runner v1.0.8
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
gitea.dev/actions-proto-go v0.6.0
gitea.dev/actionslib v0.7.0
gitea.dev/sdk v1.2.0
github.com/42wim/httpsig v1.2.4
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
@@ -254,6 +253,7 @@ require (
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stangelandcl/ppmd v0.1.1 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
+2 -4
View File
@@ -8,8 +8,6 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
gitea.com/gitea/runner v1.0.8 h1:zKfC4+zyyGIDagqhII3WVw52P+A9iAa63It0lniN4SI=
gitea.com/gitea/runner v1.0.8/go.mod h1:AGLQXo8ELz9WPzNJ5W1SvnCik8ZX3jF0o6yCPCmwLtM=
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a h1:JHoBrfuTSF9Ke9aNfSYj1XRPBHjKPgCApVprnt2Am0M=
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a/go.mod h1:FOsLJIMdpiHzBp3Vby6Wfkdw2ppGscrjgU1IC7E4/zQ=
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
@@ -24,8 +22,8 @@ gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHq
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.dev/actions-proto-go v0.6.0 h1:gjllYQ5vmwlkqOeofTQu5qKTZpmf7kWsafoHvoPCSzY=
gitea.dev/actions-proto-go v0.6.0/go.mod h1:p4RX+D9oqiEEzzkPMXscw2CmaGuYFPWFc6xIOmDNDqs=
gitea.dev/actionslib v0.7.0 h1:JCV8eeIGwjlXcuSr7ojEdQC22VoE2466+K+D9vuQWKQ=
gitea.dev/actionslib v0.7.0/go.mod h1:DI3Lqp+8TrycM7/semMdqsDQOHaBskjIAuMn+SXfZJ0=
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"strings"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/shared/types"
+1 -1
View File
@@ -6,7 +6,7 @@ package actions
import (
"slices"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/modules/translation"
)
+1 -1
View File
@@ -6,7 +6,7 @@ package actions
import (
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
"github.com/stretchr/testify/assert"
)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"strings"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
"gitea.dev/models/unit"
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"sync/atomic"
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
-268
View File
@@ -1,268 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package jobparser
import (
"errors"
"fmt"
"math"
"reflect"
"regexp"
"strconv"
"strings"
"gitea.com/gitea/runner/act/exprparser"
"go.yaml.in/yaml/v4"
)
// ExpressionEvaluator is copied from runner.expressionEvaluator,
// to avoid unnecessary dependencies
type ExpressionEvaluator struct {
interpreter exprparser.Interpreter
}
func NewExpressionEvaluator(interpreter exprparser.Interpreter) *ExpressionEvaluator {
return &ExpressionEvaluator{interpreter: interpreter}
}
func (ee ExpressionEvaluator) evaluateScalarYamlNode(node *yaml.Node) error {
var in string
if err := node.Decode(&in); err != nil {
return err
}
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return nil
}
res, err := ee.evaluateScalar(in)
if err != nil {
return err
}
return node.Encode(res)
}
// GitHub has this undocumented feature to merge maps, called insert directive
var insertDirective = regexp.MustCompile(`\${{\s*insert\s*}}`)
func (ee ExpressionEvaluator) evaluateMappingYamlNode(node *yaml.Node) error {
for i := 0; i < len(node.Content)/2; {
k := node.Content[i*2]
v := node.Content[i*2+1]
if err := ee.EvaluateYamlNode(v); err != nil {
return err
}
var sk string
// Merge the nested map of the insert directive
if k.Decode(&sk) == nil && insertDirective.MatchString(sk) {
node.Content = append(append(node.Content[:i*2], v.Content...), node.Content[(i+1)*2:]...)
i += len(v.Content) / 2
} else {
if err := ee.EvaluateYamlNode(k); err != nil {
return err
}
i++
}
}
return nil
}
func (ee ExpressionEvaluator) evaluateSequenceYamlNode(node *yaml.Node) error {
for i := 0; i < len(node.Content); {
v := node.Content[i]
// Preserve nested sequences
wasseq := v.Kind == yaml.SequenceNode
if err := ee.EvaluateYamlNode(v); err != nil {
return err
}
// GitHub has this undocumented feature to merge sequences / arrays
// We have a nested sequence via evaluation, merge the arrays
if v.Kind == yaml.SequenceNode && !wasseq {
node.Content = append(append(node.Content[:i], v.Content...), node.Content[i+1:]...)
i += len(v.Content)
} else {
i++
}
}
return nil
}
func (ee ExpressionEvaluator) EvaluateYamlNode(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
return ee.evaluateScalarYamlNode(node)
case yaml.MappingNode:
return ee.evaluateMappingYamlNode(node)
case yaml.SequenceNode:
return ee.evaluateSequenceYamlNode(node)
default:
return nil
}
}
// interpolate evaluates every part on its own, so a malformed one cannot restructure its neighbours
func (ee ExpressionEvaluator) interpolate(in string) (string, error) {
parts, err := splitSubExpressions(in)
if err != nil {
return "", err
}
if len(parts) == 1 && !parts[0].isExpr {
return in, nil
}
var out strings.Builder
out.Grow(len(in))
for _, part := range parts {
if !part.isExpr {
out.WriteString(part.text)
continue
}
evaluated, err := ee.interpreter.Evaluate(part.text, exprparser.DefaultStatusCheckNone)
if err != nil {
return "", err
}
out.WriteString(coerceToString(evaluated))
}
return out.String(), nil
}
// evaluateScalar keeps the type of a lone expression, so `${{ fromJSON('[1,2]') }}` stays an array
func (ee ExpressionEvaluator) evaluateScalar(in string) (any, error) {
parts, err := splitSubExpressions(in)
if err != nil {
return nil, err
}
if len(parts) == 1 && parts[0].isExpr {
return ee.interpreter.Evaluate(parts[0].text, exprparser.DefaultStatusCheckNone)
}
return ee.interpolate(in)
}
// evaluateCondition evaluates an `if:`, an expression even without `${{ }}`. Mixed content
// interpolates to a string, so the success() default applies to it separately.
func (ee ExpressionEvaluator) evaluateCondition(in string) (bool, error) {
parts, err := splitSubExpressions(in)
if err != nil {
return false, err
}
if len(parts) == 1 {
evaluated, err := ee.interpreter.Evaluate(parts[0].text, exprparser.DefaultStatusCheckSuccess)
if err != nil {
return false, err
}
return exprparser.IsTruthy(evaluated), nil
}
// mixed content is a string, so the success() default applies to it separately
if !expressionCallsFunction(in, "success", "always", "failure", "cancelled") {
status, err := ee.interpreter.Evaluate("success()", exprparser.DefaultStatusCheckNone)
if err != nil {
return false, err
}
if !exprparser.IsTruthy(status) {
return false, nil
}
}
interpolated, err := ee.interpolate(in)
if err != nil {
return false, err
}
return exprparser.IsTruthy(interpolated), nil
}
// coerceToString converts an evaluated expression value to a string the way GitHub does,
// see https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#operators
// An already reflected value is accepted as-is, since Interface() would panic on an invalid one.
func coerceToString(v any) string {
value, ok := v.(reflect.Value)
if !ok {
value = reflect.ValueOf(v)
}
switch value.Kind() {
case reflect.Invalid:
return ""
case reflect.Bool:
return strconv.FormatBool(value.Bool())
case reflect.String:
return value.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(value.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(value.Uint(), 10)
case reflect.Float32, reflect.Float64:
if math.IsInf(value.Float(), 1) {
return "Infinity"
} else if math.IsInf(value.Float(), -1) {
return "-Infinity"
}
return fmt.Sprintf("%.15G", value.Float())
case reflect.Slice, reflect.Array:
return "Array"
// contexts such as `github` are pointers to structs, so they stringify as objects too
case reflect.Map, reflect.Struct:
return "Object"
case reflect.Interface, reflect.Pointer:
if value.IsNil() {
return ""
}
return coerceToString(value.Elem())
}
return fmt.Sprintf("%v", value)
}
type exprPart struct {
text string
isExpr bool
}
// splitSubExpressions splits in the way GitHub's template reader does, leaving a value without a
// complete expression literal.
func splitSubExpressions(in string) ([]exprPart, error) {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return []exprPart{{text: in}}, nil
}
parts := make([]exprPart, 0, 2*strings.Count(in, "${{")+1)
for {
start := strings.Index(in, "${{")
if start < 0 {
if in != "" {
parts = append(parts, exprPart{text: in})
}
return parts, nil
}
if start > 0 {
parts = append(parts, exprPart{text: in[:start]})
}
rest := in[start+len("${{"):]
end := indexExprEnd(rest)
if end < 0 {
return nil, errors.New("unclosed expression")
}
parts = append(parts, exprPart{text: strings.TrimSpace(rest[:end]), isExpr: true})
in = rest[end+len("}}"):]
}
}
// indexExprEnd returns the offset of the `}}` ending an expression, or -1. A quote toggles string
// state, so a `}}` inside a string does not end it.
func indexExprEnd(in string) int {
inString := false
for i := range len(in) {
switch {
case in[i] == '\'':
inString = !inString
case !inString && in[i] == '}' && i+1 < len(in) && in[i+1] == '}':
return i
}
}
return -1
}
+3 -4
View File
@@ -4,8 +4,9 @@
package jobparser
import (
"gitea.com/gitea/runner/act/exprparser"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/exprparser"
"gitea.dev/actionslib/pkg/model"
"go.yaml.in/yaml/v4"
)
@@ -59,8 +60,6 @@ func NewInterpeter(
ee := &exprparser.EvaluationEnvironment{
Github: gitCtx,
Env: nil, // no need
// Job must be non-nil because cancelled() dereferences Job.Status unconditionally.
// See: https://gitea.com/gitea/runner/src/commit/ad967330a8788c9b8ab723abbc1a86d53c3bc5e6/act/exprparser/functions.go#L299
// TODO: The empty JobContext.Status is right for now because Gitea never checks `if` condition when the workflow run is cancelled.
// This is an implementation gap in Gitea Actions. When a workflow run is cancelled, Gitea should check the job's `if` condition,
// and if the condition is met (e.g. `if: ${{ cancelled() }}` ), the job should be executed rather than cancelled.
+16 -51
View File
@@ -11,8 +11,10 @@ import (
"sort"
"strings"
"gitea.com/gitea/runner/act/exprparser"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/expreval"
"gitea.dev/actionslib/pkg/exprparser"
"gitea.dev/actionslib/pkg/model"
"github.com/rhysd/actionlint"
"go.yaml.in/yaml/v4"
)
@@ -72,15 +74,7 @@ func ExpressionReadsMatrix(ifValue string) bool {
// the status functions that run a job whatever its needs did rather than under the implicit success().
// Keep in sync with act's exprparser, which owns the same list for the evaluation itself.
func ExpressionIgnoresNeedResults(ifValue string) bool {
return expressionCallsFunction(asIfExpression(ifValue), "always", "failure", "cancelled")
}
// expressionCallsFunction reports whether any ${{ }} expression in value calls one of the functions.
func expressionCallsFunction(value string, names ...string) bool {
return expressionsMatch(value, func(node actionlint.ExprNode) bool {
call, ok := node.(*actionlint.FuncCallNode)
return ok && slices.Contains(names, strings.ToLower(call.Callee))
})
return expreval.CallsFunction(asIfExpression(ifValue), "always", "failure", "cancelled")
}
// asIfExpression wraps an `if:` that omits the `${{ }}`, which GitHub evaluates as one expression anyway.
@@ -95,40 +89,12 @@ func asIfExpression(ifValue string) string {
// expressionReadsContext reports whether value holds a ${{ }} expression reading the named context.
func expressionReadsContext(value, contextName string) bool {
return expressionsMatch(value, func(node actionlint.ExprNode) bool {
return expreval.Match(value, func(node actionlint.ExprNode) bool {
variable, ok := node.(*actionlint.VariableNode)
return ok && strings.EqualFold(variable.Name, contextName)
})
}
// expressionsMatch reports whether any ${{ }} expression in value holds a node the predicate accepts.
func expressionsMatch(value string, match func(node actionlint.ExprNode) bool) bool {
parts, err := splitSubExpressions(value)
if err != nil {
return true // unparseable here, let the expansion report it against the real values
}
for _, part := range parts {
if !part.isExpr {
continue
}
// The lexer needs the closing `}}` that the scanner strips.
expr, err := actionlint.NewExprParser().Parse(actionlint.NewExprLexer(part.text + "}}"))
if err != nil {
return true // unparseable here, let the expansion report it against the real values
}
matched := false
actionlint.VisitExprNode(expr, func(node, _ actionlint.ExprNode, entering bool) {
if entering && match(node) {
matched = true
}
})
if matched {
return true
}
}
return false
}
func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
origin, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
@@ -162,8 +128,8 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
return nil, fmt.Errorf("invalid jobs: %w", err)
}
evaluator := NewExpressionEvaluator(exprparser.NewInterpeter(&exprparser.EvaluationEnvironment{Github: pc.gitContext, Vars: pc.vars, Inputs: pc.inputs}, exprparser.Config{}))
if workflow.RunName, err = evaluator.interpolate(workflow.RunName); err != nil {
evaluator := expreval.New(exprparser.NewInterpeter(&exprparser.EvaluationEnvironment{Github: pc.gitContext, Vars: pc.vars, Inputs: pc.inputs}, exprparser.Config{}).Evaluate)
if workflow.RunName, err = evaluator.Interpolate(workflow.RunName); err != nil {
return nil, fmt.Errorf("interpolate run-name: %w", err)
}
@@ -230,7 +196,7 @@ func ExpandMatrixWithNeeds(jobID string, job *Job, gitCtx *model.GithubContext,
}}
// Resolve fromJson(needs.*.outputs.*) and friends into concrete matrix values.
if err := NewExpressionEvaluator(NewInterpeter(jobID, actJob, nil, gitCtx, results, vars, inputs)).
if err := expreval.New(NewInterpeter(jobID, actJob, nil, gitCtx, results, vars, inputs).Evaluate).
EvaluateYamlNode(&actJob.Strategy.RawMatrix); err != nil {
return nil, fmt.Errorf("evaluate matrix: %w", err)
}
@@ -253,7 +219,6 @@ func ExpandMatrixWithNeeds(jobID string, job *Job, gitCtx *model.GithubContext,
// matrixesOf is this package's only entry to act's GetMatrixes, so that every caller is covered by
// the filter check below. A deferred placeholder is the first thing carrying a raw matrix this far,
// and the emitter reads its `if:` before expanding it.
// TODO: drop the check once gitea.com/gitea/runner validates the shape itself.
func matrixesOf(job *model.Job) ([]map[string]any, error) {
if err := validateMatrixFilters(job); err != nil {
return nil, err
@@ -265,9 +230,9 @@ func matrixesOf(job *model.Job) ([]map[string]any, error) {
return matrixes, nil
}
// validateMatrixFilters rejects an `include`/`exclude` that is not a list of mappings. act asserts
// that shape without checking, so anything else panics there; an unevaluated ${{ }} expression, which
// is still a scalar, is the usual way to reach it.
// validateMatrixFilters rejects an `include`/`exclude` that is not a list of mappings, so that the
// usual way to get there, an unevaluated ${{ }} expression that is still a scalar, is named as such
// instead of surfacing from the middle of the expansion.
func validateMatrixFilters(job *model.Job) error {
if job.Strategy == nil || job.Strategy.RawMatrix.Kind != yaml.MappingNode {
return nil
@@ -306,13 +271,13 @@ func buildMatrixCombos(jobID string, src *Job, matrixes []map[string]any, actJob
combo.Name = jobID
}
combo.Strategy.RawMatrix = encodeMatrix(matrix)
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, gitCtx, results, vars, inputs))
evaluator := expreval.New(NewInterpeter(jobID, actJob, matrix, gitCtx, results, vars, inputs).Evaluate)
if combo.Name, err = nameWithMatrix(combo.Name, matrix, evaluator); err != nil {
return nil, fmt.Errorf("interpolate name for job %q: %w", jobID, err)
}
runsOn := slices.Clone(srcRunsOn)
for i := range runsOn {
if runsOn[i], err = evaluator.interpolate(runsOn[i]); err != nil {
if runsOn[i], err = evaluator.Interpolate(runsOn[i]); err != nil {
return nil, fmt.Errorf("interpolate runs-on for job %q: %w", jobID, err)
}
}
@@ -386,7 +351,7 @@ func encodeRunsOn(runsOn []string) yaml.Node {
return node
}
func nameWithMatrix(name string, m map[string]any, evaluator *ExpressionEvaluator) (string, error) {
func nameWithMatrix(name string, m map[string]any, evaluator expreval.Evaluator) (string, error) {
if len(m) == 0 {
return name, nil
}
@@ -395,7 +360,7 @@ func nameWithMatrix(name string, m map[string]any, evaluator *ExpressionEvaluato
return name + " " + matrixName(m), nil
}
return evaluator.interpolate(name)
return evaluator.Interpolate(name)
}
func matrixName(m map[string]any) string {
+5 -5
View File
@@ -8,7 +8,8 @@ import (
"strings"
"testing"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
@@ -286,8 +287,8 @@ func evaluateJobIf(t *testing.T, matrixYAML, ifExpr string, deferred bool) (bool
}
func TestRejectsUnevaluatedMatrixFilters(t *testing.T) {
// act dereferences include/exclude entries as mappings without checking, so an unevaluated
// expression panics there. Every entry point into act's matrix expansion must reject it. The
// An unevaluated expression is still a scalar, which is not a filter act can apply.
// Every entry point into act's matrix expansion must reject it. The
// expression here reads `vars`, which is available while planning, so the job is not deferred and
// nothing will ever resolve the filter: the error is the right answer at both entry points.
// A deferred placeholder is the other case, covered by TestEvaluateJobIfExpressionLeavesRawMatrixUnavailable.
@@ -328,8 +329,7 @@ jobs:
parseCount int // what Parse makes of the stored payload
parseErrHas string // ... or the error it fails with
}{
// The canonical GitHub dynamic-matrix idiom. act dereferences include entries as mappings, so
// validateMatrixFilters rejects the still-scalar expression outright.
// The canonical GitHub dynamic-matrix idiom. validateMatrixFilters rejects the still-scalar expression outright.
{name: "include expression", matrix: "include: ${{ fromJson(needs.setup.outputs.m) }}", parseErrHas: "must be a list of mappings"},
// A static vector crossed with the unevaluated expression: one workflow per static value.
{name: "static vector and expression", matrix: "os: [a, b]\n version: ${{ fromJson(needs.setup.outputs.m) }}", parseCount: 2},
+7 -4
View File
@@ -8,7 +8,10 @@ import (
"errors"
"fmt"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/expreval"
"gitea.dev/actionslib/pkg/exprparser"
"gitea.dev/actionslib/pkg/model"
"go.yaml.in/yaml/v4"
)
@@ -276,7 +279,7 @@ func EvaluateConcurrency(rc *model.RawConcurrency, jobID string, job *Job, gitCt
matrix = matrixes[0]
}
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs))
evaluator := expreval.New(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs).Evaluate)
var node yaml.Node
if err := node.Encode(rc); err != nil {
return "", false, fmt.Errorf("failed to encode concurrency: %w", err)
@@ -524,8 +527,8 @@ func EvaluateJobIfExpression(jobID string, job *Job, gitCtx map[string]any, resu
matrix = matrixes[0]
}
}
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs))
return evaluator.evaluateCondition(job.If.Value)
evaluator := expreval.New(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs).Evaluate)
return evaluator.EvalBool(job.If.Value, exprparser.DefaultStatusCheckSuccess)
}
// parseMappingNode parse a mapping node and preserve order.
+2 -1
View File
@@ -8,7 +8,8 @@ import (
"strings"
"testing"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
+7 -6
View File
@@ -9,11 +9,12 @@ import (
"regexp"
"strings"
"gitea.dev/actionslib/pkg/expreval"
"gitea.dev/actionslib/pkg/exprparser"
"gitea.dev/actionslib/pkg/model"
"gitea.dev/modules/container"
"gitea.dev/modules/util"
"gitea.com/gitea/runner/act/exprparser"
"gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
@@ -185,7 +186,7 @@ func EvaluateCallerWith(
matrix = matrixes[0]
}
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs))
evaluator := expreval.New(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs).Evaluate)
out := make(map[string]any, len(job.With))
for k, raw := range job.With {
@@ -260,7 +261,7 @@ func MatchCallerInputsAgainstSpec(spec *WorkflowCallSpec, evaluated map[string]a
func parseWorkflowCallInput(name string, typ InputType, v any) (any, error) {
switch typ {
case InputTypeString:
return coerceToString(v), nil
return exprparser.CoerceToString(v), nil
case InputTypeBoolean:
// strict type matching: a boolean input only accepts a native bool, not a "true"/"false" string
if b, ok := v.(bool); ok {
@@ -361,11 +362,11 @@ func EvaluateWorkflowCallOutputs(spec *WorkflowCallSpec, gitCtx *model.GithubCon
Vars: vars,
Inputs: inputs,
}
evaluator := NewExpressionEvaluator(exprparser.NewInterpeter(env, exprparser.Config{}))
evaluator := expreval.New(exprparser.NewInterpeter(env, exprparser.Config{}).Evaluate)
out := make(map[string]string, len(spec.Outputs))
for name, o := range spec.Outputs {
v, err := evaluator.interpolate(o.Value)
v, err := evaluator.Interpolate(o.Value)
if err != nil {
return nil, fmt.Errorf("workflow_call output %q: %w", name, err)
}
@@ -7,7 +7,8 @@ import (
"maps"
"testing"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4"
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"strings"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/dbfs"
"gitea.dev/modules/log"
"gitea.dev/modules/storage"
+1 -1
View File
@@ -11,6 +11,7 @@ import (
"slices"
"strings"
"gitea.dev/actionslib/pkg/model"
"gitea.dev/modules/actions/jobparser"
"gitea.dev/modules/actions/workflowpattern"
"gitea.dev/modules/git"
@@ -21,7 +22,6 @@ import (
"gitea.dev/modules/util"
webhook_module "gitea.dev/modules/webhook"
"gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"fmt"
"net/http"
pingv1 "gitea.dev/actions-proto-go/ping/v1"
"gitea.dev/actions-proto-go/ping/v1/pingv1connect"
pingv1 "gitea.dev/actionslib/ping/v1"
"gitea.dev/actionslib/ping/v1/pingv1connect"
"gitea.dev/modules/log"
"connectrpc.com/connect"
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"net/http/httptest"
"testing"
pingv1 "gitea.dev/actions-proto-go/ping/v1"
"gitea.dev/actions-proto-go/ping/v1/pingv1connect"
pingv1 "gitea.dev/actionslib/ping/v1"
"gitea.dev/actionslib/ping/v1/pingv1connect"
"connectrpc.com/connect"
"github.com/stretchr/testify/assert"
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"strings"
"time"
"gitea.dev/actionslib/pkg/protocol"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/modules/log"
@@ -22,8 +23,8 @@ import (
)
const (
uuidHeaderKey = "x-runner-uuid"
tokenHeaderKey = "x-runner-token"
uuidHeaderKey = protocol.UUIDHeader
tokenHeaderKey = protocol.TokenHeader
)
var withRunner = connect.WithInterceptors(connect.UnaryInterceptorFunc(func(unaryFunc connect.UnaryFunc) connect.UnaryFunc {
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"net/http"
"slices"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
"gitea.dev/actions-proto-go/runner/v1/runnerv1connect"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/actionslib/runner/v1/runnerv1connect"
actions_model "gitea.dev/models/actions"
repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user"
+1 -1
View File
@@ -6,7 +6,7 @@ package runner
import (
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
"github.com/stretchr/testify/assert"
+1 -2
View File
@@ -16,6 +16,7 @@ import (
"strings"
"time"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
@@ -32,8 +33,6 @@ import (
"gitea.dev/services/context"
"gitea.dev/services/convert"
secret_service "gitea.dev/services/secrets"
"gitea.com/gitea/runner/act/model"
)
// ListActionsSecrets list a repo's actions secrets
+1 -1
View File
@@ -14,6 +14,7 @@ import (
"slices"
"strings"
act_model "gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
git_model "gitea.dev/models/git"
@@ -33,7 +34,6 @@ import (
"gitea.dev/services/context"
"gitea.dev/services/convert"
act_model "gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+1 -1
View File
@@ -9,6 +9,7 @@ import (
"strings"
"testing"
act_model "gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
@@ -17,7 +18,6 @@ import (
"gitea.dev/modules/test"
web_context "gitea.dev/services/context"
act_model "gitea.com/gitea/runner/act/model"
"github.com/stretchr/testify/assert"
)
+1 -2
View File
@@ -17,6 +17,7 @@ import (
"strings"
"time"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
git_model "gitea.dev/models/git"
@@ -41,8 +42,6 @@ import (
"gitea.dev/routers/common"
actions_service "gitea.dev/services/actions"
context_module "gitea.dev/services/context"
"gitea.com/gitea/runner/act/model"
)
func findCurrentJobByPathParam(ctx *context_module.Context, jobs []*actions_model.ActionRunJob) (job *actions_model.ActionRunJob, hasPathParam bool) {
+1 -1
View File
@@ -7,10 +7,10 @@ import (
"context"
"fmt"
act_model "gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/modules/actions/jobparser"
act_model "gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+1 -2
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"strconv"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
actions_module "gitea.dev/modules/actions"
@@ -20,8 +21,6 @@ import (
"gitea.dev/modules/setting"
api "gitea.dev/modules/structs"
"gitea.dev/modules/util"
"gitea.com/gitea/runner/act/model"
)
type GiteaContext map[string]any
+1 -1
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"testing"
act_model "gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
@@ -17,7 +18,6 @@ import (
api "gitea.dev/modules/structs"
webhook_module "gitea.dev/modules/webhook"
act_model "gitea.com/gitea/runner/act/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+1 -2
View File
@@ -10,6 +10,7 @@ import (
"slices"
"strings"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
issues_model "gitea.dev/models/issues"
@@ -27,8 +28,6 @@ import (
api "gitea.dev/modules/structs"
webhook_module "gitea.dev/modules/webhook"
"gitea.dev/services/convert"
"gitea.com/gitea/runner/act/model"
)
type methodCtxKeyType struct{}
+1 -1
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"slices"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
@@ -18,7 +19,6 @@ import (
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
"gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+1 -1
View File
@@ -7,13 +7,13 @@ import (
"context"
"fmt"
act_model "gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
"gitea.dev/modules/actions/jobparser"
"gitea.dev/modules/log"
"gitea.dev/modules/util"
act_model "gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
secret_model "gitea.dev/models/secret"
+1 -1
View File
@@ -6,6 +6,7 @@ package actions
import (
"fmt"
"gitea.dev/actionslib/pkg/model"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/perm"
access_model "gitea.dev/models/perm/access"
@@ -21,7 +22,6 @@ import (
"gitea.dev/services/context"
"gitea.dev/services/convert"
"gitea.com/gitea/runner/act/model"
"go.yaml.in/yaml/v4"
)
+2 -1
View File
@@ -6,7 +6,8 @@ package actions
import (
"testing"
"gitea.com/gitea/runner/act/model"
"gitea.dev/actionslib/pkg/model"
"github.com/stretchr/testify/assert"
)
+2 -3
View File
@@ -14,7 +14,8 @@ import (
"strconv"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
"gitea.dev/actionslib/pkg/model"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
asymkey_model "gitea.dev/models/asymkey"
"gitea.dev/models/auth"
@@ -38,8 +39,6 @@ import (
webhook_module "gitea.dev/modules/webhook"
asymkey_service "gitea.dev/services/asymkey"
"gitea.dev/services/gitdiff"
"gitea.com/gitea/runner/act/model"
)
// ToEmail convert models.EmailAddress to api.Email
@@ -11,7 +11,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/unittest"
@@ -12,7 +12,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/unittest"
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
@@ -9,7 +9,7 @@ import (
"os"
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/dbfs"
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
@@ -11,7 +11,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"net/url"
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
+4 -4
View File
@@ -10,10 +10,10 @@ import (
"testing"
"time"
pingv1 "gitea.dev/actions-proto-go/ping/v1"
"gitea.dev/actions-proto-go/ping/v1/pingv1connect"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
"gitea.dev/actions-proto-go/runner/v1/runnerv1connect"
pingv1 "gitea.dev/actionslib/ping/v1"
"gitea.dev/actionslib/ping/v1/pingv1connect"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/actionslib/runner/v1/runnerv1connect"
auth_model "gitea.dev/models/auth"
"gitea.dev/modules/setting"
@@ -12,7 +12,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
@@ -16,7 +16,7 @@ import (
"strings"
"testing"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
@@ -18,7 +18,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
db_model "gitea.dev/models/db"