-
Notifications
You must be signed in to change notification settings - Fork 2
244 lines (214 loc) · 8.49 KB
/
Copy pathpublish.yml
File metadata and controls
244 lines (214 loc) · 8.49 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
244
name: Publish to PyPI
on:
release:
types: [published]
permissions:
contents: read
jobs:
verify:
name: Verify release candidate
if: github.event.release.prerelease == false
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- name: Install verification dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[dev]' pip-audit 'build==1.5.0' 'jsonschema>=4.17,<4.24'
- name: Verify release tag matches package version and protected main
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
PACKAGE_VERSION="$(python scripts/package_version.py)"
if [ "$RELEASE_TAG" != "v$PACKAGE_VERSION" ]; then
echo "::error::Release tag $RELEASE_TAG does not match package version $PACKAGE_VERSION"
exit 1
fi
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
RELEASE_COMMIT="$(git rev-parse "$RELEASE_TAG^{commit}")"
if [ "$RELEASE_COMMIT" != "$(git rev-parse HEAD)" ]; then
echo "::error::Checked-out commit does not match $RELEASE_TAG"
exit 1
fi
if ! git merge-base --is-ancestor "$RELEASE_COMMIT" origin/main; then
echo "::error::$RELEASE_TAG is not reachable from protected main"
exit 1
fi
- name: Lint source with ruff
run: ruff check oilpriceapi/
- name: Run unit tests
run: pytest tests/ --ignore=tests/integration --ignore=tests/contract -m 'not slow' --cov=oilpriceapi -v
- name: Audit installed dependencies
run: pip-audit
- name: Validate public storefront claims
run: python scripts/validate_storefront_claims.py
- name: Build package
run: python -m build
- name: Validate exact built source distribution
run: |
set -euo pipefail
PACKAGE_VERSION="$(python scripts/package_version.py)"
SDIST="dist/oilpriceapi-${PACKAGE_VERSION}.tar.gz"
if [ ! -f "$SDIST" ]; then
echo "::error::Exact source distribution not found: $SDIST"
exit 1
fi
python scripts/validate_storefront_claims.py --sdist "$SDIST"
- name: Install and import the exact built wheel
run: ./scripts/clean-wheel-smoke.sh
- name: Build signed snippet manifest
run: |
python scripts/generate_snippet_manifest.py \
--source-commit "$(git rev-parse HEAD)" \
--output artifacts/snippets/oilpriceapi-python-snippets-v1.json
- name: Prepare checksummed release artifact
run: |
set -euo pipefail
ARTIFACT_DIR="$RUNNER_TEMP/release-artifact"
mkdir -p "$ARTIFACT_DIR/dist" "$ARTIFACT_DIR/snippets"
cp dist/* "$ARTIFACT_DIR/dist/"
cp artifacts/snippets/* "$ARTIFACT_DIR/snippets/"
PACKAGE_VERSION="$(python scripts/package_version.py)"
printf 'PACKAGE_VERSION=%s\n' "$PACKAGE_VERSION" > "$ARTIFACT_DIR/release.env"
(
cd "$ARTIFACT_DIR"
find dist snippets -type f -print0 \
| sort -z \
| xargs -0 sha256sum > artifact.sha256
sha256sum release.env >> artifact.sha256
)
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: verified-pypi-package
path: ${{ runner.temp }}/release-artifact/
if-no-files-found: error
retention-days: 1
publish:
name: Publish verified package to PyPI
needs: verify
if: github.event.release.prerelease == false
runs-on: ubuntu-latest
timeout-minutes: 15
environment: pypi
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: verified-pypi-package
path: ${{ runner.temp }}/release-artifact
- name: Verify exact artifact checksums
working-directory: ${{ runner.temp }}/release-artifact
run: |
set -euo pipefail
manifest_files="$RUNNER_TEMP/manifest-files"
actual_files="$RUNNER_TEMP/actual-files"
sed -n 's/^[0-9a-f]\{64\} //p' artifact.sha256 \
| LC_ALL=C sort > "$manifest_files"
{
find dist snippets -type f -print
printf '%s\n' release.env
} | LC_ALL=C sort > "$actual_files"
if [ -n "$(find dist snippets -type l -print -quit)" ]; then
echo "::error::Verified release artifact contains a symlink"
exit 1
fi
if ! cmp -s "$manifest_files" "$actual_files"; then
echo "::error::Checksum manifest does not cover the exact release files"
exit 1
fi
sha256sum -c artifact.sha256
- name: Publish exact verified distributions
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 2026-07-28
with:
packages-dir: ${{ runner.temp }}/release-artifact/dist/
skip-existing: true
readback:
name: Verify public PyPI artifact hashes
needs: publish
if: github.event.release.prerelease == false
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: verified-pypi-package
path: ${{ runner.temp }}/release-artifact
- name: Verify exact public PyPI hashes
working-directory: ${{ runner.temp }}/release-artifact
run: |
set -euo pipefail
sha256sum -c artifact.sha256
PACKAGE_VERSION="$(sed -n 's/^PACKAGE_VERSION=//p' release.env)"
if ! printf '%s' "$PACKAGE_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Invalid package version in verified artifact"
exit 1
fi
for attempt in $(seq 1 24); do
curl --fail --silent --show-error --max-time 10 \
"https://pypi.org/pypi/oilpriceapi/$PACKAGE_VERSION/json" \
> "$RUNNER_TEMP/pypi.json" || true
all_present=true
for file in dist/*; do
filename="$(basename "$file")"
expected="$(sha256sum "$file" | cut -d' ' -f1)"
actual="$(jq -r --arg filename "$filename" \
'[.urls[]? | select(.filename == $filename) | .digests.sha256][0] // empty' \
"$RUNNER_TEMP/pypi.json" 2>/dev/null || true)"
if [ -z "$actual" ]; then
all_present=false
elif [ "$actual" != "$expected" ]; then
echo "::error::PyPI $filename has an unexpected immutable hash"
exit 1
fi
done
if [ "$all_present" = true ]; then
echo "Verified every public oilpriceapi $PACKAGE_VERSION distribution hash."
exit 0
fi
if [ "$attempt" -lt 6 ]; then
sleep_seconds=$((attempt * 2))
else
sleep_seconds=10
fi
sleep "$sleep_seconds"
done
echo "::error::PyPI public readback did not expose every verified distribution"
exit 1
release_assets:
name: Attach verified release assets
needs: readback
if: github.event.release.prerelease == false
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: verified-pypi-package
path: ${{ runner.temp }}/release-artifact
- name: Attach checksummed snippet manifest
working-directory: ${{ runner.temp }}/release-artifact
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
sha256sum -c artifact.sha256
gh release upload "$RELEASE_TAG" snippets/* artifact.sha256 --clobber