Skip to content

Introduce target artifact staging for Python - #7718

Open
nccatoni wants to merge 13 commits into
mainfrom
dd/nccatoni/target-artifact-staging-system
Open

nccatoni wants to merge 13 commits into
mainfrom
dd/nccatoni/target-artifact-staging-system

Conversation

@nccatoni

Copy link
Copy Markdown
Collaborator

Motivation

Artifact selection is currently centralized in a large compatibility script and often relies on mutable selectors. A smaller, target-owned staging model is needed to make selected artifacts explicit, bounded, and safe to refresh before builds consume them.

This PR intentionally introduces the framework with Python as the only migrated language so the API and ownership model can be reviewed independently from the remaining language migrations and GitLab cache integration.

Changes

  • Add a shared target artifact staging API with typed resolvers, bounded selector entries, and a generated ownership manifest.
  • Preserve manual artifacts and entries from other targets while safely refreshing stale entries owned by the staged target.
  • Reject invalid manifest paths so generated cleanup cannot affect files outside binaries/.
  • Migrate Python development and production selection as the demonstration implementation while retaining the legacy loader for every other target.
  • Document the migration contract and explicitly leave GitLab integration and other languages for follow-up PRs.

Testing

  • TEST_THE_TEST focused suite: 62 passed for staging, resolvers, manifest safety, compatibility loading, and Python dev/custom behavior.
  • Full mypy, ruff, YAML, manifest, and shell checks passed locally. The repository-wide formatter reached the final Node lint container pull, which is unavailable in the sandbox.

Workflow

  1. ⚠️ Create your PR as draft ⚠️
  2. Work on you PR until the CI passes
  3. Mark it as ready for review
    • Test logic is modified? -> Get a review from RFC owner.
    • Framework is modified, or non obvious usage of it -> get a review from R&P team

🚀 Once your PR is reviewed and the CI green, you can merge it!

🛟 #apm-shared-testing 🛟

Reviewer checklist

  • Anything but tests/ or manifests/ is modified ? I have the approval from R&P team
  • A docker base image is modified?
    • the relevant build-XXX-image label is present
  • A scenario is added, removed or renamed?

PR by Bits - View session in Datadog

Comment @DataDog to request changes

@datadog-datadog-prod-us1-2

Copy link
Copy Markdown

View session in Datadog

Bits Code status: ✅ Done

CI Auto-fix: Disabled | Enable

Comment @DataDog to request changes

@datadog-prod-us1-6

Copy link
Copy Markdown

I can only run on private repositories.

@github-actions

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

docs/internals/target-artifact-staging-spec.md                          @DataDog/system-tests-core
tests/test_the_test/test_target_artifacts.py                            @DataDog/system-tests-reviewers
utils/build/docker/python/artifact.py                                   @DataDog/system-tests-reviewers
utils/scripts/stage-target-artifacts.py                                 @DataDog/system-tests-core
utils/target_artifacts/__init__.py                                      @DataDog/system-tests-core
utils/target_artifacts/__main__.py                                      @DataDog/system-tests-core
utils/target_artifacts/cli.py                                           @DataDog/system-tests-core
utils/target_artifacts/entry_helpers.py                                 @DataDog/system-tests-core
utils/target_artifacts/models.py                                        @DataDog/system-tests-core
utils/target_artifacts/orchestrator.py                                  @DataDog/system-tests-core
utils/target_artifacts/resolvers.py                                     @DataDog/system-tests-core
docs/execute/binaries.md                                                @DataDog/system-tests-core
docs/glossary.md                                                        @DataDog/system-tests-core
docs/internals/README.md                                                @DataDog/system-tests-core
tests/test_the_test/test_load_binary.py                                 @DataDog/system-tests-reviewers
utils/__init__.py                                                       @DataDog/system-tests-core
utils/scripts/load-binary.sh                                            @DataDog/system-tests-core

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Sep 14, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 20 Pipeline jobs failed

Testing the test | System Tests (php, prod) / End-to-end #1 / apache-mod-7.4 1 — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

Testing the test | System Tests (php, prod) / End-to-end #1 / php-fpm-7.2 1 — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

Testing the test | System Tests (php, prod) / End-to-end #2 / apache-mod-8.0 2 — 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

