Repository navigation
243 lines (224 loc) · 11 KB
/
Copy pathrelease-please.yml
File metadata and controls
243 lines (224 loc) · 11 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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
name: Release Please
on:
push:
branches:
- main
workflow_dispatch:
permissions:
actions: write
contents: write
pull-requests: write
statuses: write
concurrency:
group: release-please
cancel-in-progress: false
jobs:
release-please:
runs-on: ubuntu-24.04
outputs:
prs_created: ${{ steps.release.outputs.prs_created }}
release_branch: ${{ steps.release_branch.outputs.name }}
release_pr: ${{ steps.release_branch.outputs.pr }}
steps:
- name: Create delivery token
id: delivery-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.CHIO_DELIVERY_APP_ID }}
private-key: ${{ secrets.CHIO_DELIVERY_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-actions: write
permission-contents: write
permission-issues: write
permission-pull-requests: write
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{ steps.delivery-token.outputs.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Publish created release
if: steps.release.outputs.release_created == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.release.outputs.tag_name }}
run: |
gh workflow run publish.yml --repo "${GITHUB_REPOSITORY}" --ref main \
-f "tag=${RELEASE_TAG}"
- name: Resolve release branch
id: release_branch
if: steps.release.outputs.prs_created == 'true'
env:
RELEASE_PRS: ${{ steps.release.outputs.prs }}
run: |
release_branch="$(jq -r '.[0].headBranchName // empty' <<< "${RELEASE_PRS}")"
release_pr="$(jq -r '.[0].number // empty' <<< "${RELEASE_PRS}")"
if [[ -z "${release_branch}" || -z "${release_pr}" ]]; then
echo "Release Please reported a created PR without a head branch or number" >&2
exit 1
fi
echo "name=${release_branch}" >> "${GITHUB_OUTPUT}"
echo "pr=${release_pr}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v4
if: steps.release.outputs.prs_created == 'true'
with:
ref: ${{ steps.release_branch.outputs.name }}
token: ${{ steps.delivery-token.outputs.token }}
- uses: astral-sh/setup-uv@v5
if: steps.release.outputs.prs_created == 'true'
with:
version: "0.12.3"
- uses: dtolnay/rust-toolchain@master
if: steps.release.outputs.prs_created == 'true'
with:
toolchain: stable
- name: Install Python
if: steps.release.outputs.prs_created == 'true'
run: uv python install 3.12
- name: Refresh release lockfiles
if: steps.release.outputs.prs_created == 'true'
env:
RELEASE_BRANCH: ${{ steps.release_branch.outputs.name }}
# Release Please only bumps the sqlbuild-cli and sqlbuild-python crate versions, so updating
# the workspace entries of Cargo.lock is enough and needs no compile; metadata --locked
# proves the lock is complete.
run: |
cargo update --workspace
cargo metadata --locked --format-version 1 > /dev/null
uv lock --python 3.12
uv sync --locked --all-groups --all-extras --python 3.12
if git diff --quiet -- Cargo.lock uv.lock; then
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Cargo.lock uv.lock
git commit -m "chore: refresh release lockfiles"
git push origin "HEAD:${RELEASE_BRANCH}"
# A separate job so that "Re-run failed jobs" re-dispatches and re-evaluates every trusted check
# and repaints their statuses and Verify on the current head of the release branch.
release-checks:
name: Release pull request checks
needs: release-please
if: needs.release-please.outputs.prs_created == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.release_branch }}
- name: Run release pull request checks
env:
GH_TOKEN: ${{ github.token }}
RELEASE_BRANCH: ${{ needs.release-please.outputs.release_branch }}
RELEASE_PR: ${{ needs.release-please.outputs.release_pr }}
REQUEST_ID: ${{ github.run_id }}-${{ github.run_attempt }}
run: |
head_sha="$(git rev-parse HEAD)"
recovery="rerun the failed Release Please job to re-check"
post_status() {
gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
-f "state=$1" -f "context=$2" -f "description=$3" ${4:+-f "target_url=$4"} >/dev/null
}
fail_verify() {
post_status failure Verify "$1; ${recovery}" "${2:-}"
{
echo "### Release pull request checks failed"
echo
echo "$1. After fixing or confirming a transient cause, use **Re-run failed jobs**"
echo "on this workflow run: it re-dispatches every trusted check and repaints Verify."
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
}
# Verify is the required status. On Release Please branches this commit status is its
# only source, because the Verify workflow reports its job under another name there, so
# the pull request merges only after every trusted check passes on this head, which
# already includes any lockfile refresh. The checks include building the distributions
# that Publish reuses once the release is tagged. Verify's test suites skip release pull
# requests, whose product code already passed them on main; release performance runs on
# main after each merge and does not gate. Each dispatched run carries this attempt's
# request id in its run name and is matched on it exactly.
if ! python3 -m scripts.check_native_layering; then
fail_verify "Release versions or Cargo.lock workspace versions disagree"
fi
workflows=(pull-request-metadata.yml version-guard.yml release-distributions.yml)
contexts=("Pull Request Metadata" "Block automatic 1.0 releases" "Release distributions")
for context in "${contexts[@]}"; do
post_status pending "${context}" "Trusted release check is running"
done
post_status pending Verify "Validating trusted release metadata and distributions"
dispatched_after="$(date -u -d '-2 minutes' +%Y-%m-%dT%H:%M:%SZ)"
gh workflow run pull-request-metadata.yml --ref "${RELEASE_BRANCH}" \
-f "pr_number=${RELEASE_PR}" -f "request_id=${REQUEST_ID}"
gh workflow run version-guard.yml --ref "${RELEASE_BRANCH}" \
-f "request_id=${REQUEST_ID}"
gh workflow run release-distributions.yml --ref "${RELEASE_BRANCH}" \
-f "request_id=${REQUEST_ID}"
for index in "${!workflows[@]}"; do
workflow="${workflows[${index}]}"
context="${contexts[${index}]}"
run_id=""
for _ in {1..30}; do
run_id="$(gh run list --workflow "${workflow}" --branch "${RELEASE_BRANCH}" \
--event workflow_dispatch --limit 50 \
--json databaseId,headSha,displayTitle,createdAt \
--jq ".[] | select(.headSha == \"${head_sha}\"
and (.displayTitle | endswith(\" [${REQUEST_ID}]\"))
and .createdAt >= \"${dispatched_after}\") | .databaseId" | head -n 1)"
[[ -n "${run_id}" ]] && break
sleep 2
done
if [[ -z "${run_id}" ]]; then
post_status failure "${context}" "Trusted release check did not start"
fail_verify "${context} did not start"
fi
run_url="$(gh run view "${run_id}" --json url --jq .url)"
if gh run watch "${run_id}" --exit-status --interval 30; then
post_status success "${context}" "Trusted release check passed" "${run_url}"
else
post_status failure "${context}" "Trusted release check failed" "${run_url}"
fail_verify "${context} failed" "${run_url}"
fi
done
# Keep this allowlist in sync with the release metadata check in verify.yml.
unexpected_files="$(gh pr view "${RELEASE_PR}" --repo "${GITHUB_REPOSITORY}" \
--json files --jq '[.files[].path | select(. != ".release-please-manifest.json" and . != "CHANGELOG.md" and . != "pyproject.toml" and . != "uv.lock" and . != "Cargo.lock" and . != "crates/sqlbuild-cli/Cargo.toml" and . != "crates/sqlbuild-python/Cargo.toml")] | join("\n")')"
if [[ -n "${unexpected_files}" ]]; then
echo "Release pull request contains non-metadata changes:" >&2
echo "${unexpected_files}" >&2
post_status failure Verify "Release pull request contains non-metadata changes"
exit 1
fi
post_status success Verify \
"Product changes passed verification before release metadata update"
# The checks can wait longer than an installation token lives, so mint one just before use.
- name: Create delivery token
id: delivery-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.CHIO_DELIVERY_APP_ID }}
private-key: ${{ secrets.CHIO_DELIVERY_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-actions: write
permission-contents: write
permission-issues: write
permission-pull-requests: write
- name: Enable release pull request auto-merge
env:
# The delivery app merges, so the resulting push to main starts the tag and publish runs.
GH_TOKEN: ${{ steps.delivery-token.outputs.token }}
RELEASE_PR: ${{ needs.release-please.outputs.release_pr }}
PROCESSED_SHA: ${{ github.sha }}
run: |
# A commit merged to main while these checks ran is not in this release pull request yet.
# Its own Release Please run is queued behind this one and refreshes and re-checks the
# pull request, so merging now would tag a release whose changelog omits that commit.
main_sha="$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha)"
if [[ "${main_sha}" != "${PROCESSED_SHA}" ]]; then
gh pr merge "${RELEASE_PR}" --repo "${GITHUB_REPOSITORY}" --disable-auto 2>/dev/null || true
echo "main moved from ${PROCESSED_SHA} to ${main_sha}; leaving the merge to the queued run" \
>> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
gh pr merge "${RELEASE_PR}" --repo "${GITHUB_REPOSITORY}" --auto --squash