Skip to content

Commit 8a100fc

Browse files
feat(fakeintake): pin fakeintake image version for e2e tests (#53728)
## What & why Today e2e tests consume the fakeintake image via a hardcoded `:latest` tag. `:latest` is published only on merge to main, which has two drawbacks: 1. **No PR feedback** — a fakeintake code change is not exercised by e2e on the PR that makes it. CI already builds an immutable `v<sha>` image and sets `FAKEINTAKE_IMAGE_OVERRIDE`, but only 2 suites honored that var; everything else used `:latest`. 2. **Uncontrolled propagation** — merging moves `:latest`, so every branch instantly picks up the new fakeintake; a breaking change hits everywhere at once with no reproducibility. This PR **pins the fakeintake version**: fakeintake changes are exercised on the PR, and branches use a fixed tag — picking up a new fakeintake only when they rebase onto a main that bumped the pin. ## Changes - **`test/fakeintake/version/`** — new package: `VERSION` file (source of truth, starts at `v1`) + `ImageURL()` helper that returns `FAKEINTAKE_IMAGE_OVERRIDE` when set, else `<registry>:<pinned-tag>`. - **Consumers** — all 4 fakeintake image defaults (aws/azure/gcp scenarios + local docker component) now resolve through `version.ImageURL(...)`, so the override is honored **globally**. The two ad-hoc env reads were simplified away. - **Publish** — `publish_fakeintake_pinned` publishes `fakeintake:<VERSION>` on main. `publish_fakeintake` (`v<sha>`) and `publish_fakeintake_latest` are unchanged (`:latest` still published for external consumers; no test references it). - **Enforcement** — `dda inv fakeintake.check-version-bump` requires a **strictly-increasing** VERSION when `test/fakeintake/**` changes, run in the merge queue so two concurrent same-value bumps force a re-bump (no clobbered/mutable tags). - Docs updated in both AGENTS.md files. ## Developer workflow Change fakeintake code → bump `test/fakeintake/version/VERSION` in the same PR. Your PR's e2e runs your built image (via the override); on merge the pinned tag is published; other branches adopt it on rebase (visible as a VERSION bump in their diff). ## Verification `dda inv fakeintake.build`, version unit tests (4/4), `dda inv linter.go` on all changed Go packages, `linter.python`, and `linter.gitlab-ci` all pass locally. ## Open items to confirm on CI - **GCP registry coverage** — the pinned job uses `IMG_REGISTRIES: public` (same as the `:latest` job that already reaches `registry.datadoghq.com`, gcp's registry), so the pinned tag should land there too. The `dd-pkg` registry-group mapping lives outside this repo — worth confirming against a real CI run. - **Immutable publish** — `dd-pkg publish-image` has no fail-on-overwrite flag; immutability rests on the strictly-increasing merge-queue check instead (noted in a job comment). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: kevin.fairise <kevin.fairise@datadoghq.com>
1 parent daaeb81 commit 8a100fc

23 files changed

Lines changed: 369 additions & 44 deletions

File tree

.gitlab-ci.yml

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,22 @@ variables:
475475
- .gitlab/deploy/container_build/fakeintake.yml
476476
- .gitlab/deploy/dev_container_deploy/fakeintake.yml
477477

478+
# fakeintake changes that rebuild the published image (server binary only).
479+
# Client/CLI/docs changes don't. Keep in sync with _is_server_file() in tasks/fakeintake.py.
480+
.fakeintake_server_paths: &fakeintake_server_paths
481+
paths:
482+
- test/fakeintake/cmd/server/**/*
483+
- test/fakeintake/server/**/*
484+
- test/fakeintake/aggregator/**/*
485+
- test/fakeintake/api/**/*
486+
- test/fakeintake/go.mod
487+
- test/fakeintake/go.sum
488+
- test/fakeintake/Dockerfile
489+
- test/fakeintake/version/VERSION
490+
- .gitlab/build/binary_build/fakeintake.yml
491+
- .gitlab/deploy/container_build/fakeintake.yml
492+
- .gitlab/deploy/dev_container_deploy/fakeintake.yml
493+
478494
# Paths that impact the final build artifacts (rpm, deb, docker images, etc.)
479495
# Used to conditionally skip expensive tests like SMP when only non-artifact files change
480496
# like documentation, CI config, e2e tests.
@@ -924,6 +940,15 @@ workflow:
924940
- <<: *if_disable_e2e_tests
925941
when: never
926942
- !reference [.except_mergequeue]
943+
# A fakeintake server change rebuilds the image: run e2e against the built
944+
# v<sha> image via the override. Must precede the run-all/main/release/rc cases
945+
# (first-match-wins) so the override is never dropped. Needs publish_fakeintake.
946+
- changes:
947+
<<: *fakeintake_server_paths
948+
compare_to: $COMPARE_TO_BRANCH
949+
variables:
950+
E2E_FAKEINTAKE_IMAGE_OVERRIDE: "public.ecr.aws/datadog/fakeintake:v$CI_COMMIT_SHORT_SHA"
951+
when: on_success
927952
- <<: *if_run_all_e2e_tests
928953
when: on_success
929954
- <<: *if_main_branch
@@ -932,6 +957,12 @@ workflow:
932957
when: on_success
933958
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$/
934959
when: on_success
960+
# Other fakeintake changes (client/CLI/docs) don't rebuild the image but do
961+
# compile into the test binaries — run e2e against the pinned image (no override).
962+
- changes:
963+
<<: *fakeintake_paths
964+
compare_to: $COMPARE_TO_BRANCH
965+
when: on_success
935966
- changes:
936967
paths:
937968
- .gitlab/test/e2e/e2e.yml
@@ -977,14 +1008,9 @@ workflow:
9771008
- <<: *if_windows_installer_changes
9781009
when: on_success
9791010

1011+
# fakeintake changes (and the override) are handled by .on_e2e_main_release_or_rc.
9801012
.on_e2e_or_fakeintake_changes_or_manual:
9811013
- !reference [.on_e2e_main_release_or_rc]
982-
- changes:
983-
<<: *fakeintake_paths
984-
compare_to: $COMPARE_TO_BRANCH
985-
variables:
986-
FAKEINTAKE_IMAGE_OVERRIDE: "public.ecr.aws/datadog/fakeintake:v$CI_COMMIT_SHORT_SHA"
987-
when: on_success
9881014
- changes:
9891015
paths:
9901016
- test/new-e2e/test-infra-definition/*
@@ -1476,22 +1502,16 @@ workflow:
14761502
- .go-version
14771503
compare_to: $COMPARE_TO_BRANCH
14781504

1505+
# Gates build/publish/version-check jobs on image-rebuilding (server) changes.
1506+
# The e2e override is set in .on_e2e_main_release_or_rc, not here.
14791507
.on_fakeintake_changes:
14801508
- changes:
1481-
<<: *fakeintake_paths
1509+
<<: *fakeintake_server_paths
14821510
compare_to: $COMPARE_TO_BRANCH
14831511

1484-
.on_fakeintake_changes_with_image_override:
1485-
- changes:
1486-
<<: *fakeintake_paths
1487-
compare_to: $COMPARE_TO_BRANCH
1488-
variables:
1489-
FAKEINTAKE_IMAGE_OVERRIDE: "public.ecr.aws/datadog/fakeintake:v$CI_COMMIT_SHORT_SHA"
1490-
when: on_success
1491-
14921512
.on_fakeintake_changes_on_main:
14931513
- changes:
1494-
<<: *fakeintake_paths
1514+
<<: *fakeintake_server_paths
14951515
<<: *if_main_branch
14961516

14971517
.fast_tests_variables_named_branches:

.gitlab/JOBOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ slack_teams_channels_check @DataDog/agent-devx
99
lint* @DataDog/agent-devx
1010
notify* @DataDog/agent-devx
1111
check_modules_replace @DataDog/agent-devx
12+
fakeintake_check_version_bump @DataDog/agent-devx
13+
publish_fakeintake_pinned @DataDog/agent-devx
1214

1315
# Deps build
1416
build_clang_* @DataDog/ebpf-platform

.gitlab/build/binary_build/fakeintake.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,18 @@ build_fakeintake:
1010
tags: ["arch:amd64", "specific:true"]
1111
script:
1212
- dda inv -- fakeintake.build
13+
14+
# Runs in the merge queue (no .except_mergequeue) on purpose: the queue rebases each PR
15+
# onto up-to-date main before running this, so two PRs that bumped VERSION to the same
16+
# value will have the second one fail here and be forced to re-bump. See
17+
# tasks/fakeintake.py:check_version_bump and test/fakeintake/AGENTS.md.
18+
fakeintake_check_version_bump:
19+
stage: binary_build
20+
timeout: 15m
21+
rules:
22+
- !reference [.on_fakeintake_changes]
23+
needs: []
24+
image: registry.ddbuild.io/ci/datadog-agent-buildimages/linux$CI_IMAGE_LINUX_SUFFIX:$CI_IMAGE_LINUX
25+
tags: ["arch:amd64", "specific:true"]
26+
script:
27+
- dda inv -- fakeintake.check-version-bump

.gitlab/deploy/dev_container_deploy/fakeintake.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,29 @@ publish_fakeintake_latest:
2828
IMG_DESTINATIONS: fakeintake:latest
2929
IMG_REGISTRIES: public
3030
IMG_SIGNING: "false"
31+
32+
# Publishes the pinned tag (fakeintake:<VERSION>) from main only, when the VERSION
33+
# file changes. Gating on VERSION means a bump always targets a brand-new tag;
34+
# strict monotonicity (fakeintake_check_version_bump) prevents clobbering a released
35+
# tag, since dd-pkg publish-image has no fail-on-overwrite flag.
36+
publish_fakeintake_pinned:
37+
extends: .docker_publish_job_definition
38+
stage: dev_container_deploy
39+
rules:
40+
- !reference [.except_mergequeue]
41+
# if_main_branch inlined: YAML anchors are file-local (only !reference is cross-file).
42+
# No compare_to: it defaults to main, so on main it would be a main-vs-main diff that
43+
# never matches; the default diff vs the previous commit detects the merge's VERSION bump.
44+
- if: $CI_COMMIT_BRANCH == "main"
45+
changes:
46+
paths:
47+
- test/fakeintake/version/VERSION
48+
needs:
49+
- job: docker_build_fakeintake
50+
optional: false
51+
before_script:
52+
- export IMG_DESTINATIONS="fakeintake:$(cat test/fakeintake/version/VERSION)"
53+
variables:
54+
IMG_SOURCES: registry.ddbuild.io/ci/datadog-agent/fakeintake:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}
55+
IMG_REGISTRIES: public
56+
IMG_SIGNING: "false"

.gitlab/test/e2e/e2e.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,23 @@
138138
annotations:
139139
- $EXTERNAL_LINKS_PATH
140140

141+
# Wait for the fakeintake image to publish (v<sha> on PRs, pinned tag on main).
142+
# Both optional: absent on non-fakeintake pipelines. Reference from every e2e
143+
# needs block (directly or via .needs_new_e2e_template) so no job races the image.
144+
.needs_fakeintake_publish:
145+
- job: publish_fakeintake
146+
optional: true
147+
- job: publish_fakeintake_pinned
148+
optional: true
149+
141150
.needs_new_e2e_template:
142151
- go_e2e_deps
143152
- job: go_e2e_test_binaries
144153
artifacts: false
145154
- go_tools_deps
146155
- job: new-e2e-base-coverage
147156
optional: true
157+
- !reference [.needs_fakeintake_publish]
148158

149159
new-e2e-base-coverage:
150160
extends: .new_e2e_template
@@ -289,6 +299,7 @@ new-e2e-containers-eks-init:
289299
needs:
290300
- go_e2e_deps
291301
- go_tools_deps
302+
- !reference [.needs_fakeintake_publish]
292303
rules:
293304
- !reference [.on_container_or_e2e_changes]
294305
- !reference [.manual]
@@ -326,6 +337,7 @@ new-e2e-containers-openshift-init:
326337
needs:
327338
- go_e2e_deps
328339
- go_tools_deps
340+
- !reference [.needs_fakeintake_publish]
329341
rules:
330342
- !reference [.on_e2e_main_release_or_rc]
331343
- changes:
@@ -445,14 +457,11 @@ new-e2e-privateactionrunner:
445457
needs:
446458
- !reference [.needs_new_e2e_template]
447459
- agent_deb-x64-a7
448-
- job: publish_fakeintake
449-
optional: true
450460
- qa_agent_linux
451461
- qa_dca
452462
rules:
453463
- !reference [.except_disable_e2e_tests]
454464
- !reference [.except_mergequeue]
455-
- !reference [.on_fakeintake_changes_with_image_override]
456465
- !reference [.on_privateactionrunner_or_e2e_changes]
457466
- !reference [.manual]
458467
variables:
@@ -525,6 +534,7 @@ new-e2e-npm-eks-init:
525534
needs:
526535
- go_e2e_deps
527536
- go_tools_deps
537+
- !reference [.needs_fakeintake_publish]
528538
rules:
529539
- !reference [.on_npm_or_e2e_changes]
530540
- !reference [.manual]
@@ -1023,6 +1033,7 @@ new-e2e-otel-eks-init:
10231033
needs:
10241034
- go_e2e_deps
10251035
- go_tools_deps
1036+
- !reference [.needs_fakeintake_publish]
10261037
variables:
10271038
TARGETS: ./tests/otel
10281039
TEAM: otel
@@ -1089,6 +1100,7 @@ new-e2e-otel:
10891100
needs:
10901101
- go_e2e_deps
10911102
- go_tools_deps
1103+
- !reference [.needs_fakeintake_publish]
10921104
rules:
10931105
- !reference [.on_e2e_main_release_or_rc]
10941106
- changes:

.gitlab/test/e2e_pre_test/e2e_pre_test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ e2e_pre_test:
1010
- job: go_e2e_test_binaries
1111
artifacts: false
1212
- go_tools_deps
13+
# PR pipelines run against v<sha> (publish_fakeintake); main uses the pinned
14+
# tag (publish_fakeintake_pinned). Both optional: absent otherwise.
1315
- job: publish_fakeintake
1416
optional: true
17+
- job: publish_fakeintake_pinned
18+
optional: true
1519
script:
1620
- dda inv -- -e new-e2e-tests.run --targets ./test-infra-definition --result-json $E2E_RESULT_JSON --junit-tar junit-${CI_JOB_ID}.tgz ${EXTRA_PARAMS} --test-washer -c ddagent:imagePullRegistry=669783387624.dkr.ecr.us-east-1.amazonaws.com -c ddagent:imagePullUsername=AWS -c ddagent:imagePullPassword=$(aws ecr get-login-password)
1721
after_script:

tasks/fakeintake.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,36 @@
22
Build or use the fake intake client CLI
33
"""
44

5+
import os
6+
57
from invoke import task
8+
from invoke.exceptions import Exit
69

10+
from tasks.libs.common.color import color_message
11+
from tasks.libs.common.git import get_ancestor_base_branch, get_changed_files, get_common_ancestor
712
from tasks.libs.common.go import go_build
813

14+
VERSION_FILE = "test/fakeintake/version/VERSION"
15+
16+
# Paths that rebuild the published image (client/CLI/docs don't).
17+
# Keep in sync with .fakeintake_server_paths in .gitlab-ci.yml.
18+
SERVER_PATH_PREFIXES = (
19+
"test/fakeintake/cmd/server/",
20+
"test/fakeintake/server/",
21+
"test/fakeintake/aggregator/",
22+
"test/fakeintake/api/",
23+
)
24+
SERVER_FILES = (
25+
"test/fakeintake/go.mod",
26+
"test/fakeintake/go.sum",
27+
"test/fakeintake/Dockerfile",
28+
)
29+
30+
31+
def _is_server_file(path: str) -> bool:
32+
"""True if changing `path` rebuilds the fakeintake image (needs a VERSION bump)."""
33+
return path in SERVER_FILES or path.startswith(SERVER_PATH_PREFIXES)
34+
935

1036
@task
1137
def build(ctx):
@@ -24,3 +50,69 @@ def test(ctx):
2450
"""
2551
with ctx.cd("test/fakeintake"):
2652
ctx.run("go test ./...")
53+
54+
55+
def _parse_version(raw: str) -> int:
56+
version = raw.strip()
57+
if not version.startswith("v") or not version[1:].isdigit():
58+
raise Exit(
59+
code=1,
60+
message=color_message(
61+
f"Invalid {VERSION_FILE} content {raw!r}: expected a 'v<int>' tag (e.g. 'v1')", "red"
62+
),
63+
)
64+
return int(version[1:])
65+
66+
67+
@task
68+
def check_version_bump(ctx):
69+
"""
70+
Ensure test/fakeintake/version/VERSION is bumped whenever the fakeintake image changes.
71+
72+
The pinned tag in VERSION is what e2e-framework's fakeintake defaults resolve to
73+
(see test/fakeintake/version). Only server-side changes rebuild the published image
74+
(see _is_server_file); such a merge must ship a strictly greater VERSION than its base
75+
branch so the newly published image gets a unique, immutable tag (see
76+
test/fakeintake/AGENTS.md). Client/CLI/docs changes don't touch the image, so they
77+
don't require a bump.
78+
"""
79+
base_branch = os.environ.get("COMPARE_TO_BRANCH") or get_ancestor_base_branch()
80+
81+
# Resolve the merge-base as a concrete commit. get_common_ancestor fetches the
82+
# base ref when it is missing (CI does shallow clones with S3 caching), which a
83+
# raw `git diff <base>...HEAD` cannot do — that fails with "unknown revision".
84+
merge_base = get_common_ancestor(ctx, "HEAD", base_branch)
85+
86+
changed_files = [f.strip() for f in get_changed_files(ctx, base=merge_base) if f.strip()]
87+
server_changes = [f for f in changed_files if _is_server_file(f)]
88+
89+
if not server_changes:
90+
print(color_message("No fakeintake image (server) changes detected, VERSION bump not required", "green"))
91+
return
92+
93+
with open(VERSION_FILE) as f:
94+
new_version_raw = f.read()
95+
new_version = _parse_version(new_version_raw)
96+
97+
# VERSION may not exist at the merge-base (bootstrap PR / baseline reset), so
98+
# warn=True and treat a missing base file as version 0 instead of crashing.
99+
base_version_result = ctx.run(f"git show {merge_base}:{VERSION_FILE}", hide=True, warn=True)
100+
base_version = _parse_version(base_version_result.stdout) if base_version_result.ok else 0
101+
102+
if new_version <= base_version:
103+
raise Exit(
104+
code=1,
105+
message=color_message(
106+
f"fakeintake image changed ({len(server_changes)} server file(s), e.g. {server_changes[0]}) but "
107+
f"{VERSION_FILE} was not bumped: it is 'v{new_version}', which must be strictly greater than "
108+
f"{base_branch}'s 'v{base_version}'. Bump {VERSION_FILE} to at least 'v{base_version + 1}' in this PR.",
109+
"red",
110+
),
111+
)
112+
113+
print(
114+
color_message(
115+
f"{VERSION_FILE} bumped from 'v{base_version}' to 'v{new_version}', OK",
116+
"green",
117+
)
118+
)

test/e2e-framework/AGENTS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,28 @@ dda inv new-e2e-tests.run --targets=./tests/<area>/...
182182
Use `e2e.WithDevMode()` to keep infrastructure alive after a failure so you can
183183
SSH in and inspect the agent directly.
184184

185+
## Fakeintake image version
186+
187+
Every fakeintake default (`scenarios/{aws,azure,gcp}/fakeintake/params.go`,
188+
`components/datadog/fakeintake/docker.go`) resolves through
189+
`components/datadog/fakeintake.ImageURL(...)`: it uses the
190+
`FakeintakeImageOverride` runner parameter (`E2E_FAKEINTAKE_IMAGE_OVERRIDE`) when
191+
set — read through the runner parameter store like any other `E2E_*` value, not
192+
`os.Getenv` — otherwise the pinned tag from `test/fakeintake/version.Tag`.
193+
`WithImageURL(...)` on any fakeintake provisioner still wins over both.
194+
195+
CI wiring (`.gitlab-ci.yml`): the `.on_e2e_main_release_or_rc` rule — inherited
196+
by every e2e job through its team rule (`.on_<team>_or_e2e_changes`) — sets
197+
`E2E_FAKEINTAKE_IMAGE_OVERRIDE` to the PR-built `v<sha>` image on a fakeintake
198+
*server* change (`.fakeintake_server_paths`). So such a PR runs the **whole**
199+
e2e suite against the PR's image (including mixed PRs), and no e2e job can miss
200+
the override. `.needs_new_e2e_template` gains optional needs on `publish_fakeintake`
201+
(PR `v<sha>`) and `publish_fakeintake_pinned` (main pinned tag) so e2e waits for
202+
the image to exist. Plain `.on_fakeintake_changes` is for non-consuming
203+
build/publish/version-check jobs only. See `test/fakeintake/AGENTS.md`
204+
§ "Image version pinning" for the full workflow (bumping VERSION, the
205+
strictly-increasing CI check, publish jobs).
206+
185207
## Key files
186208

187209
- `testing/e2e/suite.go``BaseSuite` and `Run()` (test entry point)

test/e2e-framework/components/datadog/fakeintake/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ go_library(
55
srcs = [
66
"component.go",
77
"docker.go",
8+
"imageurl.go",
89
],
910
importpath = "github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/fakeintake",
1011
visibility = ["//visibility:public"],
1112
deps = [
1213
"//test/e2e-framework/common/config",
1314
"//test/e2e-framework/components",
15+
"//test/e2e-framework/testing/runner",
16+
"//test/e2e-framework/testing/runner/parameters",
1417
"//test/fakeintake/server/rcstore",
18+
"//test/fakeintake/version",
1519
"@com_github_pulumi_pulumi_docker_sdk_v4//go/docker",
1620
"@com_github_pulumi_pulumi_sdk_v3//go/pulumi",
1721
],

0 commit comments

Comments
 (0)