From 0e977aea7b992ba93c83f0bc48a37809a31ac26c Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:50:09 +0000 Subject: [PATCH 01/14] APMSP-3752 automate SSI Agent updates Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../self.gitlab-update-agent-version.sts.yaml | 18 +++++ .gitlab-ci.yml | 51 ++++++++++++ .../test_update_agent_version.py | 55 +++++++++++++ .../ci/gitlab/docker/system-tests.Dockerfile | 1 + utils/ci/gitlab/main.yml | 2 +- .../scripts/libraries_and_scenarios_rules.yml | 3 + utils/scripts/update_agent_version.py | 81 +++++++++++++++++++ 7 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 .github/chainguard/self.gitlab-update-agent-version.sts.yaml create mode 100644 tests/test_the_test/test_update_agent_version.py create mode 100755 utils/scripts/update_agent_version.py diff --git a/.github/chainguard/self.gitlab-update-agent-version.sts.yaml b/.github/chainguard/self.gitlab-update-agent-version.sts.yaml new file mode 100644 index 00000000000..0b43039563b --- /dev/null +++ b/.github/chainguard/self.gitlab-update-agent-version.sts.yaml @@ -0,0 +1,18 @@ +--- +# Policy for: update_agent_version in DataDog/system-tests/.gitlab-ci.yml +issuer: https://gitlab.ddbuild.io + +subject_pattern: "project_path:DataDog/system-tests:ref_type:branch:ref:main" + +claim_pattern: + project_path: "DataDog/system-tests" + ref: "main" + ref_type: "branch" + ref_path: "refs/heads/main" + ref_protected: "true" + pipeline_source: "schedule" + ci_config_ref_uri: "gitlab\\.ddbuild\\.io/DataDog/system-tests//\\.gitlab-ci\\.yml@refs/heads/main" + +permissions: + contents: write + pull_requests: write diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 259be154b69..0e29dba7430 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -280,6 +280,57 @@ check_merge_labels: rules: - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main" +update_agent_version: + image: $CI_IMAGE + tags: + - arch:amd64 + stage: system-tests-utils + resource_group: update-agent-version + id_tokens: + DDOCTOSTS_ID_TOKEN: + aud: dd-octo-sts + needs: + - job: build_ci_image + artifacts: false + optional: true + before_script: + - dd-octo-sts version + - dd-octo-sts debug --scope DataDog/system-tests --policy self.gitlab-update-agent-version + - dd-octo-sts token --scope DataDog/system-tests --policy self.gitlab-update-agent-version > token.txt + - export GH_TOKEN="$(cat token.txt)" + - gh auth setup-git + - git remote set-url origin https://github.com/DataDog/system-tests.git + script: + - export LATEST_AGENT_VERSION="$(gh api repos/DataDog/datadog-agent/releases/latest --jq .tag_name)" + - python3 utils/scripts/update_agent_version.py "$LATEST_AGENT_VERSION" + - | + if git diff --quiet; then + echo "Agent pins are already up to date" + exit 0 + fi + - export BRANCH_NAME="apmsp-3752/update-agent-version" + - git switch --force-create "$BRANCH_NAME" + - git add utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml + - git config user.name "github-actions[bot]" + - git config user.email "github-actions[bot]@users.noreply.github.com" + - git commit -m "APMSP-3752 update Agent to ${LATEST_AGENT_VERSION#v}" + - git push --force --set-upstream origin "$BRANCH_NAME" + - | + PR_NUMBER="$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')" + if [ -z "$PR_NUMBER" ]; then + PR_URL="$(gh pr create \ + --base main \ + --head "$BRANCH_NAME" \ + --title "APMSP-3752 Update Agent to ${LATEST_AGENT_VERSION#v}" \ + --body "Automated daily update of the Agent version pinned by SSI tests. The PR will merge automatically after all required checks pass.")" + PR_NUMBER="${PR_URL##*/}" + fi + gh pr merge "$PR_NUMBER" --auto --squash + after_script: + - if [ -f token.txt ]; then dd-octo-sts revoke -t "$(cat token.txt)"; fi + rules: + - if: '$SCHEDULED_JOB == "update_agent_version" && $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == "main"' + generate_system_tests_lambda_proxy_image: image: registry.ddbuild.io/ci/libdatadog-build/ci_docker_base:100425777 tags: ["docker-in-docker:amd64"] diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py new file mode 100644 index 00000000000..070313a48e6 --- /dev/null +++ b/tests/test_the_test/test_update_agent_version.py @@ -0,0 +1,55 @@ +from pathlib import Path + +import pytest + +from utils.scripts.update_agent_version import ( + DOCKER_COMPOSE_PROVISION, + INSTALLER_PROVISION, + normalize_version, + update_agent_version, +) + + +def write_pins(root: Path) -> None: + installer = root / INSTALLER_PROVISION + installer.parent.mkdir(parents=True) + installer.write_text( + "remote-command: |\n" + " # Pin to 7.78.4 agent release. APMSP-3059\n" + " export DD_AGENT_MAJOR_VERSION=7\n" + " export DD_AGENT_MINOR_VERSION=78.4\n" + " echo install\n" + ) + compose = root / DOCKER_COMPOSE_PROVISION + compose.parent.mkdir(parents=True) + compose.write_text( + "services:\n" + " datadog:\n" + " # Pin to 7.78.4 agent release. APMSP-3059\n" + " image: gcr.io/datadoghq/agent:7.78.4\n" + ) + + +def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: + write_pins(tmp_path) + + assert update_agent_version(tmp_path, "v7.82.3") + assert "DD_AGENT_MINOR_VERSION=82.3" in (tmp_path / INSTALLER_PROVISION).read_text() + assert "gcr.io/datadoghq/agent:7.82.3" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() + assert "updated automatically by APMSP-3752" in (tmp_path / INSTALLER_PROVISION).read_text() + + assert not update_agent_version(tmp_path, "7.82.3") + + +@pytest.mark.parametrize("version", ["8.0.0", "7.82", "7.82.3-rc.1", "latest"]) +def test_normalize_version_rejects_unsupported_versions(version: str) -> None: + with pytest.raises(ValueError, match="Expected a stable Agent 7 version"): + normalize_version(version) + + +def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> None: + write_pins(tmp_path) + (tmp_path / INSTALLER_PROVISION).write_text("remote-command: |\n echo install\n") + + with pytest.raises(RuntimeError, match="exactly one Agent version pin"): + update_agent_version(tmp_path, "7.82.3") diff --git a/utils/ci/gitlab/docker/system-tests.Dockerfile b/utils/ci/gitlab/docker/system-tests.Dockerfile index 213339bea11..c2852092108 100644 --- a/utils/ci/gitlab/docker/system-tests.Dockerfile +++ b/utils/ci/gitlab/docker/system-tests.Dockerfile @@ -31,6 +31,7 @@ RUN clean-apt install \ ca-certificates \ curl \ git \ + gh \ python3.12 \ python3.12-venv diff --git a/utils/ci/gitlab/main.yml b/utils/ci/gitlab/main.yml index cec5958ba53..39a86da0628 100644 --- a/utils/ci/gitlab/main.yml +++ b/utils/ci/gitlab/main.yml @@ -79,7 +79,7 @@ variables: # Tag = first 12 chars of sha256(utils/ci/gitlab/docker/system-tests.Dockerfile + requirements.txt) # Update this when either file changes: # cat utils/ci/gitlab/docker/system-tests.Dockerfile requirements.txt | sha256sum | cut -c1-12 - CI_IMAGE: "registry.ddbuild.io/system-tests/ci-runner:1728376181ec" + CI_IMAGE: "registry.ddbuild.io/system-tests/ci-runner:d39b6010c0a4" SYSTEM_TESTS_SPLIT_PIPELINE: "$[[ inputs.split_pipeline ]]" .system_tests_param_base: diff --git a/utils/scripts/libraries_and_scenarios_rules.yml b/utils/scripts/libraries_and_scenarios_rules.yml index 7ec983d3bed..0584e4db5e0 100644 --- a/utils/scripts/libraries_and_scenarios_rules.yml +++ b/utils/scripts/libraries_and_scenarios_rules.yml @@ -286,6 +286,9 @@ patterns: libraries: null utils/scripts/ssi_wizards/*: scenario_groups: null + utils/scripts/update_agent_version.py: + scenario_groups: null + libraries: null utils/scripts/update_change_log.sh: scenario_groups: null libraries: null diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py new file mode 100755 index 00000000000..f51c835f8c0 --- /dev/null +++ b/utils/scripts/update_agent_version.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import re +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence + + +PIN_COMMENT = "Pinned Agent version, updated automatically by APMSP-3752" +PIN_COMMENT_PATTERN = rf"(?:Pin to .* agent release\. APMSP-[0-9]+|{re.escape(PIN_COMMENT)})" +VERSION_PATTERN = re.compile(r"^7\.[0-9]+\.[0-9]+$") + +INSTALLER_PROVISION = Path("utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml") +DOCKER_COMPOSE_PROVISION = Path( + "utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml" +) + + +def normalize_version(version: str) -> str: + normalized = version.removeprefix("v") + if VERSION_PATTERN.fullmatch(normalized) is None: + raise ValueError(f"Expected a stable Agent 7 version, got: {version}") + return normalized + + +def _replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> bool: + content = path.read_text() + updated, replacement_count = pattern.subn(replacement, content) + if replacement_count != 1: + raise RuntimeError(f"Expected exactly one Agent version pin in {path}, found {replacement_count}") + if updated == content: + return False + path.write_text(updated) + return True + + +def update_agent_version(root: Path, version: str) -> bool: + normalized = normalize_version(version) + minor_version = normalized.removeprefix("7.") + + installer_changed = _replace_once( + root / INSTALLER_PROVISION, + re.compile( + rf"(?m)^ # {PIN_COMMENT_PATTERN}\n" + r" export DD_AGENT_MAJOR_VERSION=[^\n]+\n" + r" export DD_AGENT_MINOR_VERSION=[^\n]+$" + ), + f" # {PIN_COMMENT}\n export DD_AGENT_MAJOR_VERSION=7\n export DD_AGENT_MINOR_VERSION={minor_version}", + ) + compose_changed = _replace_once( + root / DOCKER_COMPOSE_PROVISION, + re.compile( + rf"(?m)^ # {PIN_COMMENT_PATTERN}\n" + r" image: gcr\.io/datadoghq/agent:[^\n]+$" + ), + f" # {PIN_COMMENT}\n image: gcr.io/datadoghq/agent:{normalized}", + ) + return installer_changed or compose_changed + + +def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Update the Agent version pinned by SSI scenarios") + parser.add_argument("version", help="Stable Agent 7 version, for example 7.82.3") + parser.add_argument("--root", type=Path, default=Path.cwd(), help=argparse.SUPPRESS) + return parser.parse_args(argv) + + +def main(argv: Sequence[str] | None = None) -> int: + args = parse_args(argv) + changed = update_agent_version(args.root, args.version) + print(f"Agent pins {'updated' if changed else 'already current'}: {normalize_version(args.version)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From f5d9870346ebb0e19baf1496e628dca240f9c2b2 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:57:38 +0000 Subject: [PATCH 02/14] APMSP-3752 move CI logic into script Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .gitlab-ci.yml | 35 +----- .../test_update_agent_version.py | 106 ++++++++++++++++++ utils/scripts/update_agent_version.py | 101 ++++++++++++++++- 3 files changed, 203 insertions(+), 39 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0e29dba7430..08869a4a7d2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -293,41 +293,8 @@ update_agent_version: - job: build_ci_image artifacts: false optional: true - before_script: - - dd-octo-sts version - - dd-octo-sts debug --scope DataDog/system-tests --policy self.gitlab-update-agent-version - - dd-octo-sts token --scope DataDog/system-tests --policy self.gitlab-update-agent-version > token.txt - - export GH_TOKEN="$(cat token.txt)" - - gh auth setup-git - - git remote set-url origin https://github.com/DataDog/system-tests.git script: - - export LATEST_AGENT_VERSION="$(gh api repos/DataDog/datadog-agent/releases/latest --jq .tag_name)" - - python3 utils/scripts/update_agent_version.py "$LATEST_AGENT_VERSION" - - | - if git diff --quiet; then - echo "Agent pins are already up to date" - exit 0 - fi - - export BRANCH_NAME="apmsp-3752/update-agent-version" - - git switch --force-create "$BRANCH_NAME" - - git add utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml - - git config user.name "github-actions[bot]" - - git config user.email "github-actions[bot]@users.noreply.github.com" - - git commit -m "APMSP-3752 update Agent to ${LATEST_AGENT_VERSION#v}" - - git push --force --set-upstream origin "$BRANCH_NAME" - - | - PR_NUMBER="$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')" - if [ -z "$PR_NUMBER" ]; then - PR_URL="$(gh pr create \ - --base main \ - --head "$BRANCH_NAME" \ - --title "APMSP-3752 Update Agent to ${LATEST_AGENT_VERSION#v}" \ - --body "Automated daily update of the Agent version pinned by SSI tests. The PR will merge automatically after all required checks pass.")" - PR_NUMBER="${PR_URL##*/}" - fi - gh pr merge "$PR_NUMBER" --auto --squash - after_script: - - if [ -f token.txt ]; then dd-octo-sts revoke -t "$(cat token.txt)"; fi + - python3 utils/scripts/update_agent_version.py rules: - if: '$SCHEDULED_JOB == "update_agent_version" && $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == "main"' diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 070313a48e6..defb56a346d 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -1,11 +1,16 @@ +import subprocess from pathlib import Path import pytest from utils.scripts.update_agent_version import ( + AUTOMATION_BRANCH, DOCKER_COMPOSE_PROVISION, INSTALLER_PROVISION, + automate_update, normalize_version, + publish_update, + run_automation, update_agent_version, ) @@ -53,3 +58,104 @@ def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> Non with pytest.raises(RuntimeError, match="exactly one Agent version pin"): update_agent_version(tmp_path, "7.82.3") + + +def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + write_pins(tmp_path) + published: list[tuple[Path, str]] = [] + monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _root, _env: "7.82.3") + monkeypatch.setattr( + "utils.scripts.update_agent_version.publish_update", + lambda root, version, _env: published.append((root, version)), + ) + + assert automate_update(tmp_path) + assert published == [(tmp_path, "7.82.3")] + + +def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + write_pins(tmp_path) + update_agent_version(tmp_path, "7.82.3") + monkeypatch.setattr( + "utils.scripts.update_agent_version.publish_update", + lambda _root, _version, _env: pytest.fail("publish should not run"), + ) + + assert not automate_update(tmp_path, "7.82.3") + + +@pytest.mark.parametrize("existing_pr", ["", "1234"]) +def test_publish_update_creates_only_missing_pr( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, existing_pr: str +) -> None: + commands: list[list[str]] = [] + + def fake_run( + _root: Path, + args: list[str], + *, + capture_output: bool = False, + env: dict[str, str] | None = None, + ) -> subprocess.CompletedProcess[str]: + assert env == {"GH_TOKEN": "token"} + commands.append(args) + stdout = existing_pr if capture_output and args[:3] == ["gh", "pr", "list"] else "" + return subprocess.CompletedProcess(args, 0, stdout=stdout) + + monkeypatch.setattr("utils.scripts.update_agent_version.run_command", fake_run) + + publish_update(tmp_path, "7.82.3", {"GH_TOKEN": "token"}) + + assert ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH] in commands + assert any(command[:3] == ["gh", "pr", "create"] for command in commands) is (not existing_pr) + assert commands[-1] == ["gh", "pr", "merge", AUTOMATION_BRANCH, "--auto", "--squash"] + + +def test_run_automation_revokes_token_after_failure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + commands: list[list[str]] = [] + + def fake_run( + _root: Path, + args: list[str], + *, + capture_output: bool = False, + env: dict[str, str] | None = None, + ) -> subprocess.CompletedProcess[str]: + assert env is None + commands.append(args) + stdout = "secret-token" if capture_output else "" + return subprocess.CompletedProcess(args, 0, stdout=stdout) + + def fail_update(_root: Path, _version: str | None, env: dict[str, str]) -> bool: + assert env["GH_TOKEN"] == "secret-token" + raise RuntimeError("publish failed") + + monkeypatch.setattr("utils.scripts.update_agent_version.run_command", fake_run) + monkeypatch.setattr("utils.scripts.update_agent_version.automate_update", fail_update) + + with pytest.raises(RuntimeError, match="publish failed"): + run_automation(tmp_path) + + assert commands[-1] == ["dd-octo-sts", "revoke", "-t", "secret-token"] + + +def test_run_automation_rejects_empty_token(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + commands: list[list[str]] = [] + + def fake_run( + _root: Path, + args: list[str], + *, + capture_output: bool = False, + env: dict[str, str] | None = None, + ) -> subprocess.CompletedProcess[str]: + assert env is None + commands.append(args) + return subprocess.CompletedProcess(args, 0, stdout="" if capture_output else None) + + monkeypatch.setattr("utils.scripts.update_agent_version.run_command", fake_run) + + with pytest.raises(RuntimeError, match="empty GitHub token"): + run_automation(tmp_path) + + assert not any(command[:2] == ["dd-octo-sts", "revoke"] for command in commands) diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index f51c835f8c0..c347860b912 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -3,17 +3,22 @@ from __future__ import annotations import argparse +import os import re +import subprocess from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Mapping, Sequence PIN_COMMENT = "Pinned Agent version, updated automatically by APMSP-3752" PIN_COMMENT_PATTERN = rf"(?:Pin to .* agent release\. APMSP-[0-9]+|{re.escape(PIN_COMMENT)})" VERSION_PATTERN = re.compile(r"^7\.[0-9]+\.[0-9]+$") +AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" +REPOSITORY = "DataDog/system-tests" +OCTO_STS_POLICY = "self.gitlab-update-agent-version" INSTALLER_PROVISION = Path("utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml") DOCKER_COMPOSE_PROVISION = Path( @@ -63,17 +68,103 @@ def update_agent_version(root: Path, version: str) -> bool: return installer_changed or compose_changed +def run_command( + root: Path, + args: Sequence[str], + *, + capture_output: bool = False, + env: Mapping[str, str] | None = None, +) -> subprocess.CompletedProcess[str]: + return subprocess.run(args, cwd=root, check=True, capture_output=capture_output, text=True, env=env) + + +def latest_agent_version(root: Path, env: Mapping[str, str] | None = None) -> str: + result = run_command( + root, + ["gh", "api", "repos/DataDog/datadog-agent/releases/latest", "--jq", ".tag_name"], + capture_output=True, + env=env, + ) + return normalize_version(result.stdout.strip()) + + +def publish_update(root: Path, version: str, env: Mapping[str, str] | None = None) -> None: + run_command(root, ["gh", "auth", "setup-git"], env=env) + run_command(root, ["git", "remote", "set-url", "origin", f"https://github.com/{REPOSITORY}.git"], env=env) + run_command(root, ["git", "switch", "--force-create", AUTOMATION_BRANCH], env=env) + run_command(root, ["git", "add", str(INSTALLER_PROVISION), str(DOCKER_COMPOSE_PROVISION)], env=env) + run_command(root, ["git", "config", "user.name", "github-actions[bot]"], env=env) + run_command(root, ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], env=env) + run_command(root, ["git", "commit", "-m", f"APMSP-3752 update Agent to {version}"], env=env) + run_command(root, ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH], env=env) + + existing_pr = run_command( + root, + ["gh", "pr", "list", "--head", AUTOMATION_BRANCH, "--state", "open", "--json", "number", "--jq", ".[0].number"], + capture_output=True, + env=env, + ).stdout.strip() + if not existing_pr: + run_command( + root, + [ + "gh", + "pr", + "create", + "--base", + "main", + "--head", + AUTOMATION_BRANCH, + "--title", + f"APMSP-3752 Update Agent to {version}", + "--body", + "Automated daily update of the Agent version pinned by SSI tests. " + "The PR will merge automatically after all required checks pass.", + ], + env=env, + ) + run_command(root, ["gh", "pr", "merge", AUTOMATION_BRANCH, "--auto", "--squash"], env=env) + + +def automate_update(root: Path, version: str | None = None, env: Mapping[str, str] | None = None) -> bool: + normalized = normalize_version(version) if version is not None else latest_agent_version(root, env) + if not update_agent_version(root, normalized): + print(f"Agent pins already current: {normalized}") + return False + + publish_update(root, normalized, env) + print(f"Published Agent pin update: {normalized}") + return True + + +def run_automation(root: Path, version: str | None = None) -> bool: + scope_args = ["--scope", REPOSITORY, "--policy", OCTO_STS_POLICY] + run_command(root, ["dd-octo-sts", "version"]) + run_command(root, ["dd-octo-sts", "debug", *scope_args]) + token = run_command(root, ["dd-octo-sts", "token", *scope_args], capture_output=True).stdout.strip() + if not token: + raise RuntimeError("dd-octo-sts returned an empty GitHub token") + + github_env = os.environ.copy() + github_env["GH_TOKEN"] = token + try: + return automate_update(root, version, github_env) + finally: + run_command(root, ["dd-octo-sts", "revoke", "-t", token]) + + def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Update the Agent version pinned by SSI scenarios") - parser.add_argument("version", help="Stable Agent 7 version, for example 7.82.3") + parser = argparse.ArgumentParser( + description="Publish an automated update of the Agent version pinned by SSI scenarios" + ) + parser.add_argument("--version", help="Override the latest stable Agent 7 version") parser.add_argument("--root", type=Path, default=Path.cwd(), help=argparse.SUPPRESS) return parser.parse_args(argv) def main(argv: Sequence[str] | None = None) -> int: args = parse_args(argv) - changed = update_agent_version(args.root, args.version) - print(f"Agent pins {'updated' if changed else 'already current'}: {normalize_version(args.version)}") + run_automation(args.root, args.version) return 0 From e1e37ed33d2a2a09f6bb59860ad285ab99602ac6 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:16:49 +0000 Subject: [PATCH 03/14] APMSP-3752 use nightly schedule Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 08869a4a7d2..88258e5333e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -296,7 +296,7 @@ update_agent_version: script: - python3 utils/scripts/update_agent_version.py rules: - - if: '$SCHEDULED_JOB == "update_agent_version" && $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == "main"' + - if: '$SCHEDULED_JOB == "nightly" && $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == "main"' generate_system_tests_lambda_proxy_image: image: registry.ddbuild.io/ci/libdatadog-build/ci_docker_base:100425777 From 35af3976895dc7ca99b5e072836a0467a84e9bec Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:07:33 +0000 Subject: [PATCH 04/14] APMSP-3752 fix CI bootstrap failures Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 43 +++++-- .../ci/gitlab/docker/system-tests.Dockerfile | 1 - utils/ci/gitlab/main.yml | 2 +- utils/scripts/update_agent_version.py | 107 +++++++++++------- 4 files changed, 102 insertions(+), 51 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index defb56a346d..671a0c8ecb8 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -6,6 +6,7 @@ from utils.scripts.update_agent_version import ( AUTOMATION_BRANCH, DOCKER_COMPOSE_PROVISION, + GitHubApi, INSTALLER_PROVISION, automate_update, normalize_version, @@ -14,6 +15,8 @@ update_agent_version, ) +pytestmark = pytest.mark.scenario("TEST_THE_TEST") + def write_pins(root: Path) -> None: installer = root / INSTALLER_PROVISION @@ -63,25 +66,27 @@ def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> Non def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: write_pins(tmp_path) published: list[tuple[Path, str]] = [] - monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _root, _env: "7.82.3") + github = GitHubApi("token") + monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _github: "7.82.3") monkeypatch.setattr( "utils.scripts.update_agent_version.publish_update", - lambda root, version, _env: published.append((root, version)), + lambda root, version, _github, _env: published.append((root, version)), ) - assert automate_update(tmp_path) + assert automate_update(tmp_path, github, {}) assert published == [(tmp_path, "7.82.3")] def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: write_pins(tmp_path) update_agent_version(tmp_path, "7.82.3") + github = GitHubApi("token") monkeypatch.setattr( "utils.scripts.update_agent_version.publish_update", - lambda _root, _version, _env: pytest.fail("publish should not run"), + lambda _root, _version, _github, _env: pytest.fail("publish should not run"), ) - assert not automate_update(tmp_path, "7.82.3") + assert not automate_update(tmp_path, github, {}, "7.82.3") @pytest.mark.parametrize("existing_pr", ["", "1234"]) @@ -90,6 +95,21 @@ def test_publish_update_creates_only_missing_pr( ) -> None: commands: list[list[str]] = [] + class FakeGitHubApi(GitHubApi): + def __init__(self) -> None: + super().__init__("token") + self.calls: list[tuple[str, str]] = [] + + def request(self, method: str, path: str, data: dict[str, object] | None = None) -> object: + self.calls.append((method, path)) + if method == "GET": + return [{"node_id": "PR_node_id"}] if existing_pr else [] + if path.endswith("/pulls"): + return {"node_id": "PR_node_id"} + assert path == "/graphql" + assert data is not None + return {"data": {}} + def fake_run( _root: Path, args: list[str], @@ -99,16 +119,17 @@ def fake_run( ) -> subprocess.CompletedProcess[str]: assert env == {"GH_TOKEN": "token"} commands.append(args) - stdout = existing_pr if capture_output and args[:3] == ["gh", "pr", "list"] else "" - return subprocess.CompletedProcess(args, 0, stdout=stdout) + return subprocess.CompletedProcess(args, 0, stdout="" if capture_output else None) monkeypatch.setattr("utils.scripts.update_agent_version.run_command", fake_run) - publish_update(tmp_path, "7.82.3", {"GH_TOKEN": "token"}) + github = FakeGitHubApi() + publish_update(tmp_path, "7.82.3", github, {"GH_TOKEN": "token"}) assert ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH] in commands - assert any(command[:3] == ["gh", "pr", "create"] for command in commands) is (not existing_pr) - assert commands[-1] == ["gh", "pr", "merge", AUTOMATION_BRANCH, "--auto", "--squash"] + assert not any(command[0] == "gh" for command in commands) + assert any(method == "POST" and path.endswith("/pulls") for method, path in github.calls) is (not existing_pr) + assert github.calls[-1] == ("POST", "/graphql") def test_run_automation_revokes_token_after_failure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -126,7 +147,7 @@ def fake_run( stdout = "secret-token" if capture_output else "" return subprocess.CompletedProcess(args, 0, stdout=stdout) - def fail_update(_root: Path, _version: str | None, env: dict[str, str]) -> bool: + def fail_update(_root: Path, _github: GitHubApi, env: dict[str, str], _version: str | None) -> bool: assert env["GH_TOKEN"] == "secret-token" raise RuntimeError("publish failed") diff --git a/utils/ci/gitlab/docker/system-tests.Dockerfile b/utils/ci/gitlab/docker/system-tests.Dockerfile index c2852092108..213339bea11 100644 --- a/utils/ci/gitlab/docker/system-tests.Dockerfile +++ b/utils/ci/gitlab/docker/system-tests.Dockerfile @@ -31,7 +31,6 @@ RUN clean-apt install \ ca-certificates \ curl \ git \ - gh \ python3.12 \ python3.12-venv diff --git a/utils/ci/gitlab/main.yml b/utils/ci/gitlab/main.yml index 39a86da0628..cec5958ba53 100644 --- a/utils/ci/gitlab/main.yml +++ b/utils/ci/gitlab/main.yml @@ -79,7 +79,7 @@ variables: # Tag = first 12 chars of sha256(utils/ci/gitlab/docker/system-tests.Dockerfile + requirements.txt) # Update this when either file changes: # cat utils/ci/gitlab/docker/system-tests.Dockerfile requirements.txt | sha256sum | cut -c1-12 - CI_IMAGE: "registry.ddbuild.io/system-tests/ci-runner:d39b6010c0a4" + CI_IMAGE: "registry.ddbuild.io/system-tests/ci-runner:1728376181ec" SYSTEM_TESTS_SPLIT_PIPELINE: "$[[ inputs.split_pipeline ]]" .system_tests_param_base: diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index c347860b912..49535ea9351 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -3,9 +3,12 @@ from __future__ import annotations import argparse +import json import os import re import subprocess +import urllib.parse +import urllib.request from pathlib import Path from typing import TYPE_CHECKING @@ -19,6 +22,7 @@ AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" REPOSITORY = "DataDog/system-tests" OCTO_STS_POLICY = "self.gitlab-update-agent-version" +GITHUB_API_URL = "https://api.github.com" INSTALLER_PROVISION = Path("utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml") DOCKER_COMPOSE_PROVISION = Path( @@ -78,61 +82,87 @@ def run_command( return subprocess.run(args, cwd=root, check=True, capture_output=capture_output, text=True, env=env) -def latest_agent_version(root: Path, env: Mapping[str, str] | None = None) -> str: - result = run_command( - root, - ["gh", "api", "repos/DataDog/datadog-agent/releases/latest", "--jq", ".tag_name"], - capture_output=True, - env=env, - ) - return normalize_version(result.stdout.strip()) +class GitHubApi: + def __init__(self, token: str) -> None: + self.token = token + + def request(self, method: str, path: str, data: dict[str, object] | None = None) -> object: + request = urllib.request.Request( # noqa: S310 + f"{GITHUB_API_URL}{path}", + data=json.dumps(data).encode() if data is not None else None, + method=method, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json", + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + with urllib.request.urlopen(request) as response: # noqa: S310 + return json.load(response) + + +def latest_agent_version(github: GitHubApi) -> str: + release = github.request("GET", "/repos/DataDog/datadog-agent/releases/latest") + if not isinstance(release, dict) or not isinstance(release.get("tag_name"), str): + raise TypeError("GitHub returned an invalid latest Agent release") + return normalize_version(release["tag_name"]) -def publish_update(root: Path, version: str, env: Mapping[str, str] | None = None) -> None: - run_command(root, ["gh", "auth", "setup-git"], env=env) +def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str, str]) -> None: run_command(root, ["git", "remote", "set-url", "origin", f"https://github.com/{REPOSITORY}.git"], env=env) run_command(root, ["git", "switch", "--force-create", AUTOMATION_BRANCH], env=env) run_command(root, ["git", "add", str(INSTALLER_PROVISION), str(DOCKER_COMPOSE_PROVISION)], env=env) run_command(root, ["git", "config", "user.name", "github-actions[bot]"], env=env) run_command(root, ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], env=env) + run_command( + root, + ["git", "config", "credential.helper", "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"], + env=env, + ) run_command(root, ["git", "commit", "-m", f"APMSP-3752 update Agent to {version}"], env=env) run_command(root, ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH], env=env) - existing_pr = run_command( - root, - ["gh", "pr", "list", "--head", AUTOMATION_BRANCH, "--state", "open", "--json", "number", "--jq", ".[0].number"], - capture_output=True, - env=env, - ).stdout.strip() - if not existing_pr: - run_command( - root, - [ - "gh", - "pr", - "create", - "--base", - "main", - "--head", - AUTOMATION_BRANCH, - "--title", - f"APMSP-3752 Update Agent to {version}", - "--body", - "Automated daily update of the Agent version pinned by SSI tests. " + head = urllib.parse.quote(f"DataDog:{AUTOMATION_BRANCH}", safe="") + pull_requests = github.request("GET", f"/repos/{REPOSITORY}/pulls?head={head}&state=open") + if not isinstance(pull_requests, list): + raise TypeError("GitHub returned an invalid pull request list") + if pull_requests: + pull_request = pull_requests[0] + else: + pull_request = github.request( + "POST", + f"/repos/{REPOSITORY}/pulls", + { + "base": "main", + "head": AUTOMATION_BRANCH, + "title": f"APMSP-3752 Update Agent to {version}", + "body": "Automated daily update of the Agent version pinned by SSI tests. " "The PR will merge automatically after all required checks pass.", - ], - env=env, + }, ) - run_command(root, ["gh", "pr", "merge", AUTOMATION_BRANCH, "--auto", "--squash"], env=env) + if not isinstance(pull_request, dict) or not isinstance(pull_request.get("node_id"), str): + raise TypeError("GitHub returned an invalid pull request") + auto_merge_result = github.request( + "POST", + "/graphql", + { + "query": "mutation($pullRequestId: ID!) { enablePullRequestAutoMerge(input: {" + "pullRequestId: $pullRequestId, mergeMethod: SQUASH}) { pullRequest { number } } }", + "variables": {"pullRequestId": pull_request["node_id"]}, + }, + ) + if not isinstance(auto_merge_result, dict) or auto_merge_result.get("errors"): + raise RuntimeError("GitHub failed to enable pull request auto-merge") -def automate_update(root: Path, version: str | None = None, env: Mapping[str, str] | None = None) -> bool: - normalized = normalize_version(version) if version is not None else latest_agent_version(root, env) +def automate_update(root: Path, github: GitHubApi, env: Mapping[str, str], version: str | None = None) -> bool: + normalized = normalize_version(version) if version is not None else latest_agent_version(github) if not update_agent_version(root, normalized): print(f"Agent pins already current: {normalized}") return False - publish_update(root, normalized, env) + publish_update(root, normalized, github, env) print(f"Published Agent pin update: {normalized}") return True @@ -147,8 +177,9 @@ def run_automation(root: Path, version: str | None = None) -> bool: github_env = os.environ.copy() github_env["GH_TOKEN"] = token + github = GitHubApi(token) try: - return automate_update(root, version, github_env) + return automate_update(root, github, github_env, version) finally: run_command(root, ["dd-octo-sts", "revoke", "-t", token]) From a4f5d7c04261ab79c90c9687308ec20a89b0d358 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:31:39 +0000 Subject: [PATCH 05/14] APMSP-3752 use scenario test decorator Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- tests/test_the_test/test_update_agent_version.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 671a0c8ecb8..223f2afee07 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -3,6 +3,7 @@ import pytest +from utils import scenarios from utils.scripts.update_agent_version import ( AUTOMATION_BRANCH, DOCKER_COMPOSE_PROVISION, @@ -15,8 +16,6 @@ update_agent_version, ) -pytestmark = pytest.mark.scenario("TEST_THE_TEST") - def write_pins(root: Path) -> None: installer = root / INSTALLER_PROVISION @@ -38,6 +37,7 @@ def write_pins(root: Path) -> None: ) +@scenarios.test_the_test def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: write_pins(tmp_path) @@ -49,12 +49,14 @@ def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: assert not update_agent_version(tmp_path, "7.82.3") +@scenarios.test_the_test @pytest.mark.parametrize("version", ["8.0.0", "7.82", "7.82.3-rc.1", "latest"]) def test_normalize_version_rejects_unsupported_versions(version: str) -> None: with pytest.raises(ValueError, match="Expected a stable Agent 7 version"): normalize_version(version) +@scenarios.test_the_test def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> None: write_pins(tmp_path) (tmp_path / INSTALLER_PROVISION).write_text("remote-command: |\n echo install\n") @@ -63,6 +65,7 @@ def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> Non update_agent_version(tmp_path, "7.82.3") +@scenarios.test_the_test def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: write_pins(tmp_path) published: list[tuple[Path, str]] = [] @@ -77,6 +80,7 @@ def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: p assert published == [(tmp_path, "7.82.3")] +@scenarios.test_the_test def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: write_pins(tmp_path) update_agent_version(tmp_path, "7.82.3") @@ -89,6 +93,7 @@ def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: assert not automate_update(tmp_path, github, {}, "7.82.3") +@scenarios.test_the_test @pytest.mark.parametrize("existing_pr", ["", "1234"]) def test_publish_update_creates_only_missing_pr( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, existing_pr: str @@ -132,6 +137,7 @@ def fake_run( assert github.calls[-1] == ("POST", "/graphql") +@scenarios.test_the_test def test_run_automation_revokes_token_after_failure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: commands: list[list[str]] = [] @@ -160,6 +166,7 @@ def fail_update(_root: Path, _github: GitHubApi, env: dict[str, str], _version: assert commands[-1] == ["dd-octo-sts", "revoke", "-t", "secret-token"] +@scenarios.test_the_test def test_run_automation_rejects_empty_token(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: commands: list[list[str]] = [] From fb6716aed3c7e25654f514bcbfb6e9f63c9b4790 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:36:29 +0000 Subject: [PATCH 06/14] APMSP-3752 redact token revocation errors Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 34 +++++++++++++++++++ utils/scripts/update_agent_version.py | 9 ++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 223f2afee07..71cdc39b24d 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -12,6 +12,7 @@ automate_update, normalize_version, publish_update, + revoke_token, run_automation, update_agent_version, ) @@ -166,6 +167,39 @@ def fail_update(_root: Path, _github: GitHubApi, env: dict[str, str], _version: assert commands[-1] == ["dd-octo-sts", "revoke", "-t", "secret-token"] +@scenarios.test_the_test +def test_revoke_token_hides_token_and_command_output( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + token = "secret-token" + + def fail_run( + _root: Path, + args: list[str], + *, + capture_output: bool = False, + env: dict[str, str] | None = None, + ) -> subprocess.CompletedProcess[str]: + assert capture_output + assert env is None + raise subprocess.CalledProcessError( + 1, + args, + output=f"stdout containing {token}", + stderr=f"stderr containing {token}", + ) + + monkeypatch.setattr("utils.scripts.update_agent_version.run_command", fail_run) + + with pytest.raises(RuntimeError, match="token revocation failed with exit code 1") as error: + revoke_token(tmp_path, token) + + captured = capsys.readouterr() + assert token not in str(error.value) + assert captured.out == "" + assert captured.err == "" + + @scenarios.test_the_test def test_run_automation_rejects_empty_token(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: commands: list[list[str]] = [] diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 49535ea9351..a3ca5b05de2 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -167,6 +167,13 @@ def automate_update(root: Path, github: GitHubApi, env: Mapping[str, str], versi return True +def revoke_token(root: Path, token: str) -> None: + try: + run_command(root, ["dd-octo-sts", "revoke", "-t", token], capture_output=True) + except subprocess.CalledProcessError as error: + raise RuntimeError(f"dd-octo-sts token revocation failed with exit code {error.returncode}") from None + + def run_automation(root: Path, version: str | None = None) -> bool: scope_args = ["--scope", REPOSITORY, "--policy", OCTO_STS_POLICY] run_command(root, ["dd-octo-sts", "version"]) @@ -181,7 +188,7 @@ def run_automation(root: Path, version: str | None = None) -> bool: try: return automate_update(root, github, github_env, version) finally: - run_command(root, ["dd-octo-sts", "revoke", "-t", token]) + revoke_token(root, token) def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: From af80727af3b33828a90772bda79c54219b728b52 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:55:41 +0000 Subject: [PATCH 07/14] APMSP-3752 use latest Agent release Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_the_test/test_update_agent_version.py | 18 ++++++++++-------- utils/scripts/update_agent_version.py | 12 +++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 71cdc39b24d..ecec86ac806 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -42,18 +42,20 @@ def write_pins(root: Path) -> None: def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: write_pins(tmp_path) - assert update_agent_version(tmp_path, "v7.82.3") - assert "DD_AGENT_MINOR_VERSION=82.3" in (tmp_path / INSTALLER_PROVISION).read_text() - assert "gcr.io/datadoghq/agent:7.82.3" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() + assert update_agent_version(tmp_path, "v8.0.1") + installer_content = (tmp_path / INSTALLER_PROVISION).read_text() + assert "DD_AGENT_MAJOR_VERSION=8" in installer_content + assert "DD_AGENT_MINOR_VERSION=0.1" in installer_content + assert "gcr.io/datadoghq/agent:8.0.1" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() assert "updated automatically by APMSP-3752" in (tmp_path / INSTALLER_PROVISION).read_text() - assert not update_agent_version(tmp_path, "7.82.3") + assert not update_agent_version(tmp_path, "8.0.1") @scenarios.test_the_test -@pytest.mark.parametrize("version", ["8.0.0", "7.82", "7.82.3-rc.1", "latest"]) +@pytest.mark.parametrize("version", ["7.82", "7.82.3-rc.1", "latest"]) def test_normalize_version_rejects_unsupported_versions(version: str) -> None: - with pytest.raises(ValueError, match="Expected a stable Agent 7 version"): + with pytest.raises(ValueError, match="Expected a stable Agent version"): normalize_version(version) @@ -71,14 +73,14 @@ def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: p write_pins(tmp_path) published: list[tuple[Path, str]] = [] github = GitHubApi("token") - monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _github: "7.82.3") + monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _github: "8.0.1") monkeypatch.setattr( "utils.scripts.update_agent_version.publish_update", lambda root, version, _github, _env: published.append((root, version)), ) assert automate_update(tmp_path, github, {}) - assert published == [(tmp_path, "7.82.3")] + assert published == [(tmp_path, "8.0.1")] @scenarios.test_the_test diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index a3ca5b05de2..7f3fc06e094 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -18,7 +18,7 @@ PIN_COMMENT = "Pinned Agent version, updated automatically by APMSP-3752" PIN_COMMENT_PATTERN = rf"(?:Pin to .* agent release\. APMSP-[0-9]+|{re.escape(PIN_COMMENT)})" -VERSION_PATTERN = re.compile(r"^7\.[0-9]+\.[0-9]+$") +VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" REPOSITORY = "DataDog/system-tests" OCTO_STS_POLICY = "self.gitlab-update-agent-version" @@ -33,7 +33,7 @@ def normalize_version(version: str) -> str: normalized = version.removeprefix("v") if VERSION_PATTERN.fullmatch(normalized) is None: - raise ValueError(f"Expected a stable Agent 7 version, got: {version}") + raise ValueError(f"Expected a stable Agent version, got: {version}") return normalized @@ -50,7 +50,7 @@ def _replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> boo def update_agent_version(root: Path, version: str) -> bool: normalized = normalize_version(version) - minor_version = normalized.removeprefix("7.") + major_version, minor_version = normalized.split(".", maxsplit=1) installer_changed = _replace_once( root / INSTALLER_PROVISION, @@ -59,7 +59,9 @@ def update_agent_version(root: Path, version: str) -> bool: r" export DD_AGENT_MAJOR_VERSION=[^\n]+\n" r" export DD_AGENT_MINOR_VERSION=[^\n]+$" ), - f" # {PIN_COMMENT}\n export DD_AGENT_MAJOR_VERSION=7\n export DD_AGENT_MINOR_VERSION={minor_version}", + f" # {PIN_COMMENT}\n" + f" export DD_AGENT_MAJOR_VERSION={major_version}\n" + f" export DD_AGENT_MINOR_VERSION={minor_version}", ) compose_changed = _replace_once( root / DOCKER_COMPOSE_PROVISION, @@ -195,7 +197,7 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser( description="Publish an automated update of the Agent version pinned by SSI scenarios" ) - parser.add_argument("--version", help="Override the latest stable Agent 7 version") + parser.add_argument("--version", help="Override the latest stable Agent version") parser.add_argument("--root", type=Path, default=Path.cwd(), help=argparse.SUPPRESS) return parser.parse_args(argv) From 9925c5292ab3c2bc9ca86c1601c47e8f4dfef7a1 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:06:58 +0000 Subject: [PATCH 08/14] APMSP-3752 generalize pin comments Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- tests/test_the_test/test_update_agent_version.py | 6 +++--- .../auto-inject/auto-inject_installer_manual.yml | 2 +- .../auto-inject/docker/docker-compose-agent-prod.yml | 2 +- utils/scripts/update_agent_version.py | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index ecec86ac806..1f6f5ac6361 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -23,7 +23,7 @@ def write_pins(root: Path) -> None: installer.parent.mkdir(parents=True) installer.write_text( "remote-command: |\n" - " # Pin to 7.78.4 agent release. APMSP-3059\n" + " # Pinned Agent version, updated automatically\n" " export DD_AGENT_MAJOR_VERSION=7\n" " export DD_AGENT_MINOR_VERSION=78.4\n" " echo install\n" @@ -33,7 +33,7 @@ def write_pins(root: Path) -> None: compose.write_text( "services:\n" " datadog:\n" - " # Pin to 7.78.4 agent release. APMSP-3059\n" + " # Pinned Agent version, updated automatically\n" " image: gcr.io/datadoghq/agent:7.78.4\n" ) @@ -47,7 +47,7 @@ def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: assert "DD_AGENT_MAJOR_VERSION=8" in installer_content assert "DD_AGENT_MINOR_VERSION=0.1" in installer_content assert "gcr.io/datadoghq/agent:8.0.1" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() - assert "updated automatically by APMSP-3752" in (tmp_path / INSTALLER_PROVISION).read_text() + assert "Pinned Agent version, updated automatically" in installer_content assert not update_agent_version(tmp_path, "8.0.1") diff --git a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml index 0c83daa82d3..a72e9a1cf61 100644 --- a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml +++ b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml @@ -9,7 +9,7 @@ local_path: binaries/ remote-command: | - # Pin to 7.78.4 agent release. APMSP-3059 + # Pinned Agent version, updated automatically export DD_AGENT_MAJOR_VERSION=7 export DD_AGENT_MINOR_VERSION=78.4 # Check if Docker is installed and ensure it's running diff --git a/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml b/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml index ee6c3fe3d54..8fdd8f7450a 100644 --- a/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml +++ b/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml @@ -1,7 +1,7 @@ services: datadog: container_name: dd-agent - # Pin to 7.78.4 agent release. APMSP-3059 + # Pinned Agent version, updated automatically image: gcr.io/datadoghq/agent:7.78.4 environment: - DD_API_KEY=${DD_API_KEY} diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 7f3fc06e094..2685ae10639 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -16,8 +16,7 @@ from collections.abc import Mapping, Sequence -PIN_COMMENT = "Pinned Agent version, updated automatically by APMSP-3752" -PIN_COMMENT_PATTERN = rf"(?:Pin to .* agent release\. APMSP-[0-9]+|{re.escape(PIN_COMMENT)})" +PIN_COMMENT = "Pinned Agent version, updated automatically" VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" REPOSITORY = "DataDog/system-tests" @@ -55,7 +54,7 @@ def update_agent_version(root: Path, version: str) -> bool: installer_changed = _replace_once( root / INSTALLER_PROVISION, re.compile( - rf"(?m)^ # {PIN_COMMENT_PATTERN}\n" + rf"(?m)^ # {re.escape(PIN_COMMENT)}\n" r" export DD_AGENT_MAJOR_VERSION=[^\n]+\n" r" export DD_AGENT_MINOR_VERSION=[^\n]+$" ), @@ -66,7 +65,7 @@ def update_agent_version(root: Path, version: str) -> bool: compose_changed = _replace_once( root / DOCKER_COMPOSE_PROVISION, re.compile( - rf"(?m)^ # {PIN_COMMENT_PATTERN}\n" + rf"(?m)^ # {re.escape(PIN_COMMENT)}\n" r" image: gcr\.io/datadoghq/agent:[^\n]+$" ), f" # {PIN_COMMENT}\n image: gcr.io/datadoghq/agent:{normalized}", From 4795d2d3d11ffc9050a1a14a3668d4f1bfbe5137 Mon Sep 17 00:00:00 2001 From: Nicolas Catoni Date: Fri, 18 Sep 2026 17:01:33 +0200 Subject: [PATCH 09/14] Automate SSI Agent updates (#7765) Co-authored-by: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 19 ++++++++++++++- utils/scripts/update_agent_version.py | 24 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 1f6f5ac6361..db0d526a77c 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -26,7 +26,12 @@ def write_pins(root: Path) -> None: " # Pinned Agent version, updated automatically\n" " export DD_AGENT_MAJOR_VERSION=7\n" " export DD_AGENT_MINOR_VERSION=78.4\n" - " echo install\n" + ' if [ -f "install_script_agent7.sh" ]; then\n' + " cp install_script_agent7.sh install_script.sh\n" + " else\n" + ' # bash -c "$(curl -L https://example.test/install_script_agent7.sh)"\n' + " curl -L https://example.test/install_script_agent7.sh -o install_script.sh\n" + " fi\n" ) compose = root / DOCKER_COMPOSE_PROVISION compose.parent.mkdir(parents=True) @@ -46,6 +51,8 @@ def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: installer_content = (tmp_path / INSTALLER_PROVISION).read_text() assert "DD_AGENT_MAJOR_VERSION=8" in installer_content assert "DD_AGENT_MINOR_VERSION=0.1" in installer_content + assert installer_content.count("install_script_agent8.sh") == 4 + assert "install_script_agent7.sh" not in installer_content assert "gcr.io/datadoghq/agent:8.0.1" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() assert "Pinned Agent version, updated automatically" in installer_content @@ -68,6 +75,16 @@ def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> Non update_agent_version(tmp_path, "7.82.3") +@scenarios.test_the_test +def test_update_agent_version_fails_when_an_install_script_reference_is_missing(tmp_path: Path) -> None: + write_pins(tmp_path) + installer = tmp_path / INSTALLER_PROVISION + installer.write_text(installer.read_text().replace("install_script_agent7.sh", "install_script.sh", 1)) + + with pytest.raises(RuntimeError, match="exactly 4 Agent install script references"): + update_agent_version(tmp_path, "8.0.1") + + @scenarios.test_the_test def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: write_pins(tmp_path) diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 2685ae10639..00ac4416d61 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -18,6 +18,8 @@ PIN_COMMENT = "Pinned Agent version, updated automatically" VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") +INSTALL_SCRIPT_REFERENCE_PATTERN = re.compile(r"install_script_agent[0-9]+\.sh") +INSTALL_SCRIPT_REFERENCE_COUNT = 4 AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" REPOSITORY = "DataDog/system-tests" OCTO_STS_POLICY = "self.gitlab-update-agent-version" @@ -47,12 +49,29 @@ def _replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> boo return True +def _replace_install_script_references(path: Path, major_version: str) -> bool: + content = path.read_text() + updated, replacement_count = INSTALL_SCRIPT_REFERENCE_PATTERN.subn( + f"install_script_agent{major_version}.sh", content + ) + if replacement_count != INSTALL_SCRIPT_REFERENCE_COUNT: + raise RuntimeError( + f"Expected exactly {INSTALL_SCRIPT_REFERENCE_COUNT} Agent install script references in {path}, " + f"found {replacement_count}" + ) + if updated == content: + return False + path.write_text(updated) + return True + + def update_agent_version(root: Path, version: str) -> bool: normalized = normalize_version(version) major_version, minor_version = normalized.split(".", maxsplit=1) + installer_path = root / INSTALLER_PROVISION installer_changed = _replace_once( - root / INSTALLER_PROVISION, + installer_path, re.compile( rf"(?m)^ # {re.escape(PIN_COMMENT)}\n" r" export DD_AGENT_MAJOR_VERSION=[^\n]+\n" @@ -62,6 +81,7 @@ def update_agent_version(root: Path, version: str) -> bool: f" export DD_AGENT_MAJOR_VERSION={major_version}\n" f" export DD_AGENT_MINOR_VERSION={minor_version}", ) + install_script_changed = _replace_install_script_references(installer_path, major_version) compose_changed = _replace_once( root / DOCKER_COMPOSE_PROVISION, re.compile( @@ -70,7 +90,7 @@ def update_agent_version(root: Path, version: str) -> bool: ), f" # {PIN_COMMENT}\n image: gcr.io/datadoghq/agent:{normalized}", ) - return installer_changed or compose_changed + return installer_changed or install_script_changed or compose_changed def run_command( From 358387bd7f3afd1a8a3212753aecafb2fa517b44 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:50:40 +0000 Subject: [PATCH 10/14] APMSP-3752 centralize Agent version Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 133 ++++++++++++------ .../provisions/auto-inject/agent.lock | 2 + .../auto-inject_installer_manual.yml | 17 ++- .../docker/docker-compose-agent-prod.yml | 3 +- .../autoinstall/execute_install_script.sh | 14 +- .../auto-inject_container_script.yml | 4 +- .../provision.yml | 3 +- .../auto-inject_host_script.yml | 2 + .../provision.yml | 9 +- .../common/create_and_run_app_container.sh | 18 ++- .../create_and_run_app_multicontainer.sh | 6 + .../weblogs/common/pull_agent_image.sh | 21 ++- ...t-app-java_docker_compose_run_buildpack.sh | 9 +- utils/scripts/update_agent_version.py | 53 +------ 14 files changed, 182 insertions(+), 112 deletions(-) create mode 100644 utils/build/virtual_machine/provisions/auto-inject/agent.lock diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index db0d526a77c..ae819222189 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -1,3 +1,4 @@ +import os import subprocess from pathlib import Path @@ -5,10 +6,9 @@ from utils import scenarios from utils.scripts.update_agent_version import ( + AGENT_VERSION_LOCK, AUTOMATION_BRANCH, - DOCKER_COMPOSE_PROVISION, GitHubApi, - INSTALLER_PROVISION, automate_update, normalize_version, publish_update, @@ -18,43 +18,20 @@ ) -def write_pins(root: Path) -> None: - installer = root / INSTALLER_PROVISION - installer.parent.mkdir(parents=True) - installer.write_text( - "remote-command: |\n" - " # Pinned Agent version, updated automatically\n" - " export DD_AGENT_MAJOR_VERSION=7\n" - " export DD_AGENT_MINOR_VERSION=78.4\n" - ' if [ -f "install_script_agent7.sh" ]; then\n' - " cp install_script_agent7.sh install_script.sh\n" - " else\n" - ' # bash -c "$(curl -L https://example.test/install_script_agent7.sh)"\n' - " curl -L https://example.test/install_script_agent7.sh -o install_script.sh\n" - " fi\n" - ) - compose = root / DOCKER_COMPOSE_PROVISION - compose.parent.mkdir(parents=True) - compose.write_text( - "services:\n" - " datadog:\n" - " # Pinned Agent version, updated automatically\n" - " image: gcr.io/datadoghq/agent:7.78.4\n" - ) +def write_lock(root: Path) -> None: + lock = root / AGENT_VERSION_LOCK + lock.parent.mkdir(parents=True) + lock.write_text("# Pinned Agent version, updated automatically\nDD_AGENT_VERSION=7.78.4\n") @scenarios.test_the_test -def test_update_agent_version_updates_both_ssi_pins(tmp_path: Path) -> None: - write_pins(tmp_path) +def test_update_agent_version_updates_lock(tmp_path: Path) -> None: + write_lock(tmp_path) assert update_agent_version(tmp_path, "v8.0.1") - installer_content = (tmp_path / INSTALLER_PROVISION).read_text() - assert "DD_AGENT_MAJOR_VERSION=8" in installer_content - assert "DD_AGENT_MINOR_VERSION=0.1" in installer_content - assert installer_content.count("install_script_agent8.sh") == 4 - assert "install_script_agent7.sh" not in installer_content - assert "gcr.io/datadoghq/agent:8.0.1" in (tmp_path / DOCKER_COMPOSE_PROVISION).read_text() - assert "Pinned Agent version, updated automatically" in installer_content + assert (tmp_path / AGENT_VERSION_LOCK).read_text() == ( + "# Pinned Agent version, updated automatically\nDD_AGENT_VERSION=8.0.1\n" + ) assert not update_agent_version(tmp_path, "8.0.1") @@ -68,26 +45,93 @@ def test_normalize_version_rejects_unsupported_versions(version: str) -> None: @scenarios.test_the_test def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> None: - write_pins(tmp_path) - (tmp_path / INSTALLER_PROVISION).write_text("remote-command: |\n echo install\n") + write_lock(tmp_path) + (tmp_path / AGENT_VERSION_LOCK).write_text("# Missing Agent version\n") with pytest.raises(RuntimeError, match="exactly one Agent version pin"): update_agent_version(tmp_path, "7.82.3") @scenarios.test_the_test -def test_update_agent_version_fails_when_an_install_script_reference_is_missing(tmp_path: Path) -> None: - write_pins(tmp_path) - installer = tmp_path / INSTALLER_PROVISION - installer.write_text(installer.read_text().replace("install_script_agent7.sh", "install_script.sh", 1)) +def test_agent_version_consumers_load_lock() -> None: + root = Path(__file__).resolve().parents[2] + auto_inject = root / "utils/build/virtual_machine/provisions/auto-inject" + + lock_lines = (auto_inject / "agent.lock").read_text().splitlines() + assert lock_lines[0] == "# Pinned Agent version, updated automatically" + assert len(lock_lines) == 2 + locked_version = lock_lines[1].removeprefix("DD_AGENT_VERSION=") + assert lock_lines[1] == f"DD_AGENT_VERSION={normalize_version(locked_version)}" + assert "agent:${DD_AGENT_VERSION}" in (auto_inject / "docker/docker-compose-agent-prod.yml").read_text() + + compose_path = "utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml" + lock_path = "utils/build/virtual_machine/provisions/auto-inject/agent.lock" + provision_root = root / "utils/build/virtual_machine" + compose_copy_points = [path for path in provision_root.rglob("*.yml") if compose_path in path.read_text()] + assert compose_copy_points + for copy_point in compose_copy_points: + assert lock_path in copy_point.read_text() + + lock_consumers = ( + auto_inject / "auto-inject_installer_manual.yml", + auto_inject / "repositories/autoinstall/execute_install_script.sh", + root / "utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml", + root / "utils/build/virtual_machine/weblogs/common/pull_agent_image.sh", + ) + for consumer in lock_consumers: + content = consumer.read_text() + assert "agent.lock" in content + assert "DD_AGENT_VERSION" in content + assert "install_script_agent7.sh" not in content + + compose_launchers = ( + root / "utils/build/virtual_machine/weblogs/common/create_and_run_app_container.sh", + root / "utils/build/virtual_machine/weblogs/common/create_and_run_app_multicontainer.sh", + root / "utils/build/virtual_machine/weblogs/java/test-app-java-buildpack/" + "test-app-java_docker_compose_run_buildpack.sh", + ) + for launcher in compose_launchers: + content = launcher.read_text() + assert 'AGENT_LOCK="agent.lock"' in content + assert '. "./${AGENT_LOCK}"' in content + + +@scenarios.test_the_test +def test_pull_agent_image_resolves_version_from_lock(tmp_path: Path) -> None: + root = Path(__file__).resolve().parents[2] + (tmp_path / "agent.lock").write_text("DD_AGENT_VERSION=8.0.1\n") + (tmp_path / "docker-compose-agent-prod.yml").write_text( + "services:\n datadog:\n image: gcr.io/datadoghq/agent:${DD_AGENT_VERSION}\n" + ) + + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + call_log = tmp_path / "sudo.log" + fake_sudo = fake_bin / "sudo" + fake_sudo.write_text('#!/bin/sh\nprintf "%s\\n" "$*" >> "$CALL_LOG"\n') + fake_sudo.chmod(0o755) + env = os.environ.copy() + env.update( + { + "CALL_LOG": str(call_log), + "DOCKER_PULL_MAX_RETRIES": "1", + "PATH": f"{fake_bin}:{env['PATH']}", + } + ) + + subprocess.run( + ["bash", str(root / "utils/build/virtual_machine/weblogs/common/pull_agent_image.sh")], + cwd=tmp_path, + check=True, + env=env, + ) - with pytest.raises(RuntimeError, match="exactly 4 Agent install script references"): - update_agent_version(tmp_path, "8.0.1") + assert call_log.read_text() == "docker pull gcr.io/datadoghq/agent:8.0.1\n" @scenarios.test_the_test def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: - write_pins(tmp_path) + write_lock(tmp_path) published: list[tuple[Path, str]] = [] github = GitHubApi("token") monkeypatch.setattr("utils.scripts.update_agent_version.latest_agent_version", lambda _github: "8.0.1") @@ -102,7 +146,7 @@ def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: p @scenarios.test_the_test def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: - write_pins(tmp_path) + write_lock(tmp_path) update_agent_version(tmp_path, "7.82.3") github = GitHubApi("token") monkeypatch.setattr( @@ -151,6 +195,7 @@ def fake_run( github = FakeGitHubApi() publish_update(tmp_path, "7.82.3", github, {"GH_TOKEN": "token"}) + assert ["git", "add", str(AGENT_VERSION_LOCK)] in commands assert ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH] in commands assert not any(command[0] == "gh" for command in commands) assert any(method == "POST" and path.endswith("/pulls") for method, path in github.calls) is (not existing_pr) diff --git a/utils/build/virtual_machine/provisions/auto-inject/agent.lock b/utils/build/virtual_machine/provisions/auto-inject/agent.lock new file mode 100644 index 00000000000..309976b046d --- /dev/null +++ b/utils/build/virtual_machine/provisions/auto-inject/agent.lock @@ -0,0 +1,2 @@ +# Pinned Agent version, updated automatically +DD_AGENT_VERSION=7.78.4 diff --git a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml index a72e9a1cf61..2daae9a3ad0 100644 --- a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml +++ b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml @@ -5,13 +5,16 @@ local_path: utils/build/virtual_machine/provisions/auto-inject/tracer_debug/debug_config.yaml - name: copy-docker-config local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker_config.yaml + - name: copy-agent-version-lock + local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock - name: copy-binaries local_path: binaries/ remote-command: | - # Pinned Agent version, updated automatically - export DD_AGENT_MAJOR_VERSION=7 - export DD_AGENT_MINOR_VERSION=78.4 + . ./agent.lock + export DD_AGENT_MAJOR_VERSION="${DD_AGENT_VERSION%%.*}" + export DD_AGENT_MINOR_VERSION="${DD_AGENT_VERSION#*.}" + AGENT_INSTALL_SCRIPT="install_script_agent${DD_AGENT_MAJOR_VERSION}.sh" # Check if Docker is installed and ensure it's running if command -v docker >/dev/null 2>&1; then echo "Docker is installed, ensuring service is enabled and running..." @@ -118,21 +121,21 @@ sudo cat /etc/datadog-agent/application_monitoring.yaml || true # Check if install script exists locally in binaries folder - if [ -f "install_script_agent7.sh" ]; then + if [ -f "${AGENT_INSTALL_SCRIPT}" ]; then echo "*** Execute installation script from provided binaries ***" - cp install_script_agent7.sh install_script.sh + cp "${AGENT_INSTALL_SCRIPT}" install_script.sh chmod +x install_script.sh else echo "Download installation script from S3" # dns install.datadoghq.com or the network interfaces are no very steady, so we need to retry # On the old machines the curl command doesn't support the --retry-connrefused flag, we implement the retry policy in a loop - # standard exec: DD_REPO_URL=${DD_injection_repo_url} bash -c "$(curl -L https://dd-agent.s3.amazonaws.com/scripts/install_script_agent7.sh)" + # standard exec: DD_REPO_URL=${DD_injection_repo_url} bash -c "$(curl -L https://dd-agent.s3.amazonaws.com/scripts/${AGENT_INSTALL_SCRIPT})" MAX_RETRIES=5 RETRY_DELAY=5 COUNTER=0 while [ $COUNTER -lt $MAX_RETRIES ]; do - curl -L https://dd-agent.s3.amazonaws.com/scripts/install_script_agent7.sh -o install_script.sh && break + curl -L "https://dd-agent.s3.amazonaws.com/scripts/${AGENT_INSTALL_SCRIPT}" -o install_script.sh && break echo "Retrying in $RETRY_DELAY seconds..." sleep $RETRY_DELAY ((COUNTER++)) diff --git a/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml b/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml index 8fdd8f7450a..e7637cb8097 100644 --- a/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml +++ b/utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml @@ -1,8 +1,7 @@ services: datadog: container_name: dd-agent - # Pinned Agent version, updated automatically - image: gcr.io/datadoghq/agent:7.78.4 + image: gcr.io/datadoghq/agent:${DD_AGENT_VERSION} environment: - DD_API_KEY=${DD_API_KEY} - DD_SITE=datadoghq.com diff --git a/utils/build/virtual_machine/provisions/auto-inject/repositories/autoinstall/execute_install_script.sh b/utils/build/virtual_machine/provisions/auto-inject/repositories/autoinstall/execute_install_script.sh index 193949ffa3c..fcfd3a40d5e 100755 --- a/utils/build/virtual_machine/provisions/auto-inject/repositories/autoinstall/execute_install_script.sh +++ b/utils/build/virtual_machine/provisions/auto-inject/repositories/autoinstall/execute_install_script.sh @@ -2,8 +2,14 @@ # This script is needed only for this reason: https://datadoghq.atlassian.net/browse/AP-2165 -if [ -z "$INSTALLER_URL" ]; then - INSTALLER_URL="https://dd-agent.s3.amazonaws.com/scripts/install_script_agent7.sh" +# shellcheck source=/dev/null +. ./agent.lock +export DD_AGENT_MAJOR_VERSION="${DD_AGENT_VERSION%%.*}" +export DD_AGENT_MINOR_VERSION="${DD_AGENT_VERSION#*.}" +AGENT_INSTALL_SCRIPT="install_script_agent${DD_AGENT_MAJOR_VERSION}.sh" + +if [ -z "${INSTALLER_URL:-}" ]; then + INSTALLER_URL="https://dd-agent.s3.amazonaws.com/scripts/${AGENT_INSTALL_SCRIPT}" fi if [ "$DD_APM_INSTRUMENTATION_ENABLED" == "docker" ]; then @@ -62,9 +68,9 @@ fi sudo sh -c "sudo mkdir -p /etc/datadog-agent && printf \"api_key: ${DD_API_KEY}\nsite: datadoghq.com\n\" > /etc/datadog-agent/datadog.yaml" -if [ -f "install_script_agent7.sh" ]; then +if [ -f "${AGENT_INSTALL_SCRIPT}" ]; then echo "*** Execute installation script from provided binaries ***" - cp install_script_agent7.sh install_script.sh + cp "${AGENT_INSTALL_SCRIPT}" install_script.sh chmod +x install_script.sh else echo "Download installation script from S3" diff --git a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml index 542a81e216e..0ddda5e738d 100644 --- a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml +++ b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml @@ -9,6 +9,8 @@ local_path: utils/build/virtual_machine/provisions/auto-inject/tracer_debug/debug_config.yaml - name: copy-agent-docker-compose local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml + - name: copy-agent-version-lock + local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock - name: copy-binaries local_path: binaries/ remote-command: | @@ -25,4 +27,4 @@ printf "DD_APM_RECEIVER_SOCKET=/opt/datadog/apm/inject/run/apm.socket\nDD_DOGSTATSD_SOCKET=/opt/datadog/apm/inject/run/dsd.socket\nDD_USE_DOGSTATSD=true\n" | sudo tee /var/run/datadog-installer/environment DD_APM_INSTRUMENTATION_ENABLED=docker bash execute_install_script.sh sudo cp docker_config.yaml /etc/datadog-agent/inject/docker_config.yaml - sudo cp debug_config.yaml /etc/datadog-agent/inject/debug_config.yaml \ No newline at end of file + sudo cp debug_config.yaml /etc/datadog-agent/inject/debug_config.yaml diff --git a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml index b605ffb6134..103f8b79a5a 100644 --- a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml @@ -41,9 +41,10 @@ install-agent: copy_files: - name: copy-agent-docker-compose local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml + - name: copy-agent-version-lock + local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock remote-command: cat docker-compose-agent-prod.yml autoinjection_install_script: install: !include utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml - diff --git a/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml b/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml index 59d1c5376bf..3dc63d7e744 100644 --- a/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml +++ b/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml @@ -5,6 +5,8 @@ local_path: utils/build/virtual_machine/provisions/auto-inject/repositories/autoinstall/execute_install_script.sh - name: copy-tracer-debug-config local_path: utils/build/virtual_machine/provisions/auto-inject/tracer_debug/debug_config.yaml + - name: copy-agent-version-lock + local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock - name: copy-binaries local_path: binaries/ remote-command: | diff --git a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml index 0f4b2e29904..f1e8bf8a31d 100644 --- a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml @@ -24,8 +24,15 @@ init-config: install-agent: install: - os_type: linux + copy_files: + - name: copy-agent-version-lock + local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock remote-command: | - REPO_URL=$DD_agent_repo_url DD_AGENT_DIST_CHANNEL=$DD_agent_dist_channel DD_AGENT_MAJOR_VERSION=$DD_agent_major_version bash -c "$(curl -L https://dd-agent.s3.amazonaws.com/scripts/install_script_agent7.sh)" + . ./agent.lock + export DD_AGENT_MAJOR_VERSION="${DD_AGENT_VERSION%%.*}" + export DD_AGENT_MINOR_VERSION="${DD_AGENT_VERSION#*.}" + AGENT_INSTALL_SCRIPT="install_script_agent${DD_AGENT_MAJOR_VERSION}.sh" + REPO_URL=$DD_agent_repo_url DD_AGENT_DIST_CHANNEL=$DD_agent_dist_channel bash -c "$(curl -L https://dd-agent.s3.amazonaws.com/scripts/${AGENT_INSTALL_SCRIPT})" autoinjection_install_script: install: !include utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml diff --git a/utils/build/virtual_machine/weblogs/common/create_and_run_app_container.sh b/utils/build/virtual_machine/weblogs/common/create_and_run_app_container.sh index 11e4a53c9be..ac6f861727f 100755 --- a/utils/build/virtual_machine/weblogs/common/create_and_run_app_container.sh +++ b/utils/build/virtual_machine/weblogs/common/create_and_run_app_container.sh @@ -14,6 +14,7 @@ set -e readonly DIAGNOSTICS_LOG="${HOME}/dd-agent-diagnostics.log" readonly SCRIPT_MARKER="create_and_run_app_container.sh diagnostics-v3" readonly AGENT_COMPOSE="docker-compose-agent-prod.yml" +readonly AGENT_LOCK="agent.lock" readonly WEBLOG_LOG_DIR="/var/log/datadog_weblog" readonly DOCKER_BUILD_MAX_RETRIES=3 PULL_AGENT_IMAGE_SCRIPT="$(dirname "$0")/pull_agent_image.sh" @@ -24,6 +25,15 @@ agent_is_enabled() { [ -f "${AGENT_COMPOSE}" ] } +load_agent_version() { + agent_is_enabled || return 0 + + set -a + # shellcheck source=/dev/null + . "./${AGENT_LOCK}" + set +a +} + # --------------------------------------------------------------------------- # Diagnostics (collected on every run, and on failure, to help debugging in CI) # --------------------------------------------------------------------------- @@ -39,7 +49,7 @@ dump_dd_agent_diagnostics() { { echo "..:: DD-AGENT DIAGNOSTICS (${SCRIPT_MARKER}) ::.." date -u '+%Y-%m-%dT%H:%M:%SZ' - sudo docker-compose -f "${AGENT_COMPOSE}" ps 2>&1 || true + sudo -E docker-compose -f "${AGENT_COMPOSE}" ps 2>&1 || true if sudo docker inspect dd-agent >/dev/null 2>&1; then echo "..:: DD-AGENT HEALTH ::.." sudo docker inspect dd-agent --format '{{json .State.Health}}' 2>&1 || true @@ -50,7 +60,7 @@ dump_dd_agent_diagnostics() { sudo docker ps -a 2>&1 || true fi echo "..:: DD-AGENT LOGS (docker-compose logs) ::.." - sudo docker-compose -f "${AGENT_COMPOSE}" logs --no-color datadog 2>&1 || true + sudo -E docker-compose -f "${AGENT_COMPOSE}" logs --no-color datadog 2>&1 || true } 2>&1 | tee -a "${DIAGNOSTICS_LOG}" # Mirror diagnostics into the log folder downloaded by the test harness. @@ -152,7 +162,7 @@ print_services_output() { sudo docker-compose ps if agent_is_enabled; then echo "..:: DATADOG AGENT OUTPUT ::.." - sudo docker-compose -f "${AGENT_COMPOSE}" logs datadog + sudo -E docker-compose -f "${AGENT_COMPOSE}" logs datadog fi echo "..:: WEBLOG APP OUTPUT ::.." sudo docker-compose logs @@ -163,6 +173,8 @@ print_services_output() { # Main # --------------------------------------------------------------------------- main() { + load_agent_version + # Always dump agent diagnostics on failure too (trap is deduplicated against the success dump). trap _on_exit EXIT diff --git a/utils/build/virtual_machine/weblogs/common/create_and_run_app_multicontainer.sh b/utils/build/virtual_machine/weblogs/common/create_and_run_app_multicontainer.sh index 2b6170e2d9a..3c756c998a3 100755 --- a/utils/build/virtual_machine/weblogs/common/create_and_run_app_multicontainer.sh +++ b/utils/build/virtual_machine/weblogs/common/create_and_run_app_multicontainer.sh @@ -4,6 +4,7 @@ set -e readonly AGENT_COMPOSE="docker-compose-agent-prod.yml" +readonly AGENT_LOCK="agent.lock" PULL_AGENT_IMAGE_SCRIPT="$(dirname "$0")/pull_agent_image.sh" readonly PULL_AGENT_IMAGE_SCRIPT @@ -20,6 +21,11 @@ sudo rm -rf system-tests || true sudo docker-compose -f docker-compose.yml build --parallel if [ -f "${AGENT_COMPOSE}" ]; then + set -a + # shellcheck source=/dev/null + . "./${AGENT_LOCK}" + set +a + # Agent may be installed in a different way. Pull with retries before compose up # so GCR rate limits / timeouts do not fail the provision on the first attempt. bash "${PULL_AGENT_IMAGE_SCRIPT}" diff --git a/utils/build/virtual_machine/weblogs/common/pull_agent_image.sh b/utils/build/virtual_machine/weblogs/common/pull_agent_image.sh index f472d89d7fa..5e842b123e0 100644 --- a/utils/build/virtual_machine/weblogs/common/pull_agent_image.sh +++ b/utils/build/virtual_machine/weblogs/common/pull_agent_image.sh @@ -14,6 +14,7 @@ fi set -e readonly AGENT_COMPOSE="${1:-${AGENT_COMPOSE:-docker-compose-agent-prod.yml}}" +readonly AGENT_LOCK="${AGENT_LOCK:-agent.lock}" readonly DOCKER_PULL_MAX_RETRIES="${DOCKER_PULL_MAX_RETRIES:-3}" if [ ! -f "${AGENT_COMPOSE}" ]; then @@ -21,8 +22,26 @@ if [ ! -f "${AGENT_COMPOSE}" ]; then exit 0 fi +if [ ! -f "${AGENT_LOCK}" ]; then + echo "Agent version lock ${AGENT_LOCK} not found" + exit 1 +fi + +# shellcheck source=/dev/null +. "${AGENT_LOCK}" +if [ -z "${DD_AGENT_VERSION:-}" ]; then + echo "DD_AGENT_VERSION is missing from ${AGENT_LOCK}" + exit 1 +fi + agent_compose_image() { - awk '/^[[:space:]]*image:[[:space:]]*/ { print $2; exit }' "${AGENT_COMPOSE}" + awk -v version="${DD_AGENT_VERSION}" ' + /^[[:space:]]*image:[[:space:]]*/ { + gsub(/\$\{DD_AGENT_VERSION\}/, version, $2) + print $2 + exit + } + ' "${AGENT_COMPOSE}" } pull_docker_image() { diff --git a/utils/build/virtual_machine/weblogs/java/test-app-java-buildpack/test-app-java_docker_compose_run_buildpack.sh b/utils/build/virtual_machine/weblogs/java/test-app-java-buildpack/test-app-java_docker_compose_run_buildpack.sh index 8af81e92841..9d7134bd871 100755 --- a/utils/build/virtual_machine/weblogs/java/test-app-java-buildpack/test-app-java_docker_compose_run_buildpack.sh +++ b/utils/build/virtual_machine/weblogs/java/test-app-java-buildpack/test-app-java_docker_compose_run_buildpack.sh @@ -3,6 +3,8 @@ set -e +readonly AGENT_LOCK="agent.lock" + # Function to retry commands up to 3 times retry_command() { local max_attempts=3 @@ -52,6 +54,11 @@ retry_command "sudo ./gradlew -PdockerImageRepo=system-tests/local -PdockerImage echo "**************** RUN SERVICES*****************" if [ -f docker-compose-agent-prod.yml ]; then + set -a + # shellcheck source=/dev/null + . "./${AGENT_LOCK}" + set +a + # Agent may be installed in a different way. Pull with retries before compose # up so GCR rate limits / timeouts do not fail the provision on the first attempt. bash "$(dirname "$0")/pull_agent_image.sh" @@ -73,7 +80,7 @@ echo "**************** RUNNING DOCKER SERVICES *****************" sudo docker-compose ps if [ -f docker-compose-agent-prod.yml ]; then echo "**************** DATADOG AGENT OUTPUT ********************" - sudo docker-compose -f docker-compose-agent-prod.yml logs datadog + sudo -E docker-compose -f docker-compose-agent-prod.yml logs datadog fi echo "**************** WEBLOG APP OUTPUT********************" sudo docker-compose logs diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 00ac4416d61..dec9d2fed2a 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -16,19 +16,13 @@ from collections.abc import Mapping, Sequence -PIN_COMMENT = "Pinned Agent version, updated automatically" VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") -INSTALL_SCRIPT_REFERENCE_PATTERN = re.compile(r"install_script_agent[0-9]+\.sh") -INSTALL_SCRIPT_REFERENCE_COUNT = 4 AUTOMATION_BRANCH = "apmsp-3752/update-agent-version" REPOSITORY = "DataDog/system-tests" OCTO_STS_POLICY = "self.gitlab-update-agent-version" GITHUB_API_URL = "https://api.github.com" -INSTALLER_PROVISION = Path("utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml") -DOCKER_COMPOSE_PROVISION = Path( - "utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml" -) +AGENT_VERSION_LOCK = Path("utils/build/virtual_machine/provisions/auto-inject/agent.lock") def normalize_version(version: str) -> str: @@ -49,48 +43,13 @@ def _replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> boo return True -def _replace_install_script_references(path: Path, major_version: str) -> bool: - content = path.read_text() - updated, replacement_count = INSTALL_SCRIPT_REFERENCE_PATTERN.subn( - f"install_script_agent{major_version}.sh", content - ) - if replacement_count != INSTALL_SCRIPT_REFERENCE_COUNT: - raise RuntimeError( - f"Expected exactly {INSTALL_SCRIPT_REFERENCE_COUNT} Agent install script references in {path}, " - f"found {replacement_count}" - ) - if updated == content: - return False - path.write_text(updated) - return True - - def update_agent_version(root: Path, version: str) -> bool: normalized = normalize_version(version) - major_version, minor_version = normalized.split(".", maxsplit=1) - installer_path = root / INSTALLER_PROVISION - - installer_changed = _replace_once( - installer_path, - re.compile( - rf"(?m)^ # {re.escape(PIN_COMMENT)}\n" - r" export DD_AGENT_MAJOR_VERSION=[^\n]+\n" - r" export DD_AGENT_MINOR_VERSION=[^\n]+$" - ), - f" # {PIN_COMMENT}\n" - f" export DD_AGENT_MAJOR_VERSION={major_version}\n" - f" export DD_AGENT_MINOR_VERSION={minor_version}", - ) - install_script_changed = _replace_install_script_references(installer_path, major_version) - compose_changed = _replace_once( - root / DOCKER_COMPOSE_PROVISION, - re.compile( - rf"(?m)^ # {re.escape(PIN_COMMENT)}\n" - r" image: gcr\.io/datadoghq/agent:[^\n]+$" - ), - f" # {PIN_COMMENT}\n image: gcr.io/datadoghq/agent:{normalized}", + return _replace_once( + root / AGENT_VERSION_LOCK, + re.compile(r"(?m)^DD_AGENT_VERSION=[^\n]+$"), + f"DD_AGENT_VERSION={normalized}", ) - return installer_changed or install_script_changed or compose_changed def run_command( @@ -133,7 +92,7 @@ def latest_agent_version(github: GitHubApi) -> str: def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str, str]) -> None: run_command(root, ["git", "remote", "set-url", "origin", f"https://github.com/{REPOSITORY}.git"], env=env) run_command(root, ["git", "switch", "--force-create", AUTOMATION_BRANCH], env=env) - run_command(root, ["git", "add", str(INSTALLER_PROVISION), str(DOCKER_COMPOSE_PROVISION)], env=env) + run_command(root, ["git", "add", str(AGENT_VERSION_LOCK)], env=env) run_command(root, ["git", "config", "user.name", "github-actions[bot]"], env=env) run_command(root, ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], env=env) run_command( From d00a37f27173f5b66255f3927cffe48568542730 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 21 Sep 2026 11:37:47 +0000 Subject: [PATCH 11/14] APMSP-3752 simplify lock updates Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 10 +-------- utils/scripts/update_agent_version.py | 22 +++++-------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index ae819222189..bf92cbd1387 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -27,6 +27,7 @@ def write_lock(root: Path) -> None: @scenarios.test_the_test def test_update_agent_version_updates_lock(tmp_path: Path) -> None: write_lock(tmp_path) + (tmp_path / AGENT_VERSION_LOCK).write_text("This content is replaced completely.\n") assert update_agent_version(tmp_path, "v8.0.1") assert (tmp_path / AGENT_VERSION_LOCK).read_text() == ( @@ -43,15 +44,6 @@ def test_normalize_version_rejects_unsupported_versions(version: str) -> None: normalize_version(version) -@scenarios.test_the_test -def test_update_agent_version_fails_when_a_pin_is_missing(tmp_path: Path) -> None: - write_lock(tmp_path) - (tmp_path / AGENT_VERSION_LOCK).write_text("# Missing Agent version\n") - - with pytest.raises(RuntimeError, match="exactly one Agent version pin"): - update_agent_version(tmp_path, "7.82.3") - - @scenarios.test_the_test def test_agent_version_consumers_load_lock() -> None: root = Path(__file__).resolve().parents[2] diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index dec9d2fed2a..1942b836e87 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -32,24 +32,14 @@ def normalize_version(version: str) -> str: return normalized -def _replace_once(path: Path, pattern: re.Pattern[str], replacement: str) -> bool: - content = path.read_text() - updated, replacement_count = pattern.subn(replacement, content) - if replacement_count != 1: - raise RuntimeError(f"Expected exactly one Agent version pin in {path}, found {replacement_count}") - if updated == content: - return False - path.write_text(updated) - return True - - def update_agent_version(root: Path, version: str) -> bool: normalized = normalize_version(version) - return _replace_once( - root / AGENT_VERSION_LOCK, - re.compile(r"(?m)^DD_AGENT_VERSION=[^\n]+$"), - f"DD_AGENT_VERSION={normalized}", - ) + lock_path = root / AGENT_VERSION_LOCK + updated = f"# Pinned Agent version, updated automatically\nDD_AGENT_VERSION={normalized}\n" + if lock_path.read_text() == updated: + return False + lock_path.write_text(updated) + return True def run_command( From dbe3dc527bf5b7c83e4620d64ef37e1a8a8a8b16 Mon Sep 17 00:00:00 2001 From: Nicolas Catoni Date: Mon, 21 Sep 2026 14:50:22 +0200 Subject: [PATCH 12/14] APMSP-3752 Automate SSI Agent updates (#7775) Co-authored-by: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 24 ++++++++---- .../provision.yml | 2 +- .../provision.yml | 2 +- utils/scripts/update_agent_version.py | 38 +++++++++++++++---- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index bf92cbd1387..286da7ebe9a 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -137,7 +137,10 @@ def test_automate_update_publishes_latest_version(tmp_path: Path, monkeypatch: p @scenarios.test_the_test -def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.parametrize("version", ["7.82.3", "7.82.2", "6.53.4"]) +def test_automate_update_skips_publish_when_not_newer( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, version: str +) -> None: write_lock(tmp_path) update_agent_version(tmp_path, "7.82.3") github = GitHubApi("token") @@ -146,7 +149,8 @@ def test_automate_update_skips_publish_when_current(tmp_path: Path, monkeypatch: lambda _root, _version, _github, _env: pytest.fail("publish should not run"), ) - assert not automate_update(tmp_path, github, {}, "7.82.3") + assert not automate_update(tmp_path, github, {}, version) + assert (tmp_path / AGENT_VERSION_LOCK).read_text().endswith("DD_AGENT_VERSION=7.82.3\n") @scenarios.test_the_test @@ -160,16 +164,18 @@ class FakeGitHubApi(GitHubApi): def __init__(self) -> None: super().__init__("token") self.calls: list[tuple[str, str]] = [] + self.descriptions: list[dict[str, object]] = [] def request(self, method: str, path: str, data: dict[str, object] | None = None) -> object: self.calls.append((method, path)) if method == "GET": - return [{"node_id": "PR_node_id"}] if existing_pr else [] - if path.endswith("/pulls"): - return {"node_id": "PR_node_id"} - assert path == "/graphql" + return [{"node_id": "PR_node_id", "number": int(existing_pr)}] if existing_pr else [] + if path == "/graphql": + assert data is not None + return {"data": {}} assert data is not None - return {"data": {}} + self.descriptions.append(data) + return {"node_id": "PR_node_id"} def fake_run( _root: Path, @@ -191,6 +197,10 @@ def fake_run( assert ["git", "push", "--force", "--set-upstream", "origin", AUTOMATION_BRANCH] in commands assert not any(command[0] == "gh" for command in commands) assert any(method == "POST" and path.endswith("/pulls") for method, path in github.calls) is (not existing_pr) + if existing_pr: + assert ("PATCH", f"/repos/DataDog/system-tests/pulls/{existing_pr}") in github.calls + # Whether it is created or refreshed, the PR describes the version that was just pushed. + assert [description["title"] for description in github.descriptions] == ["APMSP-3752 Update Agent to 7.82.3"] assert github.calls[-1] == ("POST", "/graphql") diff --git a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml index 103f8b79a5a..6db97b0c5fb 100644 --- a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml @@ -15,7 +15,7 @@ provision_steps: - prepare-docker #Prepare the docker environment - amazon-ecr-credential-helper # Install AWS ECR helper to download images from ECR - patch-docker-daemon #Patch the docker daemon to avoid networking issues/ip conflicts incident-31160 - - install-agent #Install the agent (allways latest release) + - install-agent #Install the agent (version pinned in provisions/auto-inject/agent.lock) - autoinjection_install_script #Install the auto-injection softaware 'datadog-apm-inject' and 'datadog-apm-library-$DD_LANG' init-config: diff --git a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml index f1e8bf8a31d..fd2ec14a501 100644 --- a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml @@ -12,7 +12,7 @@ vm_logs: #Mandatory: Steps to install provision provision_steps: - init-config #Very first machine actions, like disable auto updates - - install-agent #Install the agent (allways latest release) + - install-agent #Install the agent (version pinned in provisions/auto-inject/agent.lock) - autoinjection_install_script #Install the auto-injection softaware 'datadog-apm-inject' and 'datadog-apm-library-$DD_LANG' using the agent install script - install-local-apm-library diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 1942b836e87..db068095350 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -32,6 +32,18 @@ def normalize_version(version: str) -> str: return normalized +def version_key(version: str) -> tuple[int, int, int]: + major, minor, patch = normalize_version(version).split(".") + return int(major), int(minor), int(patch) + + +def locked_agent_version(root: Path) -> str: + for line in (root / AGENT_VERSION_LOCK).read_text().splitlines(): + if line.startswith("DD_AGENT_VERSION="): + return normalize_version(line.removeprefix("DD_AGENT_VERSION=")) + raise ValueError(f"No DD_AGENT_VERSION found in {AGENT_VERSION_LOCK}") + + def update_agent_version(root: Path, version: str) -> bool: normalized = normalize_version(version) lock_path = root / AGENT_VERSION_LOCK @@ -97,19 +109,22 @@ def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str pull_requests = github.request("GET", f"/repos/{REPOSITORY}/pulls?head={head}&state=open") if not isinstance(pull_requests, list): raise TypeError("GitHub returned an invalid pull request list") + description: dict[str, object] = { + "title": f"APMSP-3752 Update Agent to {version}", + "body": "Automated daily update of the Agent version pinned by SSI tests. " + "The PR will merge automatically after all required checks pass.", + } if pull_requests: - pull_request = pull_requests[0] + # The branch is force-pushed, so the open PR now describes the previous version: overwrite it. + existing = pull_requests[0] + if not isinstance(existing, dict) or not isinstance(existing.get("number"), int): + raise TypeError("GitHub returned an invalid pull request list") + pull_request = github.request("PATCH", f"/repos/{REPOSITORY}/pulls/{existing['number']}", description) else: pull_request = github.request( "POST", f"/repos/{REPOSITORY}/pulls", - { - "base": "main", - "head": AUTOMATION_BRANCH, - "title": f"APMSP-3752 Update Agent to {version}", - "body": "Automated daily update of the Agent version pinned by SSI tests. " - "The PR will merge automatically after all required checks pass.", - }, + {"base": "main", "head": AUTOMATION_BRANCH, **description}, ) if not isinstance(pull_request, dict) or not isinstance(pull_request.get("node_id"), str): raise TypeError("GitHub returned an invalid pull request") @@ -128,6 +143,13 @@ def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str def automate_update(root: Path, github: GitHubApi, env: Mapping[str, str], version: str | None = None) -> bool: normalized = normalize_version(version) if version is not None else latest_agent_version(github) + locked = locked_agent_version(root) + if version_key(normalized) <= version_key(locked): + # GitHub reports the most recently published release as the latest one, not the highest + # version: a patch released on an older branch must not roll the pin backward. + print(f"Agent pin {locked} is not older than {normalized}") + return False + if not update_agent_version(root, normalized): print(f"Agent pins already current: {normalized}") return False From e9f496bdfdcb7d8002ad965215b45a0eaaf16adf Mon Sep 17 00:00:00 2001 From: Nicolas Catoni Date: Mon, 21 Sep 2026 15:40:05 +0200 Subject: [PATCH 13/14] APMSP-3752 Automate SSI Agent version updates (#7776) Co-authored-by: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- .../test_update_agent_version.py | 42 ++++++++++++++++++- utils/scripts/update_agent_version.py | 36 +++++++++++----- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index 286da7ebe9a..fb6007e15c5 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -10,6 +10,7 @@ AUTOMATION_BRANCH, GitHubApi, automate_update, + enable_auto_merge, normalize_version, publish_update, revoke_token, @@ -172,7 +173,13 @@ def request(self, method: str, path: str, data: dict[str, object] | None = None) return [{"node_id": "PR_node_id", "number": int(existing_pr)}] if existing_pr else [] if path == "/graphql": assert data is not None - return {"data": {}} + # GitHub rejects enabling auto-merge on a PR that already has it, i.e. on every refresh. + if existing_pr: + return { + "data": {"enablePullRequestAutoMerge": None}, + "errors": [{"message": "Pull request Auto merge is already enabled."}], + } + return {"data": {"enablePullRequestAutoMerge": {"pullRequest": {"number": 42}}}} assert data is not None self.descriptions.append(data) return {"node_id": "PR_node_id"} @@ -204,6 +211,39 @@ def fake_run( assert github.calls[-1] == ("POST", "/graphql") +def fake_github(result: object) -> GitHubApi: + class FakeGitHubApi(GitHubApi): + def request(self, method: str, path: str, data: dict[str, object] | None = None) -> object: # noqa: ARG002 + return result + + return FakeGitHubApi("token") + + +@scenarios.test_the_test +@pytest.mark.parametrize( + "result", + [ + {"errors": [{"message": "Pull request Auto merge is not allowed for this repository"}]}, + { + "errors": [ + {"message": "Pull request Auto merge is already enabled."}, + {"message": "Something else went wrong"}, + ] + }, + ], +) +def test_enable_auto_merge_reports_real_failures(result: object) -> None: + with pytest.raises(RuntimeError, match="failed to enable pull request auto-merge"): + enable_auto_merge(fake_github(result), "PR_node_id") + + +@scenarios.test_the_test +@pytest.mark.parametrize("result", [[], {"errors": "boom"}]) +def test_enable_auto_merge_rejects_invalid_responses(result: object) -> None: + with pytest.raises(TypeError, match="invalid auto-merge response"): + enable_auto_merge(fake_github(result), "PR_node_id") + + @scenarios.test_the_test def test_run_automation_revokes_token_after_failure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: commands: list[list[str]] = [] diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index db068095350..23ca8220c05 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -21,6 +21,7 @@ REPOSITORY = "DataDog/system-tests" OCTO_STS_POLICY = "self.gitlab-update-agent-version" GITHUB_API_URL = "https://api.github.com" +AUTO_MERGE_ALREADY_ENABLED = "auto merge is already enabled" AGENT_VERSION_LOCK = Path("utils/build/virtual_machine/provisions/auto-inject/agent.lock") @@ -91,6 +92,29 @@ def latest_agent_version(github: GitHubApi) -> str: return normalize_version(release["tag_name"]) +def enable_auto_merge(github: GitHubApi, pull_request_node_id: str) -> None: + result = github.request( + "POST", + "/graphql", + { + "query": "mutation($pullRequestId: ID!) { enablePullRequestAutoMerge(input: {" + "pullRequestId: $pullRequestId, mergeMethod: SQUASH}) { pullRequest { number } } }", + "variables": {"pullRequestId": pull_request_node_id}, + }, + ) + if not isinstance(result, dict): + raise TypeError("GitHub returned an invalid auto-merge response") + errors = result.get("errors") or [] + if not isinstance(errors, list): + raise TypeError("GitHub returned an invalid auto-merge response") + # A refreshed PR keeps the auto-merge enabled by a previous run, and GitHub rejects enabling it twice. + if any( + not isinstance(error, dict) or AUTO_MERGE_ALREADY_ENABLED not in str(error.get("message", "")).lower() + for error in errors + ): + raise RuntimeError("GitHub failed to enable pull request auto-merge") + + def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str, str]) -> None: run_command(root, ["git", "remote", "set-url", "origin", f"https://github.com/{REPOSITORY}.git"], env=env) run_command(root, ["git", "switch", "--force-create", AUTOMATION_BRANCH], env=env) @@ -128,17 +152,7 @@ def publish_update(root: Path, version: str, github: GitHubApi, env: Mapping[str ) if not isinstance(pull_request, dict) or not isinstance(pull_request.get("node_id"), str): raise TypeError("GitHub returned an invalid pull request") - auto_merge_result = github.request( - "POST", - "/graphql", - { - "query": "mutation($pullRequestId: ID!) { enablePullRequestAutoMerge(input: {" - "pullRequestId: $pullRequestId, mergeMethod: SQUASH}) { pullRequest { number } } }", - "variables": {"pullRequestId": pull_request["node_id"]}, - }, - ) - if not isinstance(auto_merge_result, dict) or auto_merge_result.get("errors"): - raise RuntimeError("GitHub failed to enable pull request auto-merge") + enable_auto_merge(github, pull_request["node_id"]) def automate_update(root: Path, github: GitHubApi, env: Mapping[str, str], version: str | None = None) -> bool: From c0bb4ba2b469952cd3a6e0d6bfd3c23aed512132 Mon Sep 17 00:00:00 2001 From: datadog-bits <263423550+datadog-bits@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:55:44 +0000 Subject: [PATCH 14/14] Relocate shared Agent lock file Co-authored-by: nccatoni <222672590+nccatoni@users.noreply.github.com> --- tests/test_the_test/test_update_agent_version.py | 7 ++++--- .../{provisions/auto-inject => }/agent.lock | 0 .../auto-inject/auto-inject_installer_manual.yml | 2 +- .../auto-inject_container_script.yml | 2 +- .../container-auto-inject-install-script/provision.yml | 5 ++--- .../auto-inject_host_script.yml | 2 +- .../local-auto-inject-install-script/provision.yml | 4 ++-- utils/scripts/update_agent_version.py | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) rename utils/build/virtual_machine/{provisions/auto-inject => }/agent.lock (100%) diff --git a/tests/test_the_test/test_update_agent_version.py b/tests/test_the_test/test_update_agent_version.py index fb6007e15c5..213672d9728 100644 --- a/tests/test_the_test/test_update_agent_version.py +++ b/tests/test_the_test/test_update_agent_version.py @@ -48,9 +48,10 @@ def test_normalize_version_rejects_unsupported_versions(version: str) -> None: @scenarios.test_the_test def test_agent_version_consumers_load_lock() -> None: root = Path(__file__).resolve().parents[2] + virtual_machine = root / "utils/build/virtual_machine" auto_inject = root / "utils/build/virtual_machine/provisions/auto-inject" - lock_lines = (auto_inject / "agent.lock").read_text().splitlines() + lock_lines = (virtual_machine / "agent.lock").read_text().splitlines() assert lock_lines[0] == "# Pinned Agent version, updated automatically" assert len(lock_lines) == 2 locked_version = lock_lines[1].removeprefix("DD_AGENT_VERSION=") @@ -58,8 +59,8 @@ def test_agent_version_consumers_load_lock() -> None: assert "agent:${DD_AGENT_VERSION}" in (auto_inject / "docker/docker-compose-agent-prod.yml").read_text() compose_path = "utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml" - lock_path = "utils/build/virtual_machine/provisions/auto-inject/agent.lock" - provision_root = root / "utils/build/virtual_machine" + lock_path = "utils/build/virtual_machine/agent.lock" + provision_root = virtual_machine compose_copy_points = [path for path in provision_root.rglob("*.yml") if compose_path in path.read_text()] assert compose_copy_points for copy_point in compose_copy_points: diff --git a/utils/build/virtual_machine/provisions/auto-inject/agent.lock b/utils/build/virtual_machine/agent.lock similarity index 100% rename from utils/build/virtual_machine/provisions/auto-inject/agent.lock rename to utils/build/virtual_machine/agent.lock diff --git a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml index 2daae9a3ad0..4850db186c4 100644 --- a/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml +++ b/utils/build/virtual_machine/provisions/auto-inject/auto-inject_installer_manual.yml @@ -6,7 +6,7 @@ - name: copy-docker-config local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker_config.yaml - name: copy-agent-version-lock - local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock + local_path: utils/build/virtual_machine/agent.lock - name: copy-binaries local_path: binaries/ diff --git a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml index 0ddda5e738d..f354185057e 100644 --- a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml +++ b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml @@ -10,7 +10,7 @@ - name: copy-agent-docker-compose local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml - name: copy-agent-version-lock - local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock + local_path: utils/build/virtual_machine/agent.lock - name: copy-binaries local_path: binaries/ remote-command: | diff --git a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml index 6db97b0c5fb..30ac97e29c3 100644 --- a/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/container-auto-inject-install-script/provision.yml @@ -15,7 +15,7 @@ provision_steps: - prepare-docker #Prepare the docker environment - amazon-ecr-credential-helper # Install AWS ECR helper to download images from ECR - patch-docker-daemon #Patch the docker daemon to avoid networking issues/ip conflicts incident-31160 - - install-agent #Install the agent (version pinned in provisions/auto-inject/agent.lock) + - install-agent #Install the agent (version pinned in utils/build/virtual_machine/agent.lock) - autoinjection_install_script #Install the auto-injection softaware 'datadog-apm-inject' and 'datadog-apm-library-$DD_LANG' init-config: @@ -42,9 +42,8 @@ install-agent: - name: copy-agent-docker-compose local_path: utils/build/virtual_machine/provisions/auto-inject/docker/docker-compose-agent-prod.yml - name: copy-agent-version-lock - local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock + local_path: utils/build/virtual_machine/agent.lock remote-command: cat docker-compose-agent-prod.yml autoinjection_install_script: install: !include utils/build/virtual_machine/provisions/container-auto-inject-install-script/auto-inject_container_script.yml - diff --git a/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml b/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml index 3dc63d7e744..eb98f275934 100644 --- a/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml +++ b/utils/build/virtual_machine/provisions/host-auto-inject-install-script/auto-inject_host_script.yml @@ -6,7 +6,7 @@ - name: copy-tracer-debug-config local_path: utils/build/virtual_machine/provisions/auto-inject/tracer_debug/debug_config.yaml - name: copy-agent-version-lock - local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock + local_path: utils/build/virtual_machine/agent.lock - name: copy-binaries local_path: binaries/ remote-command: | diff --git a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml index fd2ec14a501..053c38fa5a7 100644 --- a/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml +++ b/utils/build/virtual_machine/provisions/local-auto-inject-install-script/provision.yml @@ -12,7 +12,7 @@ vm_logs: #Mandatory: Steps to install provision provision_steps: - init-config #Very first machine actions, like disable auto updates - - install-agent #Install the agent (version pinned in provisions/auto-inject/agent.lock) + - install-agent #Install the agent (version pinned in utils/build/virtual_machine/agent.lock) - autoinjection_install_script #Install the auto-injection softaware 'datadog-apm-inject' and 'datadog-apm-library-$DD_LANG' using the agent install script - install-local-apm-library @@ -26,7 +26,7 @@ install-agent: - os_type: linux copy_files: - name: copy-agent-version-lock - local_path: utils/build/virtual_machine/provisions/auto-inject/agent.lock + local_path: utils/build/virtual_machine/agent.lock remote-command: | . ./agent.lock export DD_AGENT_MAJOR_VERSION="${DD_AGENT_VERSION%%.*}" diff --git a/utils/scripts/update_agent_version.py b/utils/scripts/update_agent_version.py index 23ca8220c05..2454b23ed7e 100755 --- a/utils/scripts/update_agent_version.py +++ b/utils/scripts/update_agent_version.py @@ -23,7 +23,7 @@ GITHUB_API_URL = "https://api.github.com" AUTO_MERGE_ALREADY_ENABLED = "auto merge is already enabled" -AGENT_VERSION_LOCK = Path("utils/build/virtual_machine/provisions/auto-inject/agent.lock") +AGENT_VERSION_LOCK = Path("utils/build/virtual_machine/agent.lock") def normalize_version(version: str) -> str: