Devcontainer feature#861
Conversation
…untu in dev container tests
…roving output assertions
There was a problem hiding this comment.
Pull request overview
Adds a reusable Dev Container Feature under devcontainer/ to install apm-cli (plus prerequisites) in containers, along with unit + integration test coverage for the feature.
Changes:
- Introduces a devcontainer feature (
devcontainer-feature.json+install.sh) that installsuv, ensures Python 3.10+ andgit, and installsapm-cliwith a PEP 668 retry path. - Adds Bats unit tests for
install.shand devcontainer CLI integration scenarios across several base images and configuration variants. - Adds supporting docs (
devcontainer/README.md), a local sync helper script, and git submodules for the Bats test framework.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| devcontainer/src/apm/install.sh | Feature install script: uv install, Python/git setup, pip install of apm-cli with PEP 668 retry |
| devcontainer/src/apm/devcontainer-feature.json | Feature manifest and version option + installsAfter ordering |
| devcontainer/test/apm/scenarios.json | Integration test matrix (distros + pinned version + python feature) |
| devcontainer/test/apm/*.sh | Scenario validation scripts + shared generic checks |
| devcontainer/test/apm/unit/install.bats | Stub-based unit tests covering install.sh branches |
| devcontainer/scripts/sync-local-devcontainer.sh | Helper to copy the local feature into .devcontainer/ for VS Code consumption |
| devcontainer/README.md | Feature usage, structure, and test documentation |
| .gitmodules | Adds bats-core, bats-support, bats-assert as submodules |
APM Review Panel VerdictDisposition: APPROVE (with two minor pre-merge fixes) Per-persona findingsPython Architect: This PR adds zero Python code -- all 18 files are shell scripts, JSON, Markdown, bats tests, and git submodule pointers. Applying the "purely procedural" clause from the PR review contract. Structural placement: Submodules: bats-core, bats-support, bats-assert -- all three pinned to specific commit SHAs. No floating-branch references. Repetition: The package-manager detection cascade (apt-get / apk / dnf) is written three times in OO/class diagram (module boundaries -- no Python classes in scope): classDiagram
direction LR
class DevcontainerFeature {
<<IOBoundary>>
+id string
+version string
+installsAfter list
}
class InstallScript {
<<Pure>>
+validate_version()
+check_root()
+install_uv()
+ensure_python3()
+ensure_git()
+locate_pip()
+install_apm_cli()
}
class PackageManagerDetector {
<<Pure>>
+apt_get bool
+apk bool
+dnf bool
}
class BatsTestSuite {
<<IOBoundary>>
+37 unit tests
+run_with_stubs()
+make_stub()
}
class BatsDeps {
<<ExternalDep>>
+bats_core SHA pinned
+bats_support SHA pinned
+bats_assert SHA pinned
}
DevcontainerFeature *-- InstallScript : executes
InstallScript *-- PackageManagerDetector : uses inline
BatsTestSuite ..> InstallScript : stubs and exercises
BatsTestSuite *-- BatsDeps : loads helpers
classDef touched fill:#fff3b0,stroke:#d47600
class DevcontainerFeature:::touched
class InstallScript:::touched
class BatsTestSuite:::touched
class PackageManagerDetector:::touched
class BatsDeps:::touched
Execution flow diagram: flowchart TD
A["devcontainer build\ndevcontainer.json declares feature"] --> B["[EXEC] inject VERSION env var\ninstall.sh:4"]
B --> C{"validate VERSION\nlatest or X.Y.Z\ninstall.sh:6-11"}
C -->|invalid| D["[x] exit 1 -- VERSION error"]
C -->|valid| E{"id -u == 0\ninstall.sh:13-16"}
E -->|not root| F["[x] exit 1 -- must run as root"]
E -->|root| G{"uv on PATH\ninstall.sh:399"}
G -->|present| H["skip -- uv already installed"]
G -->|absent| I{"curl on PATH\ninstall.sh:403"}
I -->|absent| J["[EXEC] install curl\napt-get/apk/dnf\ninstall.sh:405-415"]
I -->|present| K["[NET] curl astral.sh/uv/install.sh to tmp\ninstall.sh:420"]
J --> K
K --> L["[EXEC] sh _uv_tmp UV_INSTALL_DIR=/usr/local/bin\ninstall.sh:421"]
H --> M{"python3 on PATH\ninstall.sh:427"}
L --> M
M -->|absent| N["[EXEC] install python3+pip+git\napt-get/apk/dnf\ninstall.sh:429-441"]
M -->|present| O{"python3 >= 3.10\ninstall.sh:460-466"}
N --> O
O -->|fail| P["[x] exit 1 -- Python version"]
O -->|ok| Q{"git on PATH\ninstall.sh:444"}
Q -->|absent| R["[EXEC] install git\napt-get/apk/dnf\ninstall.sh:445-456"]
Q -->|present| S["locate pip3/pip/ensurepip\ninstall.sh:470-485"]
R --> S
S --> T["[NET] pip install apm-cli PKG_SPEC\ninstall.sh:497-508"]
T --> U{"externally-managed-environment\ninstall.sh:500"}
U -->|detected| V["[NET] pip install --break-system-packages\ninstall.sh:502"]
U -->|other error| W["[x] exit 1"]
V --> X{"apk present, no bash\ninstall.sh:511"}
U -->|success| X
X -->|yes| Y["[EXEC] apk add bash\ninstall.sh:512"]
X -->|no| Z{"apm on PATH\ninstall.sh:516"}
Y --> Z
Z -->|yes| AA["[+] APM version installed\ninstall.sh:517"]
Z -->|no| AB["[!] warn not on PATH\ninstall.sh:519-521"]
Design patterns
CLI Logging Expert: No APM Python output infrastructure (CommandLogger, Output quality against APM STATUS_SYMBOLS conventions:
One minor note: progress lines ( DevX UX Expert: Feature follows the devcontainer spec exactly -- developers familiar with Error messages are strong: every failure includes a concrete recovery action. The warn-not-fail PATH case is correct UX (install succeeded; the PATH issue is advisory). The Critical gap: {
"features": {
"ghcr.io/<org>/<collection>/apm:1": {}
}
}
Unit test submodule init step is documented in README section 5 -- acceptable discoverability. Supply Chain Security Expert: No critical gaps. Full threat-surface analysis:
Auth Expert: Not activated -- PR touches only OSS Growth Hacker: High-leverage adoption move. Dev Container Features are indexed on Two blocking gaps for growth:
One near-term gap: the main Side-channel to CEO: Merge this quickly -- it signals responsiveness to an external contributor and unlocks a distribution channel. The publish workflow (not in this PR) is the single biggest growth multiplier; it should ship as the immediate follow-up. Story angle when shipping: "Install APM in any Codespace or dev container with one line" -- retweetable, reinforces the reproducible-AI-dev-environments narrative, and is technically accurate once the publish workflow lands. CEO arbitrationSpecialists are in agreement; no conflicts to resolve. The feature is well-implemented, well-tested (37 stub-isolated bats unit tests plus a 6-distro integration matrix), and architecturally clean. Two pre-merge fixes are required: a Required actions before merge
Optional follow-ups
|
APM Review Panel VerdictDisposition: REQUEST_CHANGES (two lightweight pre-merge items -- no code changes required) Per-persona findingsPython Architect: This PR contains zero Python code changes. All 18 changed files are POSIX sh, JSON, Bash (bats tests), and Markdown. The architecture is a textbook Dev Container Feature shape: a declarative manifest + a procedural install script + a comprehensive test suite. No OO structural concerns. Two minor shell observations: OO / class diagram (module boundaries): classDiagram
direction LR
class DevContainerFeature {
<<ExternalProtocol>>
+id: str = "apm"
+version: str = "1.0.0"
+options: dict
+installsAfter: list
}
class InstallScript {
<<IOBoundary>>
+apt_update_once() void
+validate_version() void
+install_uv() void
+ensure_python3() void
+ensure_git() void
+locate_pip() void
+install_apm() void
+ensure_bash_alpine() void
+verify_apm_path() void
}
class PackageManagers {
<<Adapter>>
+apt_get: cmd
+apk: cmd
+dnf: cmd
}
class ExternalSources {
<<ExternalDependency>>
+astral_sh_uv_install: url
+pypi_apm_cli: pkg
}
class BatsTestSuite {
<<TestFixture>>
+setup() STUB_BIN
+run_with_stubs() void
+make_stub() void
+make_pkg_mgr_stub() void
}
DevContainerFeature ..> InstallScript : executes via devcontainer CLI
InstallScript ..> PackageManagers : dispatches per platform
InstallScript ..> ExternalSources : fetches
BatsTestSuite ..> InstallScript : stubs and exercises
class InstallScript:::touched
class DevContainerFeature:::touched
class BatsTestSuite:::touched
classDef touched fill:#fff3b0,stroke:#d47600
Execution flow diagram: flowchart TD
A["devcontainer CLI build"] --> B["[EXEC] install.sh\nVERSION from feature env"]
B --> C{VERSION = 'latest'\nor semver X.Y.Z?}
C -- "no" --> D["[x] exit 1\n'VERSION must be latest or semver'"]
C -- "yes" --> E{id -u == 0?}
E -- "no" --> F["[x] exit 1\n'must run as root'"]
E -- "yes" --> G{uv on PATH?}
G -- "yes" --> J["skip uv install"]
G -- "no" --> H{curl on PATH?}
H -- "no" --> H2["[NET] install curl\napt-get/apk/dnf"]
H -- "yes" --> I
H2 --> I["[NET] curl astral.sh/uv/install.sh to mktemp\ntrap rm tmpfile EXIT/INT/TERM\n[EXEC] sh tmpfile UV_INSTALL_DIR=/usr/local/bin"]
I --> J
J --> K{python3 on PATH?}
K -- "no" --> L["[FS] install python3+pip+git\napt-get/apk/dnf"]
K -- "yes" --> M
L --> M{git on PATH?}
M -- "no" --> N["[FS] install git\napt-get/apk/dnf"]
M -- "yes" --> O["assert python3 >= 3.10\nsys.version_info.major/minor"]
N --> O
O -- "< 3.10" --> P["[x] exit 1\n'apm-cli requires Python 3.10+'"]
O -- ">= 3.10" --> Q["locate pip:\npip3 > pip > python3 -m ensurepip"]
Q -- "none found" --> QF["[x] exit 1\n'pip not available'"]
Q -- "found" --> R["install_apm()\n$PIP_CMD install apm-cli\[==VERSION\] 2>&1"]
R -- "success" --> S
R -- "externally-managed-environment" --> T["[NET] retry:\n--break-system-packages"]
T -- "success" --> S
T -- "fail" --> U["[x] exit 1"]
R -- "other error" --> U
S["[FS] apk: add bash if missing"] --> V{apm on PATH?}
V -- "yes" --> W["[+] APM version installed at PATH"]
V -- "no" --> X["[!] warn: not in PATH\nno exit 1"]
Design patterns
CLI Logging Expert: No changes to Python CLI logging infrastructure (CommandLogger, DiagnosticCollector, DevX UX Expert: The devcontainer feature mental model (one JSON declaration, Critical UX gap: Section 4 "Option B" of the README uses Supply Chain Security Expert: Two new external download surfaces are introduced in the container build path:
No path traversal surface (install.sh does not interact with APM's Auth Expert: Not activated -- all 18 changed files are in OSS Growth Hacker: High-conviction growth beat. "APM works out-of-the-box in any Dev Container" is a compelling one-liner that opens a new acquisition channel: VS Code Remote Containers users. The friction reduction (from a custom CEO arbitrationSpecialists are in agreement; no disagreements to arbitrate. This is a well-scoped, well-tested greenfield addition that sits squarely within APM's mission. Two pre-merge items are required: the missing CHANGELOG entry (repo convention, non-negotiable) and the README Option B clarification (first-impression UX, cannot let users hit a silent OCI pull failure). The supply chain risk from the uv curl-install pattern is accepted ecosystem practice and does not block. The publish-to-GHCR workflow is the natural follow-up issue -- it does not need to land in this PR, but it must be tracked explicitly. Once the two required actions are addressed, this is approved. Required actions before merge
Optional follow-ups
|
|
@coakenfold let me think about the publishing pipeline and how to get the artifact published. If you have any suggestions or proposed steps, please post here. |
|
@danielmeppiel sounds good. It's not clear to me if I need to do anything else. Please let me know if something needs to change. Thx for the review & consideration. |
Folds five maintainer-side concerns into PR microsoft#861 so merging gives users a working ghcr.io/microsoft/apm/apm-cli:1 reference on day one: - Rename feature id from 'apm' -> 'apm-cli' to avoid the well-known APM=Application Performance Monitoring collision (Datadog/Elastic/ NewRelic/OpenTel all ship 'apm' devcontainer features). Final OCI ref: ghcr.io/microsoft/apm/apm-cli. Also renames src/apm -> src/apm-cli and test/apm -> test/apm-cli with matching scenarios.json + bats path + sync-local helper updates. - Replace 3 placeholder OCI refs in devcontainer/README.md with the real ghcr.io/microsoft/apm/apm-cli:1 reference and add a tag-selection table (:1 vs :1.0 vs :1.0.0 vs :latest). Reorder so 'consume from GHCR' is the quick start and 'local development' is last, matching every devcontainers/features/* README. - New .github/workflows/devcontainer-feature-test.yml -- PR + merge_group gate runs bats unit tests + 4-scenario integration matrix (default-ubuntu-24, default-alpine-3, pinned-version, with-python-feature). Nightly cron extends to 6 scenarios with default-debian-12 + default-fedora to catch base-image drift. Single aggregation job 'Devcontainer Feature - Test Result' for branch protection wiring. - New .github/workflows/devcontainer-feature-publish.yml -- push to main path-filtered on devcontainer/src/** publishes via devcontainers/action@v1 with disable-repo-tagging:true (avoids cluttering apm-cli v* git tags), permissions:{contents:read, packages:write}, concurrency:cancel-in-progress:false (no partial manifest pushes), workflow_dispatch for manual recovery. - CHANGELOG.md entry under [Unreleased] -> Added. 37/37 bats unit tests pass locally. Both workflows pass actionlint. Co-authored-by: coakenfold <[email protected]> Co-authored-by: Copilot <[email protected]>
|
Hey @coakenfold -- with your What changed in that commit:
Validated locally: 37/37 bats unit tests pass, both new workflows pass Post-merge ceremony I'll handle on the maintainer side: flip the GHCR package visibility to Public + connect to the repo (without this, users get silent unauthorized pulls), add Thanks again for the patient back-and-forth on this -- it's a really clean feature. |
…es substring grep Two CI failures from the previous push, both pre-existing test-design issues that only surface on the Ubuntu CI runner: 1. bats unit tests: the test setup uses PATH="$STUB_BIN:/bin" to isolate install.sh from the real environment. On Ubuntu 24.04 /bin is a symlink to /usr/bin (merged-/usr layout), so real apt-get / pip / curl shadow the stubs and negative-path tests false-pass. Run the unit-bats job inside an alpine:3.20 container where /bin is busybox-only and apk lives in /sbin (not on the test PATH), letting the stubs shadow as the test author designed. Tests pass on the author's macOS (small /bin) and now on CI. 2. pinned-version scenario asserted 'apm --version | grep -qx 0.8.11' (exact line match), but apm --version actually outputs 'Agent Package Manager (APM) CLI version 0.8.11 (<sha>)'. Switched to a substring grep, which is the correct semantic for 'reports the pinned version'. Co-authored-by: Copilot <[email protected]>
microsoft org GHCR policy blocks first-time package creation under ghcr.io/microsoft/apm/* for both GITHUB_TOKEN and member PATs (read allowed, write returns no WWW-Authenticate). Working on org bootstrap via the microsoft/opensource process; until that completes the publish job will fail and add red noise to every push to main. Comment out the push trigger but keep workflow_dispatch so we can validate the bootstrap with a single manual run the moment policy is granted, without another commit. Tag-fanout (1, 1.0, 1.0.0, latest) and the rest of the workflow body are unchanged. Follow-up to #861. Co-authored-by: Copilot <[email protected]>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <[email protected]>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <[email protected]>
* chore(release): cut 0.11.0 Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <[email protected]> * chore(changelog): tighten 0.11.0 entries to lead with user impact Co-authored-by: Copilot <[email protected]> * chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published) Co-authored-by: Copilot <[email protected]> * chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer) Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
Description
Packages
apm-clias a reusable Dev Container Feature underdevcontainer/.The feature installs
uv, Python 3.10+,git, andapm-cliinside the container. It supports aversionoption (latestor semver), declaresinstallsAfter: ghcr.io/devcontainers/features/pythonfor correct ordering, and handles the PEP 668--break-system-packagesretry on Ubuntu 24.04.Also included: 37 bats unit tests covering every branch of
install.sh(stub-based, no Docker needed), and a full integration test matrix across Ubuntu 24.04, Ubuntu 22.04, Debian 12, Alpine 3.20, Fedora 41, and a pinned-version + Python-feature scenario.See
devcontainer/README.mdfor more infoFixes #717
Type of change
Testing
Unit tests (no Docker required):
cd devcontainer/test/apm/unit ../../bats/bin/bats install.batsIntegration tests (requires Docker and
@devcontainers/cli):devcontainer features test \ --features apm \ --skip-autogenerated \ --project-folder devcontainer