-
Notifications
You must be signed in to change notification settings - Fork 2.6k
171 lines (159 loc) · 7.67 KB
/
Copy pathtag-stable-release.yml
File metadata and controls
171 lines (159 loc) · 7.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
---
name: Validate and Tag Release
permissions: {}
'on':
pull_request:
branches: [master]
types: [opened, synchronize, reopened, edited]
pull_request_target: # zizmor: ignore[dangerous-triggers] closed merges only; trusted code validates the exact merge tree
branches: [master]
types: [closed]
concurrency:
group: tag-release-${{ github.event.pull_request.number }}
cancel-in-progress: false
jobs:
validate:
name: Validate release
if: >-
(github.event_name == 'pull_request' || github.event.pull_request.merged == true) &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.head.ref == 'release/version'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout trusted release code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
path: trusted
persist-credentials: false
sparse-checkout: |
.github/scripts/create-tag.js
.github/scripts/prepare-stable-release.py
sparse-checkout-cone-mode: false
- name: Recognize release version pull request
id: release
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
RELEASE_APP_BOT_ID: ${{ vars.FOUNDRY_RELEASE_APP_BOT_ID }}
with:
script: |
const pr = context.payload.pull_request;
const repository = `${context.repo.owner}/${context.repo.repo}`;
if (!process.env.RELEASE_APP_BOT_ID) {
throw new Error('FOUNDRY_RELEASE_APP_BOT_ID is not configured');
}
if (pr.user?.type !== 'Bot' || String(pr.user.id) !== process.env.RELEASE_APP_BOT_ID) {
throw new Error('Release version pull request was not created by the release App');
}
if (pr.base.ref !== 'master' || pr.base.repo.full_name !== repository) {
throw new Error('Release version pull request does not target this repository master');
}
if (pr.head.ref !== 'release/version' || pr.head.repo.full_name !== repository) {
throw new Error('Release version pull request did not use the release/version branch');
}
const marker = '<!-- foundry-release-version-pr -->';
const metadata = new RegExp(
`${marker}\\nPrepares an exact Foundry release transition\\.\\n\\n` +
'- Operation: `(stable|start|advance|promote)`\\n' +
'- Base branch: `master`\\n' +
'- Source master SHA: `([0-9a-f]{40})`\\n' +
'- Source tag: `(v[^`]+)`\\n' +
'- Target version: `([^`]+)`\\n' +
'- Target tag: `(v[^`]+)`\\n' +
'- Changelog fragments: (\\d+)\\n' +
'- Workspace packages: (\\d+)\\n?$',
).exec(pr.body || '');
if (!metadata) throw new Error('Release version pull request metadata is malformed');
if (metadata[5] !== `v${metadata[4]}`) {
throw new Error('Release version pull request tag does not match its version');
}
if (pr.title !== `chore: prepare ${metadata[5]}`) {
throw new Error('Release version pull request title does not match its tag');
}
if ((metadata[1] === 'promote') !== (Number(metadata[6]) === 0)) {
throw new Error('Release version pull request has invalid fragment semantics');
}
if (context.eventName === 'pull_request') {
const master = await github.rest.repos.getCommit({ ...context.repo, ref: 'master' });
if (metadata[2] !== pr.base.sha || metadata[2] !== master.data.sha) {
throw new Error('Release version pull request was prepared from a stale master commit');
}
}
const sha = context.eventName === 'pull_request' ? context.sha : pr.merge_commit_sha;
if (!/^[0-9a-f]{40}$/i.test(sha || '')) {
throw new Error('Release version pull request has no exact validation commit');
}
core.setOutput('operation', metadata[1]);
core.setOutput('source_sha', metadata[2]);
core.setOutput('source_tag', metadata[3]);
core.setOutput('version', metadata[4]);
core.setOutput('tag', metadata[5]);
core.setOutput('fragment_count', metadata[6]);
core.setOutput('package_count', metadata[7]);
core.setOutput('sha', sha);
- name: Checkout exact release tree
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ steps.release.outputs.sha }}
path: release
fetch-depth: 0
persist-credentials: false
- name: Verify test merge parents
if: github.event_name == 'pull_request'
env:
SHA: ${{ steps.release.outputs.sha }}
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
parents=$(git -C release show --no-patch --format='%P' "$SHA")
if [[ "$parents" != "$SOURCE_SHA $HEAD_SHA" ]]; then
echo "test merge parents do not match the prepared master and PR head" >&2
exit 1
fi
- name: Validate exact release tree
run: |
python3 trusted/.github/scripts/prepare-stable-release.py \
--validate-merged \
--root release \
--operation "$OPERATION" \
--expected-sha "$MERGE_SHA" \
--expected-source-sha "$SOURCE_SHA" \
--expected-source-tag "$SOURCE_TAG" \
--expected-version "$EXPECTED_VERSION" \
--expected-tag "$EXPECTED_TAG" \
--expected-fragment-count "$EXPECTED_FRAGMENT_COUNT" \
--expected-package-count "$EXPECTED_PACKAGE_COUNT"
env:
OPERATION: ${{ steps.release.outputs.operation }}
MERGE_SHA: ${{ steps.release.outputs.sha }}
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
SOURCE_TAG: ${{ steps.release.outputs.source_tag }}
EXPECTED_VERSION: ${{ steps.release.outputs.version }}
EXPECTED_TAG: ${{ steps.release.outputs.tag }}
EXPECTED_FRAGMENT_COUNT: ${{ steps.release.outputs.fragment_count }}
EXPECTED_PACKAGE_COUNT: ${{ steps.release.outputs.package_count }}
- name: Create repository-scoped App token
id: app-token
if: github.event_name == 'pull_request_target' && github.event.pull_request.merged == true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ vars.FOUNDRY_RELEASE_APP_ID }}
private-key: ${{ secrets.FOUNDRY_RELEASE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
- name: Create exact release tag
if: github.event_name == 'pull_request_target' && github.event.pull_request.merged == true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
TAG_NAME: ${{ steps.release.outputs.tag }}
MERGE_SHA: ${{ steps.release.outputs.sha }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const createTag = require('./trusted/.github/scripts/create-tag.js');
await createTag({ github, context }, process.env.TAG_NAME, process.env.MERGE_SHA);