refactor: move act/model and act/exprparser to actions-proto-go

Gitea needs the workflow model and the expression evaluator to parse
workflows and to build the task payload this runner consumes, so it had
to depend on gitea.com/gitea/runner for it. Both packages now live in
gitea.dev/actions-proto-go, the module both sides already share, and this
repository consumes them from there.

The GithubContext helpers that need a git checkout on disk (SetRef,
SetSha and SetRepositoryAndOwner) are not moved: they are runner only and
would drag a git client and the act context logger into the shared
module. They become functions of the new act/ghcontext package, together
with their tests.

act/common.CartesianProduct moved to the shared model package as well,
act/model was its only user. act/model/testdata/container-volumes is now
act/runner/testdata/container-volumes, its only user is runner_test.go.

The x-runner-uuid and x-runner-token header names come from
actions-proto-go too, so the runner and Gitea cannot drift apart.

Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
Lunny Xiao
2026-08-03 17:53:08 -07:00
parent 34bfa19150
commit a9d47fb5ed
71 changed files with 212 additions and 5328 deletions

View File

@@ -0,0 +1,138 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2021 The nektos/act Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package ghcontext fills a model.GithubContext from the local git checkout.
// The pure data parts of the context live in the shared
// gitea.dev/actions-proto-go/pkg/model package, only the helpers that need a
// git repository on disk are kept here.
package ghcontext
import (
"context"
"fmt"
"strings"
"gitea.com/gitea/runner/act/common"
"gitea.com/gitea/runner/act/common/git"
"gitea.dev/actions-proto-go/pkg/model"
)
var (
findGitRef = git.FindGitRef
findGitRevision = git.FindGitRevision
findGithubRepo = git.FindGithubRepo
)
func withDefaultBranch(ctx context.Context, b string, event map[string]any) map[string]any {
repoI, ok := event["repository"]
if !ok {
repoI = make(map[string]any)
}
repo, ok := repoI.(map[string]any)
if !ok {
common.Logger(ctx).Warnf("unable to set default branch to %v", b)
return event
}
// if the branch is already there return with no changes
if _, ok = repo["default_branch"]; ok {
return event
}
repo["default_branch"] = b
event["repository"] = repo
return event
}
// SetRef resolves the ref of the context from its event payload, falling back
// to the ref checked out in repoPath.
func SetRef(ctx context.Context, ghc *model.GithubContext, defaultBranch, repoPath string) {
logger := common.Logger(ctx)
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName {
case "pull_request_target":
ghc.Ref = "refs/heads/" + ghc.BaseRef
case "pull_request", "pull_request_review", "pull_request_review_comment":
ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"])
case "deployment", "deployment_status":
ghc.Ref = model.AsString(model.NestedMapLookup(ghc.Event, "deployment", "ref"))
case "release":
ghc.Ref = "refs/tags/" + model.AsString(model.NestedMapLookup(ghc.Event, "release", "tag_name"))
case "push", "create", "workflow_dispatch":
ghc.Ref = model.AsString(ghc.Event["ref"])
default:
defaultBranch := model.AsString(model.NestedMapLookup(ghc.Event, "repository", "default_branch"))
if defaultBranch != "" {
ghc.Ref = "refs/heads/" + defaultBranch
}
}
if ghc.Ref == "" {
ref, err := findGitRef(ctx, repoPath)
if err != nil {
logger.Warningf("unable to get git ref: %v", err)
} else {
logger.Debugf("using github ref: %s", ref)
ghc.Ref = ref
}
// set the branch in the event data
if defaultBranch != "" {
ghc.Event = withDefaultBranch(ctx, defaultBranch, ghc.Event)
} else {
ghc.Event = withDefaultBranch(ctx, "master", ghc.Event)
}
if ghc.Ref == "" {
ghc.Ref = "refs/heads/" + model.AsString(model.NestedMapLookup(ghc.Event, "repository", "default_branch"))
}
}
}
// SetSha resolves the commit of the context from its event payload, falling
// back to the revision checked out in repoPath.
func SetSha(ctx context.Context, ghc *model.GithubContext, repoPath string) {
logger := common.Logger(ctx)
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName {
case "pull_request_target":
ghc.Sha = model.AsString(model.NestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
case "deployment", "deployment_status":
ghc.Sha = model.AsString(model.NestedMapLookup(ghc.Event, "deployment", "sha"))
case "push", "create", "workflow_dispatch":
if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
ghc.Sha = model.AsString(ghc.Event["after"])
}
}
if ghc.Sha == "" {
_, sha, err := findGitRevision(ctx, repoPath)
if err != nil {
logger.Warningf("unable to get git revision: %v", err)
} else {
ghc.Sha = sha
}
}
}
// SetRepositoryAndOwner resolves the repository of the context from the git
// remote in repoPath when it is not set yet, and derives its owner.
func SetRepositoryAndOwner(ctx context.Context, ghc *model.GithubContext, githubInstance, remoteName, repoPath string) {
if ghc.Repository == "" {
repo, err := findGithubRepo(ctx, repoPath, githubInstance, remoteName)
if err != nil {
common.Logger(ctx).Warningf("unable to get git repo (githubInstance: %v; remoteName: %v, repoPath: %v): %v", githubInstance, remoteName, repoPath, err)
return
}
ghc.Repository = repo
}
ghc.RepositoryOwner = strings.Split(ghc.Repository, "/")[0]
}

