forked from ChelseaKR/ctdl-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
151 lines (143 loc) · 5.75 KB
/
Copy pathrelease.yml
File metadata and controls
151 lines (143 loc) · 5.75 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
name: release
# Trusted-main release (RELEASE-AND-VERSIONING-STANDARD): dispatch with an
# existing signed vX.Y.Z tag; the reusable authorize workflow verifies the
# tag signature and that the tagged commit is an ancestor of main before
# anything builds. The publish target is the GitHub Release itself: build
# sdist+wheel, attest SLSA build provenance, and attach the build to a
# GitHub Release whose notes are the matching CHANGELOG section.
#
# No tag exists yet (pyproject version 0.1.0 is unreleased); the workflow is
# wired ahead of the first tag so a release can never ship a commit that did
# not pass its own gate. Tag like `v0.1.0`:
# `git tag -s v0.1.0 -m v0.1.0 && git push origin v0.1.0`.
on:
workflow_dispatch:
inputs:
tag:
description: "Existing signed stable SemVer tag (vX.Y.Z)"
required: true
type: string
permissions:
contents: read
# A release publishes artifacts to the GitHub Release: never race two tags,
# but never silently drop a re-run either.
concurrency:
group: release
cancel-in-progress: false
jobs:
authorize:
uses: ChelseaKR/.github/.github/workflows/release-authorize.yml@315a513ff3b4e7c5c0628428909052d947f4f1ab
permissions:
contents: read
with:
tag: ${{ inputs.tag }}
# Never publish untested code: re-run the full merge-blocking gate set
# (lint, format, typecheck, coverage-gated tests, pip-audit) at the tagged
# commit, the exact `make verify` CI runs on every push, not the PR's
# prior checkmark.
release-tests:
needs: authorize
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
UV_PYTHON: "3.12"
UV_PYTHON_DOWNLOADS: never
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.authorize.outputs.release-commit }}
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
check-latest: false
# No cross-run cache on the release path: a poisoned cache entry could
# ride into a published artifact. ci.yml keeps its cache; mutable-branch
# runs carry materially lower risk.
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.1"
enable-cache: false
- name: Run the local verification gate at the tagged commit
run: make verify
build:
needs: [authorize, release-tests]
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
id-token: write # SLSA build-provenance attestation (keyless/OIDC)
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.authorize.outputs.release-commit }}
persist-credentials: false
# No cache in the build/publish path either: a cache hit is an
# unverified input into the artifact about to be attested and shipped.
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.1"
enable-cache: false
- name: Build sdist and wheel
run: uv build
- name: Require tag, package, and changelog versions to match
env:
TAG: ${{ needs.authorize.outputs.release-tag }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
PACKAGE_VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
test "${VERSION}" = "${PACKAGE_VERSION}"
awk -v ver="${VERSION}" '
index($0, "## [" ver "]") == 1 { found=1; print; next }
found && /^## \[/ { exit }
found { print }
' CHANGELOG.md > release-notes.md
test -s release-notes.md
- name: Attest build provenance (SLSA, keyless/OIDC via Sigstore)
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: "dist/*.whl,dist/*.tar.gz"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
if-no-files-found: error
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-notes
path: release-notes.md
if-no-files-found: error
# Create the GitHub Release: attach the build and use the matching
# CHANGELOG section as release notes. Deliberately checkout-free; the only
# inputs are the verified artifacts and the live tag object, re-checked
# against what the authorize job verified.
github-release:
needs: [authorize, build]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Download release notes
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-notes
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.authorize.outputs.release-tag }}
TAG_OBJECT_SHA: ${{ needs.authorize.outputs.tag-object-sha }}
run: |
set -euo pipefail
LIVE_TAG_OBJECT="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq .object.sha)"
test "${LIVE_TAG_OBJECT}" = "${TAG_OBJECT_SHA}"
gh release create "${TAG}" dist/* \
--title "${TAG}" --notes-file release-notes.md