[e2e] Build images from source by default instead of pulling rc.2

The agent-network code under test lives in this branch, so the e2e should
exercise it rather than a frozen published release. Flip the harness
default: combined/proxy/client are now built from their in-repo
Dockerfiles (combined/Dockerfile.multistage, proxy/Dockerfile.multistage,
e2e/harness/Dockerfile.client) under local tags. Pulling a published image
stays available by setting NB_E2E_*_IMAGE to a registry reference.

Builds now go through buildx --load so the Dockerfile cache mounts are
honored and the result is loaded for testcontainers. The CI workflow adds
a container-driver builder and a local layer cache (NB_E2E_BUILDX_CACHE)
persisted via actions/cache, which caches the base/apt/dep-download layers
across runs. The Go compile still re-runs each time, as BuildKit mount
caches cannot be exported to the GitHub cache.
This commit is contained in:
mlsmaycon
2026-06-30 11:19:38 +02:00
parent 69e17d0470
commit 3590ee6436
4 changed files with 61 additions and 32 deletions

View File

@@ -27,8 +27,27 @@ jobs:
with:
go-version-file: "go.mod"
# Container-driver builder so the harness can build the combined/proxy/
# client images from source with a local layer cache.
- name: Set up Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
# Persist the Docker layer cache across runs. This caches the base, apt,
# and go-mod-download layers; the Go compile still re-runs, as BuildKit
# mount caches cannot be exported to the GitHub cache.
- name: Cache Docker layers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-anet-e2e-buildx-${{ hashFiles('go.sum', 'combined/Dockerfile.multistage', 'proxy/Dockerfile.multistage', 'e2e/harness/Dockerfile.client') }}
restore-keys: |
${{ runner.os }}-anet-e2e-buildx-
- name: Run agent-network e2e
env:
# Build the images from source (this branch's code) with the shared
# local layer cache.
NB_E2E_BUILDX_CACHE: /tmp/.buildx-cache
# Provider credentials. Each provider scenario skips if its
# token (and URL, for gateways) is unset, so partial coverage is fine.
OPENAI_TOKEN: ${{ secrets.E2E_OPENAI_TOKEN }}

View File

@@ -17,10 +17,10 @@ import (
const (
clientDockerfile = "e2e/harness/Dockerfile.client"
// defaultClientImage is the published NetBird client release used by
// default. Override with NB_E2E_CLIENT_IMAGE; a value without a "/" is built
// locally from clientDockerfile.
defaultClientImage = "netbirdio/netbird:0.74.0-rc.2"
// defaultClientImage is the local tag the client is built under from
// clientDockerfile. Override with NB_E2E_CLIENT_IMAGE: a value with a "/" is
// pulled as a published image; a bare tag is built under that name.
defaultClientImage = "netbird-client:e2e"
clientAlias = "client"
curlImage = "curlimages/curl:latest"
)

View File

@@ -24,11 +24,11 @@ import (
const (
combinedDockerfile = "combined/Dockerfile.multistage"
// defaultCombinedImage is the published combined-server release used by
// default (the artifact operators run manually). Override with
// NB_E2E_COMBINED_IMAGE; a value without a "/" is built locally from
// combinedDockerfile instead of pulled.
defaultCombinedImage = "netbirdio/netbird-server:0.74.0-rc.2"
// defaultCombinedImage is the local tag the combined server is built under
// from combinedDockerfile, so the e2e exercises this branch's code. Override
// with NB_E2E_COMBINED_IMAGE: a value containing a "/" is pulled as a
// published image; a bare tag is built under that name instead.
defaultCombinedImage = "netbird-combined:e2e"
combinedHTTPPort = "8080/tcp"
// combinedAlias is the combined server's network alias AND the deployment
@@ -149,33 +149,42 @@ func StartCombined(ctx context.Context) (*Combined, error) {
}, nil
}
// resolveImage returns the image to run for a component. The env override (or
// the default) is used as-is when it looks like a registry reference (contains
// "/") — testcontainers pulls it. A bare local tag is built from the repo
// Dockerfile instead, so developers can still test source builds.
func resolveImage(ctx context.Context, root, envKey, defaultImage, dockerfile string) (string, error) {
img := defaultImage
// resolveImage returns the image to run for a component. By default it builds
// the image from the repo Dockerfile under localTag, so the e2e exercises the
// branch's code. The env override changes this: a value containing a "/" is a
// registry reference that testcontainers pulls (e.g. to test a published
// release); a bare tag is built under that name instead.
func resolveImage(ctx context.Context, root, envKey, localTag, dockerfile string) (string, error) {
if v := os.Getenv(envKey); v != "" {
img = v
if strings.Contains(v, "/") {
return v, nil
}
localTag = v
}
if strings.Contains(img, "/") {
return img, nil
}
if err := buildImage(ctx, root, dockerfile, img); err != nil {
if err := buildImage(ctx, root, dockerfile, localTag); err != nil {
return "", err
}
return img, nil
return localTag, nil
}
// buildImage builds an image from a repo Dockerfile via the docker CLI with
// BuildKit enabled, so cache mounts work and unchanged sources reuse the layer
// + go caches. Tags are stable so reruns are cheap.
// buildImage builds an image from a repo Dockerfile via buildx with BuildKit, so
// the Dockerfile cache mounts are honored and unchanged layers are reused. The
// result is loaded into the docker image store so testcontainers runs it by tag.
// When NB_E2E_BUILDX_CACHE names a directory (CI, with a container-driver
// builder from docker/setup-buildx-action), layer cache is read from and written
// to it as a local cache so actions/cache can persist it across runs; the Go
// compile itself still re-runs, as BuildKit mount caches can't be exported.
func buildImage(ctx context.Context, root, dockerfile, tag string) error {
cmd := exec.CommandContext(ctx, "docker", "build",
"-f", dockerfile,
"-t", tag,
".",
)
args := []string{"buildx", "build", "-f", dockerfile, "-t", tag, "--load"}
if dir := os.Getenv("NB_E2E_BUILDX_CACHE"); dir != "" {
args = append(args,
"--cache-from", "type=local,src="+dir,
"--cache-to", "type=local,dest="+dir+",mode=max",
)
}
args = append(args, ".")
cmd := exec.CommandContext(ctx, "docker", args...)
cmd.Dir = root
cmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=1")
if out, err := cmd.CombinedOutput(); err != nil {

View File

@@ -15,9 +15,10 @@ import (
const (
proxyDockerfile = "proxy/Dockerfile.multistage"
// defaultProxyImage is the published reverse-proxy release used by default.
// Override with NB_E2E_PROXY_IMAGE; a value without a "/" is built locally.
defaultProxyImage = "netbirdio/reverse-proxy:0.74.0-rc.2"
// defaultProxyImage is the local tag the reverse proxy is built under from
// proxyDockerfile. Override with NB_E2E_PROXY_IMAGE: a value with a "/" is
// pulled as a published image; a bare tag is built under that name.
defaultProxyImage = "netbird-reverse-proxy:e2e"
proxyAlias = "proxy"
// AgentNetworkCluster is the proxy cluster the e2e provider bootstraps and