Skip to content

Commit 1e5edb3

Browse files
authored
feat(ci): make major version releases safer (#8799)
1 parent 66c7386 commit 1e5edb3

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Major Version Release Requires 2+ yoshi-php Approvals
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review, edited]
6+
branches: ['main']
7+
8+
permissions:
9+
contents: read
10+
pull-requests: read
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
approval-count-check:
18+
runs-on: ubuntu-latest
19+
if: github.event.pull_request.user.login == 'release-please[bot]' && contains(github.event.pull_request.body, 'MAJOR_VERSION_ALLOWED=')
20+
steps:
21+
- name: Run Approval Check
22+
uses: actions/github-script@v7
23+
env:
24+
GH_TOKEN: ${{ secrets.SPLIT_TOKEN }}
25+
with:
26+
script: |
27+
const { execSync } = require('child_process');
28+
29+
const requiredApprovals = 2;
30+
31+
try {
32+
// 1. Get PR approvals
33+
const pull_number = context.payload.pull_request.number;
34+
35+
core.info(`Fetching approvals for PR #${pull_number}...`);
36+
const reviews = await github.rest.pulls.listReviews({
37+
pull_number,
38+
owner: context.repo.owner,
39+
repo: context.repo.repo
40+
});
41+
const approvals = new Set(
42+
reviews.data
43+
.filter(review => review.state === 'APPROVED')
44+
.map(review => review.user.login)
45+
);
46+
if (approvals.size < requiredApprovals) {
47+
core.setFailed(`Requires ${requiredApprovals}+ approvals from the yoshi-php team. Only ${approvals.size} total approvals found.`)
48+
core.error('RE-RUN THIS WORKFLOW AFTER THE APPROVAL REQUIREMENTS ARE MET')
49+
return;
50+
}
51+
core.info(`Total approval count: ${approvals.size}`);
52+
53+
// 2. Get team members
54+
core.info('Fetching yoshi-php team members...');
55+
const teamMembersCmd = `gh api --paginate orgs/googleapis/teams/yoshi-php/members --jq '.[].login'`;
56+
const teamMembersOutput = execSync(teamMembersCmd, { encoding: 'utf8', env: process.env });
57+
if (!teamMembersOutput.trim()) {
58+
core.error('Could not fetch any members for the yoshi-php team.');
59+
}
60+
const teamMembers = new Set(teamMembersOutput.trim().split('\n'));
61+
62+
// 3. Compare
63+
const matchingApprovals = [...approvals].filter(login => teamMembers.has(login));
64+
const count = matchingApprovals.length;
65+
66+
core.info(`Found ${count} approval(s) from the yoshi-php team.`);
67+
68+
if (count >= requiredApprovals) {
69+
core.info(`Success: Requirement of ${requiredApprovals}+ yoshi-php approvals met with ${count} approvals.`);
70+
} else {
71+
core.setFailed(`Requires ${requiredApprovals}+ approvals from the yoshi-php team. Only ${count} yoshi-php approvals found.`);
72+
core.error('RE-RUN THIS WORKFLOW AFTER THE APPROVAL REQUIREMENTS ARE MET')
73+
}
74+
} catch (error) {
75+
core.setFailed(`${error.message}`);
76+
}

.github/workflows/release-checks.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ jobs:
8787
fi
8888
}; done
8989
if [[ "$FAIL" == "true" ]]; then
90-
echo "Add \"MAJOR_VERSION_ALLOWED=component1,component2\" to the PR description to allow "
90+
echo "⚠️ IMPORTANT ⚠️ This check is meant to prevent the accidental release of new major versions. New "
91+
echo "major versions should only be released intentionally. If you are not explicitly trying to release a "
92+
echo "new major version and you don't have explicit approval from the Language Lead, "
93+
echo "⚠️ DO NOT BYPASS THIS CHECK ⚠️"
94+
echo -e "\nAdd \"MAJOR_VERSION_ALLOWED=component1,component2\" to the PR description to allow "
9195
echo "major version releases for those components"
9296
exit 1
9397
fi

0 commit comments

Comments
 (0)