@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
yml 212 lines 8.92 KB
Raw
1 name: Release Proposal Approval Tracker
2
3 # pull_request_target (instead of pull_request) runs in the context of the
4 # base branch, so runs for fork PRs don't sit waiting for workflow approval
5 # and expire. That's safe here because these jobs never check out or run
6 # code from the PR: the only checkout is of the base branch, for the shared
7 # release proposal script.
8 on:
9 pull_request_review:
10 types: [submitted, dismissed]
11 pull_request_target:
12 types: [labeled, unlabeled, synchronize, closed]
13
14 permissions:
15 contents: read
16 pull-requests: write
17 issues: write
18
19 jobs:
20 check-approvals:
21 name: Track Maintainer Approvals
22 runs-on: ubuntu-latest
23 # Only run on open release proposals, which are created by the Release
24 # Proposal workflow from a branch in this repository
25 if: |
26 github.repository == 'caddyserver/caddy' &&
27 contains(github.event.pull_request.labels.*.name, 'release-proposal') &&
28 github.event.pull_request.state == 'open' &&
29 github.event.pull_request.head.repo.full_name == github.repository
30
31 steps:
32 - name: Checkout release scripts
33 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
34 with:
35 ref: ${{ github.event.repository.default_branch }}
36 sparse-checkout: .github/scripts
37 persist-credentials: false
38
39 - name: Check approvals and update PR
40 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
41 env:
42 MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }}
43 with:
44 script: |
45 const { checkProposal, REQUIRED_APPROVALS } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/release-proposal.js`);
46 const payloadPR = context.payload.pull_request;
47
48 // Extract version from PR title (e.g., "Release Proposal: v1.2.3")
49 const versionMatch = payloadPR.title.match(/^Release Proposal: (v\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)$/);
50 if (!versionMatch) {
51 console.log(`Not a release proposal title: ${payloadPR.title}`);
52 return;
53 }
54 const version = versionMatch[1];
55
56 // Same checks as the release workflow runs when the tag is pushed
57 const { pr, targetCommit, approvers, approved } = await checkProposal({
58 github, context, version, maintainerLogins: process.env.MAINTAINER_LOGINS,
59 });
60 if (pr.number !== payloadPR.number) {
61 core.setFailed(`PR #${payloadPR.number} is not the open proposal for ${version} (#${pr.number} is)`);
62 return;
63 }
64
65 console.log(`Version: ${version}, Target Commit: ${targetCommit}`);
66 console.log(`Found ${approvers.length} maintainer approvals of ${pr.head.sha} from: ${approvers.join(', ')}`);
67
68 // Get current labels
69 const currentLabels = pr.labels.map(label => label.name);
70 const hasApprovedLabel = currentLabels.includes('approved');
71 const hasAwaitingApprovalLabel = currentLabels.includes('awaiting-approval');
72
73 if (approved && !hasApprovedLabel) {
74 console.log('✅ Quorum reached! Updating PR...');
75
76 // Remove awaiting-approval label if present
77 if (hasAwaitingApprovalLabel) {
78 await github.rest.issues.removeLabel({
79 owner: context.repo.owner,
80 repo: context.repo.repo,
81 issue_number: pr.number,
82 name: 'awaiting-approval'
83 }).catch(e => console.log('Label not found:', e.message));
84 }
85
86 // Add approved label
87 await github.rest.issues.addLabels({
88 owner: context.repo.owner,
89 repo: context.repo.repo,
90 issue_number: pr.number,
91 labels: ['approved']
92 });
93
94 // Add comment with tagging instructions
95 const approversList = approvers.map(u => `@${u}`).join(', ');
96 const commentBody = [
97 '## ✅ Approval Quorum Reached',
98 '',
99 `This release proposal has been approved by ${approvers.length} maintainers: ${approversList}`,
100 '',
101 '### Tagging Instructions',
102 '',
103 'A maintainer should now create and push the signed tag:',
104 '',
105 '```bash',
106 `git checkout ${targetCommit}`,
107 `git tag -s ${version} -m "Release ${version}"`,
108 `git push origin ${version}`,
109 `git checkout -`,
110 '```',
111 '',
112 'The release workflow will automatically start when the tag is pushed.'
113 ].join('\n');
114
115 await github.rest.issues.createComment({
116 owner: context.repo.owner,
117 repo: context.repo.repo,
118 issue_number: pr.number,
119 body: commentBody
120 });
121
122 console.log('Posted tagging instructions');
123 } else if (!approved && hasApprovedLabel) {
124 console.log('⚠️ Approval count dropped below quorum, removing approved label');
125
126 // Remove approved label
127 await github.rest.issues.removeLabel({
128 owner: context.repo.owner,
129 repo: context.repo.repo,
130 issue_number: pr.number,
131 name: 'approved'
132 }).catch(e => console.log('Label not found:', e.message));
133
134 // Add awaiting-approval label
135 if (!hasAwaitingApprovalLabel) {
136 await github.rest.issues.addLabels({
137 owner: context.repo.owner,
138 repo: context.repo.repo,
139 issue_number: pr.number,
140 labels: ['awaiting-approval']
141 });
142 }
143 } else {
144 console.log(`⏳ Waiting for more approvals (${approvers.length}/${REQUIRED_APPROVALS} required)`);
145 }
146
147
148 handle-pr-closed:
149 name: Handle PR Closed Without Tag
150 runs-on: ubuntu-latest
151 if: |
152 github.repository == 'caddyserver/caddy' &&
153 contains(github.event.pull_request.labels.*.name, 'release-proposal') &&
154 github.event.pull_request.head.repo.full_name == github.repository &&
155 github.event.action == 'closed' && !contains(github.event.pull_request.labels.*.name, 'released')
156
157 steps:
158 - name: Add cancelled label and comment
159 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
160 with:
161 script: |
162 const pr = context.payload.pull_request;
163
164 // Check if the release-in-progress label is present
165 const hasReleaseInProgress = pr.labels.some(label => label.name === 'release-in-progress');
166
167 if (hasReleaseInProgress) {
168 // PR was closed while release was in progress - this is unusual
169 await github.rest.issues.createComment({
170 owner: context.repo.owner,
171 repo: context.repo.repo,
172 issue_number: pr.number,
173 body: '⚠️ **Warning:** This PR was closed while a release was in progress. This may indicate an error. Please verify the release status.'
174 });
175 } else {
176 // PR was closed before tag was created - this is normal cancellation
177 const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
178 const version = versionMatch ? versionMatch[1] : 'unknown';
179
180 await github.rest.issues.createComment({
181 owner: context.repo.owner,
182 repo: context.repo.repo,
183 issue_number: pr.number,
184 body: `## 🚫 Release Proposal Cancelled\n\nThis release proposal for ${version} was closed without creating the tag.\n\nIf you want to proceed with this release later, you can create a new release proposal.`
185 });
186 }
187
188 // Add cancelled label
189 await github.rest.issues.addLabels({
190 owner: context.repo.owner,
191 repo: context.repo.repo,
192 issue_number: pr.number,
193 labels: ['cancelled']
194 });
195
196 // Remove other workflow labels if present
197 const labelsToRemove = ['awaiting-approval', 'approved', 'release-in-progress'];
198 for (const label of labelsToRemove) {
199 try {
200 await github.rest.issues.removeLabel({
201 owner: context.repo.owner,
202 repo: context.repo.repo,
203 issue_number: pr.number,
204 name: label
205 });
206 } catch (e) {
207 console.log(`Label ${label} not found or already removed`);
208 }
209 }
210
211 console.log('Added cancelled label and cleaned up workflow labels');
212