mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
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>
This commit is contained in:
+47
-11
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -89,7 +90,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
||||
envs := make(map[string]string, len(cfg.Runner.Envs))
|
||||
maps.Copy(envs, cfg.Runner.Envs)
|
||||
var cacheHandler *artifactcache.Handler
|
||||
if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled {
|
||||
if cacheEnabled(cfg) {
|
||||
if cfg.Cache.ExternalServer != "" {
|
||||
warnIgnoredCachePolicy(cfg)
|
||||
// The v1 client appends its path to this without a separator, so the slash is required.
|
||||
@@ -109,6 +110,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
|
||||
} else {
|
||||
cacheHandler = handler
|
||||
envs["ACTIONS_CACHE_URL"] = handler.ExternalURL() + "/"
|
||||
warnIfCacheUnreachable(cfg, handler.ExternalURL())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,7 +390,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
|
||||
// Added per task because this job's service containers must be reached directly, and
|
||||
// act reaches them by their workflow key.
|
||||
proxyEnv := JobProxyEnv(envs, envs["ACTIONS_CACHE_URL"], slices.Sorted(maps.Keys(job.Services)))
|
||||
proxyEnv := JobProxyEnv(envs, r.builtInCacheURL(), slices.Sorted(maps.Keys(job.Services)))
|
||||
maps.Copy(envs, proxyEnv)
|
||||
|
||||
if r.capabilities != "" {
|
||||
@@ -454,14 +456,7 @@ 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()
|
||||
// A cache server that agreed to forward the artifact half is the whole results service, so the
|
||||
// job is pointed at it.
|
||||
if resultsURL != "" {
|
||||
envs["ACTIONS_RESULTS_URL"] = resultsURL
|
||||
if r.cacheServiceV2() {
|
||||
envs[runner.CacheServiceV2Env] = "true"
|
||||
}
|
||||
}
|
||||
r.setResultsService(envs, resultsURL)
|
||||
|
||||
eventJSON, err := json.Marshal(preset.Event)
|
||||
if err != nil {
|
||||
@@ -584,12 +579,43 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
return execErr
|
||||
}
|
||||
|
||||
func (r *Runner) builtInCacheURL() string {
|
||||
if r.cacheHandler == nil {
|
||||
return ""
|
||||
}
|
||||
return r.envs["ACTIONS_CACHE_URL"]
|
||||
}
|
||||
|
||||
func cacheEnabled(cfg *config.Config) bool {
|
||||
return cfg.Cache.Enabled == nil || *cfg.Cache.Enabled
|
||||
}
|
||||
|
||||
// cacheServiceV2 reports whether jobs are told the cache service speaks v2. It is all cache.v2
|
||||
// turns off: the bundle edit that reaches it is what the artifact actions need too.
|
||||
func (r *Runner) cacheServiceV2() bool {
|
||||
return r.cfg.Cache.V2 == nil || *r.cfg.Cache.V2
|
||||
}
|
||||
|
||||
func (r *Runner) setResultsService(envs map[string]string, resultsURL string) {
|
||||
v2 := resultsURL != "" && r.cacheServiceV2()
|
||||
if v2 {
|
||||
envs[runner.CacheServiceV2Env] = "true"
|
||||
} else {
|
||||
delete(envs, runner.CacheServiceV2Env)
|
||||
}
|
||||
// Cache v2 clients read no other variable for it, and some cannot use the instance address.
|
||||
if v2 || (resultsURL != "" && r.instanceOutOfReach(envs["ACTIONS_RESULTS_URL"])) {
|
||||
envs["ACTIONS_RESULTS_URL"] = resultsURL
|
||||
}
|
||||
}
|
||||
|
||||
// These clients keep only the origin they are handed, and reach it on the job's own trust.
|
||||
func (r *Runner) instanceOutOfReach(instance string) bool {
|
||||
parsed, err := url.Parse(instance)
|
||||
return err != nil || strings.Trim(parsed.Path, "/") != "" ||
|
||||
(r.cfg.Runner.Insecure && parsed.Scheme == "https")
|
||||
}
|
||||
|
||||
// registerCacheForTask tells the cache server to accept requests authenticated
|
||||
// with the given runtime token for the duration of this task. Returns a
|
||||
// function the caller must invoke (typically via defer) to revoke the
|
||||
@@ -616,7 +642,7 @@ func (r *Runner) registerCacheForTask(token, repo string, reporter *report.Repor
|
||||
if r.cacheHandler != nil {
|
||||
return r.cacheHandler.RegisterJob(token, cred), r.cacheHandler.ResultsURL(cred)
|
||||
}
|
||||
if r.cfg.Cache.ExternalServer != "" && r.cfg.Cache.ExternalSecret != "" {
|
||||
if cacheEnabled(r.cfg) && r.cfg.Cache.ExternalServer != "" && r.cfg.Cache.ExternalSecret != "" {
|
||||
return r.registerExternalCacheJob(token, cred, reporter)
|
||||
}
|
||||
// No cache server to register against: caching is disabled, or the built-in server failed to start.
|
||||
@@ -799,3 +825,13 @@ 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
|
||||
}
|
||||
if _, err := os.Stat("/.dockerenv"); 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user