mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-26 14:43:39 +09:00
feat(actions): update actionslib, support self:, misc fixes (#39358)
Updates actionslib to https://gitea.com/gitea/actionslib/releases/tag/v1.2.1, moves workflow parsing into it and aligns behaviour with GitHub. 1. `uses:` supports `self:` (Gitea-only feature) and `$/` paths. 1. `strategy`, `matrix`, `max-parallel` and `fail-fast` accept expressions, including over `needs`. A job whose `name`, `runs-on` or `continue-on-error` reads `needs` is resolved once they finish. 1. A job `if:` may only read `github`, `needs`, `vars` and `inputs` and is decided before the matrix, as on github.com. 1. Matrix `fail-fast` cancels the other combinations, and `always()` jobs keep running when a run is cancelled. 1. Invalid workflow files, including a malformed `on:` and unknown or cyclic `needs`, show up on push as failed runs with the error. 1. A job whose `if:` or `concurrency:` fails to evaluate is skipped or failed with the error, instead of staying blocked. 1. Reusable workflows: a missing and an unreadable repository fail alike, public callers cannot use private workflows, nested jobs cannot exceed the caller's token permissions. 1. Runner labels match case-insensitively, and `runs-on` accepts an array from an expression. Runner PR: https://gitea.com/gitea/runner/pulls/1247 Docs PR: https://gitea.com/gitea/docs/pulls/553 Fixes: https://github.com/go-gitea/gitea/issues/38990 Fixes: https://github.com/go-gitea/gitea/issues/39382 Fixes: https://github.com/go-gitea/gitea/issues/32364 Fixes: https://github.com/go-gitea/gitea/issues/36077 Fixes: https://github.com/go-gitea/gitea/issues/23277 Fixes: https://github.com/go-gitea/gitea/issues/29020 Co-authored-by: Claude (Opus 5) <noreply@anthropic.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
@@ -7,11 +7,11 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
actions_module "gitea.dev/modules/actions"
|
||||
"gitea.dev/modules/actions/jobparser"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
@@ -55,6 +55,7 @@ func TestCheckCallerChain_Cycle(t *testing.T) {
|
||||
)
|
||||
err := checkCallerChain(t.Context(), chain[len(chain)-1])
|
||||
assert.ErrorContains(t, err, "cycle detected")
|
||||
assert.Equal(t, canonicalCallUses(&actions_model.ActionRunJob{CallUses: "owner/repo/.gitea/workflows/a.yml@v1"}), canonicalCallUses(&actions_model.ActionRunJob{CallUses: "self:owner/repo/.gitea/workflows/a.yml@v1"}))
|
||||
})
|
||||
|
||||
t.Run("NoCycle", func(t *testing.T) {
|
||||
@@ -67,6 +68,37 @@ func TestCheckCallerChain_Cycle(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, checkCallerChain(t.Context(), chain[len(chain)-1]))
|
||||
})
|
||||
|
||||
t.Run("SameLocalPathInOtherRepo", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
chain := buildCallerChain(t,
|
||||
"./.gitea/workflows/a.yml",
|
||||
"owner/lib/.gitea/workflows/lib.yml@v1",
|
||||
"./.gitea/workflows/a.yml",
|
||||
)
|
||||
leaf := chain[len(chain)-1]
|
||||
leaf.WorkflowSourceRepoID = 2
|
||||
require.NoError(t, checkCallerChain(t.Context(), leaf))
|
||||
})
|
||||
|
||||
t.Run("ResolvedIdentityCycle", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
chain := buildCallerChain(t,
|
||||
"./.gitea/workflows/a.yml",
|
||||
"owner/repo/.gitea/workflows/b.yml@v1",
|
||||
"owner/repo/.gitea/workflows/a.yml@v1",
|
||||
)
|
||||
chain[1].WorkflowSourceRepoID = 4
|
||||
chain[1].WorkflowSourceCommitSHA = "first-commit"
|
||||
_, err := actions_model.UpdateRunJob(t.Context(), chain[1], nil, "workflow_source_repo_id", "workflow_source_commit_sha")
|
||||
require.NoError(t, err)
|
||||
chain[2].WorkflowSourceRepoID = 5
|
||||
chain[2].WorkflowSourceCommitSHA = "second-commit"
|
||||
|
||||
require.NoError(t, checkCallerChain(t.Context(), chain[2]))
|
||||
require.ErrorContains(t, checkResolvedCallerCycle(t.Context(), chain[2], 4, "first-commit", ".gitea/workflows/a.yml"), "cycle detected")
|
||||
require.NoError(t, checkResolvedCallerCycle(t.Context(), chain[2], 4, "other-commit", ".gitea/workflows/a.yml"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckCallerChain_DepthLimit(t *testing.T) {
|
||||
@@ -162,11 +194,11 @@ func TestResolveUses(t *testing.T) {
|
||||
// Same-repo and cross-repo forms are not URLs and are parsed as-is.
|
||||
ref, err := ResolveUses(ctx, "./.gitea/workflows/build.yml")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yml"}, *ref)
|
||||
assert.Equal(t, model.ReusableWorkflowUses{Path: ".gitea/workflows/build.yml"}, *ref)
|
||||
|
||||
ref, err = ResolveUses(ctx, "owner/repo/.gitea/workflows/build.yml@v1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalCrossRepo, Owner: "owner", Repo: "repo", Path: ".gitea/workflows/build.yml", Ref: "v1"}, *ref)
|
||||
assert.Equal(t, model.ReusableWorkflowUses{Owner: "owner", Repo: "repo", Path: ".gitea/workflows/build.yml", Ref: "v1"}, *ref)
|
||||
})
|
||||
|
||||
t.Run("DirectoryAllowlist", func(t *testing.T) {
|
||||
@@ -179,11 +211,17 @@ func TestResolveUses(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path)
|
||||
|
||||
ref, err = ResolveUses(ctx, "self:owner/repo/.gitea/scoped_workflows/lib.yml@v1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path)
|
||||
|
||||
// A directory that is neither WORKFLOW_DIRS nor SCOPED_WORKFLOW_DIRS parses but is rejected by the allowlist.
|
||||
_, err = ResolveUses(ctx, "./not-workflows/build.yml")
|
||||
require.Error(t, err)
|
||||
_, err = ResolveUses(ctx, "owner/repo/lib/build.yml@v1")
|
||||
require.Error(t, err)
|
||||
_, err = ResolveUses(ctx, "self:owner/repo/lib/build.yml@v1")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("ConfigurableWorkflowDirs", func(t *testing.T) {
|
||||
@@ -201,7 +239,7 @@ func TestResolveUses(t *testing.T) {
|
||||
// An absolute URL on this instance (incl. AppSubURL) resolves to the equivalent cross-repo ref.
|
||||
ref, err := ResolveUses(ctx, "https://gitea.example.com/sub/owner/repo/.gitea/workflows/ci.yml@refs/heads/main")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalCrossRepo, Owner: "owner", Repo: "repo", Path: ".gitea/workflows/ci.yml", Ref: "refs/heads/main"}, *ref)
|
||||
assert.Equal(t, model.ReusableWorkflowUses{Owner: "owner", Repo: "repo", Path: ".gitea/workflows/ci.yml", Ref: "refs/heads/main"}, *ref)
|
||||
})
|
||||
|
||||
t.Run("InvalidSyntax", func(t *testing.T) {
|
||||
@@ -222,6 +260,16 @@ func TestResolveUses(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadReusableWorkflowSourceFailsAlikeForMissingAndPrivateRepo(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
run := &actions_model.ActionRun{RepoID: 4, TriggerUserID: 1}
|
||||
for _, repoName := range []string{"missing", "repo3"} {
|
||||
_, _, _, err := loadReusableWorkflowSource(t.Context(), run, nil, &model.ReusableWorkflowUses{Owner: "org3", Repo: repoName, Path: ".gitea/workflows/build.yml", Ref: "main"})
|
||||
assert.EqualError(t, err, "reusable workflow repository org3/"+repoName+" does not exist or is not readable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRunJobLimit(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user