Skip to content

Commit cdacc17

Browse files
PawelWMSAntonio Salinas
andauthored
feat: add sources upload pipeline (#16656)
Adding a pipeline to trigger CT's workflow for uploading sources for new or updated components. It's meant to be triggered by each merge queue item. Notes: - `.github/workflows/scripts/check_rendered_specs.py` is temporary until #16656 is merged and can be switched to the version of that script from that PR. - The pipeline won't fail for the time being until we know it's stable and can be marked as required. - The pipeline is meant for non-fork PRs **only** - enabling forks require security hardening (out of scope for this PR). Other work: - Added AI instructions for how we want the ADO pipelines written. - Refactored scripts into subdirectories for clearer grouping and to scope down the dependencies for each inside their `requiremenets.txt` files. Co-authored-by: Antonio Salinas <asalinas@microsoft.com>
1 parent 225d678 commit cdacc17

17 files changed

Lines changed: 1816 additions & 59 deletions

.github/instructions/ado-pipeline.instructions.md

Lines changed: 302 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Microsoft Corporation
2+
#
3+
# Wrapper pipeline — passed to ADO as the entry point. This file owns all
4+
# OneBranch-specific wiring (governed templates repo, Official vs NonOfficial
5+
# variant, featureFlags) and delegates the actual stages/jobs/steps to the
6+
# raw stages template at:
7+
# .github/workflows/ado/templates/sources-upload-stages.yml
8+
#
9+
# Authenticates via Workload Identity Federation (OIDC) and calls the Control
10+
# Tower prcheck API with PR context.
11+
#
12+
# Prerequisites (ADO / Azure Portal):
13+
# 1. Entra ID App Registration with audience URI
14+
# "api://<ControlTower-ClientId>" (see variable group below).
15+
# 2. Federated identity credential on the app registration for the ADO
16+
# service connection (issuer: https://vstoken.dev.azure.com/<org-id>,
17+
# subject: sc://<org>/<project>/<service-connection-name>).
18+
# 3. ARM service connection in ADO project settings using Workload Identity
19+
# Federation (manual).
20+
# 4. ADO branch policy or pipeline PR trigger configured to fire on PRs.
21+
#
22+
# Variable Group (ADO Pipelines > Library):
23+
# Name: "ControlTower-PRCheck"
24+
# Required variables:
25+
# - ApiAudience : Entra ID audience URI for the Control Tower app
26+
# - ApiBaseUrl : Base URL of the Control Tower service
27+
# - AzldevCommit : Commit hash for azldev (go install ...@<commit>)
28+
29+
# Trigger controlled by ADO branch policy — not YAML triggers.
30+
trigger: none
31+
32+
pr: none
33+
34+
resources:
35+
repositories:
36+
- repository: templates
37+
type: git
38+
name: OneBranch.Pipelines/GovernedTemplates
39+
ref: refs/heads/main
40+
41+
extends:
42+
template: v2/OneBranch.Official.CrossPlat.yml@templates
43+
parameters:
44+
featureFlags:
45+
golang:
46+
internalModuleProxy:
47+
enabled: true
48+
LinuxHostVersion:
49+
Network: R1
50+
runOnHost: true
51+
EnableCDPxPAT: false
52+
53+
# https://aka.ms/obpipelines/sdl
54+
globalSdl:
55+
disableLegacyManifest: true
56+
sbom:
57+
enabled: false
58+
tsa:
59+
enabled: false
60+
61+
stages:
62+
- template: /.github/workflows/ado/templates/sources-upload-stages.yml@self
63+
parameters:
64+
outputDirectory: $(Build.ArtifactStagingDirectory)/output
65+
artifactBaseName: prcheck
66+
containerImage: mcr.microsoft.com/onebranch/azurelinux/build:3.0
67+
poolType: linux
68+
serviceConnection: CT-Endpoints-Access-ServiceConnection-DEV
69+
variableGroup: ControlTower-PRCheck
70+
# Must exceed the script's --poll-timeout-seconds (default 7200s = 120m)
71+
# with enough headroom for setup steps and the final API call.
72+
timeoutInMinutes: 150
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Microsoft Corporation
2+
#
3+
# Raw stages template for the sources-upload PR check pipeline.
4+
#
5+
# This template is OneBranch-agnostic: it declares the stages/jobs/steps that
6+
# do the actual work and exposes the OneBranch-coupled knobs as parameters.
7+
# The wrapper at .github/workflows/ado/sources-upload.yml is responsible for
8+
# choosing the OneBranch governed template variant (Official vs NonOfficial),
9+
# configuring its featureFlags, and supplying concrete values for the
10+
# parameters declared below.
11+
12+
parameters:
13+
- name: outputDirectory
14+
type: string
15+
- name: artifactBaseName
16+
type: string
17+
- name: containerImage
18+
type: string
19+
- name: poolType
20+
type: string
21+
default: linux
22+
- name: serviceConnection
23+
type: string
24+
- name: variableGroup
25+
type: string
26+
- name: timeoutInMinutes
27+
type: number
28+
29+
stages:
30+
- stage: PRCheck
31+
jobs:
32+
- job: CallControlTowerAPI
33+
# Non-blocking PR check: failing steps still render red error annotations
34+
# and surface in the build-issues view, but the job resolves to
35+
# SucceededWithIssues and the run to PartiallySucceeded, which the Azure
36+
# Pipelines GitHub integration reports as success to GitHub PR checks and
37+
# the merge queue. Errors and warnings remain visible to the user.
38+
# TODO: Remove `continueOnError` once the pipeline is stable so failures
39+
# block PRs and the merge queue.
40+
# ADO task: 19179
41+
continueOnError: true
42+
# Must exceed the script's --poll-timeout-seconds (default 7200s = 120m)
43+
# with enough headroom for setup steps and the final API call.
44+
timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
45+
pool:
46+
type: ${{ parameters.poolType }}
47+
variables:
48+
- group: ${{ parameters.variableGroup }}
49+
- name: ob_outputDirectory
50+
value: ${{ parameters.outputDirectory }}
51+
- name: ob_artifactBaseName
52+
value: ${{ parameters.artifactBaseName }}
53+
- name: LinuxContainerImage
54+
value: ${{ parameters.containerImage }}
55+
steps:
56+
- script: |
57+
set -euo pipefail
58+
59+
# For both triggers, Build.SourceVersion is the relevant source commit:
60+
# - PR trigger: ADO sets it to the GitHub-created merge commit (refs/pull/{n}/merge).
61+
# - CI/merge-queue trigger: it is the commit that landed on the base branch.
62+
source_hash="$SOURCE_COMMIT"
63+
64+
if [[ "$BUILD_REASON" == "PullRequest" ]]; then
65+
# PR trigger: ADO provides the target branch directly.
66+
target_hash=$(git ls-remote "$UPSTREAM_REPO_URL" "$PR_TARGET_BRANCH" | cut -f1)
67+
else
68+
# CI/merge-queue trigger: extract the base branch from the merge-queue branch name.
69+
# Branch format: refs/heads/gh-readonly-queue/{base_branch}/pr-{pr_number}-{head_sha}
70+
if ! base_branch=$(grep -oP '(?<=^refs/heads/gh-readonly-queue/).+(?=/pr-[^/]+$)' <<< "$SOURCE_BRANCH"); then
71+
echo "##[error]Unsupported SOURCE_BRANCH '$SOURCE_BRANCH' for non-PullRequest build. Expected 'refs/heads/gh-readonly-queue/<base>/pr-<n>-<sha>'."
72+
exit 1
73+
fi
74+
target_hash=$(git ls-remote "$UPSTREAM_REPO_URL" "refs/heads/$base_branch" | cut -f1)
75+
fi
76+
77+
echo "Source commit: $source_hash"
78+
echo "Target commit: $target_hash"
79+
80+
echo "##vso[task.setvariable variable=sourceCommit]$source_hash"
81+
echo "##vso[task.setvariable variable=targetCommit]$target_hash"
82+
env:
83+
BUILD_REASON: $(Build.Reason)
84+
PR_TARGET_BRANCH: $(System.PullRequest.TargetBranch)
85+
SOURCE_BRANCH: $(Build.SourceBranch)
86+
SOURCE_COMMIT: $(Build.SourceVersion)
87+
UPSTREAM_REPO_URL: $(Build.Repository.Uri)
88+
displayName: "Determine source and target commit hashes"
89+
90+
- task: PipAuthenticate@1
91+
displayName: "Authenticate pip"
92+
inputs:
93+
artifactFeeds: "azl/ControlTowerFeed"
94+
95+
- script: |
96+
set -euo pipefail
97+
98+
echo "##[group]Mock"
99+
tdnf install -y mock mock-rpmautospec python3-chardet
100+
sudo usermod -aG mock "$(whoami)"
101+
echo "##[endgroup]"
102+
103+
echo "##[group]Azldev"
104+
echo "Installing azldev@${AZLDEV_COMMIT}..."
105+
go install "github.com/microsoft/azure-linux-dev-tools/cmd/azldev@${AZLDEV_COMMIT}"
106+
107+
go_bin_path="$(go env GOPATH)/bin"
108+
echo "##vso[task.prependpath]$go_bin_path"
109+
110+
"$go_bin_path/azldev" --version
111+
echo "##[endgroup]"
112+
113+
echo "##[group]Python dependencies"
114+
pip install -r .github/workflows/scripts/control-tower-prcheck/requirements.txt
115+
echo "##[endgroup]"
116+
displayName: "Install dependencies"
117+
env:
118+
AZLDEV_COMMIT: $(AzldevCommit)
119+
120+
- script: |
121+
set -euo pipefail
122+
123+
# Workaround for an ADO git config error during spec rendering.
124+
# The config key may not be present on every agent image, so tolerate its absence.
125+
git config --unset extensions.worktreeConfig || true
126+
127+
# Full history is needed for spec rendering to work.
128+
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
129+
echo "##[group]Fetching full git history"
130+
git fetch --unshallow
131+
echo "##[endgroup]"
132+
fi
133+
134+
# TODO: Replace with using the TOML config to read the specs dir once available.
135+
# The '-o "$specs_dir"' should be removed as part of the update. Future command:
136+
# specs_dir="$(azldev config dump -q -f json | jq -r .project.renderedSpecsDir)"
137+
# Similarly, the component rendering below should change to:
138+
# azldev component render -q -a
139+
# ADO task: 19149
140+
specs_dir="specs"
141+
142+
echo "##[group]Specs rendering"
143+
azldev component render -q -a -o "$specs_dir" --force --clean-stale
144+
echo "##[endgroup]"
145+
146+
# Check for any new or modified files under the specs directory.
147+
# TODO: Remove and replace this script with the one coming from PR #16674.
148+
# ADO task: 19186
149+
if ! python3 .github/workflows/scripts/check_rendered_specs.py --specs-dir "$specs_dir"; then
150+
echo "##[group]Git diff"
151+
git --no-pager diff -- "$specs_dir"
152+
echo "##[endgroup]"
153+
echo "##[error]Specs are not up to date. Run 'azldev component render -q -a' and try again."
154+
exit 1
155+
fi
156+
displayName: "Verify rendered specs are up to date"
157+
# TODO: Re-enabled failing once specs in the main branch are updated and stable.
158+
# ADO task: 19179
159+
continueOnError: true
160+
161+
- script: |
162+
set -euo pipefail
163+
164+
cat << EOF
165+
NOTE: It's possible more components changed between the source and target commits than displayed below.
166+
This workflow only checks for updates in the 'sources' files and ignores all other changes.
167+
Only components present on the source commit are reported; components that exist only on the
168+
target branch are intentionally excluded. Pure renames are reported under their source-branch
169+
name, since the component name is part of the cache blob name we upload.
170+
EOF
171+
172+
# Diff direction is TARGET -> SOURCE so additions/modifications represent the source-side state:
173+
# * --no-renames decomposes a rename into an add (new path, source side) + delete (old path,
174+
# target side); we want the source-side name because the component name is part of the cache
175+
# blob name uploaded for the PR. It also silences git's diff.renameLimit warning on large diffs.
176+
# * --diff-filter=d (lowercase) excludes deletions, which here correspond to paths present only
177+
# on the target branch -- those components are not on the PR and must not be uploaded.
178+
# Net effect: the listed '*/sources' files are exactly those present on the source commit that
179+
# differ from target, including pure renames surfaced under the source-branch name.
180+
sources_files="$(
181+
git diff --no-renames --diff-filter=d "$TARGET_COMMIT" "$SOURCE_COMMIT" --name-only \
182+
| { grep '/sources$' || true; } \
183+
| sort -u
184+
)"
185+
186+
components=()
187+
while IFS= read -r line; do
188+
# Skip the spurious empty iteration when no '*/sources' files changed.
189+
if [[ -n "$line" ]]; then
190+
components+=("$(basename "$(dirname "$line")")")
191+
fi
192+
done <<< "$sources_files"
193+
194+
components="$(IFS=, ; echo "${components[*]}")"
195+
echo "Affected components: $components"
196+
echo "##vso[task.setvariable variable=components]$components"
197+
env:
198+
SOURCE_COMMIT: $(sourceCommit)
199+
TARGET_COMMIT: $(targetCommit)
200+
displayName: "Determine affected components"
201+
202+
- task: AzureCLI@2
203+
displayName: "Call Control Tower 'prcheck' API"
204+
inputs:
205+
azureSubscription: ${{ parameters.serviceConnection }}
206+
scriptType: bash
207+
scriptLocation: inlineScript
208+
inlineScript: |
209+
set -euo pipefail
210+
211+
python3 .github/workflows/scripts/control-tower-prcheck/run_control_tower_prcheck.py \
212+
--api-audience "$API_AUDIENCE" \
213+
--api-base-url "$API_BASE_URL" \
214+
--build-reason "$BUILD_REASON" \
215+
--components "$COMPONENTS" \
216+
--source-commit "$SOURCE_COMMIT" \
217+
--repo-uri "$UPSTREAM_REPO_URL"
218+
env:
219+
API_AUDIENCE: $(ApiAudience)
220+
API_BASE_URL: $(ApiBaseUrl)
221+
BUILD_REASON: $(Build.Reason)
222+
COMPONENTS: $(components)
223+
SOURCE_COMMIT: $(sourceCommit)
224+
# TODO: Target commit is not used. Will be needed once we move detection of affected components to CT.
225+
# ADO task: 18816
226+
TARGET_COMMIT: $(targetCommit)
227+
UPSTREAM_REPO_URL: $(Build.Repository.Uri)

