forked from ChelseaKR/habitable
-
Notifications
You must be signed in to change notification settings - Fork 0
157 lines (149 loc) · 6.87 KB
/
Copy pathrelease.yml
File metadata and controls
157 lines (149 loc) · 6.87 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
# SPDX-License-Identifier: AGPL-3.0-or-later
name: release
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Existing tag to (re)build and attest"
required: true
permissions:
contents: read
# A tag push and a manual re-run of the same tag must never race each other into
# two concurrent publishes of the same release; queue them instead of cancelling
# (cancelling a half-finished publish is worse than waiting).
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
name: build · SBOM · provenance · publish
runs-on: ubuntu-latest
# P3 (CICD-04): write grants scoped to the job, not the whole workflow —
# rewards a tighter blast radius (OpenSSF Scorecard Token-Permissions check).
permissions:
contents: write # create/edit the GitHub release
id-token: write # sigstore signing for provenance
attestations: write # build-provenance attestations
steps:
# P1-3 (SEC-04): audit-mode egress monitoring; see ci.yml's Harden Runner
# step for the telemetry trade-off note. Flip to `block` + an explicit
# allowlist once a few real release runs show the actual endpoint set
# (PyPI/uv index, GitHub API, Sigstore) — don't guess the allowlist blind.
- name: Harden Runner (audit mode)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout (full history + tags — needed to verify the tag signature)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Resolve the release tag
id: tag
env:
# Bind the (potentially attacker-influenceable, on a workflow_dispatch
# input) values to an env var rather than expanding `${{ }}` directly
# into the shell command — avoids template-injection (zizmor High).
INPUT_TAG: ${{ github.event.inputs.tag }}
REF_NAME: ${{ github.ref_name }}
run: echo "tag=${INPUT_TAG:-$REF_NAME}" >> "$GITHUB_OUTPUT"
# --- REL-02/03/08: release identity guard ---------------------------------
# An evidence tool's artifact identity is part of its safety case: a tag whose
# version doesn't match pyproject, or that isn't signed, must never reach a
# published release. This fails BEFORE any build step runs.
#
# Uses SSH-format signature verification (git's `gpg.format=ssh`): simpler to
# bootstrap in CI than importing a GPG key, and git has supported it natively
# since 2.34. `.github/allowed_signers` must contain the maintainer's real
# public signing key for this to ever succeed — see that file's header.
- name: Configure tag-signature verification (SSH format)
run: |
git config gpg.format ssh
git config gpg.ssh.allowedSignersFile "${GITHUB_WORKSPACE}/.github/allowed_signers"
- name: "Guard: tag must be signed"
env:
TAG: ${{ steps.tag.outputs.tag }}
run: |
if ! git tag -v "$TAG" 2>/tmp/tag-verify.log; then
echo "::error::Tag '$TAG' is not a valid signed tag (git tag -v failed)."
echo "::error::habitable requires signed release tags (REL-08). See docs/releasing.md."
cat /tmp/tag-verify.log
exit 1
fi
- name: "Guard: tag version must match pyproject.toml"
env:
TAG: ${{ steps.tag.outputs.tag }}
run: |
TAG_VERSION="${TAG#v}"
PYPROJECT_VERSION="$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')"
if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
echo "::error::Tag '$TAG' (version $TAG_VERSION) does not match pyproject.toml version '$PYPROJECT_VERSION'."
exit 1
fi
echo "Tag version $TAG_VERSION matches pyproject.toml."
# --- REL-14/15: release re-runs the full merge gate before it ships --------
- name: Install uv (pinned)
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.11.19"
enable-cache: false # this job runs once per release; a stale cache is not worth the risk
- name: Sync environment (Python 3.14, locked, with dev tools)
run: uv sync --frozen
- name: Run the full gate at the tagged commit (ruff + mypy --strict + pytest + coverage + i18n)
run: make verify
- name: Build wheel + sdist, verifying the build is reproducible
run: make repro
- name: Smoke-test the installed wheel and packaged app
run: |
uv venv --python 3.14 /tmp/habitable-wheel-smoke
uv pip install --python /tmp/habitable-wheel-smoke/bin/python dist/*.whl
/tmp/habitable-wheel-smoke/bin/python scripts/smoke_test_installed_wheel.py
- name: Generate a runtime SBOM (CycloneDX)
run: |
uv sync --frozen --no-dev
uvx cyclonedx-py environment .venv > dist/sbom.cdx.json
- name: Attest build provenance (signed via Sigstore)
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: "dist/*.whl,dist/*.tar.gz"
- name: Publish artifacts to the GitHub release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
gh release view "$TAG" >/dev/null 2>&1 \
|| gh release create "$TAG" --title "habitable $TAG" --generate-notes
gh release upload "$TAG" dist/* --clobber
pypi-publish:
name: publish to PyPI (Trusted Publishing)
needs: release
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/habitable
permissions:
contents: read # checkout the tagged source
id-token: write # OIDC token for PyPI Trusted Publishing (no long-lived secret)
steps:
- name: Harden Runner (audit mode)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install uv (pinned)
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.11.19"
enable-cache: false
- name: Build wheel + sdist
run: uv build
# Trusted Publishing: requires a one-time PyPI "pending publisher" for project
# "habitable" (repo ChelseaKR/habitable, workflow release.yml, environment pypi).
# See docs/releasing.md. No API token needed once configured.
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0