Skip to content

Commit e79528a

Browse files
committed
split to stub
1 parent ae69590 commit e79528a

4 files changed

Lines changed: 463 additions & 295 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Stub workflow — A copy of this workflow must live on the default branch (3.0) so that the
2+
# pull_request_target event can trigger it with access to GITHUB_TOKEN (pull-requests: write).
3+
# It delegates all real work to the reusable template on tomls/base/main.
4+
#
5+
# This two-stage design lets fork PRs trigger the check safely: the stub runs in the
6+
# context of the default branch (with write token), but the reusable workflow checks out
7+
# the PR's data files (TOML configs, specs) into a separate directory — never mixing
8+
# untrusted code with execution context.
9+
#
10+
# The stub must exist on the default branch because pull_request_target always runs
11+
# workflows from there. The reusable workflow on tomls/base/main has the actual scripts,
12+
# container setup, and rendering logic.
13+
name: Check Rendered Specs
14+
15+
# pull_request_target gives us a GITHUB_TOKEN with pull-requests: write even for fork PRs.
16+
# The stub itself runs NO code from the PR — it only delegates to a trusted reusable
17+
# workflow pinned to tomls/base/main, which checks out PR data (not code) into an
18+
# isolated subdirectory.
19+
on: # zizmor: ignore[dangerous-triggers]
20+
pull_request_target:
21+
branches:
22+
- tomls/base/main
23+
24+
permissions: {}
25+
26+
concurrency:
27+
group: render-check-${{ github.event.pull_request.number }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
check:
32+
# Prevent forks from running a stale/vulnerable copy of this stub with Actions enabled
33+
if: github.repository == 'microsoft/azurelinux'
34+
# Intentionally branch-pinned so the reusable workflow picks up updates automatically.
35+
uses: microsoft/azurelinux/.github/workflows/check-rendered-specs.yml@tomls/base/main # zizmor: ignore[unpinned-uses]
36+
permissions:
37+
contents: read
38+
pull-requests: write # Post/update/delete drift comments on PRs
39+
with:
40+
pr-head-sha: ${{ github.event.pull_request.head.sha }}
41+
pr-head-repo: ${{ github.event.pull_request.head.repo.full_name }}
42+
pr-number: ${{ github.event.pull_request.number }}
43+
repo: ${{ github.repository }}

.github/workflows/check-rendered-specs.yml

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
1-
name: Check Rendered Specs
1+
# Reusable workflow — renders specs from a PR and checks for drift.
2+
#
3+
# Called by the stub on the default branch (check-rendered-specs-stub.yml) via
4+
# pull_request_target. The stub provides the PR details; this workflow does all
5+
# the real work:
6+
# 1. Checks out base branch (trusted tools/scripts)
7+
# 2. Checks out PR head into pr-head/ (untrusted data — TOML configs, specs)
8+
# 3. Renders specs inside a privileged container using azldev -C pr-head/
9+
# 4. Checks for drift (compares rendered output against PR's committed specs)
10+
# 5. Posts a PR comment with results + downloadable fix patch
11+
#
12+
# Security: the PR checkout is data-only. We never execute code from the PR —
13+
# azldev is installed from upstream, scripts come from the base branch checkout.
14+
name: "Check Rendered Specs"
215

316
on:
4-
pull_request:
5-
branches:
6-
- tomls/base/main
17+
workflow_call:
18+
inputs:
19+
pr-head-sha:
20+
required: true
21+
type: string
22+
pr-head-repo:
23+
required: true
24+
type: string
25+
pr-number:
26+
required: true
27+
type: string
28+
repo:
29+
required: true
30+
type: string
731

832
permissions: {}
933

10-
concurrency:
11-
group: render-check-${{ github.event.pull_request.number }}
12-
cancel-in-progress: true
13-
1434
jobs:
1535
check:
1636
name: Rendered specs freshness
@@ -19,11 +39,23 @@ jobs:
1939
contents: read
2040
pull-requests: write # Post/update/delete drift comments on PRs
2141
steps:
22-
- name: Checkout
42+
# --- Trusted base branch (tools, scripts, container config) ---
43+
- name: Checkout base (trusted)
2344
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2445
with:
46+
repository: ${{ inputs.repo }}
47+
ref: tomls/base/main
2548
persist-credentials: false
49+
50+
# --- PR head (untrusted data — TOML configs, overlays, specs) ---
51+
- name: Checkout PR head (data)
52+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
53+
with:
54+
repository: ${{ inputs.pr-head-repo }}
55+
ref: ${{ inputs.pr-head-sha }}
56+
path: pr-head
2657
fetch-depth: 0
58+
persist-credentials: false
2759

2860
- name: Set up Go
2961
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
@@ -46,13 +78,18 @@ jobs:
4678
-f .github/workflows/containers/render.Dockerfile \
4779
.github/workflows/containers/
4880
81+
# Render runs inside a privileged container because mock needs mount
82+
# namespaces. Only the PR checkout is mounted (not the full workspace) —
83+
# this prevents a malicious spec's %() macro from writing to the trusted
84+
# base checkout where our scripts live. The azldev binary is mounted
85+
# read-only from the host's GOPATH.
4986
- name: Render specs
5087
env:
5188
WORKSPACE: ${{ github.workspace }}
5289
run: |
5390
set -o pipefail
5491
docker run --privileged --rm \
55-
-v "$WORKSPACE:/workdir" \
92+
-v "$WORKSPACE/pr-head:/workdir" \
5693
-v "$(go env GOPATH)/bin:/gobin:ro" \
5794
-e PATH="/gobin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
5895
azldev-render \
@@ -62,7 +99,7 @@ jobs:
6299
- name: Resolve specs directory
63100
id: specs-dir
64101
run: |
65-
SPECS_DIR=$(azldev config dump -q | grep 'rendered-specs-dir' | cut -d"'" -f2)
102+
SPECS_DIR=$(azldev -C pr-head config dump -q | grep 'rendered-specs-dir' | cut -d"'" -f2)
66103
echo "path=$SPECS_DIR" >> "$GITHUB_OUTPUT"
67104
68105
- name: Check for drift
@@ -71,10 +108,11 @@ jobs:
71108
env:
72109
SPECS_DIR: ${{ steps.specs-dir.outputs.path }}
73110
run: |
74-
python .github/workflows/scripts/check_rendered_specs.py \
111+
cd pr-head
112+
python -I "$GITHUB_WORKSPACE/.github/workflows/scripts/check_rendered_specs.py" \
75113
--specs-dir "$SPECS_DIR" \
76-
--report render-check-report.json \
77-
--patch rendered-specs.patch
114+
--report "$GITHUB_WORKSPACE/render-check-report.json" \
115+
--patch "$GITHUB_WORKSPACE/rendered-specs.patch"
78116
79117
- name: Upload fix patch
80118
id: upload-patch
@@ -85,8 +123,6 @@ jobs:
85123
archive: false
86124

87125
# Also upload as a zipped artifact so `gh run download` works.
88-
# The non-zipped upload above is for browser preview/download, but
89-
# `gh run download` doesn't support non-zipped artifacts yet.
90126
# See: https://github.com/cli/cli/issues/13012
91127
- name: Upload fix patch (for gh cli)
92128
if: hashFiles('rendered-specs.patch') != ''
@@ -100,14 +136,12 @@ jobs:
100136
continue-on-error: true
101137
env:
102138
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
103-
PR_REPO: ${{ github.repository }}
104-
PR_NUMBER: ${{ github.event.pull_request.number }}
105-
SPECS_DIR: ${{ steps.specs-dir.outputs.path }}
139+
PR_REPO: ${{ inputs.repo }}
140+
PR_NUMBER: ${{ inputs.pr-number }}
106141
PATCH_URL: ${{ steps.upload-patch.outputs.artifact-url }}
107142
RUN_ID: ${{ github.run_id }}
108143
run: |
109-
python .github/workflows/scripts/check_rendered_specs.py \
110-
--specs-dir "$SPECS_DIR" \
144+
python -I .github/workflows/scripts/post_render_comment.py \
111145
--repo "$PR_REPO" \
112146
--pr "$PR_NUMBER" \
113147
--report render-check-report.json \
@@ -121,4 +155,3 @@ jobs:
121155
name: render-output
122156
path: |
123157
render-output.json
124-
render-check-report.json

0 commit comments

Comments
 (0)