Skip to content

feat(runtimes): add Python runtime extension with UsePythonVersion@0 and internal feed support#399

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/implement-python-runtime-extension
Draft

feat(runtimes): add Python runtime extension with UsePythonVersion@0 and internal feed support#399
Copilot wants to merge 2 commits intomainfrom
copilot/implement-python-runtime-extension

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

Adds a Python runtime extension (runtimes: python:) that installs Python via the UsePythonVersion@0 ADO task and optionally redirects pip/uv to internal package feeds by injecting env vars into the AWF agent sandbox.

Changes

New: Python runtime (src/runtimes/python/)

  • PythonRuntimeConfig — accepts true (simple enablement) or object with version, index-url, extra-index-url
  • PythonExtension — implements CompilerExtension:
    • Emits UsePythonVersion@0 task step into prepare_steps when version: is set
    • Adds python, python3, pip, pip3 to bash allow-list
    • Adds python ecosystem identifier to AWF domain allowlist (expands to PyPI domains)
    • Injects PIP_INDEX_URL + UV_DEFAULT_INDEX (from index-url:) and PIP_EXTRA_INDEX_URL (from extra-index-url:) into the agent env: block

New: agent_env_vars() extension hook

  • Added agent_env_vars() -> Vec<(String, String)> to CompilerExtension trait — lets any extension contribute env vars to the AWF agent step's env: block
  • collect_agent_env_vars() in compile_shared() collects, deduplicates (first-wins), and YAML-formats these into the engine env block

Front matter example

runtimes:
  python:
    version: "3.12"
    index-url: "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/pypi/simple/"
    extra-index-url: "https://pypi.org/simple/"  # optional fallback

Validation

Feed URLs are validated against: ADO expression injection ($(, ${{), pipeline commands (##vso[), template markers ({{), newlines, and ASCII control characters.

Test plan

cargo test — all existing tests pass; new tests added for PythonExtension behavior (prepare steps, agent env vars, validation) and collect_agent_env_vars (deduplication, YAML escaping).

Copilot AI and others added 2 commits May 5, 2026 08:06
…and internal feed support

Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/c1dc19f4-a62e-4e64-8add-7e292dcf7e0b

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
…ation and escaping docs

Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/c1dc19f4-a62e-4e64-8add-7e292dcf7e0b

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
jamesadevine added a commit that referenced this pull request May 5, 2026
* feat(runtimes): add unified Node.js and Python runtime extensions

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>

* fix(runtimes): ensure .npmrc exists before npmAuthenticate@0

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>

* fix(runtimes): fix dead-code validation ordering, accept node config 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>

* fix(runtimes): reject double-quote in feed URLs and agent env var values

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>

* fix(runtimes): Python config: produces warning instead of error

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>

* refactor(runtimes): make auth tasks conditional on feed-url or config

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>

* fix(runtimes): reject single-quote in feed URLs, fix doc comments, soften 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>

* fix(runtimes): harden env var collection, fix docs and bash quoting

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>

* refactor(runtimes): PipAuthenticate requires feed-url, not config alone

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>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants