Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
version: 2

updates:
# Python tooling dependencies — keep pip-installed scripts patched.
# `directories` (plural) supports globbing, so new requirements.txt files
# under these paths are picked up automatically without editing this file.
# `directory` (singular) does NOT support globbing.
- package-ecosystem: pip
directories:
- /scripts/**
schedule:
interval: weekly
open-pull-requests-limit: 5
labels:
- dependencies
- python
# Wait until a release has been public for a few days before opening a PR,
# so yanked/broken releases get caught upstream first. Majors wait longer.
cooldown:
default-days: 7
semver-major-days: 14
# Bundle all python tooling bumps into a single PR per run.
groups:
python:
patterns:
- "*"

# GitHub Actions used by PR-check workflows.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
labels:
- dependencies
- github-actions
# github-actions cooldown only supports a flat default-days (no semver granularity).
cooldown:
default-days: 7
groups:
actions:
patterns:
- "*"
18 changes: 9 additions & 9 deletions .github/instructions/ado-pipeline.instructions.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
applyTo: ".github/workflows/ado/*.yml,.github/workflows/ado/templates/*.yml,.github/workflows/scripts/**"
description: "Authoring and maintenance rules for Azure DevOps YAML pipelines under .github/workflows/ado/ (wrappers + raw stage templates under templates/) and their helper scripts under .github/workflows/scripts/ that run as GitHub PR checks or in the merge queue. Apply when creating or modifying any pipeline in that folder, or any script invoked by one — covers the wrapper/raw split, required OneBranch templates (Official vs NonOfficial), Workload Identity Federation service connections, Control Tower audience URIs, internal-only dependency sources (Go / Python / container images), Python-over-shell scripting, and security hardening."
applyTo: ".github/workflows/ado/*.yml,.github/workflows/ado/templates/*.yml,scripts/ci/**"
description: "Authoring and maintenance rules for Azure DevOps YAML pipelines under .github/workflows/ado/ (wrappers + raw stage templates under templates/) and their helper scripts under scripts/ci/ that run as GitHub PR checks or in the merge queue. Apply when creating or modifying any pipeline in that folder, or any script invoked by one — covers the wrapper/raw split, required OneBranch templates (Official vs NonOfficial), Workload Identity Federation service connections, Control Tower audience URIs, internal-only dependency sources (Go / Python / container images), Python-over-shell scripting, and security hardening."
---

# Azure DevOps Pipelines (PR check & merge queue)

These instructions cover ADO YAML pipelines under `.github/workflows/ado/` that run as GitHub PR checks or in the merge queue, plus their helper scripts under `.github/workflows/scripts/`. Follow every MUST below — they encode internal Microsoft policy plus security hardening for this repo.
These instructions cover ADO YAML pipelines under `.github/workflows/ado/` that run as GitHub PR checks or in the merge queue, plus their helper scripts under `scripts/ci/`. Follow every MUST below — they encode internal Microsoft policy plus security hardening for this repo.

Helper scripts invoked from these pipelines MUST live under `.github/workflows/scripts/<area>/`. Two layouts are supported:
Helper scripts invoked from these pipelines MUST live under `scripts/ci/<area>/`. Two layouts are supported:

1. **Per-pipeline** — `<area>/` is a logical area or pipeline name (e.g. `control-tower/`, `render-specs-check/`). The default.
2. **Cross-pipeline shared** — `<area>/` = `components/`. Helpers here are deliberately consumed by multiple pipelines (GitHub Actions PR gates AND the ADO Control Tower pipeline). See [`.github/workflows/scripts/components/README.md`](../workflows/scripts/components/README.md) for the caller contract. A regression in this area breaks multiple gates at once, so changes need extra care.
2. **Cross-pipeline shared** — `<area>/` = `components/`. Helpers here are deliberately consumed by multiple pipelines (GitHub Actions PR gates AND the ADO Control Tower pipeline). See [`scripts/ci/components/README.md`](../../scripts/ci/components/README.md) for the caller contract. A regression in this area breaks multiple gates at once, so changes need extra care.

In either case, keep helpers self-contained and follow the same security hardening rules as the pipeline YAML itself.

Expand Down Expand Up @@ -159,7 +159,7 @@ The authoritative list and rules live at https://eng.ms/docs/more/containers-sec

## Scripting

Avoid shell scripts beyond the smallest possible wiring (env exports, `##vso[...]` log commands, single-command tool invocations). For anything more complex — argument parsing, control flow, JSON/YAML handling, HTTP, branch-name/SHA parsing — write a **Python script** under `.github/workflows/scripts/<area>/` and invoke it from the pipeline:
Avoid shell scripts beyond the smallest possible wiring (env exports, `##vso[...]` log commands, single-command tool invocations). For anything more complex — argument parsing, control flow, JSON/YAML handling, HTTP, branch-name/SHA parsing — write a **Python script** under `scripts/ci/<area>/` and invoke it from the pipeline:

```yaml
- task: AzureCLI@2
Expand All @@ -170,7 +170,7 @@ Avoid shell scripts beyond the smallest possible wiring (env exports, `##vso[...
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
python3 .github/workflows/scripts/<area>/<script>.py \
python3 scripts/ci/<area>/<script>.py \
--api-audience "$API_AUDIENCE" \
--api-base-url "$API_BASE_URL"
env:
Expand Down Expand Up @@ -291,7 +291,7 @@ stages:
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
python3 .github/workflows/scripts/<area>/<script>.py \
python3 scripts/ci/<area>/<script>.py \
--api-audience "$API_AUDIENCE" \
--api-base-url "$API_BASE_URL"
env:
Expand All @@ -314,6 +314,6 @@ Run through this list whenever you add or modify a pipeline in `.github/workflow
- [ ] All Azure / Control Tower access goes through `AzureCLI@2` with a least-privilege Service Connection (WIF/OIDC). No PATs or password logins.
- [ ] Control Tower calls use the correct **per-environment audience URI** sourced from a variable group; nothing hardcoded.
- [ ] No upstream dependency pulls: Go uses `internalModuleProxy`, Python uses `PipAuthenticate@1`, container images come from `mcr.microsoft.com`.
- [ ] Any non-trivial logic lives in a Python script under `.github/workflows/scripts/`, not inline bash.
- [ ] Any non-trivial logic lives in a Python script under `scripts/ci/`, not inline bash.
- [ ] Secrets passed via `env:` (never inline `$(...)`); `set -euo pipefail` in shell blocks; tool versions pinned (only `GovernedTemplates@main` exempted).
- [ ] Explicit `timeoutInMinutes` on each job; PR-supplied inputs validated with strict regex.
2 changes: 1 addition & 1 deletion .github/instructions/pr-check-workflows.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Build it with the caller's UID so bind-mounted writes don't end up root-owned:
| ----- | ---- | ------- |
| `pr-head/` → `/workdir` | rw | PR checkout. rw because `azldev` writes to `specs/`, `base/build/`, etc. |
| `<host-output-dir>/` → `/output` | rw | Trusted-shape outputs (JSON reports, patches, ...) the container produces for the host to consume after the run. |
| `.github/workflows/scripts/<area>/` → `/scripts` | ro | Helper scripts from the trusted base checkout. Mount the narrowest subdirectory the container actually needs — do **not** bind-mount the whole `scripts/` tree if only one area is invoked inside. |
| `scripts/ci/<area>/` → `/scripts` | ro | Helper scripts from the trusted base checkout. Mount the narrowest subdirectory the container actually needs — do **not** bind-mount the whole `scripts/` tree if only one area is invoked inside. |

#### Sandbox flags (minimum viable for `mock`)

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ado/sources-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
# from the merge queue, not here.
# 2. Submit scratch package builds for changed components.
#
# Helper scripts live under .github/workflows/scripts/control-tower/
# (Control Tower-specific) and .github/workflows/scripts/components/
# Helper scripts live under scripts/ci/control-tower/
# (Control Tower-specific) and scripts/ci/components/
# (cross-pipeline azldev helpers shared with the GH Actions PR gates).
#
# Prerequisites (ADO / Azure Portal):
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/ado/templates/sources-upload-stages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ stages:
echo "##[endgroup]"

echo "##[group]Python dependencies"
pip install -r .github/workflows/scripts/control-tower/requirements.txt
pip install -r scripts/ci/control-tower/requirements.txt
echo "##[endgroup]"
displayName: "Install dependencies"

# Verify lock files are current. --check-only validates without
# writing, exits nonzero if any lock would change.
- script: |
set -euo pipefail
.github/workflows/scripts/control-tower/verify_locks.sh \
scripts/ci/control-tower/verify_locks.sh \
--output-file "$(Build.ArtifactStagingDirectory)/lock-update.json" \
--publish-dir "$(ob_outputDirectory)"
displayName: "Verify lock files are up to date"
Expand Down Expand Up @@ -168,7 +168,7 @@ stages:
trap publish_artifact EXIT

echo "##[group]Preparing change set"
.github/workflows/scripts/components/compute_change_set.sh \
scripts/ci/components/compute_change_set.sh \
--output-dir "$change_set_dir" \
--source-commit "$(sourceCommit)" \
--target-commit "$(targetCommit)"
Expand Down Expand Up @@ -232,7 +232,7 @@ stages:
inlineScript: |
set -euo pipefail

python3 .github/workflows/scripts/control-tower/run_prcheck.py \
python3 scripts/ci/control-tower/run_prcheck.py \
--api-audience "$API_AUDIENCE" \
--api-base-url "$API_BASE_URL" \
--build-reason "$BUILD_REASON" \
Expand All @@ -259,7 +259,7 @@ stages:
inlineScript: |
set -euo pipefail

python3 .github/workflows/scripts/control-tower/run_package_build.py \
python3 scripts/ci/control-tower/run_package_build.py \
--api-audience "$API_AUDIENCE" \
--api-base-url "$API_BASE_URL" \
--build-reason "$BUILD_REASON" \
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/check-rendered-specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ jobs:
PATCH_URL: ${{ needs.update_locks.outputs.patch-url }}
RUN_ID: ${{ github.run_id }}
run: |
python .github/workflows/scripts/locks-check/post_locks_comment.py \
python scripts/ci/locks-check/post_locks_comment.py \
--repo "$PR_REPO" \
--pr "$PR_NUMBER" \
--update-output update-output/update-output.json \
Expand Down Expand Up @@ -341,8 +341,8 @@ jobs:
--security-opt apparmor=unconfined \
-v "$WORKSPACE/pr-head:/workdir" \
-v "$WORKSPACE/render-output:/output" \
-v "$WORKSPACE/.github/workflows/scripts/render-specs-check:/scripts-render:ro" \
-v "$WORKSPACE/.github/workflows/scripts/components:/scripts-components:ro" \
-v "$WORKSPACE/scripts/ci/render-specs-check:/scripts-render:ro" \
-v "$WORKSPACE/scripts/ci/components:/scripts-components:ro" \
-e PR_BASE_SHA \
-e PR_HEAD_SHA \
-e BASE_REPO \
Expand Down Expand Up @@ -512,7 +512,7 @@ jobs:
PATCH_URL: ${{ needs.render.outputs.patch-url }}
RUN_ID: ${{ github.run_id }}
run: |
python .github/workflows/scripts/render-specs-check/post_render_comment.py \
python scripts/ci/render-specs-check/post_render_comment.py \
--repo "$PR_REPO" \
--pr "$PR_NUMBER" \
--report render-output/render-check-report.json \
Expand Down
74 changes: 74 additions & 0 deletions .github/workflows/dependency-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: "Dependency Smoke Test"

# Sanity-check that the pip-installed helper scripts still install and import
# after a dependency bump. Primarily a guard for Dependabot PRs that touch a
# requirements.txt, but also runs on any edit to the scripts themselves.
on:
pull_request:
paths:
- "scripts/**/requirements.txt"
- "scripts/**/*.py"
- ".github/workflows/dependency-smoke.yml"

