From aab249000c238cce71a38c685561e88a1e1014f1 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 18 Feb 2026 05:37:21 +0000 Subject: [PATCH] chore(deps): update act to v0.261.8 (#791) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Update `gitea.com/gitea/act` from pseudo-version `v0.261.7-0.20251202193638-5417d3ac6742` to `v0.261.8` - Update yaml import in `workflow.go` from `gopkg.in/yaml.v3` to `go.yaml.in/yaml/v4` to match act's yaml v4 migration - Add tests to verify yaml/v4 upgrade works correctly ## Changes included in act v0.261.8 - Recover from panics in parallel executor workers ([gitea/act#153](https://gitea.com/gitea/act/pulls/153)) - Fix max-parallel support for matrix jobs ([gitea/act#150](https://gitea.com/gitea/act/pulls/150)) - Fix yaml with prefixed newline broken setjob + yaml v4 ([gitea/act#144](https://gitea.com/gitea/act/pulls/144)) - Fixed typo ([gitea/act#151](https://gitea.com/gitea/act/pulls/151)) ## Tests added - **`Test_generateWorkflow`**: 7 new cases (was 1), covering: no needs, needs as list, workflow env/triggers, container+services, matrix strategy, special YAML characters, and invalid YAML error handling - **`Test_yamlV4NodeRoundTrip`**: Directly exercises `go.yaml.in/yaml/v4` — `yaml.Node` construction, marshal/unmarshal round-trip, and node kind constants Fixes: https://gitea.com/gitea/act_runner/issues/371 Fixes: https://gitea.com/gitea/act_runner/issues/772 Reviewed-on: https://gitea.com/gitea/act_runner/pulls/791 Reviewed-by: Lunny Xiao Co-authored-by: silverwind Co-committed-by: silverwind --- go.mod | 4 +- go.sum | 6 +- internal/app/run/workflow.go | 2 +- internal/app/run/workflow_test.go | 255 ++++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index a37e207f..02eaf38c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( gotest.tools/v3 v3.5.1 ) +require go.yaml.in/yaml/v4 v4.0.0-rc.2 + require ( cyphar.com/go-pathrs v0.2.1 // indirect dario.cat/mergo v1.0.0 // indirect @@ -100,7 +102,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.8 replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.16.2 diff --git a/go.sum b/go.sum index 57f2e211..5ea1f432 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742 h1:ulcquQluJbmNASkh6ina70LvcHEa9eWYfQ+DeAZ0VEE= -gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= +gitea.com/gitea/act v0.261.8 h1:rUWB5GOZOubfe2VteKb7XP3HRIbcW3UUmfh7bVAgQcA= +gitea.com/gitea/act v0.261.8/go.mod h1:lTp4136rwbZiZS3ZVQeHCvd4qRAZ7LYeiRBqOSdMY/4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -227,6 +227,8 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/app/run/workflow.go b/internal/app/run/workflow.go index a6fbb71c..5e3a6127 100644 --- a/internal/app/run/workflow.go +++ b/internal/app/run/workflow.go @@ -11,7 +11,7 @@ import ( runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "github.com/nektos/act/pkg/model" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v4" ) func generateWorkflow(task *runnerv1.Task) (*model.Workflow, string, error) { diff --git a/internal/app/run/workflow_test.go b/internal/app/run/workflow_test.go index c7598db9..94f2487f 100644 --- a/internal/app/run/workflow_test.go +++ b/internal/app/run/workflow_test.go @@ -9,6 +9,7 @@ import ( runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "github.com/nektos/act/pkg/model" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" "gotest.tools/v3/assert" ) @@ -62,13 +63,267 @@ jobs: want1: "job9", wantErr: false, }, + { + name: "no needs", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Simple workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo "hello" +`), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + job := wf.GetJob("test") + assert.DeepEqual(t, job.Needs(), []string{}) + assert.Equal(t, len(job.Steps), 2) + }, + want1: "test", + wantErr: false, + }, + { + name: "needs list", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Workflow with list needs +on: push + +jobs: + deploy: + needs: [build, test, lint] + runs-on: ubuntu-latest + steps: + - run: echo "deploying" +`), + Needs: map[string]*runnerv1.TaskNeed{ + "build": { + Outputs: map[string]string{}, + Result: runnerv1.Result_RESULT_SUCCESS, + }, + "test": { + Outputs: map[string]string{ + "coverage": "80%", + }, + Result: runnerv1.Result_RESULT_SUCCESS, + }, + "lint": { + Outputs: map[string]string{}, + Result: runnerv1.Result_RESULT_FAILURE, + }, + }, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + job := wf.GetJob("deploy") + needs := job.Needs() + assert.DeepEqual(t, needs, []string{"build", "lint", "test"}) + assert.Equal(t, wf.Jobs["test"].Outputs["coverage"], "80%") + assert.Equal(t, wf.Jobs["lint"].Result, "failure") + }, + want1: "deploy", + wantErr: false, + }, + { + name: "workflow env and defaults", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Complex workflow +on: + push: + branches: [main, develop] + pull_request: + types: [opened, synchronize] + +env: + NODE_ENV: production + CI: "true" + +jobs: + build: + runs-on: ubuntu-latest + env: + BUILD_TYPE: release + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "18" + - run: npm ci + - run: npm run build +`), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + assert.Equal(t, wf.Name, "Complex workflow") + assert.Equal(t, wf.Env["NODE_ENV"], "production") + assert.Equal(t, wf.Env["CI"], "true") + job := wf.GetJob("build") + assert.Equal(t, len(job.Steps), 4) + }, + want1: "build", + wantErr: false, + }, + { + name: "job with container and services", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Integration tests +on: push + +jobs: + integration: + runs-on: ubuntu-latest + container: + image: node:18 + services: + postgres: + image: postgres:15 + steps: + - uses: actions/checkout@v3 + - run: npm test +`), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + job := wf.GetJob("integration") + container := job.Container() + assert.Equal(t, container.Image, "node:18") + assert.Equal(t, job.Services["postgres"].Image, "postgres:15") + }, + want1: "integration", + wantErr: false, + }, + { + name: "job with matrix strategy", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Matrix build +on: push + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ["1.21", "1.22"] + steps: + - uses: actions/checkout@v3 + - run: go test ./... +`), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + job := wf.GetJob("test") + matrixes, err := job.GetMatrixes() + require.NoError(t, err) + assert.Equal(t, len(matrixes), 2) + }, + want1: "test", + wantErr: false, + }, + { + name: "special yaml characters in values", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte("name: \"Special: characters & test\"\non: push\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: 'echo \"hello: world\"'\n - run: 'echo \"quotes & ampersands\"'\n - run: |\n echo \"multiline\"\n echo \"script\"\n"), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: func(t *testing.T, wf *model.Workflow) { + assert.Equal(t, wf.Name, "Special: characters & test") + job := wf.GetJob("test") + assert.Equal(t, len(job.Steps), 3) + }, + want1: "test", + wantErr: false, + }, + { + name: "invalid yaml", + args: args{ + task: &runnerv1.Task{ + WorkflowPayload: []byte(` +name: Bad workflow +on: push +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo "ok" + bad-indent: true +`), + Needs: map[string]*runnerv1.TaskNeed{}, + }, + }, + assert: nil, + want1: "", + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1, err := generateWorkflow(tt.args.task) + if tt.wantErr { + require.Error(t, err) + return + } require.NoError(t, err) tt.assert(t, got) assert.Equal(t, got1, tt.want1) }) } } + +func Test_yamlV4NodeRoundTrip(t *testing.T) { + t.Run("marshal sequence node", func(t *testing.T) { + node := yaml.Node{ + Kind: yaml.SequenceNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "a"}, + {Kind: yaml.ScalarNode, Value: "b"}, + {Kind: yaml.ScalarNode, Value: "c"}, + }, + } + + out, err := yaml.Marshal(&node) + require.NoError(t, err) + assert.Equal(t, string(out), "- a\n- b\n- c\n") + }) + + t.Run("unmarshal and re-marshal workflow", func(t *testing.T) { + input := []byte("name: test\non: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hello\n") + + var wf map[string]interface{} + err := yaml.Unmarshal(input, &wf) + require.NoError(t, err) + assert.Equal(t, wf["name"], "test") + + out, err := yaml.Marshal(wf) + require.NoError(t, err) + + var wf2 map[string]interface{} + err = yaml.Unmarshal(out, &wf2) + require.NoError(t, err) + assert.Equal(t, wf2["name"], "test") + }) + + t.Run("node kind constants", func(t *testing.T) { + // Verify yaml/v4 node kind constants are usable (same API as v3) + require.NotEqual(t, yaml.ScalarNode, yaml.SequenceNode) + require.NotEqual(t, yaml.SequenceNode, yaml.MappingNode) + }) +}