refactor: remove dead code and consolidate tests (#1335)

Removes dead code and redundant logic and consolidates tests, so there is less to read and maintain. No behavior change.

1. **act/runner**: inline single-use wrappers, compile regexes once instead of per call, embed the trampoline as a string, drop the unused legacy result of `parseActionCommand`
2. **act/container**: `sanitizeConfig` and `mergeContainerConfigs` stop returning the configs they already mutate in place, drop never-set option fields and the unused `user` parameter of `ExecWithCmdLine`
3. **act/common and cache server**: drop the unused `Trace`/`Tracef` loggers and the short SHA of `FindGitRevision`, `RegisterJob` releases through `RevokeJob`
4. **daemon, poller and reporter**: one shutdown wait for both poll modes, drop an unreachable fetch timeout branch, guard the reporter's last report time with its existing lock instead of an atomic
5. **build**: drop unused Makefile and release workflow variables, collapse the goreleaser ignore list to the same build targets
6. **tests**: fold near-duplicate tests into tables with shared setup

Throughout, hand-rolled logic gives way to `cmp.Or`, `strings.Cut`, `slices` and `min`/`max`.

Reviewed-on: https://gitea.com/gitea/runner/pulls/1335
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
This commit is contained in:
silverwind authored and silverwind committed 2026-10-22 19:41:10 +00:00
1 parent 923a0bfb59
commit d882311ec6
107 files changed
+2408 -6500

No files matched your search

+6 -23
View File
@@ -4,6 +4,7 @@
package cmd
import (
"cmp"
"errors"
"fmt"
@@ -33,32 +34,14 @@ func runCacheServer(configFile *string, cacheArgs *cacheServerArgs) func(cmd *co
return errors.New("cache-server does not support cache.s3; set cache.s3 on the runners instead")
}
var (
dir = cfg.Cache.Dir
host = cfg.Cache.Host
port = cfg.Cache.Port
)
// cacheArgs has higher priority
if cacheArgs.Dir != "" {
dir = cacheArgs.Dir
}
if cacheArgs.Host != "" {
host = cacheArgs.Host
}
if cacheArgs.Port != 0 {
port = cacheArgs.Port
}
secret := cfg.Cache.ExternalSecret
if secret == "" {
if cfg.Cache.ExternalSecret == "" {
return errors.New("cache.external_secret (or cache.external_secret_file) must be set for cache-server; configure the same value on each runner that points at this server via cache.external_server")
}
cacheHandler, err := artifactcache.StartHandler(artifactcache.Options{
Dir: dir,
OutboundIP: host,
Port: port,
InternalSecret: secret,
Dir: cmp.Or(cacheArgs.Dir, cfg.Cache.Dir),
OutboundIP: cmp.Or(cacheArgs.Host, cfg.Cache.Host),
Port: cmp.Or(cacheArgs.Port, cfg.Cache.Port),
InternalSecret: cfg.Cache.ExternalSecret,
Policy: run.CachePolicy(cfg),
Logger: logging.Default().With("module", "cache_request"),
})
+12 -28
View File
@@ -163,8 +163,8 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
}
}
if !slices.Equal(reg.Labels, ls.ToStrings()) {
reg.Labels = ls.ToStrings()
if labelStrings := ls.ToStrings(); !slices.Equal(reg.Labels, labelStrings) {
reg.Labels = labelStrings
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
return fmt.Errorf("failed to save runner config: %w", err)
}
@@ -244,26 +244,14 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
}
if daemArgs.Once || reg.Ephemeral {
done := make(chan struct{})
go func() {
defer close(done)
poller.PollOnce(func() { removeEphemeralRegistration(reg, cfg.Runner.File) })
}()
// shutdown when we complete a job or cancel is requested
select {
case <-ctx.Done():
case <-done:
}
go poller.PollOnce(func() { removeEphemeralRegistration(reg, cfg.Runner.File) })
} else {
go poller.Poll()
}
// Stop either on an external cancellation or when the poller shuts
// itself down (e.g. after the runner has been unregistered).
select {
case <-ctx.Done():
case <-poller.Done():
}
select {
case <-ctx.Done():
case <-poller.Done():
}
logging.Infof("runner: %s shutdown initiated, waiting %s for running jobs to complete before shutting down", resp.Runner.Name, cfg.Runner.ShutdownTimeout)
@@ -329,13 +317,8 @@ func splitLabels(s string) []string {
// initLogging sets up the process-wide logger.
func initLogging(cfg *config.Config) {
callPrettyfier := func(f *runtime.Frame) (string, string) {
// get function name
s := strings.Split(f.Function, ".")
funcname := "[" + s[len(s)-1] + "]"
// get file name and line number
_, filename := path.Split(f.File)
filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]"
return funcname, filename
return "[" + f.Function[strings.LastIndexByte(f.Function, '.')+1:] + "]", "[" + filename + ":" + strconv.Itoa(f.Line) + "]"
}
isTerm := term.IsTerminal(int(os.Stdout.Fd()))
@@ -407,11 +390,12 @@ func getDockerSocketPath(configDockerHost string) (string, error) {
}
for _, p := range commonSocketPaths {
if _, err := os.Lstat(os.ExpandEnv(p)); err == nil {
socket := os.ExpandEnv(p)
if _, err := os.Lstat(socket); err == nil {
if strings.HasPrefix(p, `\\.\`) {
return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), nil
return "npipe://" + filepath.ToSlash(socket), nil
}
return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), nil
return "unix://" + filepath.ToSlash(socket), nil
}
}
+43 -75
View File
@@ -203,12 +203,8 @@ func parseKVAndFile(rawKVs []string, filePath string) map[string]string {
_ = readEnvs(filePath, result)
for _, raw := range rawKVs {
parts := strings.SplitN(raw, "=", 2)
if len(parts) == 2 {
result[parts[0]] = parts[1]
} else {
result[parts[0]] = ""
}
key, value, _ := strings.Cut(raw, "=")
result[key] = value
}
return result
}
@@ -265,39 +261,25 @@ func printList(plan *model.Plan) {
for i, stage := range plan.Stages {
for _, r := range stage.Runs {
jobID := r.JobID
line := lineInfoDef{
jobID: jobID,
jobID: r.JobID,
jobName: r.String(),
stage: strconv.Itoa(i),
wfName: r.Workflow.Name,
wfFile: r.Workflow.File,
events: strings.Join(r.Workflow.On(), `,`),
}
if _, ok := jobs[jobID]; ok {
if jobs[r.JobID] {
duplicateJobIDs = true
} else {
jobs[jobID] = true
}
jobs[r.JobID] = true
lineInfos = append(lineInfos, line)
if jobIDMaxWidth < len(line.jobID) {
jobIDMaxWidth = len(line.jobID)
}
if jobNameMaxWidth < len(line.jobName) {
jobNameMaxWidth = len(line.jobName)
}
if stageMaxWidth < len(line.stage) {
stageMaxWidth = len(line.stage)
}
if wfNameMaxWidth < len(line.wfName) {
wfNameMaxWidth = len(line.wfName)
}
if wfFileMaxWidth < len(line.wfFile) {
wfFileMaxWidth = len(line.wfFile)
}
if eventsMaxWidth < len(line.events) {
eventsMaxWidth = len(line.events)
}
jobIDMaxWidth = max(jobIDMaxWidth, len(line.jobID))
jobNameMaxWidth = max(jobNameMaxWidth, len(line.jobName))
stageMaxWidth = max(stageMaxWidth, len(line.stage))
wfNameMaxWidth = max(wfNameMaxWidth, len(line.wfName))
wfFileMaxWidth = max(wfFileMaxWidth, len(line.wfFile))
eventsMaxWidth = max(eventsMaxWidth, len(line.events))
}
}
@@ -356,21 +338,15 @@ func runExecList(planner model.WorkflowPlanner, execArgs *executeArgs) error {
case execArgs.job != "":
logging.Infof("Preparing plan with a job: %s", execArgs.job)
filterPlan, err = planner.PlanJob(execArgs.job)
if err != nil {
return err
}
case filterEventName != "":
logging.Infof("Preparing plan for a event: %s", filterEventName)
filterPlan, err = planner.PlanEvent(filterEventName)
if err != nil {
return err
}
default:
logging.Infof("Preparing plan with all jobs")
filterPlan, err = planner.PlanAll()
if err != nil {
return err
}
}
if err != nil {
return err
}
printList(filterPlan)
@@ -385,32 +361,31 @@ func (i *executeArgs) runnerConfig(eventName string, env, proxyEnv map[string]st
}
return &runner.Config{
Workdir: i.Workdir(),
BindWorkdir: false,
ForcePull: i.forcePull,
ForceRebuild: i.forceRebuild,
JSONLogger: i.jsonLogger,
Env: env,
ProxyEnv: proxyEnv,
Vars: i.LoadVars(),
Secrets: i.LoadSecrets(),
InsecureSecrets: i.insecureSecrets,
Privileged: i.privileged,
UsernsMode: i.usernsMode,
ContainerArchitecture: i.containerArchitecture,
ContainerDaemonSocket: i.containerDaemonSocket,
UseGitIgnore: i.useGitIgnore,
GitHubInstance: i.githubInstance,
ContainerCapAdd: i.containerCapAdd,
ContainerCapDrop: i.containerCapDrop,
ContainerOptions: i.containerOptions,
ArtifactServerPath: i.artifactServerPath,
ArtifactServerPort: i.artifactServerPort,
ArtifactServerAddr: i.artifactServerAddr,
NoSkipCheckout: i.noSkipCheckout,
EventName: eventName,
EventJSON: eventJSON,
// PresetGitHubContext: preset,
Workdir: i.Workdir(),
BindWorkdir: false,
ForcePull: i.forcePull,
ForceRebuild: i.forceRebuild,
JSONLogger: i.jsonLogger,
Env: env,
ProxyEnv: proxyEnv,
Vars: i.LoadVars(),
Secrets: i.LoadSecrets(),
InsecureSecrets: i.insecureSecrets,
Privileged: i.privileged,
UsernsMode: i.usernsMode,
ContainerArchitecture: i.containerArchitecture,
ContainerDaemonSocket: i.containerDaemonSocket,
UseGitIgnore: i.useGitIgnore,
GitHubInstance: i.githubInstance,
ContainerCapAdd: i.containerCapAdd,
ContainerCapDrop: i.containerCapDrop,
ContainerOptions: i.containerOptions,
ArtifactServerPath: i.artifactServerPath,
ArtifactServerPort: i.artifactServerPort,
ArtifactServerAddr: i.artifactServerAddr,
NoSkipCheckout: i.noSkipCheckout,
EventName: eventName,
EventJSON: eventJSON,
ContainerNamePrefix: "GITEA-ACTIONS-TASK-" + eventName,
ContainerMaxLifetime: maxLifetime,
ContainerNetworkMode: container.NetworkMode(i.network),
@@ -465,15 +440,12 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
if execArgs.job != "" {
logging.Infof("Planning job: %s", execArgs.job)
plan, err = planner.PlanJob(execArgs.job)
if err != nil {
return err
}
} else {
logging.Infof("Planning jobs for event: %s", eventName)
plan, err = planner.PlanEvent(eventName)
if err != nil {
return err
}
}
if err != nil {
return err
}
maxLifetime := 3 * time.Hour
@@ -548,11 +520,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
config.Env["ACT_EXEC"] = "true"
config.Secrets[actionsRuntimeTokenEnvName] = actionsRuntimeToken
if t := config.Secrets["GITEA_TOKEN"]; t != "" {
config.Token = t
} else if t := config.Secrets["GITHUB_TOKEN"]; t != "" {
config.Token = t
}
config.Token = cmp.Or(config.Secrets["GITEA_TOKEN"], config.Secrets["GITHUB_TOKEN"])
if !execArgs.debug {
logLevel := logging.LevelInfo
+6 -16
View File
@@ -117,10 +117,7 @@ func (r *registerInputs) validate() error {
if r.Token == "" {
return errors.New("token is empty")
}
if len(r.Labels) > 0 {
return validateLabels(r.Labels)
}
return nil
return validateLabels(r.Labels)
}
func validateLabels(ls []string) error {
@@ -141,20 +138,14 @@ func (r *registerInputs) stageValue(stage registerStage) string {
case StageInputRunnerName:
return r.RunnerName
case StageInputLabels:
if len(r.Labels) > 0 {
return strings.Join(r.Labels, ",")
}
return strings.Join(r.Labels, ",")
}
return ""
}
func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage {
// must set instance address and token.
// if empty, keep current stage.
if stage == StageInputInstance || stage == StageInputToken {
if value == "" {
return stage
}
if (stage == StageInputInstance || stage == StageInputToken) && value == "" {
return stage
}
// set hostname for runner name if empty
@@ -222,11 +213,10 @@ func initInputs(regArgs *registerArgs) (*registerInputs, error) {
case regArgs.Token != "":
token = regArgs.Token
default:
envToken, ok := os.LookupEnv(registerTokenEnvVar)
if !ok || envToken == "" {
token = os.Getenv(registerTokenEnvVar)
if token == "" {
return nil, fmt.Errorf("missing token, token-file argument, or %s environment variable", registerTokenEnvVar)
}
token = envToken
}
inputs := &registerInputs{
InstanceAddr: strings.TrimSpace(regArgs.InstanceAddr),
+54 -89
View File
@@ -65,7 +65,6 @@ func TestRegisterInputsValidate(t *testing.T) {
func TestValidateLabels(t *testing.T) {
require.NoError(t, validateLabels([]string{"ubuntu:host", "ubuntu:docker://node:18"}))
// a colon that is not a supported schema is part of the label name
require.NoError(t, validateLabels([]string{"pool:e57e18d4-10d4-406f-93bf-60f127221bdd"}))
require.Error(t, validateLabels([]string{"ubuntu:host", ""}))
}
@@ -149,97 +148,63 @@ func TestRegisterInputsAssignToNext(t *testing.T) {
}
func TestInitInputs(t *testing.T) {
t.Run("missing token", func(t *testing.T) {
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
for _, tc := range []struct {
name, tokenFile, wantErr string
env *string
}{
{name: "missing token", wantErr: "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable"},
{name: "empty token", env: new(""), wantErr: "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable"},
{
name: "invalid token file", tokenFile: "/tmp/nonexistent", env: new("from-env"),
wantErr: "cannot read the token file: /tmp/nonexistent, open /tmp/nonexistent: no such file or directory",
},
} {
t.Run(tc.name, func(t *testing.T) {
if tc.env != nil {
t.Setenv(registerTokenEnvVar, *tc.env)
}
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
TokenFile: tc.tokenFile,
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.EqualError(t, err, tc.wantErr)
})
require.EqualError(t, err, "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable")
})
}
t.Run("empty token", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "")
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
Token: "",
TokenFile: "",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
for _, tc := range []struct {
name, address, token, runnerName, wantToken string
file bool
}{
{name: "valid token", address: " http://localhost:3000 ", token: "from-plain-arg\n", runnerName: "runner\n", wantToken: "from-plain-arg"},
{name: "valid token file", address: "http://localhost:3000", runnerName: "runner", file: true, wantToken: "from-file"},
{name: "token from environment variable", address: "http://localhost:3000", runnerName: "runner", wantToken: "from-env"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
var tokenFile string
if tc.file {
tokenFile = filepath.Join(t.TempDir(), "from-file")
require.NoError(t, os.WriteFile(tokenFile, []byte("from-file\n"), 0o600))
}
inputs, err := initInputs(&registerArgs{
InstanceAddr: tc.address,
Token: tc.token,
TokenFile: tokenFile,
RunnerName: tc.runnerName,
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, tc.wantToken, inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
require.EqualError(t, err, "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable")
})
t.Run("invalid token file", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
TokenFile: "/tmp/nonexistent",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.EqualError(t, err, "cannot read the token file: /tmp/nonexistent, open /tmp/nonexistent: no such file or directory")
})
t.Run("valid token", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
inputs, err := initInputs(&registerArgs{
InstanceAddr: " http://localhost:3000 ",
Token: "from-plain-arg\n",
RunnerName: "runner\n",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-plain-arg", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
t.Run("valid token file", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
tokenFile, createErr := os.CreateTemp(t.TempDir(), "from-file")
require.NoError(t, createErr)
defer tokenFile.Close()
_, writeErr := tokenFile.WriteString("from-file\n")
require.NoError(t, writeErr)
_ = tokenFile.Sync()
inputs, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
TokenFile: tokenFile.Name(),
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-file", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
t.Run("token from environment variable", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
inputs, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-env", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
}
t.Run("empty labels", func(t *testing.T) {
inputs, _ := initInputs(&registerArgs{