View all 20 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 55886cf | Docs | View more details | Give us feedback!

@nccatoni
nccatoni marked this pull request as ready for review September 14, 2026 16:07
@nccatoni
nccatoni requested review from a team as code owners September 14, 2026 16:07
@nccatoni
nccatoni requested review from paullegranddc and removed request for a team September 14, 2026 16:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T16:13:58.642368Z 55886cf Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55886cf0f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +37 to +38
if environment == "custom":
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clear owned selectors when entering custom mode

When a reused binaries/ directory was previously staged as dev or prod, returning here leaves its generated selector active during a subsequent custom run. This can silently select the wrong tracer: for example, with two custom Python wheels, utils/build/docker/python/install_ddtrace.sh skips the single-wheel branch, sees the stale python-load-from-s3 entry at lines 18–31, and never reaches the version-specific wheel selection at lines 38–42. Custom mode should remove entries owned by this target while preserving unowned payloads.

Useful? React with 👍 / 👎.

Comment on lines +91 to +92
if path.exists() and existing_owner is None:
raise TargetArtifactError(f"Refusing to overwrite unowned artifact entry '{filename}'")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check hashes before replacing owned files

When a user edits a previously generated selector, its manifest owner remains present, so this check permits staging to overwrite it; the stale-entry loop likewise deletes modified files. The recorded sha256 is never compared with the current content, meaning manual changes can be silently lost and experiments can run against a different artifact than intended. Before overwriting or deleting an owned entry, verify that its current hash still matches the manifest and refuse the operation otherwise.

Useful? React with 👍 / 👎.



@scenarios.test_the_test
class Test_LoadBinaryPython:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Associate every new test class with a feature

The newly added Test_LoadBinaryPython, Test_TargetArtifactStaging, Test_TargetArtifactResolvers, and Test_TargetArtifactModules classes have scenario decorators but no feature decorator. Repository rules require every test class to use either a specific @features.* association or @features.not_reported, so these framework tests should be explicitly marked rather than omitted from feature metadata.

AGENTS.md reference: AGENTS.md:L9-L17

Useful? React with 👍 / 👎.

Comment on lines +104 to +106
path = binaries_dir / filename
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(entry.content, encoding="utf-8")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid following artifact-entry symlinks

If an entry path is a broken symlink, path.exists() is false, so it is treated as safe and the later write_text() follows the link, creating or overwriting its target outside binaries/; an owned symlink can similarly overwrite an existing external file. This bypasses the filename traversal checks and can corrupt unrelated workspace files. Reject symlink entry paths or publish entries with an atomic replacement that replaces the link itself.

Useful? React with 👍 / 👎.

Comment on lines +122 to +123
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register dynamically loaded modules before execution

The module created here is executed without first being inserted into sys.modules. A target artifact.py that defines a normal @dataclass with postponed annotations then fails during import because dataclasses looks up cls.__module__ in sys.modules and receives None; other import-time tooling can rely on the same invariant. Register the synthetic module name before exec_module, restoring or removing it if execution fails.

Useful? React with 👍 / 👎.


class Prod(SimpleTarget):
inputs = (PypiLatestResolver(name="ddtrace", package="ddtrace"),)
entries = (text_entry("python-load-from-pip", "ddtrace=={ddtrace.version}"),)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject conflicting manual selectors during production staging

If binaries/ already contains an unowned python-load-from-s3 file from a legacy or manual development run, production staging adds this python-load-from-pip entry without detecting the conflict. The Python installer checks the S3 selector first at utils/build/docker/python/install_ddtrace.sh lines 18–31, so the new production selector is ignored and the tests silently exercise the development artifact. Production staging should refuse this conflicting manual selector rather than report success with an ineffective entry.

Useful? React with 👍 / 👎.

Comment on lines +43 to +46
resolved_inputs = {
artifact_resolver.name: artifact_resolver.resolve(env)
for artifact_resolver in target_environment.artifact_inputs(env)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate resolver names before resolving

When a target declares two inputs with the same name, this dictionary comprehension silently discards the first resolved value while still performing both external lookups. Templates then bind that name to the later artifact, which can stage a valid-looking but unintended selector when both resolvers return compatible models. Detect duplicate resolver names and fail before performing resolution.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants