Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ jobs:
- name: checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Expose GitHub tokens for caching
uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487 # v4.0.0

- name: Update Docker
run: |
set -e
Expand Down Expand Up @@ -294,7 +297,13 @@ jobs:
run: |
set -eu

docker buildx bake frontend
# gha cache: buildx auto-detects the runtime token/url from the
# environment exposed by ghaction-github-runtime above. The frontend
# is built per-run so it uses a gha scope; the worker reuses ghcr
# caches below.
docker buildx bake frontend \
--set "*.cache-from=type=gha,scope=ci-frontend" \
--set "*.cache-to=type=gha,mode=max,scope=ci-frontend"

if [ "${TEST_SUITE}" = "other" ]; then
exit 0
Expand All @@ -306,7 +315,13 @@ jobs:
worker="windowscross"
fi
export WORKER_TARGET=${worker}/worker
docker buildx bake worker
REPO="${GITHUB_REPOSITORY,,}"
# Workers are cached in ghcr (uncapped): :main-cache is the mode=max
# cache published on merge, :latest the released image. No gha here, so
# the 10GB cap is left to the per-test cache. Cold sources skip silently.
docker buildx bake worker \
--set "*.cache-from=type=registry,ref=ghcr.io/${REPO}/${WORKER_TARGET}:main-cache" \
--set "*.cache-from=type=registry,ref=ghcr.io/${REPO}/${WORKER_TARGET}:latest"
env:
TEST_SUITE: ${{ matrix.suite }}
- name: Run integration tests (with coverage tracking)
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/worker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,13 @@ jobs:
name: Setup (merged)
run: |
set -ex -o pipefail
EXTRA_FLAGS="--set worker.cache-to=type=gha,scope=main.${{matrix.target}},mode=max --set worker.cache-from=type=gha,scope=${MAIN_CACHE_SCOPE}"
# Publish the worker plus a registry cache (mode=max). Registry caches
# live in ghcr (uncapped) so PR CI can cache-from intermediates without
# touching the 10GB gha cap.
ref="ghcr.io/${BASE_REPO}:main"
cache="ghcr.io/${BASE_REPO}:main-cache"
EXTRA_FLAGS="--set worker.tags=${ref} --push"
EXTRA_FLAGS+=" --set worker.cache-to=type=registry,ref=${cache},mode=max --set worker.cache-from=type=registry,ref=${cache}"
echo "EXTRA_FLAGS=${EXTRA_FLAGS}" >> $GITHUB_ENV
- name: Setup QEMU
run: docker run --rm --privileged tonistiigi/binfmt:latest --install all
Expand Down
57 changes: 57 additions & 0 deletions test/testenv/buildkit.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package testenv

import (
"fmt"
"os"
"strconv"
"strings"
"testing"

"github.com/moby/buildkit/client"
)
Expand Down Expand Up @@ -61,3 +64,57 @@ func supportsFrontendAsInput(info *client.Info) bool {

return minor >= minVersion.Minor
}

// withGHCache adds the necessary cache export and import options to the solve request in order to use the GitHub Actions cache.
// It uses the test name as a scope for the cache. Each test will have its own scope.
// This means that caches are not shared between tests, but it also means that tests won't overwrite each other's cache.
//
// Github Actions sets some specific environment variables that we'll look for to even determine if we should configure the cache or not.
//
// This is effectively what `docker build --cache-from=gha,scope=foo --cache-to=gha,scope=foo` would do.
// Export uses the default min mode: only the final layers are pushed. The heavy
// base/worker layers are cached separately via the prebuilt worker images, so
// there's no need to bloat each test scope (and the 10GB cap) with mode=max.
//
// Note: we talk to buildkit via its Go client rather than buildctl/buildx, so
// the daemon does not auto-detect these values from the environment; we must
// pass them as attrs. The token/url env vars are only present in GitHub Actions
// jobs that expose the runtime (see crazy-max/ghaction-github-runtime). We only
// target the v2 cache service; v1 was retired in March 2025.
func withGHCache(t *testing.T, so *client.SolveOpt) {
if os.Getenv("GITHUB_ACTIONS") != "true" {
// This is not running in GitHub Actions, so we don't need to configure the cache.
return
}

token := os.Getenv("ACTIONS_RUNTIME_TOKEN")
if token == "" {
fmt.Fprintln(os.Stderr, "::warning::ACTIONS_RUNTIME_TOKEN is not set, skipping cache export")
return
}

url := os.Getenv("ACTIONS_RESULTS_URL")
if url == "" {
fmt.Fprintln(os.Stderr, "::warning::ACTIONS_RESULTS_URL is not set, skipping cache export")
return
}

scope := "test-integration-" + t.Name()

so.CacheExports = append(so.CacheExports, client.CacheOptionsEntry{
Type: "gha",
Attrs: map[string]string{
"scope": scope,
"token": token,
"url_v2": url,
},
})
so.CacheImports = append(so.CacheImports, client.CacheOptionsEntry{
Type: "gha",
Attrs: map[string]string{
"scope": scope,
"token": token,
"url_v2": url,
},
})
}
5 changes: 3 additions & 2 deletions test/testenv/buildx.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,13 @@ func WithSocketProxies(proxies ...socketprovider.ProxyConfig) TestRunnerOpt {
}
}

func setSolveOpts(cfg TestRunnerConfig, so *client.SolveOpt) error {
func setSolveOpts(t *testing.T, cfg TestRunnerConfig, so *client.SolveOpt) error {
if err := withProjectRoot(so); err != nil {
return err
}

withResolveLocal(so)
withGHCache(t, so)
err := withSocketProxies(cfg.SocketProxies)(so)
if err != nil {
return err
Expand Down Expand Up @@ -417,7 +418,7 @@ func (b *BuildxEnv) runTestWithStatus(ctx context.Context, t *testing.T, f TestF
assert.NilError(t, err)

var so client.SolveOpt
err = setSolveOpts(cfg, &so)
err = setSolveOpts(t, cfg, &so)
assert.NilError(t, err)

ctx, cancel := context.WithCancel(ctx)
Expand Down