diff --git a/.github/workflows/agent-network-e2e.yml b/.github/workflows/agent-network-e2e.yml index 26da4458d..c041bfbfa 100644 --- a/.github/workflows/agent-network-e2e.yml +++ b/.github/workflows/agent-network-e2e.yml @@ -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 }} diff --git a/e2e/harness/client.go b/e2e/harness/client.go index 83d5e5b31..1552ad3d4 100644 --- a/e2e/harness/client.go +++ b/e2e/harness/client.go @@ -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" ) diff --git a/e2e/harness/combined.go b/e2e/harness/combined.go index 0f948ecf6..a6f43a139 100644 --- a/e2e/harness/combined.go +++ b/e2e/harness/combined.go @@ -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 { diff --git a/e2e/harness/proxy.go b/e2e/harness/proxy.go index a4341f7b2..8db2c140f 100644 --- a/e2e/harness/proxy.go +++ b/e2e/harness/proxy.go @@ -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