mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-14 06:51:52 +02:00
Gitea needs the workflow model and the expression evaluator to parse workflows and to build the task payload this runner consumes, so today it depends on `gitea.com/gitea/runner` just for `act/model` and `act/exprparser`. Both packages now live in `gitea.dev/actionslib` (`pkg/model`, `pkg/exprparser`), the module both sides already share, and this repository consumes them from there. ### Changes - `act/model` and `act/exprparser` are deleted, all imports point at `gitea.dev/actionslib/pkg/...`. - New `act/ghcontext` package: the `GithubContext` helpers that need a git checkout on disk (`SetRef`, `SetSha`, `SetRepositoryAndOwner`) are runner only and would drag a git client plus the act context logger into the shared module, so they stay here as functions, with their tests. Only caller is `RunContext.getGithubContext`. - `act/common.CartesianProduct` moved to the shared model package, `act/model` was its only user. - `act/model/testdata/container-volumes` moved to `act/runner/testdata/container-volumes`, its only user is `runner_test.go`. - `internal/pkg/client.UUIDHeader` / `TokenHeader` now alias `pkg/protocol`, so the header names cannot drift apart from Gitea. ### Notes - No behaviour change intended: the moved files are unchanged apart from the import paths and the split described above. - `go.mod` depends on the released `gitea.dev/actionslib v0.7.0`, which carries both https://gitea.com/gitea/actionslib/pulls/11 and the `model.UsesHash` port in https://gitea.com/gitea/actionslib/pulls/14 that `main` needs after https://gitea.com/gitea/runner/pulls/1150. - Verified with `go build ./...`, `go vet ./...` and `go test ./act/... ./internal/...`; the docker based `act/runner` integration tests (`TestRunEvent`, `TestRunMatrixWithUserDefinedInclusions`) fail identically with and without this change in my environment. Assisted-by: Codet:GPT-5.1-Codex Reviewed-on: https://gitea.com/gitea/runner/pulls/1143 Reviewed-by: silverwind <[email protected]>
158 lines
5.7 KiB
Go
158 lines
5.7 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// Copyright 2023 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package runner
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
|
|
"gitea.dev/actionslib/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func runGit(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
if dir != "" {
|
|
args = append([]string{"-C", dir}, args...)
|
|
}
|
|
cmd := exec.Command("git", args...)
|
|
// Fixed identity and host-config isolation so commits succeed offline regardless of the
|
|
// host's git config (mirrors gitCmd in act/common/git).
|
|
cmd.Env = append(os.Environ(),
|
|
"GIT_AUTHOR_NAME=test", "[email protected]",
|
|
"GIT_COMMITTER_NAME=test", "[email protected]",
|
|
"GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
|
|
)
|
|
out, err := cmd.CombinedOutput()
|
|
require.NoError(t, err, string(out))
|
|
}
|
|
|
|
// TestShortShaActionRejected verifies a `uses` ref that is a shortened commit SHA is rejected
|
|
// with a clear error. The action is resolved from a local repo (via DefaultActionInstance) so
|
|
// this runs offline.
|
|
func TestShortShaActionRejected(t *testing.T) {
|
|
// a local "remote" action repo at <root>/actions/hello-world-docker-action
|
|
actionRoot := t.TempDir()
|
|
repo := filepath.Join(actionRoot, "actions", "hello-world-docker-action")
|
|
require.NoError(t, os.MkdirAll(repo, 0o755))
|
|
runGit(t, "", "init", "--initial-branch=main", repo)
|
|
require.NoError(t, os.WriteFile(filepath.Join(repo, "action.yml"),
|
|
[]byte("name: hello\nruns:\n using: node24\n main: index.js\n"), 0o644))
|
|
runGit(t, repo, "add", ".")
|
|
runGit(t, repo, "commit", "-m", "initial")
|
|
out, err := exec.Command("git", "-C", repo, "rev-parse", "HEAD").Output()
|
|
require.NoError(t, err)
|
|
shortSha := strings.TrimSpace(string(out))[:7]
|
|
|
|
// a workflow that uses the action at the short SHA
|
|
wfDir := filepath.Join(t.TempDir(), "wf")
|
|
require.NoError(t, os.MkdirAll(wfDir, 0o755))
|
|
wf := fmt.Sprintf("on: push\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/hello-world-docker-action@%s\n", shortSha)
|
|
require.NoError(t, os.WriteFile(filepath.Join(wfDir, "push.yml"), []byte(wf), 0o644))
|
|
|
|
runner, err := New(&Config{
|
|
Workdir: wfDir,
|
|
EventName: "push",
|
|
Platforms: map[string]string{"ubuntu-latest": baseImage},
|
|
GitHubInstance: "github.com",
|
|
DefaultActionInstance: actionRoot,
|
|
ContainerMaxLifetime: time.Hour,
|
|
})
|
|
require.NoError(t, err)
|
|
planner, err := model.NewWorkflowPlanner(wfDir, true)
|
|
require.NoError(t, err)
|
|
plan, err := planner.PlanEvent("push")
|
|
require.NoError(t, err)
|
|
|
|
err = runner.NewPlanExecutor(plan)(common.WithDryrun(context.Background(), true))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "shortened version of a commit SHA")
|
|
}
|
|
|
|
func TestActionCache(t *testing.T) {
|
|
a := assert.New(t)
|
|
ctx := context.Background()
|
|
|
|
// Build a local bare repo with a `js` action dir so this runs offline (formerly cloned
|
|
// github.com/nektos/act-test-actions over the network). allowAnySHA1InWant lets the
|
|
// "Fetch Sha" case fetch a commit hash directly.
|
|
remoteDir := t.TempDir()
|
|
runGit(t, "", "init", "--bare", "--initial-branch=main", remoteDir)
|
|
runGit(t, remoteDir, "config", "uploadpack.allowAnySHA1InWant", "true")
|
|
|
|
workDir := t.TempDir()
|
|
runGit(t, "", "clone", remoteDir, workDir)
|
|
require.NoError(t, os.MkdirAll(filepath.Join(workDir, "js"), 0o755))
|
|
require.NoError(t, os.WriteFile(filepath.Join(workDir, "js", "action.yml"),
|
|
[]byte("name: js\nruns:\n using: node24\n main: index.js\n"), 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(workDir, "js", "index.js"),
|
|
[]byte("console.log('hello');\n"), 0o644))
|
|
runGit(t, workDir, "add", ".")
|
|
runGit(t, workDir, "commit", "-m", "initial")
|
|
runGit(t, workDir, "push", "-u", "origin", "main")
|
|
|
|
out, err := exec.Command("git", "-C", workDir, "rev-parse", "main").Output()
|
|
require.NoError(t, err)
|
|
fullSha := strings.TrimSpace(string(out))
|
|
|
|
cache := &GoGitActionCache{
|
|
Path: t.TempDir(),
|
|
}
|
|
cacheDir := "local/act-test-actions"
|
|
refs := []struct {
|
|
Name string
|
|
Ref string
|
|
}{
|
|
{Name: "Fetch Branch Name", Ref: "main"},
|
|
{Name: "Fetch Branch Name Absolutely", Ref: "refs/heads/main"},
|
|
{Name: "Fetch HEAD", Ref: "HEAD"},
|
|
{Name: "Fetch Sha", Ref: fullSha},
|
|
}
|
|
for _, c := range refs {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
sha, err := cache.Fetch(ctx, cacheDir, remoteDir, c.Ref, "")
|
|
if !a.NoError(err) || !a.NotEmpty(sha) { //nolint:testifylint // pre-existing issue from nektos/act
|
|
return
|
|
}
|
|
atar, err := cache.GetTarArchive(ctx, cacheDir, sha, "js")
|
|
// NotNil, not NotEmpty: atar is a live io.PipeReader whose producer goroutine is
|
|
// writing concurrently; NotEmpty deep-reflects over its internals and races.
|
|
if !a.NoError(err) || !a.NotNil(atar) { //nolint:testifylint // pre-existing issue from nektos/act
|
|
return
|
|
}
|
|
// GetTarArchive streams from a background goroutine walking the shared repo.
|
|
// Drain and close so it finishes before the next subtest fetches into the same
|
|
// repo; otherwise the lingering walk races with that fetch.
|
|
defer func() {
|
|
_, _ = io.Copy(io.Discard, atar)
|
|
_ = atar.Close()
|
|
}()
|
|
mytar := tar.NewReader(atar)
|
|
th, err := mytar.Next()
|
|
if !a.NoError(err) || !a.NotEqual(0, th.Size) { //nolint:testifylint // pre-existing issue from nektos/act
|
|
return
|
|
}
|
|
buf := &bytes.Buffer{}
|
|
// G110: Potential DoS vulnerability via decompression bomb (gosec)
|
|
_, err = io.Copy(buf, mytar)
|
|
a.NoError(err) //nolint:testifylint // pre-existing issue from nektos/act
|
|
str := buf.String()
|
|
a.NotEmpty(str)
|
|
})
|
|
}
|
|
}
|