mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
fix: send artifacts to Gitea when jobs cannot reach the cache server (#1225)
Container jobs on a Docker bridge network isolated from the runner's cache server now upload artifacts to Gitea directly instead of timing out, with a job log warning on how to make the cache reachable. Fixes https://gitea.com/gitea/runner/issues/1211 Reviewed-on: https://gitea.com/gitea/runner/pulls/1225 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -67,6 +68,8 @@ type Runner struct {
|
||||
cacheHandler *artifactcache.Handler
|
||||
capabilities string
|
||||
|
||||
isolatedCacheNetwork func() string
|
||||
|
||||
runningTasks sync.Map
|
||||
runningCount atomic.Int64
|
||||
lastIdleCleanupUnixNano atomic.Int64
|
||||
@@ -110,7 +113,6 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
||||
} else {
|
||||
cacheHandler = handler
|
||||
envs["ACTIONS_CACHE_URL"] = handler.ExternalURL() + "/"
|
||||
warnIfCacheUnreachable(cfg, handler.ExternalURL())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,6 +137,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
||||
now: time.Now,
|
||||
runHealthCheck: executeHealthCheck,
|
||||
}
|
||||
runner.isolatedCacheNetwork = sync.OnceValue(runner.detectIsolatedCacheNetwork)
|
||||
return runner
|
||||
}
|
||||
|
||||
@@ -477,7 +480,6 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
// is that server's responsibility to authenticate requests.
|
||||
revokeCache, resultsURL := r.registerCacheForTask(giteaRuntimeToken, preset.Repository, reporter)
|
||||
defer revokeCache()
|
||||
r.setResultsService(envs, resultsURL)
|
||||
|
||||
eventJSON, err := json.Marshal(preset.Event)
|
||||
if err != nil {
|
||||
@@ -518,6 +520,13 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
return fallbackPlatform()
|
||||
}
|
||||
|
||||
if resultsURL != "" && r.cacheIsolatedFrom(job, platformPicker) {
|
||||
reporter.Logf("::warning::%s", runner.EscapeCommandData(fmt.Sprintf("jobs cannot reach the cache server at %s on docker network %q, so caching fails and artifacts go to Gitea directly, set cache.host and cache.port to an address jobs reach, or container.network to %[2]q",
|
||||
r.cacheHandler.ExternalURL(), r.isolatedCacheNetwork())))
|
||||
resultsURL = ""
|
||||
}
|
||||
r.setResultsService(envs, resultsURL)
|
||||
|
||||
runnerConfig := &runner.Config{
|
||||
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
|
||||
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
|
||||
@@ -847,12 +856,24 @@ func warnIgnoredCacheSecret(cfg *config.Config) {
|
||||
log.Warnf("%s is set but cache.external_server is not; the built-in cache server does not use a shared secret, so the value is ignored", key)
|
||||
}
|
||||
|
||||
func warnIfCacheUnreachable(cfg *config.Config, cacheURL string) {
|
||||
if cfg.Cache.Host != "" || cfg.Container.Network != "" {
|
||||
return
|
||||
func (r *Runner) cacheIsolatedFrom(job *model.Job, pickPlatform func([]string) string) bool {
|
||||
jobContainer := job.Container()
|
||||
return (jobContainer != nil && jobContainer.Image != "" || pickPlatform(job.RunsOn()) != labels.SelfHostedPlatform) && r.isolatedCacheNetwork() != ""
|
||||
}
|
||||
|
||||
func (r *Runner) detectIsolatedCacheNetwork() string {
|
||||
if r.cacheHandler == nil {
|
||||
return ""
|
||||
}
|
||||
if _, err := os.Stat("/.dockerenv"); err != nil {
|
||||
return
|
||||
addr, err := netip.ParseAddr(hostOf(r.cacheHandler.ExternalURL()))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
log.Warnf("jobs are given %s for the cache server; if they cannot reach it, set container.network to a network this runner is on, or cache.host", cacheURL)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
network, err := container.IsolatedNetwork(ctx, addr, r.cfg.Container.Network)
|
||||
if err != nil {
|
||||
log.Warnf("cannot check whether jobs reach the cache server: %v", err)
|
||||
}
|
||||
return network
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"gitea.com/gitea/runner/internal/pkg/ver"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -337,6 +338,14 @@ func TestNewRunnerCacheServiceV2(t *testing.T) {
|
||||
assert.Equal(t, resultsURL, envs["ACTIONS_RESULTS_URL"], instance)
|
||||
assert.Empty(t, envs[runner.CacheServiceV2Env])
|
||||
}
|
||||
|
||||
workflow, err := model.ReadWorkflow(strings.NewReader(`jobs: {native: {runs-on: native}, linux: {runs-on: linux}, containerized: {runs-on: native, container: alpine}, empty: {runs-on: native, container: ""}}`))
|
||||
require.NoError(t, err)
|
||||
r.isolatedCacheNetwork = func() string { return "compose" }
|
||||
pickPlatform := func(runsOn []string) string { return map[string]string{"native": labels.SelfHostedPlatform}[runsOn[0]] }
|
||||
for job, isolated := range map[string]bool{"native": false, "linux": true, "containerized": true, "empty": false} {
|
||||
assert.Equal(t, isolated, r.cacheIsolatedFrom(workflow.GetJob(job), pickPlatform), job)
|
||||
}
|
||||
}
|
||||
|
||||
// The v1 cache client appends its path to ACTIONS_CACHE_URL without a separator, so a configured
|
||||
|
||||
@@ -180,7 +180,8 @@ cache:
|
||||
# Serve the actions cache service v2 API. The actions that use it fall back to v1 on any host
|
||||
# they do not take for GitHub, so reaching it means editing that check out of their own bundle,
|
||||
# put back after the copy into the job. That edit is made either way, this only governs the API
|
||||
# advertised. A bundle that does not match is left alone. With v2, uploads need a reachable cache.
|
||||
# advertised. A bundle that does not match is left alone. With v2, artifact uploads go via the
|
||||
# cache server unless container jobs cannot reach its Docker network.
|
||||
#v2: true
|
||||
# How the cache server discards entries, ignored when external_server is set since that
|
||||
# server applies its own. Leave a setting out for its default; 0s or 0 turns the three
|
||||
|
||||
Reference in New Issue
Block a user