Files
act_runner/e2e/runner_harness.go
T
silverwind 2ed8cdb76e fix: stop artifact uploads depending on the cache server reaching Gitea (#1216)
Cache v2 makes the cache server the `ACTIONS_RESULTS_URL` origin, so artifact calls arrived there and were proxied on to Gitea, failing whenever it could not reach the instance.

- Artifact calls are answered with a redirect, so the cache server opens no connection to Gitea. A scheme change or an untrusted instance is still proxied, but there the cache server is the runner itself, which already reaches Gitea.
- Failures answer in twirp, not an empty `502` that clients report as `Unexpected end of JSON input`.
- `cache.v2: false` really points artifacts at Gitea now.
- Cache reservations are bound to the job that made them, so two jobs saving one key cannot commit against each other's upload, and a retry after a lost answer no longer fails a saved entry.
- The toolkit patch, which edits the GitHub-host check out of an action's bundle, was left in the shared checkout where a job running with `runner.patch_actions: false` could inherit it. It is put back after the job's copy.
- `exec` names an origin for the cache v2 it advertises, and masks its runtime token.

Behaviour changes: `no_proxy` no longer exempts `cache.external_server`, and `cache.enabled: false` also stops external registration.

Fixes https://gitea.com/gitea/runner/issues/1208
Fixes https://gitea.com/gitea/runner/issues/1211

Assisted by Claude (Opus 5).

Reviewed-on: https://gitea.com/gitea/runner/pulls/1216
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-09-08 18:45:10 +00:00

123 lines
3.4 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build e2e
package e2e
import (
"context"
"errors"
"os"
"testing"
"time"
"gitea.com/gitea/runner/internal/app/poll"
"gitea.com/gitea/runner/internal/app/run"
"gitea.com/gitea/runner/internal/pkg/client"
"gitea.com/gitea/runner/internal/pkg/config"
"gitea.com/gitea/runner/internal/pkg/labels"
"connectrpc.com/connect"
pingv1 "gitea.dev/actionslib/ping/v1"
runnerv1 "gitea.dev/actionslib/runner/v1"
)
type runnerOptions struct {
capacity int
ephemeral bool
cacheV2 *bool
cacheHost string
}
func startRunner(t *testing.T, repo, labelName string, options runnerOptions) *poll.Poller {
t.Helper()
ctx := t.Context()
token, err := fixture.RegistrationToken(ctx, repo)
if err != nil {
t.Fatalf("get registration token: %v", err)
}
cfg, err := config.LoadDefault("")
if err != nil {
t.Fatalf("load default config: %v", err)
}
if options.cacheV2 != nil {
cfg.Cache.V2 = options.cacheV2
}
cfg.Container.DockerHost = os.Getenv("DOCKER_HOST")
if cfg.Container.DockerHost == "" {
cfg.Container.DockerHost = "unix:///var/run/docker.sock"
}
cfg.Cache.Dir = t.TempDir() + "/cache"
cfg.Cache.Host = options.cacheHost
cfg.Runner.Insecure = true
cfg.Runner.FetchInterval = 250 * time.Millisecond // faster than prod defaults for local fixture
cfg.Runner.FetchIntervalMax = 250 * time.Millisecond
cfg.Runner.StateReportInterval = 500 * time.Millisecond // so cancel reaches the job quickly
cfg.Runner.LogReportInterval = 500 * time.Millisecond
cfg.Runner.Capacity = max(options.capacity, 1)
if fixture.network != "" {
cfg.Container.Network = fixture.network
}
rawLabel := labelName + ":docker://" + os.Getenv("E2E_JOB_IMAGE")
label, err := labels.Parse(rawLabel)
if err != nil {
t.Fatalf("parse label %q: %v", rawLabel, err)
}
labelNames := []string{label.Name}
pingCli := client.New(fixture.baseURL, cfg.Runner.Insecure, "", "", config.RequestTimeout)
if _, err := pingCli.Ping(ctx, connect.NewRequest(&pingv1.PingRequest{Data: t.Name()})); err != nil {
t.Fatalf("ping %s: %v", fixture.baseURL, err)
}
regResp, err := pingCli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
Name: t.Name(),
Token: token,
Version: "e2e",
Labels: labelNames,
Ephemeral: options.ephemeral,
Capabilities: run.RunnerCapabilities(),
}))
if err != nil {
t.Fatalf("register runner: %v", err)
}
if options.ephemeral && !regResp.Msg.Runner.Ephemeral {
t.Fatal("gitea did not grant ephemeral registration")
}
reg := &config.Registration{
ID: regResp.Msg.Runner.Id,
UUID: regResp.Msg.Runner.Uuid,
Name: regResp.Msg.Runner.Name,
Token: regResp.Msg.Runner.Token,
Address: fixture.baseURL,
Labels: []string{rawLabel},
Ephemeral: regResp.Msg.Runner.Ephemeral,
}
cli := client.New(fixture.baseURL, cfg.Runner.Insecure, reg.UUID, reg.Token, config.RequestTimeout)
runner := run.NewRunner(cfg, reg, cli)
declResp, err := runner.Declare(ctx, labelNames)
if err != nil {
_ = runner.Close()
t.Fatalf("declare runner: %v", err)
}
runner.SetCapabilitiesFromDeclare(declResp)
poller := poll.New(cfg, cli, runner)
go poller.Poll()
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := errors.Join(poller.Shutdown(ctx), runner.Close()); err != nil {
t.Logf("runner shutdown: %v", err)
}
})
return poller
}