-
Notifications
You must be signed in to change notification settings - Fork 0
164 lines (152 loc) · 6.9 KB
/
Copy pathrelease.yml
File metadata and controls
164 lines (152 loc) · 6.9 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
name: Release
# A GitHub Release for every kernel tag, with the tag's CHANGELOG entry as the body and the
# distributions it built attached. `publish.yml` puts the same distributions on PyPI; this
# workflow is what a visitor to the repository sees under Releases, and an empty Releases
# section beside five tags on PyPI reads as a project that does not release.
#
# Runs on every `v*` tag from now on, and by hand for the tags that predate it:
#
# gh workflow run release.yml -f tag=v0.5.0
#
# Idempotent: a tag that already has a Release is left alone rather than overwritten.
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "An existing kernel tag, e.g. v0.5.0"
required: true
type: string
permissions:
contents: read
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # to create the Release and upload its assets
id-token: write # the OIDC token Sigstore signs the provenance against
attestations: write # to record the attestation against the repository
steps:
- name: Which tag
id: tag
env:
INPUT_TAG: ${{ inputs.tag }}
REF_NAME: ${{ github.ref_name }}
EVENT: ${{ github.event_name }}
run: |
set -eu
if [ "$EVENT" = "workflow_dispatch" ]; then tag="$INPUT_TAG"; else tag="$REF_NAME"; fi
case "$tag" in
v[0-9]*) : ;;
*) echo "::error::$tag is not a kernel tag (v*)"; exit 1 ;;
esac
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ steps.tag.outputs.tag }}
- name: Already released
id: existing
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
set -eu
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "$TAG already has a Release; leaving it alone"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
if: steps.existing.outputs.exists == 'false'
with:
python-version: "3.11"
# `requirements/build.txt` is read from the tag's own tree, which every tag from 0.10.0
# on carries. The tags before it all have a Release already, so they never reach here.
- name: Build the distributions at the tag
if: steps.existing.outputs.exists == 'false'
run: |
pip install --require-hashes -r requirements/build.txt
export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
python -m build --no-isolation
python scripts/normalize_sdist.py dist/*.tar.gz
# The bill of materials for the wheel this release carries, written into `dist/` so the
# `gh release create dist/*` below attaches it. Generated from the built wheel in an empty
# environment rather than from `pyproject.toml`: a manifest-derived SBOM is the project's
# opinion of its own dependencies, and this one is a measurement of the bytes being
# released. It is attested along with everything else in `dist/`, because an unsigned
# inventory of a signed artifact is the weakest link in the pair.
- name: The bill of materials
if: steps.existing.outputs.exists == 'false'
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
pip install --require-hashes -r requirements/sbom.txt
./scripts/sbom.sh dist/*.whl "dist/ctrlrun-$VERSION.cdx.json"
# Signed provenance for the artifacts this release is about to carry. The action signs
# against the workflow's OIDC identity, so what it attests is *this* workflow, at *this*
# commit, having built *these* bytes -- there is no key anywhere, and nothing to leak or
# rotate, which is the same argument trusted publishing makes in `publish.yml`.
- name: Attest the distributions
id: attest
if: steps.existing.outputs.exists == 'false'
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: dist/*
# The bundle the action leaves in the runner's temp directory is not something a person
# downloading a release can see. This writes it into `dist/`, where the `gh release
# create dist/*` below carries it, and refuses to write anything if the statement is not
# about the artifacts in that directory. See `scripts/release_provenance.py`.
- name: The provenance a downloader can check
if: steps.existing.outputs.exists == 'false'
env:
BUNDLE: ${{ steps.attest.outputs.bundle-path }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
python scripts/release_provenance.py \
--bundle "$BUNDLE" --dist dist --name "ctrlrun-$VERSION"
# The entry for `X.Y.Z` in the CHANGELOG *as it stood at the tag*. A pre-release tag
# (0.1.0a1, 0.3.0rc1) falls back to its release's entry, since the CHANGELOG is kept per
# release and not per candidate. No entry is a failure, not an empty body.
- name: The CHANGELOG entry
if: steps.existing.outputs.exists == 'false'
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
python - <<'PY'
import os, re, sys
from pathlib import Path
version = os.environ["VERSION"]
release = re.sub(r"(a|b|rc|\.dev)\d+$", "", version)
text = Path("CHANGELOG.md").read_text(encoding="utf-8")
for candidate in (version, release):
pattern = re.compile(rf"^## \[{re.escape(candidate)}\][^\n]*\n(.*?)(?=^## \[|\Z)", re.M | re.S)
found = pattern.search(text)
if found:
body = found.group(1).strip() + "\n"
Path("release-notes.md").write_text(body, encoding="utf-8")
print(f"CHANGELOG entry for {candidate}: {len(body.splitlines())} lines")
break
else:
print(f"::error::CHANGELOG.md has no entry for {version} or {release}")
sys.exit(1)
PY
- name: Create the Release
if: steps.existing.outputs.exists == 'false'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -eu
flags=""
case "$VERSION" in *a[0-9]*|*b[0-9]*|*rc[0-9]*|*.dev[0-9]*) flags="--prerelease" ;; esac
gh release create "$TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--title "ctrlrun $VERSION" \
--notes-file release-notes.md \
$flags