Skip to content

Commit 8a47fe4

Browse files
committed
Auto-approve workflow for code flow PRs
1 parent 8bf0f7f commit 8a47fe4

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Auto-approve codeflow PRs
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
permissions:
8+
pull-requests: write
9+
10+
jobs:
11+
auto-approve-codeflow:
12+
runs-on: ubuntu-latest
13+
if: github.event.pull_request.user.login == 'dotnet-maestro[bot]'
14+
steps:
15+
- name: Validate and auto-approve codeflow PR
16+
env:
17+
GH_TOKEN: ${{ github.token }}
18+
PR_NUMBER: ${{ github.event.pull_request.number }}
19+
GITHUB_REPOSITORY: ${{ github.repository }}
20+
run: |
21+
python3 - <<'PYTHON'
22+
import os, re, subprocess, sys
23+
24+
pr_number = os.environ["PR_NUMBER"]
25+
repo = os.environ["GITHUB_REPOSITORY"]
26+
27+
def gh(*args: str) -> str:
28+
result = subprocess.run(
29+
["gh", *args, "--repo", repo],
30+
capture_output=True, text=True, check=True)
31+
return result.stdout
32+
33+
# ---- allowed files ------------------------------------------------
34+
ALLOWED_FILES = {
35+
"eng/Version.Details.xml",
36+
"eng/Version.Details.props",
37+
"eng/Versions.props",
38+
"global.json",
39+
"NuGet.config",
40+
}
41+
42+
# ---- regex helpers ------------------------------------------------
43+
VERSION = r'[0-9]+\.[0-9]+[A-Za-z0-9.\-+]*'
44+
SHA = r'[0-9a-fA-F]{7,40}'
45+
BARID = r'[0-9]+'
46+
URL = r'https://[^\s"<>]+'
47+
48+
# Patterns per file – every added/removed line must match at least one
49+
FILE_PATTERNS: dict[str, list[re.Pattern]] = {
50+
"eng/Version.Details.xml": [
51+
re.compile(rf'^\s*<Source\s+Uri="{URL}"\s+Mapping="[^"]+"\s+Sha="{SHA}"\s+BarId="{BARID}"\s*/>\s*$'),
52+
re.compile(rf'^\s*<Sha>{SHA}</Sha>\s*$'),
53+
re.compile(rf'^\s*<Dependency\s+Name="[^"]+"\s+Version="{VERSION}">\s*$'),
54+
re.compile(rf'^\s*<Uri>{URL}</Uri>\s*$'),
55+
],
56+
"eng/Version.Details.props": [
57+
re.compile(rf'^\s*<[A-Za-z][A-Za-z0-9]*>{VERSION}</[A-Za-z][A-Za-z0-9]*>\s*$'),
58+
re.compile(r'^\s*<!--.*-->\s*$'),
59+
],
60+
"eng/Versions.props": [
61+
re.compile(rf'^\s*<[A-Za-z][A-Za-z0-9]*>{VERSION}</[A-Za-z][A-Za-z0-9]*>\s*$'),
62+
re.compile(r'^\s*<!--.*-->\s*$'),
63+
],
64+
"global.json": [
65+
re.compile(rf'^\s*"[^"]+"\s*:\s*"{VERSION}"\s*,?\s*$'),
66+
],
67+
"NuGet.config": [
68+
re.compile(rf'^\s*<add\s+key="darc-[^"]+"\s+value="{URL}"\s*/>\s*$'),
69+
],
70+
}
71+
72+
# ---- validate the diff: files and content -------------------------
73+
print(f"Validating codeflow PR #{pr_number}...")
74+
75+
diff_text = gh("pr", "diff", pr_number)
76+
current_file: str | None = None
77+
errors: list[str] = []
78+
79+
for raw_line in diff_text.splitlines():
80+
if raw_line.startswith("diff --git"):
81+
parts = raw_line.split(" b/")
82+
current_file = parts[-1] if len(parts) >= 2 else None
83+
if current_file and current_file not in ALLOWED_FILES:
84+
errors.append(f"Unexpected file: {current_file}")
85+
current_file = None
86+
continue
87+
88+
if raw_line.startswith(("---", "+++", "@@", "\\ No newline")):
89+
continue
90+
91+
if not raw_line.startswith(("+", "-")):
92+
continue
93+
94+
content = raw_line[1:]
95+
if not content.strip():
96+
continue
97+
98+
if current_file is None:
99+
continue
100+
101+
patterns = FILE_PATTERNS.get(current_file)
102+
if patterns is None:
103+
errors.append(f"No validation patterns for file: {current_file}")
104+
continue
105+
106+
if not any(p.match(content) for p in patterns):
107+
errors.append(f"Unexpected change in {current_file}: {content.strip()}")
108+
109+
if errors:
110+
for e in errors:
111+
print(f"::notice::{e}")
112+
print("::notice::Skipping auto-approve – PR contains unexpected changes")
113+
sys.exit(0)
114+
115+
print("✔ All changes are expected dependency-update patterns")
116+
117+
# ---- approve the PR ------------------------------------------------
118+
gh("pr", "review", pr_number, "--approve",
119+
"--body", "Auto-approved: codeflow dependency update PR with only version/SHA bumps in expected files.")
120+
print(f"✔ PR #{pr_number} approved")
121+
PYTHON

0 commit comments

Comments
 (0)