forked from ChelseaKR/ca-tariff-parse
-
Notifications
You must be signed in to change notification settings - Fork 0
146 lines (128 loc) · 5.2 KB
/
Copy pathrelease.yml
File metadata and controls
146 lines (128 loc) · 5.2 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
# Signed-tag release, dispatched from main, with verification and publication
# held by two separate jobs.
#
# The shape matters. Repository code runs only in the read-only job. The job
# that holds `contents: write` never checks the repository out, so nothing from
# the tag can execute with a writable token. It re-reads the tag object from
# the API and refuses to publish unless it is byte for byte the object the
# verification job approved.
#
# The signer list is committed at .github/allowed_signers, one line per
# identity in the form `<principal> <keytype> <key>`, and is read from trusted
# main rather than from the tag under release. While that file names no
# principal this workflow fails closed rather than publishing an unverified
# tag.
name: release
on:
workflow_dispatch:
inputs:
tag:
description: Existing signed release tag (vX.Y.Z)
required: true
type: string
permissions:
contents: read
concurrency:
group: release
cancel-in-progress: false
env:
UV_VERSION: "0.12.1"
jobs:
verify:
name: Verify the signed tag against trusted main
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
tag: ${{ steps.identity.outputs.tag }}
tag_object_sha: ${{ steps.identity.outputs.tag_object_sha }}
source_commit: ${{ steps.identity.outputs.source_commit }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: ${{ env.UV_VERSION }}
python-version: "3.12"
enable-cache: false
- name: Establish tag identity from trusted main
id: identity
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
# The workflow itself must have been dispatched from main.
test "${GITHUB_REF}" = refs/heads/main
test "$(git rev-parse origin/main)" = "${GITHUB_SHA}"
# A release tag is a SemVer tag and nothing else.
printf '%s\n' "${TAG}" \
| grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
git show-ref --verify --quiet "refs/tags/${TAG}"
# An annotated, signed tag object, not a lightweight ref.
test "$(git cat-file -t "refs/tags/${TAG}")" = tag
# The tagged commit must already be on main. A tag pointing at code
# that never landed cannot be released.
git merge-base --is-ancestor "${TAG}^{commit}" origin/main
# Present is not the same as populated: a signer file of comments
# would let verify-tag run against an empty principal list, and the
# failure would read like a bad signature rather than a missing key.
if [ ! -s .github/allowed_signers ] \
|| ! grep -qv '^[[:space:]]*\(#\|$\)' .github/allowed_signers; then
echo ".github/allowed_signers names no principal; refusing to publish an unverified tag" >&2
exit 1
fi
git config gpg.format ssh
git config gpg.ssh.allowedSignersFile .github/allowed_signers
git verify-tag "${TAG}"
{
printf 'tag=%s\n' "${TAG}"
printf 'tag_object_sha=%s\n' "$(git rev-parse "refs/tags/${TAG}")"
printf 'source_commit=%s\n' "$(git rev-parse "${TAG}^{commit}")"
} >> "${GITHUB_OUTPUT}"
- name: Re-run the full gate at the tagged commit
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
git checkout --detach "${TAG}^{commit}"
make verify
- name: Confirm the version and changelog agree with the tag
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
version="${TAG#v}"
project_version="$(uv run python -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
test "${project_version}" = "${version}"
grep -q "${version}" CHANGELOG.md
publish:
name: Publish the release
needs: verify
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Recheck the tag object and publish
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.verify.outputs.tag }}
TAG_OBJECT_SHA: ${{ needs.verify.outputs.tag_object_sha }}
run: |
set -euo pipefail
# This job never checks the repository out. Re-read the tag object
# from the API and refuse to publish anything the verification job
# did not approve, in case the ref moved in between.
remote_object="$(gh api "repos/${GH_REPO}/git/ref/tags/${TAG}" --jq '.object.sha')"
if [ "${remote_object}" != "${TAG_OBJECT_SHA}" ]; then
echo "tag ${TAG} moved since verification; refusing to publish" >&2
exit 1
fi
gh release create "${TAG}" \
--title "${TAG}" \
--notes "See CHANGELOG.md for the changes in ${TAG}." \
--verify-tag \
--draft