feat(runtimes): add unified Node.js and Python runtime extensions#400
feat(runtimes): add unified Node.js and Python runtime extensions#400jamesadevine merged 9 commits intomainfrom
Conversation
Add Python and Node.js runtimes with consistent architecture matching the existing Lean runtime pattern: - Python: UsePythonVersion@0, PipAuthenticate@1, PIP_INDEX_URL/UV_DEFAULT_INDEX env vars - Node.js: NodeTool@0 (inline, decoupled from ado-script), npmAuthenticate@0, NPM_CONFIG_REGISTRY env var - Both use flat feed-url: field with env var injection via agent_env_vars() - Both accept config: field (recognized but errors if used, reserved for AWF proxy-auth) - Shared validate_feed_url() in validate.rs for injection checks - agent_env_vars() trait method on CompilerExtension with BLOCKED_ENV_KEYS validation - No AWF mounts/PATH prepends needed (hostedtoolcache auto-mounted by AWF) Unifies the approaches from PRs #398 and #399 into a single consistent implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture with two real bugs that need fixing before merge — one is dead code indicating a logic ordering error, and one is a pipeline reliability issue for the Node.js runtime. Findings🐛 Bugs / Logic Issues
|
npmAuthenticate@0 requires workingFile to point at an existing file, unlike PipAuthenticate@1. Emit a bash step that creates a minimal .npmrc (with the configured registry or default npmjs) when one does not already exist, preserving any repo-checked-in .npmrc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…with warning - Python: swap mutual-exclusivity check before not-yet-supported error so both paths are reachable - Node: accept config: with a warning that .npmrc won't be available inside AWF yet (instead of hard error), check mutual exclusivity first - Add tests for mutual-exclusivity errors on both runtimes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A double-quote in a feed-url or extension env var value would produce malformed YAML in the generated pipeline (the value is emitted as KEY: "value"). Reject at validation time in both validate_feed_url() and collect_agent_env_vars() with clear error messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good overall — clean architecture, solid test coverage, and good injection defenses. One security bug needs fixing before merge; one behavioral inconsistency needs resolution. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
Align with Node.js behavior — accept config: with a warning that the config file will not be available inside the AWF agent environment yet, rather than a hard compile error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PipAuthenticate@1 and npmAuthenticate@0 (plus ensure-npmrc) are now only emitted when feed-url: or config: is set. Users who enable runtimes: python: true or runtimes: node: true without an internal feed no longer get unnecessary auth steps in their pipeline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks mostly solid — good architecture, thorough validation, and strong test coverage. Three issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
…ften uv prompt Three fixes: 1. validate_feed_url() now rejects single-quote characters alongside double-quotes — a single quote in the feed URL would break the bash single-quoted string in generate_ensure_npmrc's echo command. 2. Doc comments on generate_pip_authenticate() and generate_npm_authenticate() corrected to say "emitted when feed-url or config is set" instead of "emitted unconditionally", matching the actual conditional behavior in prepare_steps(). 3. Python prompt supplement no longer claims uv is "pre-installed" — ADO hosted runners don't ship uv. Now says "install it first with pip install uv" to avoid command-not-found errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture, well-tested, a few concrete issues to address before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
Security & correctness fixes:
1. collect_agent_env_vars now calls reject_pipeline_injection (covers
ADO expressions, pipeline commands, template markers, newlines)
instead of only contains_pipeline_command. Also rejects single
quotes alongside double quotes.
2. collect_agent_env_vars now deduplicates env var keys — bails on
collision instead of silently emitting duplicate YAML keys.
3. generate_ensure_npmrc diagnostic echo lines switched from
double-quotes to single-quotes, preventing ${VAR} shell expansion
if a feed URL contained that pattern.
4. docs/runtimes.md corrected: auth tasks are conditional on feed-url
or config being set, not unconditional. Config field descriptions
updated to reflect warning-not-error behavior. Added note about
PipAuthenticate@1 empty artifactFeeds limitation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Good overall structure and security posture — a few correctness issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
PipAuthenticate@1 with empty artifactFeeds doesn't authenticate to any specific feed. Only emit it when feed-url is set — config alone is not sufficient since the config file won't be available in AWF yet anyway. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Good structure and security-conscious design — two issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
Summary
Adds unified Python and Node.js runtime extensions, replacing the divergent approaches in PRs #398 and #399 with a consistent architecture matching the existing Lean runtime pattern.
Python runtime (
runtimes: python:):UsePythonVersion@0for version pinningPipAuthenticate@1emitted whenfeed-url:is setPIP_INDEX_URL+UV_DEFAULT_INDEXenv var injection viafeed-url:fieldpython,python3,pip,pip3,uvNode.js runtime (
runtimes: node:):NodeTool@0generation (decoupled from ado-script'snode_tool_step())npmAuthenticate@0(+ ensure-.npmrcstep) emitted whenfeed-url:orconfig:is setNPM_CONFIG_REGISTRYenv var injection viafeed-url:fieldnode,npm,npxShared infrastructure:
agent_env_vars()trait method onCompilerExtensionwithextension_enum!macro dispatchcollect_agent_env_vars()in common.rs — collects, deduplicates (bails on collision), validates againstBLOCKED_ENV_KEYSandreject_pipeline_injection, formats as YAML, wired intocompile_sharedvia{{ engine_env }}validate_feed_url()in validate.rs — rejects ADO expressions, pipeline commands, template markers, newlines, and quote charactersconfig:field on both runtimes — accepted with a compile-time warning that the config file will not be available inside the AWF agent environment yet (pending gh-aw-firewall#2547)Key design decisions:
UsePythonVersion@0/NodeTool@0install to/opt/hostedtoolcache(auto-mounted read-only by AWF) and publish##vso[task.prependpath]entries that AWF merges via$GITHUB_PATHPIP_INDEX_URL,NPM_CONFIG_REGISTRY) — no config files generated, avoiding conflicts with AWF's~/.npmrcto/dev/nullcredential overlayPipAuthenticate@1requiresfeed-url:,npmAuthenticate@0requiresfeed-url:orconfig:config:accepted with warning (not error) on both runtimes — mutual exclusivity withfeed-url:enforcedSupersedes #398 and #399.
Test plan
cargo build— compiles cleanly (only pre-existing warnings)cargo test— 1224 tests pass (24+ new), 0 failurescargo clippy --all-targets --all-features— no new warningsagent_env_vars()with and withoutfeed-url:,config:warning behavior, config+feed-url mutual exclusivity (both runtimes),validate_feed_url()(valid URLs, missing scheme, injection attempts, quote rejection), prepare_steps with/without feed-url, phase ordering with Python+Node+tools, multiple runtimes simultaneously