mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-07 09:24:22 +02:00
gitea can not consume the runner's api by version while it's version mismatches the module version:
```
go: gitea.com/gitea/runner@v3.0.1: invalid version: module contains a go.mod file,
so module path must match major version ("gitea.com/gitea/runner/v3")
```
Fix that by bumping the module version now. The existing `v3.0.0` and `v3.0.1` tags stay unusable, so a new tag is needed after this lands.
Also drop the version `-X` linker flags, which would otherwise have to repeat the new path in both `Makefile` and `.goreleaser.yaml`, where a stale path makes injection silently no-op. Go has recorded the module version in the build info since 1.24, so `Version()` reads it from there, keeping the variable as an override for builds without a VCS stamp.
That part started as https://gitea.com/gitea/runner/pulls/1137 but belongs here: the stamp resolves against the tags that are legal for the module path, so without the `/v3` bump it would report `v1.0.9-0.<ts>-<sha>`. Since `release-nightly.yml` triggers on every push to `main`, splitting them would publish a nightly with a `v1` version.
---------
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1136
Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com>
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
133 lines
4.7 KiB
Go
133 lines
4.7 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/v3/act/common"
|
|
"gitea.com/gitea/runner/v3/internal/pkg/config"
|
|
"gitea.com/gitea/runner/v3/internal/pkg/metrics"
|
|
"gitea.com/gitea/runner/v3/internal/pkg/process"
|
|
"gitea.com/gitea/runner/v3/internal/pkg/report"
|
|
|
|
runnerv1 "gitea.dev/actions-proto-go/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
|
|
})
|
|
}
|