mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-07-23 13:07:45 +00:00
fix: Minor fixes (#1075)
A batch of small, self-contained fixes and docs/example additions. Fixes #625 - align the example config's `force_pull` with the actual default (`false`) Fixes #804 - return an error instead of discarding `os.UserHomeDir()` when defaulting `cache.dir`/`host.workdir_parent` Fixes #571 - send a `gitea-runner/<version>` User-Agent on API requests Fixes #650 - detect an `Unauthenticated` fetch response and exit the daemon with an error instead of retrying forever Fixes #766 - add `exec --eventpath` to supply a JSON event payload file Fixes #256 - add a `bug-report` subcommand that prints version/Go/OS-arch/CPU info Fixes #617 - add `runner.set_act_env` (default `true`) to optionally omit the `ACT=true` env var Fixes #635 - record a failure result (and guard a nil reusable-workflow caller) when the job `if`-expression fails to evaluate Fixes #1005 - remove README docs for config env-var overrides that were already removed from the code Fixes #448 - clarify in `exec --job` help that `--workflows` may be needed to disambiguate Fixes #209 - note that `host`-labelled runners still need Docker for `docker://` actions and service containers Fixes #757 - add a systemd service example with automatic restart Fixes #474 - add a Kubernetes StatefulSet example that persists the `.runner` registration across reschedules Fixes #776 - build the basic (non-dind) docker image for `linux/riscv64` Fixes #628 - build the basic (non-dind) docker image for `linux/s390x`Reviewed-on: https://gitea.com/gitea/runner/pulls/1075 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
31
internal/app/cmd/bug_report.go
Normal file
31
internal/app/cmd/bug_report.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"gitea.com/gitea/runner/internal/pkg/ver"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// loadBugReportCmd prints environment details that are useful when opening a
|
||||
// bug report, so users can paste them straight into an issue.
|
||||
func loadBugReportCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "bug-report",
|
||||
Short: "Print information useful when filing a bug report",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
w := cmd.OutOrStdout()
|
||||
fmt.Fprintf(w, "Runner version: %s\n", ver.Version())
|
||||
fmt.Fprintf(w, "Go version: %s\n", runtime.Version())
|
||||
fmt.Fprintf(w, "OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
fmt.Fprintf(w, "NumCPU: %d\n", runtime.NumCPU())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,9 @@ func Execute(ctx context.Context) {
|
||||
// ./gitea-runner exec
|
||||
rootCmd.AddCommand(loadExecCmd(ctx))
|
||||
|
||||
// ./gitea-runner bug-report
|
||||
rootCmd.AddCommand(loadBugReportCmd())
|
||||
|
||||
// ./gitea-runner config
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "generate-config",
|
||||
|
||||
@@ -176,7 +176,12 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
} else {
|
||||
go poller.Poll()
|
||||
|
||||
<-ctx.Done()
|
||||
// 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():
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("runner: %s shutdown initiated, waiting %s for running jobs to complete before shutting down", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout)
|
||||
@@ -189,6 +194,10 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
log.Warnf("runner: %s cancelled in progress jobs during shutdown", resp.Msg.Runner.Name)
|
||||
}
|
||||
|
||||
if poller.Unregistered() {
|
||||
return errors.New("runner is no longer registered with the server; please register it again")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ type executeArgs struct {
|
||||
runList bool
|
||||
job string
|
||||
event string
|
||||
eventpath string
|
||||
workdir string
|
||||
workflowsPath string
|
||||
noWorkflowRecurse bool
|
||||
@@ -441,6 +442,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
|
||||
ArtifactServerPort: execArgs.artifactServerPort,
|
||||
ArtifactServerAddr: execArgs.artifactServerAddr,
|
||||
NoSkipCheckout: execArgs.noSkipCheckout,
|
||||
EventPath: execArgs.resolve(execArgs.eventpath),
|
||||
// PresetGitHubContext: preset,
|
||||
// EventJSON: string(eventJSON),
|
||||
ContainerNamePrefix: "GITEA-ACTIONS-TASK-" + eventName,
|
||||
@@ -496,8 +498,9 @@ func loadExecCmd(ctx context.Context) *cobra.Command {
|
||||
}
|
||||
|
||||
execCmd.Flags().BoolVarP(&execArg.runList, "list", "l", false, "list workflows")
|
||||
execCmd.Flags().StringVarP(&execArg.job, "job", "j", "", "run a specific job ID")
|
||||
execCmd.Flags().StringVarP(&execArg.job, "job", "j", "", "run a specific job ID; when several workflow files define that job, also pass --workflows/-W to select the file")
|
||||
execCmd.Flags().StringVarP(&execArg.event, "event", "E", "", "run a event name")
|
||||
execCmd.Flags().StringVarP(&execArg.eventpath, "eventpath", "e", "", "path to a JSON event payload file exposed as the event that triggered the workflow")
|
||||
execCmd.PersistentFlags().StringVarP(&execArg.workflowsPath, "workflows", "W", "./.gitea/workflows/", "path to workflow file(s)")
|
||||
execCmd.PersistentFlags().StringVarP(&execArg.workdir, "directory", "C", ".", "working directory")
|
||||
execCmd.PersistentFlags().BoolVarP(&execArg.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag")
|
||||
|
||||
@@ -45,6 +45,10 @@ type Poller struct {
|
||||
shutdownJobs context.CancelFunc
|
||||
|
||||
done chan struct{}
|
||||
|
||||
// unregistered is set when the server rejects the runner with an
|
||||
// Unauthenticated response, meaning the runner is no longer registered.
|
||||
unregistered atomic.Bool
|
||||
}
|
||||
|
||||
// workerState holds the single poller's backoff state. Consecutive empty or
|
||||
@@ -137,6 +141,19 @@ func (p *Poller) PollOnce() {
|
||||
}
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed once polling has fully stopped,
|
||||
// allowing callers to react when the poller shuts itself down (e.g. after the
|
||||
// runner has been unregistered) rather than only on an external cancellation.
|
||||
func (p *Poller) Done() <-chan struct{} {
|
||||
return p.done
|
||||
}
|
||||
|
||||
// Unregistered reports whether polling stopped because the server rejected the
|
||||
// runner as unregistered (an Unauthenticated response).
|
||||
func (p *Poller) Unregistered() bool {
|
||||
return p.unregistered.Load()
|
||||
}
|
||||
|
||||
func (p *Poller) runIdleMaintenance() {
|
||||
if idleRunner, ok := p.runner.(IdleRunner); ok {
|
||||
idleRunner.OnIdle(p.jobsCtx)
|
||||
@@ -264,6 +281,15 @@ func (p *Poller) fetchTask(ctx context.Context, s *workerState) (*runnerv1.Task,
|
||||
metrics.PollFetchDuration.Observe(time.Since(start).Seconds())
|
||||
|
||||
if err != nil {
|
||||
// An Unauthenticated response means the server no longer knows this
|
||||
// runner (e.g. it was deleted). Retrying forever is pointless, so stop
|
||||
// polling and let the daemon exit with an error instead of spinning.
|
||||
if connect.CodeOf(err) == connect.CodeUnauthenticated {
|
||||
log.WithError(err).Error("server rejected the runner as unregistered, stopping poller")
|
||||
p.unregistered.Store(true)
|
||||
p.shutdownPolling()
|
||||
return nil, false
|
||||
}
|
||||
log.WithError(err).Error("failed to fetch task")
|
||||
s.consecutiveErrors++
|
||||
metrics.PollFetchTotal.WithLabelValues(metrics.LabelResultError).Inc()
|
||||
|
||||
@@ -78,6 +78,35 @@ func TestPoller_FetchErrorIncrementsErrorsOnly(t *testing.T) {
|
||||
assert.Equal(t, int64(0), s.consecutiveEmpty)
|
||||
}
|
||||
|
||||
// TestPoller_FetchUnauthenticatedStopsPolling verifies that an Unauthenticated
|
||||
// response marks the runner as unregistered and cancels the polling context so
|
||||
// the daemon can exit instead of retrying forever.
|
||||
func TestPoller_FetchUnauthenticatedStopsPolling(t *testing.T) {
|
||||
client := mocks.NewClient(t)
|
||||
client.On("FetchTask", mock.Anything, mock.Anything).Return(
|
||||
func(_ context.Context, _ *connect_go.Request[runnerv1.FetchTaskRequest]) (*connect_go.Response[runnerv1.FetchTaskResponse], error) {
|
||||
return nil, connect_go.NewError(connect_go.CodeUnauthenticated, errors.New("unregistered runner"))
|
||||
},
|
||||
)
|
||||
|
||||
cfg, err := config.LoadDefault("")
|
||||
require.NoError(t, err)
|
||||
p := New(cfg, client, nil)
|
||||
|
||||
s := &workerState{}
|
||||
_, ok := p.fetchTask(context.Background(), s)
|
||||
require.False(t, ok)
|
||||
|
||||
assert.True(t, p.Unregistered(), "runner should be marked unregistered")
|
||||
assert.Equal(t, int64(0), s.consecutiveErrors, "unauthenticated must not drive error backoff")
|
||||
|
||||
select {
|
||||
case <-p.pollingCtx.Done():
|
||||
default:
|
||||
t.Fatal("expected polling context to be cancelled after an Unauthenticated response")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPoller_CalculateInterval verifies the exponential backoff math is
|
||||
// correctly driven by the workerState counters.
|
||||
func TestPoller_CalculateInterval(t *testing.T) {
|
||||
|
||||
@@ -445,6 +445,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"),
|
||||
AutoRemove: true,
|
||||
NoSkipCheckout: true,
|
||||
DisableActEnv: r.cfg.Runner.SetActEnv != nil && !*r.cfg.Runner.SetActEnv,
|
||||
PresetGitHubContext: preset,
|
||||
EventJSON: string(eventJSON),
|
||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||
|
||||
Reference in New Issue
Block a user