.github/workflows/scripts/README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The easiest way to get quick, interactive feedback is with [GitHub Copilot](http
3838
```bash
3939
cd ~/repos/azurelinux
4040
python -m venv .venv && source .venv/bin/activate
41-
pip install -r .github/workflows/scripts/requirements.txt
41+
pip install -r .github/workflows/scripts/spec-review/requirements.txt
4242
gh copilot
4343
# OR
4444
npm install -g @github/copilot
@@ -51,19 +51,19 @@ copilot --version # verify CLI
5151
# Defaults: reviews all *.spec in repo, writes to repo root
5252
# spec_review_report.json, copilot_log.md, spec_review_kb.md
5353

54-
./.github/workflows/scripts/spec_review.sh \
54+
./.github/workflows/scripts/spec-review/spec_review.sh \
5555
--spec base/comps/azurelinux-release/azurelinux-release.spec \
5656
--spec base/comps/azurelinux-repos/azurelinux-repos.spec
5757

5858
# Validate / inspect
59-
python .github/workflows/scripts/spec_review_schema.py /tmp/spec_review_report.json --all
60-
python .github/workflows/scripts/spec_review_schema.py /tmp/spec_review_report.json --json | jq
59+
python .github/workflows/scripts/spec-review/spec_review_schema.py /tmp/spec_review_report.json --all
60+
python .github/workflows/scripts/spec-review/spec_review_schema.py /tmp/spec_review_report.json --json | jq
6161
```
6262

6363
The script supports additional options (like selecting a model, or using different URLs); run with `--help` for details.
6464

6565
```bash
66-
./.github/workflows/scripts/spec_review.sh --help
66+
./.github/workflows/scripts/spec-review/spec_review.sh --help
6767
```
6868

6969
## Run multi-model review (spec_review_multi.sh)
@@ -79,11 +79,11 @@ uses a third model pass to synthesize their findings into a single high-quality
7979

8080
```bash
8181
# Defaults: claude-opus-4.6 + gpt-5.2-codex reviewers, gpt-5.2-codex synthesizer
82-
./.github/workflows/scripts/spec_review_multi.sh \
82+
./.github/workflows/scripts/spec-review/spec_review_multi.sh \
8383
--spec base/comps/azurelinux-release/azurelinux-release.spec
8484

8585
# Custom models
86-
./.github/workflows/scripts/spec_review_multi.sh \
86+
./.github/workflows/scripts/spec-review/spec_review_multi.sh \
8787
--spec foo.spec \
8888
--model1 gpt-5.2-codex \
8989
--model2 claude-opus-4.6 \
@@ -101,7 +101,7 @@ ls /tmp/spec_review_workdir/
101101
Run with `--help` for all options:
102102

103103
```bash
104-
./.github/workflows/scripts/spec_review_multi.sh --help
104+
./.github/workflows/scripts/spec-review/spec_review_multi.sh --help
105105
```
106106

107107
---
@@ -119,7 +119,7 @@ copilot --agent spec-review
119119
```bash
120120
# For semi-automated or fully automated runs, use -i or -p flags:
121121
prompt="Review: base/comps/azurelinux-release/azurelinux-release.spec against packaging guidelines.\n"\
122-
"Write JSON to spec_review_report.json and validate with: python .github/workflows/scripts/spec_review_schema.py spec_review_report.json"
122+
"Write JSON to spec_review_report.json and validate with: python .github/workflows/scripts/spec-review/spec_review_schema.py spec_review_report.json"
123123

124124
# Semi-interactive (runs prompt, then waits for the user)
125125
copilot --agent spec-review \
@@ -144,7 +144,7 @@ copilot --agent spec-review \
144144
-p "$prompt"
145145

146146
# Review output
147-
python .github/workflows/scripts/spec_review_schema.py spec_review_report.json --all
147+
python .github/workflows/scripts/spec-review/spec_review_schema.py spec_review_report.json --all
148148
```
149149

150150
## Future work

0 commit comments

Comments
 (0)