View File

@@ -0,0 +1,217 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2022 The nektos/act Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package ghcontext
import (
"context"
"errors"
"testing"
"gitea.dev/actions-proto-go/pkg/model"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestSetRef(t *testing.T) {
log.SetLevel(log.DebugLevel)
oldFindGitRef := findGitRef
oldFindGitRevision := findGitRevision
defer func() { findGitRef = oldFindGitRef }()
defer func() { findGitRevision = oldFindGitRevision }()
findGitRef = func(ctx context.Context, file string) (string, error) {
return "refs/heads/master", nil
}
findGitRevision = func(ctx context.Context, file string) (string, string, error) {
return "", "1234fakesha", nil
}
tables := []struct {
eventName string
event map[string]any
ref string
refName string
}{
{
eventName: "pull_request_target",
event: map[string]any{},
ref: "refs/heads/master",
refName: "master",
},
{
eventName: "pull_request",
event: map[string]any{
"number": 1234.,
},
ref: "refs/pull/1234/merge",
refName: "1234/merge",
},
{
eventName: "deployment",
event: map[string]any{
"deployment": map[string]any{
"ref": "refs/heads/somebranch",
},
},
ref: "refs/heads/somebranch",
refName: "somebranch",
},
{
eventName: "release",
event: map[string]any{
"release": map[string]any{
"tag_name": "v1.0.0",
},
},
ref: "refs/tags/v1.0.0",
refName: "v1.0.0",
},
{
eventName: "push",
event: map[string]any{
"ref": "refs/heads/somebranch",
},
ref: "refs/heads/somebranch",
refName: "somebranch",
},
{
eventName: "unknown",
event: map[string]any{
"repository": map[string]any{
"default_branch": "main",
},
},
ref: "refs/heads/main",
refName: "main",
},
{
eventName: "no-event",
event: map[string]any{},
ref: "refs/heads/master",
refName: "master",
},
}
for _, table := range tables {
t.Run(table.eventName, func(t *testing.T) {
ghc := &model.GithubContext{
EventName: table.eventName,
BaseRef: "master",
Event: table.event,
}
SetRef(context.Background(), ghc, "main", "/some/dir")
ghc.SetRefTypeAndName()
assert.Equal(t, table.ref, ghc.Ref)
assert.Equal(t, table.refName, ghc.RefName)
})
}
t.Run("no-default-branch", func(t *testing.T) {
findGitRef = func(ctx context.Context, file string) (string, error) {
return "", errors.New("no default branch")
}
ghc := &model.GithubContext{
EventName: "no-default-branch",
Event: map[string]any{},
}
SetRef(context.Background(), ghc, "", "/some/dir")
assert.Equal(t, "refs/heads/master", ghc.Ref)
})
}
func TestSetSha(t *testing.T) {
log.SetLevel(log.DebugLevel)
oldFindGitRef := findGitRef
oldFindGitRevision := findGitRevision
defer func() { findGitRef = oldFindGitRef }()
defer func() { findGitRevision = oldFindGitRevision }()
findGitRef = func(ctx context.Context, file string) (string, error) {
return "refs/heads/master", nil
}
findGitRevision = func(ctx context.Context, file string) (string, string, error) {
return "", "1234fakesha", nil
}
tables := []struct {
eventName string
event map[string]any
sha string
}{
{
eventName: "pull_request_target",
event: map[string]any{
"pull_request": map[string]any{
"base": map[string]any{
"sha": "pr-base-sha",
},
},
},
sha: "pr-base-sha",
},
{
eventName: "pull_request",
event: map[string]any{
"number": 1234.,
},
sha: "1234fakesha",
},
{
eventName: "deployment",
event: map[string]any{
"deployment": map[string]any{
"sha": "deployment-sha",
},
},
sha: "deployment-sha",
},
{
eventName: "release",
event: map[string]any{},
sha: "1234fakesha",
},
{
eventName: "push",
event: map[string]any{
"after": "push-sha",
"deleted": false,
},
sha: "push-sha",
},
{
eventName: "unknown",
event: map[string]any{},
sha: "1234fakesha",
},
{
eventName: "no-event",
event: map[string]any{},
sha: "1234fakesha",
},
}
for _, table := range tables {
t.Run(table.eventName, func(t *testing.T) {
ghc := &model.GithubContext{
EventName: table.eventName,
BaseRef: "master",
Event: table.event,
}
SetSha(context.Background(), ghc, "/some/dir")
assert.Equal(t, table.sha, ghc.Sha)
})
}
}