Skip to content

Commit 27fbaf0

Browse files
daxpryceCopilot
andauthored
chore: Trusted environment publish hardening (#70)
* build: split CI/publish workflows, add trusted publishing - Rename build.yml → ci.yml (lint + build + test on push/PR only) - Remove version-munging job and manifest_version.py script; version is now solely what's in Cargo.toml - Create publish.yml triggered on v* tags with: - OIDC trusted publishing (id-token: write, no stored credentials) - pypi-publish environment (requires manual approval + tag restriction) - pypa/gh-action-pypi-publish for upload - GitHub Release creation with checksums - Remove dev/main branch publish logic and prerelease handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * build: add tag-on-main verification to publish workflow Ensures the tagged commit exists on the main branch before building and publishing. Combined with tag protection rules (v* restricted to maintainers) and environment approval, this provides defense-in-depth. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR #70 review feedback on publish workflow - Scope workflow-level permissions to contents:read; only publish job gets id-token:write and contents:write - Add tag/version mismatch check (tag v1.3.0 must match Cargo.toml 1.3.0) - Add skip-existing to pypi-publish for idempotent re-runs - Add Rust and Python unit tests to the release build job Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 017e81c commit 27fbaf0

3 files changed

Lines changed: 140 additions & 140 deletions

File tree

.github/build/manifest_version.py

Lines changed: 0 additions & 53 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: CI
2+
on: [push, pull_request]
3+
jobs:
4+
codestyle-check:
5+
runs-on: "ubuntu-latest"
6+
steps:
7+
- uses: actions/checkout@v6
8+
- name: Check format
9+
run: |
10+
cargo fmt -- --check
11+
- name: Clippy
12+
run: |
13+
cargo clippy --all-targets -- -D warnings
14+
build:
15+
needs: ['codestyle-check']
16+
runs-on: ${{ matrix.runner }}
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
include:
21+
- runner: ubuntu-latest
22+
target: x64
23+
manylinux: "2014"
24+
args: -m packages/pyo3/Cargo.toml --release --sdist
25+
artifact_name: dist-linux-x64
26+
- runner: ubuntu-24.04-arm
27+
target: aarch64
28+
manylinux: "2014"
29+
args: -m packages/pyo3/Cargo.toml --release
30+
artifact_name: dist-linux-aarch64
31+
- runner: ubuntu-latest
32+
target: x64
33+
manylinux: musllinux_1_2
34+
args: -m packages/pyo3/Cargo.toml --release
35+
artifact_name: dist-musllinux-x64
36+
- runner: ubuntu-24.04-arm
37+
target: aarch64
38+
manylinux: musllinux_1_2
39+
args: -m packages/pyo3/Cargo.toml --release
40+
artifact_name: dist-musllinux-aarch64
41+
- runner: windows-latest
42+
target: x64
43+
args: -m packages/pyo3/Cargo.toml --release
44+
artifact_name: dist-windows-x64
45+
- runner: windows-11-arm
46+
target: aarch64-pc-windows-msvc
47+
args: -m packages/pyo3/Cargo.toml --release
48+
artifact_name: dist-windows-aarch64
49+
- runner: macos-latest
50+
target: universal2-apple-darwin
51+
args: -m packages/pyo3/Cargo.toml --release
52+
artifact_name: dist-macos-universal2
53+
steps:
54+
- uses: actions/checkout@v6
55+
- uses: astral-sh/setup-uv@v6
56+
- name: Rust Unittests
57+
run: |
58+
cargo test --manifest-path packages/network_partitions/Cargo.toml
59+
60+
- uses: PyO3/maturin-action@v1
61+
if: ${{ !startsWith(matrix.runner, 'windows') }}
62+
name: Maturin Build
63+
with:
64+
command: build
65+
target: ${{ matrix.target }}
66+
args: ${{ matrix.args }}
67+
manylinux: ${{ matrix.manylinux || 'auto' }}
68+
69+
- uses: PyO3/maturin-action@v1
70+
if: ${{ startsWith(matrix.runner, 'windows') }}
71+
name: Maturin Build (Windows)
72+
with:
73+
command: build
74+
target: ${{ matrix.target }}
75+
args: ${{ matrix.args }}
76+
77+
- name: Python Unittests
78+
if: ${{ !matrix.manylinux || matrix.manylinux != 'musllinux_1_2' }}
79+
run: |
80+
cd packages/pyo3
81+
uv venv
82+
uv pip install ../../target/wheels/*.whl
83+
uv run python -m unittest
84+
shell: bash
85+
86+
- uses: actions/upload-artifact@v4
87+
with:
88+
name: ${{ matrix.artifact_name }}
89+
path: |
90+
target/wheels/*.whl
91+
target/wheels/*.tar.gz
92+
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
1-
name: graspologic-native CI
2-
on: [push, pull_request]
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: read
10+
311
jobs:
4-
codestyle-check:
5-
runs-on: "ubuntu-latest"
12+
verify-tag:
13+
runs-on: ubuntu-latest
614
steps:
715
- uses: actions/checkout@v6
8-
- name: Check format
9-
run: |
10-
cargo fmt -- --check
11-
- name: Clippy
16+
with:
17+
fetch-depth: 0
18+
- name: Verify tag is on main
1219
run: |
13-
cargo clippy --all-targets -- -D warnings
14-
version:
15-
runs-on: "ubuntu-latest"
16-
steps:
17-
- uses: actions/checkout@v6
18-
- uses: astral-sh/setup-uv@v6
19-
- name: Materialize build number
20+
if ! git branch -r --contains "${{ github.sha }}" | grep -q 'origin/main'; then
21+
echo "::error::Tag ${{ github.ref_name }} does not point to a commit on main."
22+
exit 1
23+
fi
24+
- name: Verify tag matches Cargo.toml version
2025
run: |
21-
uv run --with toml python .github/build/manifest_version.py packages/pyo3/Cargo.toml version.txt
22-
- uses: actions/upload-artifact@v4
23-
with:
24-
name: cargo-toml
25-
path: packages/pyo3/Cargo.toml
26-
- uses: actions/upload-artifact@v4
27-
with:
28-
name: version-txt
29-
path: version.txt
26+
CARGO_VERSION=$(grep '^version' packages/pyo3/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
27+
TAG_VERSION="${{ github.ref_name }}"
28+
TAG_VERSION="${TAG_VERSION#v}"
29+
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
30+
echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)"
31+
exit 1
32+
fi
33+
3034
build:
31-
needs: ['codestyle-check', 'version']
35+
needs: verify-tag
3236
runs-on: ${{ matrix.runner }}
3337
strategy:
3438
fail-fast: false
@@ -69,14 +73,7 @@ jobs:
6973
steps:
7074
- uses: actions/checkout@v6
7175
- uses: astral-sh/setup-uv@v6
72-
- uses: actions/download-artifact@v4
73-
with:
74-
name: cargo-toml
75-
path: materialized
76-
- name: Materialized Cargo Placement
77-
run: |
78-
mv materialized/Cargo.toml packages/pyo3/Cargo.toml
79-
shell: bash
76+
8077
- name: Rust Unittests
8178
run: |
8279
cargo test --manifest-path packages/network_partitions/Cargo.toml
@@ -113,75 +110,39 @@ jobs:
113110
path: |
114111
target/wheels/*.whl
115112
target/wheels/*.tar.gz
113+
116114
publish:
117115
runs-on: ubuntu-latest
118-
needs: 'build'
119-
if: github.ref=='refs/heads/main' || github.ref=='refs/heads/dev'
116+
needs: build
117+
environment: pypi-publish
120118
permissions:
119+
id-token: write
121120
contents: write
122121
steps:
123122
- uses: actions/checkout@v6
124-
- uses: astral-sh/setup-uv@v6
125-
- uses: actions/download-artifact@v4
126-
with:
127-
name: dist-linux-x64
128-
path: dist/
129-
- uses: actions/download-artifact@v4
130-
with:
131-
name: dist-linux-aarch64
132-
path: dist/
133-
- uses: actions/download-artifact@v4
134-
with:
135-
name: dist-musllinux-x64
136-
path: dist/
137-
- uses: actions/download-artifact@v4
138-
with:
139-
name: dist-musllinux-aarch64
140-
path: dist/
141-
- uses: actions/download-artifact@v4
142-
with:
143-
name: dist-windows-x64
144-
path: dist/
145123
- uses: actions/download-artifact@v4
146124
with:
147-
name: dist-windows-aarch64
125+
pattern: dist-*
126+
merge-multiple: true
148127
path: dist/
149-
- uses: actions/download-artifact@v4
150-
with:
151-
name: dist-macos-universal2
152-
path: dist/
153-
- name: Generate SHA256 files for each wheel
128+
129+
- name: Generate checksums
154130
run: |
155-
sha256sum dist/*.whl > checksums.txt
156-
sha256sum dist/*.tar.gz >> checksums.txt
131+
sha256sum dist/*.whl dist/*.tar.gz > checksums.txt
157132
cat checksums.txt
158-
- uses: actions/download-artifact@v4
133+
134+
- name: Publish to PyPI (trusted publishing)
135+
uses: pypa/gh-action-pypi-publish@release/v1
159136
with:
160-
name: version-txt
161-
path: version/
162-
- name: Release Tag from Version
163-
run: |
164-
GRASPOLOGIC_VERSION=$(cat version/version.txt)
165-
echo "GRASPOLOGIC_VERSION=$GRASPOLOGIC_VERSION" >> $GITHUB_ENV
166-
echo $GRASPOLOGIC_VERSION
167-
- name: Publish with twine
168-
env:
169-
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
170-
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
171-
run: |
172-
uv run --with twine twine upload dist/*
173-
- name: Create Github Release
137+
packages-dir: dist/
138+
skip-existing: true
139+
140+
- name: Create GitHub Release
174141
env:
175142
GH_TOKEN: ${{ github.token }}
176143
run: |
177-
PRERELEASE_FLAG=""
178-
if [ "${{ github.ref }}" = "refs/heads/dev" ]; then
179-
PRERELEASE_FLAG="--prerelease"
180-
fi
181-
gh release create "$GRASPOLOGIC_VERSION" \
182-
--title "graspologic-native-${GRASPOLOGIC_VERSION}" \
144+
gh release create "${{ github.ref_name }}" \
145+
--title "graspologic-native ${{ github.ref_name }}" \
183146
--notes-file checksums.txt \
184-
$PRERELEASE_FLAG \
185147
dist/*.whl \
186148
dist/*.tar.gz
187-

0 commit comments

Comments
 (0)