feat!: add cache service v2, add toolkit patches (#1110)

Serves `github.actions.results.api.v1.CacheService` next to the v1 cache API, sharing its store, plus the subset of the Azure blob protocol the toolkit uploads with. On by default via `cache.v2`, and works with `external_server`.

Clients reach it through two edits in the action's own bundle: the GHES check is opened, and the cache service URL is taken from `ACTIONS_CACHE_URL`.

The same GHES check is what makes the stock `actions/upload-artifact` and `download-artifact` abort on Gitea. Opening it makes them work without the `gitea-upload-artifact` fork, from `upload-artifact@v4.4.0` on.

Verified against 118 real bundles, every major version of 16 actions: 92 patched, the rest deliberately left alone, and every patched bundle checked with `node --check`. Also end to end against pinned `actions/cache@v6.1.0` with an unreachable results URL, so only the patch can make the cache work.

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1110
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
This commit is contained in:
bircni
2026-07-31 12:08:44 +00:00
parent 2398d4a527
commit 47d5b5ad03
18 changed files with 1585 additions and 75 deletions

View File

@@ -109,6 +109,12 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
}
}
if envs["ACTIONS_CACHE_URL"] != "" && (cfg.Cache.V2 == nil || *cfg.Cache.V2) {
// act patches the GHES check out of an action's bundle when it sees this, so the client
// uses the cache service v2 API this server also answers; see act/runner/toolkit_patch.go.
envs[runner.CacheServiceV2Env] = "true"
}
// set artifact gitea api
artifactGiteaAPI := strings.TrimSuffix(cli.Address(), "/") + "/api/actions_pipeline/"
envs["ACTIONS_RUNTIME_URL"] = artifactGiteaAPI
@@ -132,6 +138,11 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
return runner
}
// Close shuts down the cache server this runner exposes to job containers.
func (r *Runner) Close() error {
return r.cacheHandler.Close()
}
// removeOrphanNetworks is a variable so tests can substitute one that needs no Docker daemon.
var removeOrphanNetworks = container.RemoveOrphanNetworks

View File

@@ -7,12 +7,14 @@ import (
"context"
"testing"
"gitea.com/gitea/runner/act/runner"
clientmocks "gitea.com/gitea/runner/internal/pkg/client/mocks"
"gitea.com/gitea/runner/internal/pkg/config"
"gitea.com/gitea/runner/internal/pkg/ver"
"connectrpc.com/connect"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
@@ -91,6 +93,7 @@ func TestNewRunnerInitializesLabelsAndEnvironment(t *testing.T) {
require.Equal(t, "true", r.envs["GITEA_ACTIONS"])
require.NotEmpty(t, r.envs["GITEA_ACTIONS_RUNNER_VERSION"])
require.Nil(t, r.cacheHandler)
require.Empty(t, r.envs[runner.CacheServiceV2Env], "no cache server, nothing to serve v2 from")
}
// Proxy variables are assembled per task, because a job's service containers have to be
@@ -120,3 +123,22 @@ func taskWithDefaultActionsURL(url string) *runnerv1.Task {
},
}
}
// The cache service v2 API is announced to jobs unless it is turned off. Announcing it is what
// makes act patch the GHES check out of an action's bundle, so the client can reach it.
func TestNewRunnerCacheServiceV2(t *testing.T) {
announced := func(v2 *bool) string {
cfg := &config.Config{}
cfg.Cache.V2, cfg.Cache.Dir, cfg.Cache.Host = v2, t.TempDir(), "127.0.0.1"
cli := clientmocks.NewClient(t)
cli.On("Address").Return("https://gitea.example/").Maybe()
r := NewRunner(cfg, &config.Registration{Name: "runner"}, cli)
t.Cleanup(func() { _ = r.Close() })
return r.envs[runner.CacheServiceV2Env]
}
off := false
assert.Equal(t, "true", announced(nil))
assert.Empty(t, announced(&off))
}