# Cancel in-progress runs of this workflow if a new run is triggered.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

jobs:
smoke:
name: "Smoke: ${{ matrix.dir }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
dir:
- scripts/ci/control-tower
- scripts/ci/spec-review
- scripts/mcps
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

- name: Install and smoke-test
env:
DIR: ${{ matrix.dir }}
run: |
set -euo pipefail
cd "$DIR"

# A clean venv ensures we exercise exactly the pinned requirements.
python3 -m venv .smoke-venv
# shellcheck disable=SC1091
. .smoke-venv/bin/activate
python -m pip install --quiet --upgrade pip
pip install --quiet -r requirements.txt

shopt -s nullglob
scripts=( *.py )
if (( ${#scripts[@]} == 0 )); then
echo "No python scripts in $DIR; install-only smoke passed."
exit 0
fi

# Syntax-compile every script (works regardless of filename).
python -m py_compile "${scripts[@]}"

# Import each importable module to exercise the bumped dependencies.
# Skip files whose name is not a valid module identifier (e.g. hyphenated).
for f in "${scripts[@]}"; do
mod="${f%.py}"
case "$mod" in
*[!a-zA-Z0-9_]*)
echo "skip import: $f (filename is not a valid module name)"
continue
;;
esac
echo "import $mod"
python -c "import $mod"
done
1 change: 0 additions & 1 deletion .github/workflows/scripts/spec-review/requirements.txt

This file was deleted.

20 changes: 10 additions & 10 deletions .github/workflows/spec-review.disabled
Original file line number Diff line number Diff line change
Expand Up @@ -146,26 +146,26 @@ jobs:

- name: Install dependencies
if: steps.changed-specs.outputs.skip != 'true'
run: pip install -r .github/workflows/scripts/spec-review/requirements.txt
run: pip install -r scripts/ci/spec-review/requirements.txt

- name: Install Copilot CLI
if: steps.changed-specs.outputs.skip != 'true'
run: |
npm i -g @github/copilot
copilot --version

# Local developer guide: .github/workflows/scripts/spec-review/README.md
# Local developer guide: scripts/ci/spec-review/README.md
- name: Run Copilot spec review (multi-model)
if: steps.changed-specs.outputs.skip != 'true'
run: |
chmod +x .github/workflows/scripts/spec-review/spec_review_multi.sh
chmod +x scripts/ci/spec-review/spec_review_multi.sh
# Build a properly-quoted array from the line-delimited spec list file
SPEC_ARGS=()
while IFS= read -r spec; do
[[ -z "$spec" ]] && continue
SPEC_ARGS+=("--spec" "$spec")
done < .spec_review_paths.txt
.github/workflows/scripts/spec-review/spec_review_multi.sh "${SPEC_ARGS[@]}" \
scripts/ci/spec-review/spec_review_multi.sh "${SPEC_ARGS[@]}" \
--work-dir .spec_review/workdir
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
run: |
# NOTE: Label model names must match defaults in spec-review/spec_review_multi.sh
# (DEFAULT_MODEL1, DEFAULT_MODEL2, DEFAULT_SYNTH_MODEL)
python .github/workflows/scripts/spec-review/spec_review_schema.py compare \
python scripts/ci/spec-review/spec_review_schema.py compare \
.spec_review/workdir/report_a.json \
.spec_review/workdir/report_b.json \
.spec_review/report.json \
Expand All @@ -232,7 +232,7 @@ jobs:
id: validate
run: |
# Validate JSON structure
python .github/workflows/scripts/spec-review/spec_review_schema.py .spec_review/report.json --json
python scripts/ci/spec-review/spec_review_schema.py .spec_review/report.json --json

# Count errors and warnings
ERROR_COUNT=$(jq '[.spec_reviews[].errors | length] | add // 0' .spec_review/report.json)
Expand All @@ -251,7 +251,7 @@ jobs:
if: steps.changed-specs.outputs.skip != 'true'
run: |
# Generate workflow commands that create inline annotations
python .github/workflows/scripts/spec-review/create_check_annotations.py .spec_review/report.json \
python scripts/ci/spec-review/create_check_annotations.py .spec_review/report.json \
--repo-root "$GITHUB_WORKSPACE" --workflow-commands

- name: Post PR comment
Expand All @@ -264,7 +264,7 @@ jobs:
run: |
# Generate formatted comment to a temp file (avoids ARG_MAX issues with large comments)
COMMENT_FILE=$(mktemp)
python .github/workflows/scripts/spec-review/format_pr_comment.py \
python scripts/ci/spec-review/format_pr_comment.py \
.spec_review/report.json \
--repo "$REPO" \
--sha "$HEAD_SHA" \
Expand Down Expand Up @@ -295,7 +295,7 @@ jobs:
REPO: ${{ inputs.repo || github.repository }}
HEAD_SHA: ${{ inputs.pr-head-sha || github.sha }}
run: |
python .github/workflows/scripts/spec-review/format_pr_comment.py \
python scripts/ci/spec-review/format_pr_comment.py \
.spec_review/report.json \
--repo "$REPO" \
--sha "$HEAD_SHA" \
Expand All @@ -320,5 +320,5 @@ jobs:
echo "Found ${ERROR_COUNT} error(s) in spec review"
echo ""
# Print all findings so "View details" shows everything
python .github/workflows/scripts/spec-review/spec_review_schema.py .spec_review/report.json --all
python scripts/ci/spec-review/spec_review_schema.py .spec_review/report.json --all
exit 1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Control Tower integration pipeline
upload the output dir as they see fit.
- **azldev as root.** All `azldev` invocations use an inline
`AZLDEV_ALLOW_ROOT=1` prefix per
[`ado-pipeline.instructions.md`](../../../instructions/ado-pipeline.instructions.md).
[`ado-pipeline.instructions.md`](../../../.github/instructions/ado-pipeline.instructions.md).
Callers do **not** set this at step scope.
- **Single source of truth.** Both pipelines should call these scripts
rather than re-implementing the change-set computation. A regression
Expand Down
Loading
Loading