mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-13 22:41:54 +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]>
133 lines
4.6 KiB
Go
133 lines
4.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package run
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/internal/pkg/config"
|
|
"gitea.com/gitea/runner/internal/pkg/metrics"
|
|
"gitea.com/gitea/runner/internal/pkg/process"
|
|
"gitea.com/gitea/runner/internal/pkg/report"
|
|
|
|
runnerv1 "gitea.dev/actionslib/runner/v1"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (r *Runner) runPostTaskScript(ctx context.Context, reporter *report.Reporter, task *runnerv1.Task, workdir string) {
|
|
script := r.cfg.Runner.PostTaskScript
|
|
if script == "" {
|
|
return
|
|
}
|
|
|
|
timeout := r.cfg.Runner.PostTaskScriptTimeout
|
|
if timeout <= 0 {
|
|
timeout = config.DefaultPostTaskScriptTimeout
|
|
}
|
|
|
|
scriptCtx, cancel := postTaskScriptContext(ctx, timeout)
|
|
defer cancel()
|
|
|
|
env := r.postTaskScriptEnv(reporter, task, workdir)
|
|
log.Infof("running post-task script %q for task %d", script, task.Id)
|
|
|
|
cmd := exec.CommandContext(scriptCtx, script)
|
|
cmd.Env = envListFromMap(env)
|
|
cmd.SysProcAttr = process.SysProcAttr(script, false)
|
|
|
|
stdout := postTaskScriptLogWriter("stdout")
|
|
stderr := postTaskScriptLogWriter("stderr")
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stderr
|
|
|
|
// Kill the script's whole process tree on cancellation and bound the post-exit
|
|
// I/O wait, so a backgrounded child inheriting cmd's stdout/stderr pipe can
|
|
// never hang cmd.Wait() and the runner. See process.TreeKill.
|
|
treeKill := process.NewTreeKill(cmd)
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Warnf("post-task script %q for task %d: %v", script, task.Id, err)
|
|
return
|
|
}
|
|
if k, kerr := treeKill.Capture(cmd.Process); kerr != nil {
|
|
log.Warnf("post-task script %q for task %d: process tree kill setup failed, falling back to single-process kill: %v", script, task.Id, kerr)
|
|
} else {
|
|
defer k.Close()
|
|
}
|
|
|
|
err := cmd.Wait()
|
|
// Flush any trailing, not-yet-newline-terminated output now that the I/O
|
|
// copiers have finished (cmd.Wait, bounded by WaitDelay above, guarantees it).
|
|
common.FlushWriter(stdout)
|
|
common.FlushWriter(stderr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
log.Warnf("post-task script %q for task %d: %v", script, task.Id, err)
|
|
return
|
|
}
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) {
|
|
log.Warnf("post-task script %q for task %d exited with code %d", script, task.Id, exitErr.ExitCode())
|
|
return
|
|
}
|
|
log.Warnf("post-task script %q for task %d: %v", script, task.Id, err)
|
|
}
|
|
}
|
|
|
|
func postTaskScriptContext(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
|
// Detach from the task context's deadline and cancellation: the task has
|
|
// already finished by the time the post-task script runs, so the script must
|
|
// get its full configured timeout. Inheriting the task deadline would silently
|
|
// truncate that budget when the job completed close to its own timeout (and an
|
|
// already-cancelled task context would skip the script entirely).
|
|
// context.WithoutCancel keeps the context values while dropping the deadline.
|
|
return context.WithTimeout(context.WithoutCancel(ctx), timeout)
|
|
}
|
|
|
|
func (r *Runner) postTaskScriptEnv(reporter *report.Reporter, task *runnerv1.Task, workdir string) map[string]string {
|
|
env := r.cloneEnvs()
|
|
env["GITEA_TASK_ID"] = strconv.FormatInt(task.Id, 10)
|
|
env["GITEA_WORKSPACE"] = workdir
|
|
// GITEA_JOB_RESULT shares the runner's canonical result vocabulary
|
|
// (success/failure/cancelled/skipped/unknown), the same strings the reporter
|
|
// parses and the metrics labels use.
|
|
env["GITEA_JOB_RESULT"] = metrics.ResultToStatusLabel(reporter.Result())
|
|
if v := task.Context.Fields["run_id"].GetStringValue(); v != "" {
|
|
env["GITEA_RUN_ID"] = v
|
|
}
|
|
if v := task.Context.Fields["repository"].GetStringValue(); v != "" {
|
|
env["GITEA_REPOSITORY"] = v
|
|
}
|
|
return env
|
|
}
|
|
|
|
func envListFromMap(env map[string]string) []string {
|
|
envList := make([]string, 0, len(env))
|
|
for k, v := range env {
|
|
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
return envList
|
|
}
|
|
|
|
// postTaskScriptLogWriter returns an io.Writer that logs the script's output one
|
|
// line at a time, tagged with the stream name. It is passed as cmd.Stdout/Stderr
|
|
// (rather than a StdoutPipe) so that cmd.WaitDelay governs the copying goroutine:
|
|
// a backgrounded process holding the pipe open can never block cmd.Wait()
|
|
// indefinitely. Flush any trailing partial line with common.FlushWriter after
|
|
// cmd.Wait() returns.
|
|
func postTaskScriptLogWriter(stream string) io.Writer {
|
|
return common.NewLineWriter(func(line string) bool {
|
|
log.Infof("post-task script %s: %s", stream, strings.TrimRight(line, "\r\n"))
|
|
return true
|
|
})
|
|
}
|