ci-cd-playground 0.1.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.3
2
+ Name: ci-cd-playground
3
+ Version: 0.1.16
4
+ Summary: A sample Python project with uv, ruff, pyright, and pytest
5
+ Author: john
6
+ Author-email: john <96323078+JohnStephens1@users.noreply.github.com>
7
+ Requires-Dist: fastapi>=0.162.0
8
+ Requires-Dist: uvicorn>=0.54.0
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+
12
+ # ci-cd-playground
13
+
14
+ A Python project configured with [`uv`](https://docs.astral.sh/uv/), [`ruff`](https://docs.astral.sh/ruff/), [`pyright`](https://microsoft.github.io/pyright/), and [`pytest`](https://docs.pytest.org/), complete with GitHub Actions Continuous Integration (CI), Release automation, and automated publishing to TestPyPI.
15
+
16
+ ## Prerequisites
17
+
18
+ - [uv](https://docs.astral.sh/uv/getting-started/installation/) (v0.5+ recommended)
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Install Dependencies
23
+
24
+ Install all dependencies (including development tools) into a local virtual environment:
25
+
26
+ ```bash
27
+ uv sync
28
+ ```
29
+
30
+ ### 2. Run Quality Checks & Tests Locally
31
+
32
+ | Tool | Purpose | Command |
33
+ | :--- | :--- | :--- |
34
+ | **Ruff Check** | Linting and code style rules | `uv run ruff check .` |
35
+ | **Ruff Format** | Code formatting verification | `uv run ruff format --check .` |
36
+ | **Pyright** | Static type checking | `uv run pyright` |
37
+ | **Pytest** | Unit test suite | `uv run pytest` |
38
+ | **Build** | Build sdist and wheel | `uv build` |
39
+
40
+ To automatically fix lint violations or format code:
41
+
42
+ ```bash
43
+ # Format code
44
+ uv run ruff format .
45
+
46
+ # Auto-fix linting issues
47
+ uv run ruff check --fix .
48
+ ```
49
+
50
+ ### 3. Run the FastAPI Application
51
+
52
+ Start the local development server with auto-reload:
53
+
54
+ ```bash
55
+ uv run uvicorn ci_cd_playground:app --reload
56
+ ```
57
+
58
+ The API endpoints will be accessible at `http://127.0.0.1:8000/` and interactive Swagger docs at `http://127.0.0.1:8000/docs`.
59
+
60
+ ### 4. Build Distribution Packages
61
+
62
+ Build both the source distribution (`.tar.gz`) and wheel (`.whl`):
63
+
64
+ ```bash
65
+ uv build
66
+ ```
67
+
68
+ The output packages will be located in the `dist/` directory.
69
+
70
+ ## GitHub Actions Workflows
71
+
72
+ The repository organizes continuous integration and deployment into modular, decoupled workflows:
73
+
74
+ ### 1. Continuous Integration (CI) — [`.github/workflows/ci.yml`](.github/workflows/ci.yml)
75
+
76
+ Runs automatically on pushes and pull requests to `main`, and functions as a **reusable workflow** (`workflow_call`):
77
+
78
+ 1. **Lint & Format (Ruff)**: Verifies formatting and runs linting rules.
79
+ 2. **Type Check (Pyright)**: Validates static typing rules across `src/` and `tests/`.
80
+ 3. **Matrix Tests (Pytest)**: Runs the test suite across supported Python versions (`3.10`, `3.11`, `3.12`, `3.13`) using `astral-sh/setup-uv`.
81
+ 4. **Security Check (zizmor)**: Audits GitHub Actions workflows for security vulnerabilities and permissions issues.
82
+ 5. **Build (uv build)**: Builds source distributions and wheels, performs a smoke test by installing and importing the wheel in an isolated environment, and uploads build artifacts (`dist-packages`).
83
+
84
+ ### 2. Container Security — [`.github/workflows/container-security.yml`](.github/workflows/container-security.yml)
85
+
86
+ Runs on pull requests to `main` and via `workflow_call`:
87
+ - Builds the runtime Docker image.
88
+ - Scans for vulnerabilities with Trivy using repository ignore configurations.
89
+
90
+ ### 3. Docker Build & Publish — [`.github/workflows/docker-publish.yml`](.github/workflows/docker-publish.yml)
91
+
92
+ A **reusable workflow** (`workflow_call`) that encapsulates container lifecycle tasks:
93
+ 1. Builds the production image with `APP_VERSION` and `GIT_SHA` build arguments.
94
+ 2. Scans the container with Trivy for CRITICAL and HIGH severity issues.
95
+ 3. Starts the container locally and verifies `/health` response.
96
+ 4. Logs into GitHub Container Registry (GHCR) and publishes the tagged images (`${GITHUB_SHA}` and optional custom/release tag).
97
+
98
+ ### 4. Deploy Service — [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)
99
+
100
+ A **reusable deployment workflow** (`workflow_call`) that unifies Render deployment logic for all environments:
101
+ 1. Configures the target GitHub `environment` (e.g. `staging` or `production`), enabling environment protection rules, approvals, and secret isolation.
102
+ 2. Triggers Render deploy via REST API for the given service ID and image tag.
103
+ 3. Polls Render deployment status every 30s until `live` or failed.
104
+ 4. Verifies service availability via `/health` and validates that `/version` is running the expected commit SHA.
105
+
106
+ ### 5. Continuous Delivery (CD) — [`.github/workflows/cd.yml`](.github/workflows/cd.yml)
107
+
108
+ Triggers automatically on pushes to `main` and via manual `workflow_dispatch`:
109
+ 1. **CI Validation & Build**: Calls `ci.yml` to verify quality gates.
110
+ 2. **Docker Publish**: Calls `docker-publish.yml` to build, scan, test, and push the image to GHCR.
111
+ 3. **Deploy to Staging**: Automatically calls `deploy.yml` with `staging` settings and validates staging health.
112
+ 4. **Deploy to Production**: Calls `deploy.yml` with `production` settings. Can be gated with GitHub Environment manual approval rules or targeted selectively via manual dispatch.
113
+
114
+ ### 6. Package Build & Provenance (SLSA Level 3) — [`.github/workflows/package-build.yml`](.github/workflows/package-build.yml)
115
+
116
+ A **trusted reusable builder workflow** (`workflow_call`) that enforces isolation and build platform integrity to meet **SLSA v1.0 Build Level 3**:
117
+ 1. **Isolated Build**: Checks out the target commit and builds both distributions (`uv build --no-sources`) strictly once.
118
+ 2. **Artifact Identification**: Computes cryptographic SHA-256 digests for both the wheel (`.whl`) and sdist (`.tar.gz`), generating `checksums/SHA256SUMS`.
119
+ 3. **Individual SBOM Generation (Syft)**: Scans the actual built distributions independently with Syft to generate:
120
+ - Machine-readable SPDX 2.3 JSON: `package-wheel.spdx.json` and `package-sdist.spdx.json`
121
+ - Human-readable summary tables: `package-wheel.txt` and `package-sdist.txt`
122
+ 4. **SLSA Build Provenance Attestation**: Generates cryptographically signed SLSA provenance via `actions/attest` backed by Sigstore, bound to both distribution digests and exported to `attestations/package-provenance.intoto.jsonl`.
123
+ 5. **Individual SBOM Attestations**: Cryptographically signs each distribution's SBOM with `actions/attest`, binding the wheel SBOM to the wheel digest and the sdist SBOM to the sdist digest (`package-wheel-sbom.attestation.json` and `package-sdist-sbom.attestation.json`).
124
+ 6. **Immutable Artifact Preservation**: Uploads distributions, SBOMs, attestations, and checksums as GitHub Actions artifacts for downstream pipeline consumption.
125
+
126
+ ### 7. Release & PyPI/TestPyPI Publishing — [`.github/workflows/release.yml`](.github/workflows/release.yml)
127
+
128
+ Triggers whenever a version tag matching `v*` (e.g. `v0.1.0`) is pushed to the repository (or via manual `workflow_dispatch`):
129
+
130
+ 1. **Validate Release Reference**: Validates git tag syntax, verifies that the tag cryptographically resolves to the target commit SHA, and ensures the git tag matches `project.version` in `pyproject.toml`.
131
+ 2. **Build via Trusted Builder**: Invokes the isolated reusable workflow `package-build.yml` to produce immutable distributions, Syft SBOMs, SLSA Level 3 provenance, and SBOM attestations.
132
+ 3. **Test Built Distributions**: Installs the built wheel in an isolated `.venv-wheel` and the built sdist in an isolated `.venv-sdist`, verifying installations and import smoke-tests before promotion.
133
+ 4. **Comprehensive Automated Verification**:
134
+ - Verifies distribution checksums against `SHA256SUMS`.
135
+ - Verifies SLSA Build Provenance authenticity and subject digests online via GitHub API and offline using the bundled `.intoto.jsonl` file.
136
+ - Verifies individual SBOM attestations online and offline using `--predicate-type https://spdx.dev/Document/v2.3`.
137
+ - Validates that the builder signer workflow strictly matches `package-build.yml` (`--signer-workflow`).
138
+ 5. **Publish to TestPyPI**:
139
+ - Publishes verified distributions using Trusted Publishing (OIDC) via `uv publish --index testpypi`.
140
+ - Performs a smoke-test installation in a clean environment from TestPyPI.
141
+ 6. **Publish to PyPI**:
142
+ - Gated by the `pypi` environment (with optional environment protection / manual approval rules).
143
+ - Generates PEP 740 digital provenance attestations via `astral-sh/attest-action`.
144
+ - Publishes verified distributions to PyPI via `uv publish`.
145
+ 7. **Create GitHub Release**:
146
+ - Creates a GitHub Release with release notes and attaches:
147
+ - `dist/`: Wheel (`.whl`) and source distribution (`.tar.gz`)
148
+ - `sbom/`: SPDX JSON and readable text SBOMs (`package-wheel.spdx.json`, `package-sdist.spdx.json`, `package-wheel.txt`, `package-sdist.txt`)
149
+ - `attestations/`: `package-provenance.intoto.jsonl`, `package-wheel-sbom.attestation.json`, `package-sdist-sbom.attestation.json`
150
+ - `checksums/`: `SHA256SUMS`
151
+
152
+ #### Configuring PyPI & TestPyPI Trusted Publishing
153
+
154
+ 1. **TestPyPI**:
155
+ - Add a Pending Publisher on [TestPyPI](https://test.pypi.org/manage/account/publishing/) targeting your GitHub repository and workflow (`release.yml`), with environment name `testpypi`.
156
+ - Create a GitHub Environment named `testpypi` in repository settings (**Settings > Environments**).
157
+ 2. **Production PyPI**:
158
+ - Add a Publisher on [PyPI](https://pypi.org/manage/account/publishing/) targeting your GitHub repository and workflow (`release.yml`), with environment name `pypi`.
159
+ - Create a GitHub Environment named `pypi` in repository settings (**Settings > Environments**), optionally configuring required reviewers for manual release sign-off.
160
+
161
+ #### Creating a Release
162
+
163
+ ```bash
164
+ # 1. Bump version in pyproject.toml (e.g. 0.1.15)
165
+ uv version 0.1.15
166
+
167
+ # 2. Commit and tag
168
+ git commit -am "chore: release v0.1.15"
169
+ git tag v0.1.15
170
+
171
+ # 3. Push tag to GitHub to trigger the release workflow
172
+ git push origin v0.1.15
173
+ ```
174
+
175
+ ## Consumer Verification Guide
176
+
177
+ Consumers can independently verify the authenticity, integrity, SLSA Build Level 3 provenance, and SBOM attestations of released artifacts without trusting the build workflow's claims.
178
+
179
+ ### 1. Verify SHA-256 Checksums
180
+
181
+ Download the release assets into a local directory and verify the distribution digests:
182
+
183
+ ```bash
184
+ sha256sum -c SHA256SUMS
185
+ ```
186
+
187
+ ### 2. Verify SLSA Build Level 3 Provenance
188
+
189
+ Using the official [GitHub CLI](https://cli.github.com/) (`gh` v2.97.0+):
190
+
191
+ **Online Verification (via Sigstore transparency log):**
192
+
193
+ ```bash
194
+ # Verify wheel provenance
195
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
196
+ --repo <OWNER>/ci-cd-playground \
197
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
198
+
199
+ # Verify sdist provenance
200
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
201
+ --repo <OWNER>/ci-cd-playground \
202
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
203
+ ```
204
+
205
+ **Offline Verification (using the release attestation bundle):**
206
+
207
+ ```bash
208
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
209
+ --bundle package-provenance.intoto.jsonl \
210
+ --repo <OWNER>/ci-cd-playground \
211
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
212
+
213
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
214
+ --bundle package-provenance.intoto.jsonl \
215
+ --repo <OWNER>/ci-cd-playground \
216
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
217
+ ```
218
+
219
+ ### 3. Verify Individual SBOM Attestations
220
+
221
+ Verify that the SBOMs were cryptographically signed and bound to the exact distribution files:
222
+
223
+ **Wheel SBOM Attestation:**
224
+
225
+ ```bash
226
+ # Online
227
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
228
+ --repo <OWNER>/ci-cd-playground \
229
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
230
+ --predicate-type https://spdx.dev/Document/v2.3
231
+
232
+ # Offline
233
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
234
+ --bundle package-wheel-sbom.attestation.json \
235
+ --repo <OWNER>/ci-cd-playground \
236
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
237
+ --predicate-type https://spdx.dev/Document/v2.3
238
+ ```
239
+
240
+ **Sdist SBOM Attestation:**
241
+
242
+ ```bash
243
+ # Online
244
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
245
+ --repo <OWNER>/ci-cd-playground \
246
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
247
+ --predicate-type https://spdx.dev/Document/v2.3
248
+
249
+ # Offline
250
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
251
+ --bundle package-sdist-sbom.attestation.json \
252
+ --repo <OWNER>/ci-cd-playground \
253
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
254
+ --predicate-type https://spdx.dev/Document/v2.3
255
+ ```
256
+
257
+ ### 4. Inspect Software Bill of Materials (SBOM)
258
+
259
+ Inspect the Syft-generated human-readable summary or machine-readable SPDX JSON:
260
+
261
+ ```bash
262
+ # Human-readable table summary
263
+ cat package-wheel.txt
264
+ cat package-sdist.txt
265
+
266
+ # Query packages in SPDX JSON with jq
267
+ jq '.packages[] | {name: .name, versionInfo: .versionInfo}' package-wheel.spdx.json
268
+ ```
269
+
270
+ ## Project Structure
271
+
272
+ ```text
273
+ ci-cd-playground/
274
+ ├── .github/
275
+ │ └── workflows/
276
+ │ ├── cd.yml # Continuous delivery pipeline (main -> staging -> production)
277
+ │ ├── ci.yml # Reusable CI workflow (lint, typecheck, test, build)
278
+ │ ├── container-security.yml # Container build & Trivy scan on PRs
279
+ │ ├── deploy.yml # Reusable Render deployment workflow
280
+ │ ├── docker-publish.yml # Reusable Docker build, test & GHCR publish
281
+ │ ├── package-build.yml # Reusable SLSA Level 3 builder (uv build, Syft SBOMs, attestations)
282
+ │ └── release.yml # Release pipeline (validation, build, test, verify, TestPyPI, PyPI, GitHub)
283
+ ├── src/
284
+ │ └── ci_cd_playground/
285
+ │ ├── __init__.py # Package entrypoint and sample code
286
+ │ ├── app.py # Minimal FastAPI application
287
+ │ └── py.typed # PEP 561 typing marker
288
+ ├── tests/
289
+ │ ├── __init__.py
290
+ │ ├── test_app.py # FastAPI endpoint and factory tests
291
+ │ └── test_ci_test.py # Base unit tests
292
+ ├── .gitignore
293
+ ├── .python-version # Default local Python version
294
+ ├── pyproject.toml # Project configuration, dependencies, and tool settings
295
+ ├── uv.lock # Deterministic dependency lockfile
296
+ └── README.md
297
+ ```
@@ -0,0 +1,286 @@
1
+ # ci-cd-playground
2
+
3
+ A Python project configured with [`uv`](https://docs.astral.sh/uv/), [`ruff`](https://docs.astral.sh/ruff/), [`pyright`](https://microsoft.github.io/pyright/), and [`pytest`](https://docs.pytest.org/), complete with GitHub Actions Continuous Integration (CI), Release automation, and automated publishing to TestPyPI.
4
+
5
+ ## Prerequisites
6
+
7
+ - [uv](https://docs.astral.sh/uv/getting-started/installation/) (v0.5+ recommended)
8
+
9
+ ## Quick Start
10
+
11
+ ### 1. Install Dependencies
12
+
13
+ Install all dependencies (including development tools) into a local virtual environment:
14
+
15
+ ```bash
16
+ uv sync
17
+ ```
18
+
19
+ ### 2. Run Quality Checks & Tests Locally
20
+
21
+ | Tool | Purpose | Command |
22
+ | :--- | :--- | :--- |
23
+ | **Ruff Check** | Linting and code style rules | `uv run ruff check .` |
24
+ | **Ruff Format** | Code formatting verification | `uv run ruff format --check .` |
25
+ | **Pyright** | Static type checking | `uv run pyright` |
26
+ | **Pytest** | Unit test suite | `uv run pytest` |
27
+ | **Build** | Build sdist and wheel | `uv build` |
28
+
29
+ To automatically fix lint violations or format code:
30
+
31
+ ```bash
32
+ # Format code
33
+ uv run ruff format .
34
+
35
+ # Auto-fix linting issues
36
+ uv run ruff check --fix .
37
+ ```
38
+
39
+ ### 3. Run the FastAPI Application
40
+
41
+ Start the local development server with auto-reload:
42
+
43
+ ```bash
44
+ uv run uvicorn ci_cd_playground:app --reload
45
+ ```
46
+
47
+ The API endpoints will be accessible at `http://127.0.0.1:8000/` and interactive Swagger docs at `http://127.0.0.1:8000/docs`.
48
+
49
+ ### 4. Build Distribution Packages
50
+
51
+ Build both the source distribution (`.tar.gz`) and wheel (`.whl`):
52
+
53
+ ```bash
54
+ uv build
55
+ ```
56
+
57
+ The output packages will be located in the `dist/` directory.
58
+
59
+ ## GitHub Actions Workflows
60
+
61
+ The repository organizes continuous integration and deployment into modular, decoupled workflows:
62
+
63
+ ### 1. Continuous Integration (CI) — [`.github/workflows/ci.yml`](.github/workflows/ci.yml)
64
+
65
+ Runs automatically on pushes and pull requests to `main`, and functions as a **reusable workflow** (`workflow_call`):
66
+
67
+ 1. **Lint & Format (Ruff)**: Verifies formatting and runs linting rules.
68
+ 2. **Type Check (Pyright)**: Validates static typing rules across `src/` and `tests/`.
69
+ 3. **Matrix Tests (Pytest)**: Runs the test suite across supported Python versions (`3.10`, `3.11`, `3.12`, `3.13`) using `astral-sh/setup-uv`.
70
+ 4. **Security Check (zizmor)**: Audits GitHub Actions workflows for security vulnerabilities and permissions issues.
71
+ 5. **Build (uv build)**: Builds source distributions and wheels, performs a smoke test by installing and importing the wheel in an isolated environment, and uploads build artifacts (`dist-packages`).
72
+
73
+ ### 2. Container Security — [`.github/workflows/container-security.yml`](.github/workflows/container-security.yml)
74
+
75
+ Runs on pull requests to `main` and via `workflow_call`:
76
+ - Builds the runtime Docker image.
77
+ - Scans for vulnerabilities with Trivy using repository ignore configurations.
78
+
79
+ ### 3. Docker Build & Publish — [`.github/workflows/docker-publish.yml`](.github/workflows/docker-publish.yml)
80
+
81
+ A **reusable workflow** (`workflow_call`) that encapsulates container lifecycle tasks:
82
+ 1. Builds the production image with `APP_VERSION` and `GIT_SHA` build arguments.
83
+ 2. Scans the container with Trivy for CRITICAL and HIGH severity issues.
84
+ 3. Starts the container locally and verifies `/health` response.
85
+ 4. Logs into GitHub Container Registry (GHCR) and publishes the tagged images (`${GITHUB_SHA}` and optional custom/release tag).
86
+
87
+ ### 4. Deploy Service — [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)
88
+
89
+ A **reusable deployment workflow** (`workflow_call`) that unifies Render deployment logic for all environments:
90
+ 1. Configures the target GitHub `environment` (e.g. `staging` or `production`), enabling environment protection rules, approvals, and secret isolation.
91
+ 2. Triggers Render deploy via REST API for the given service ID and image tag.
92
+ 3. Polls Render deployment status every 30s until `live` or failed.
93
+ 4. Verifies service availability via `/health` and validates that `/version` is running the expected commit SHA.
94
+
95
+ ### 5. Continuous Delivery (CD) — [`.github/workflows/cd.yml`](.github/workflows/cd.yml)
96
+
97
+ Triggers automatically on pushes to `main` and via manual `workflow_dispatch`:
98
+ 1. **CI Validation & Build**: Calls `ci.yml` to verify quality gates.
99
+ 2. **Docker Publish**: Calls `docker-publish.yml` to build, scan, test, and push the image to GHCR.
100
+ 3. **Deploy to Staging**: Automatically calls `deploy.yml` with `staging` settings and validates staging health.
101
+ 4. **Deploy to Production**: Calls `deploy.yml` with `production` settings. Can be gated with GitHub Environment manual approval rules or targeted selectively via manual dispatch.
102
+
103
+ ### 6. Package Build & Provenance (SLSA Level 3) — [`.github/workflows/package-build.yml`](.github/workflows/package-build.yml)
104
+
105
+ A **trusted reusable builder workflow** (`workflow_call`) that enforces isolation and build platform integrity to meet **SLSA v1.0 Build Level 3**:
106
+ 1. **Isolated Build**: Checks out the target commit and builds both distributions (`uv build --no-sources`) strictly once.
107
+ 2. **Artifact Identification**: Computes cryptographic SHA-256 digests for both the wheel (`.whl`) and sdist (`.tar.gz`), generating `checksums/SHA256SUMS`.
108
+ 3. **Individual SBOM Generation (Syft)**: Scans the actual built distributions independently with Syft to generate:
109
+ - Machine-readable SPDX 2.3 JSON: `package-wheel.spdx.json` and `package-sdist.spdx.json`
110
+ - Human-readable summary tables: `package-wheel.txt` and `package-sdist.txt`
111
+ 4. **SLSA Build Provenance Attestation**: Generates cryptographically signed SLSA provenance via `actions/attest` backed by Sigstore, bound to both distribution digests and exported to `attestations/package-provenance.intoto.jsonl`.
112
+ 5. **Individual SBOM Attestations**: Cryptographically signs each distribution's SBOM with `actions/attest`, binding the wheel SBOM to the wheel digest and the sdist SBOM to the sdist digest (`package-wheel-sbom.attestation.json` and `package-sdist-sbom.attestation.json`).
113
+ 6. **Immutable Artifact Preservation**: Uploads distributions, SBOMs, attestations, and checksums as GitHub Actions artifacts for downstream pipeline consumption.
114
+
115
+ ### 7. Release & PyPI/TestPyPI Publishing — [`.github/workflows/release.yml`](.github/workflows/release.yml)
116
+
117
+ Triggers whenever a version tag matching `v*` (e.g. `v0.1.0`) is pushed to the repository (or via manual `workflow_dispatch`):
118
+
119
+ 1. **Validate Release Reference**: Validates git tag syntax, verifies that the tag cryptographically resolves to the target commit SHA, and ensures the git tag matches `project.version` in `pyproject.toml`.
120
+ 2. **Build via Trusted Builder**: Invokes the isolated reusable workflow `package-build.yml` to produce immutable distributions, Syft SBOMs, SLSA Level 3 provenance, and SBOM attestations.
121
+ 3. **Test Built Distributions**: Installs the built wheel in an isolated `.venv-wheel` and the built sdist in an isolated `.venv-sdist`, verifying installations and import smoke-tests before promotion.
122
+ 4. **Comprehensive Automated Verification**:
123
+ - Verifies distribution checksums against `SHA256SUMS`.
124
+ - Verifies SLSA Build Provenance authenticity and subject digests online via GitHub API and offline using the bundled `.intoto.jsonl` file.
125
+ - Verifies individual SBOM attestations online and offline using `--predicate-type https://spdx.dev/Document/v2.3`.
126
+ - Validates that the builder signer workflow strictly matches `package-build.yml` (`--signer-workflow`).
127
+ 5. **Publish to TestPyPI**:
128
+ - Publishes verified distributions using Trusted Publishing (OIDC) via `uv publish --index testpypi`.
129
+ - Performs a smoke-test installation in a clean environment from TestPyPI.
130
+ 6. **Publish to PyPI**:
131
+ - Gated by the `pypi` environment (with optional environment protection / manual approval rules).
132
+ - Generates PEP 740 digital provenance attestations via `astral-sh/attest-action`.
133
+ - Publishes verified distributions to PyPI via `uv publish`.
134
+ 7. **Create GitHub Release**:
135
+ - Creates a GitHub Release with release notes and attaches:
136
+ - `dist/`: Wheel (`.whl`) and source distribution (`.tar.gz`)
137
+ - `sbom/`: SPDX JSON and readable text SBOMs (`package-wheel.spdx.json`, `package-sdist.spdx.json`, `package-wheel.txt`, `package-sdist.txt`)
138
+ - `attestations/`: `package-provenance.intoto.jsonl`, `package-wheel-sbom.attestation.json`, `package-sdist-sbom.attestation.json`
139
+ - `checksums/`: `SHA256SUMS`
140
+
141
+ #### Configuring PyPI & TestPyPI Trusted Publishing
142
+
143
+ 1. **TestPyPI**:
144
+ - Add a Pending Publisher on [TestPyPI](https://test.pypi.org/manage/account/publishing/) targeting your GitHub repository and workflow (`release.yml`), with environment name `testpypi`.
145
+ - Create a GitHub Environment named `testpypi` in repository settings (**Settings > Environments**).
146
+ 2. **Production PyPI**:
147
+ - Add a Publisher on [PyPI](https://pypi.org/manage/account/publishing/) targeting your GitHub repository and workflow (`release.yml`), with environment name `pypi`.
148
+ - Create a GitHub Environment named `pypi` in repository settings (**Settings > Environments**), optionally configuring required reviewers for manual release sign-off.
149
+
150
+ #### Creating a Release
151
+
152
+ ```bash
153
+ # 1. Bump version in pyproject.toml (e.g. 0.1.15)
154
+ uv version 0.1.15
155
+
156
+ # 2. Commit and tag
157
+ git commit -am "chore: release v0.1.15"
158
+ git tag v0.1.15
159
+
160
+ # 3. Push tag to GitHub to trigger the release workflow
161
+ git push origin v0.1.15
162
+ ```
163
+
164
+ ## Consumer Verification Guide
165
+
166
+ Consumers can independently verify the authenticity, integrity, SLSA Build Level 3 provenance, and SBOM attestations of released artifacts without trusting the build workflow's claims.
167
+
168
+ ### 1. Verify SHA-256 Checksums
169
+
170
+ Download the release assets into a local directory and verify the distribution digests:
171
+
172
+ ```bash
173
+ sha256sum -c SHA256SUMS
174
+ ```
175
+
176
+ ### 2. Verify SLSA Build Level 3 Provenance
177
+
178
+ Using the official [GitHub CLI](https://cli.github.com/) (`gh` v2.97.0+):
179
+
180
+ **Online Verification (via Sigstore transparency log):**
181
+
182
+ ```bash
183
+ # Verify wheel provenance
184
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
185
+ --repo <OWNER>/ci-cd-playground \
186
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
187
+
188
+ # Verify sdist provenance
189
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
190
+ --repo <OWNER>/ci-cd-playground \
191
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
192
+ ```
193
+
194
+ **Offline Verification (using the release attestation bundle):**
195
+
196
+ ```bash
197
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
198
+ --bundle package-provenance.intoto.jsonl \
199
+ --repo <OWNER>/ci-cd-playground \
200
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
201
+
202
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
203
+ --bundle package-provenance.intoto.jsonl \
204
+ --repo <OWNER>/ci-cd-playground \
205
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml
206
+ ```
207
+
208
+ ### 3. Verify Individual SBOM Attestations
209
+
210
+ Verify that the SBOMs were cryptographically signed and bound to the exact distribution files:
211
+
212
+ **Wheel SBOM Attestation:**
213
+
214
+ ```bash
215
+ # Online
216
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
217
+ --repo <OWNER>/ci-cd-playground \
218
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
219
+ --predicate-type https://spdx.dev/Document/v2.3
220
+
221
+ # Offline
222
+ gh attestation verify ci_cd_playground-0.1.15-py3-none-any.whl \
223
+ --bundle package-wheel-sbom.attestation.json \
224
+ --repo <OWNER>/ci-cd-playground \
225
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
226
+ --predicate-type https://spdx.dev/Document/v2.3
227
+ ```
228
+
229
+ **Sdist SBOM Attestation:**
230
+
231
+ ```bash
232
+ # Online
233
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
234
+ --repo <OWNER>/ci-cd-playground \
235
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
236
+ --predicate-type https://spdx.dev/Document/v2.3
237
+
238
+ # Offline
239
+ gh attestation verify ci_cd_playground-0.1.15.tar.gz \
240
+ --bundle package-sdist-sbom.attestation.json \
241
+ --repo <OWNER>/ci-cd-playground \
242
+ --signer-workflow <OWNER>/ci-cd-playground/.github/workflows/package-build.yml \
243
+ --predicate-type https://spdx.dev/Document/v2.3
244
+ ```
245
+
246
+ ### 4. Inspect Software Bill of Materials (SBOM)
247
+
248
+ Inspect the Syft-generated human-readable summary or machine-readable SPDX JSON:
249
+
250
+ ```bash
251
+ # Human-readable table summary
252
+ cat package-wheel.txt
253
+ cat package-sdist.txt
254
+
255
+ # Query packages in SPDX JSON with jq
256
+ jq '.packages[] | {name: .name, versionInfo: .versionInfo}' package-wheel.spdx.json
257
+ ```
258
+
259
+ ## Project Structure
260
+
261
+ ```text
262
+ ci-cd-playground/
263
+ ├── .github/
264
+ │ └── workflows/
265
+ │ ├── cd.yml # Continuous delivery pipeline (main -> staging -> production)
266
+ │ ├── ci.yml # Reusable CI workflow (lint, typecheck, test, build)
267
+ │ ├── container-security.yml # Container build & Trivy scan on PRs
268
+ │ ├── deploy.yml # Reusable Render deployment workflow
269
+ │ ├── docker-publish.yml # Reusable Docker build, test & GHCR publish
270
+ │ ├── package-build.yml # Reusable SLSA Level 3 builder (uv build, Syft SBOMs, attestations)
271
+ │ └── release.yml # Release pipeline (validation, build, test, verify, TestPyPI, PyPI, GitHub)
272
+ ├── src/
273
+ │ └── ci_cd_playground/
274
+ │ ├── __init__.py # Package entrypoint and sample code
275
+ │ ├── app.py # Minimal FastAPI application
276
+ │ └── py.typed # PEP 561 typing marker
277
+ ├── tests/
278
+ │ ├── __init__.py
279
+ │ ├── test_app.py # FastAPI endpoint and factory tests
280
+ │ └── test_ci_test.py # Base unit tests
281
+ ├── .gitignore
282
+ ├── .python-version # Default local Python version
283
+ ├── pyproject.toml # Project configuration, dependencies, and tool settings
284
+ ├── uv.lock # Deterministic dependency lockfile
285
+ └── README.md
286
+ ```
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "ci-cd-playground"
3
+ version = "0.1.16"
4
+ description = "A sample Python project with uv, ruff, pyright, and pytest"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "fastapi>=0.162.0",
9
+ "uvicorn>=0.54.0",
10
+ ]
11
+
12
+ [[project.authors]]
13
+ name = "john"
14
+ email = "96323078+JohnStephens1@users.noreply.github.com"
15
+
16
+ [build-system]
17
+ requires = ["uv_build>=0.12.22,<0.14.0"]
18
+ build-backend = "uv_build"
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "httpx2>=2.14.0",
23
+ "pre-commit>=4.6.2",
24
+ "pyright>=1.1.414",
25
+ "pytest>=9.2.0",
26
+ "ruff>=0.17.3",
27
+ "zizmor>=1.30.1",
28
+ ]
29
+
30
+ [[tool.uv.index]]
31
+ name = "testpypi"
32
+ url = "https://test.pypi.org/simple/"
33
+ publish-url = "https://test.pypi.org/legacy/"
34
+ explicit = true
35
+
36
+ [tool.ruff]
37
+ line-length = 88
38
+ target-version = "py310"
39
+ src = ["src"]
40
+
41
+ [tool.ruff.lint]
42
+ select = [
43
+ "E",
44
+ "W",
45
+ "F",
46
+ "I",
47
+ "B",
48
+ "UP",
49
+ ]
50
+ ignore = []
51
+
52
+ [tool.ruff.format]
53
+ quote-style = "double"
54
+ indent-style = "space"
55
+
56
+ [tool.pyright]
57
+ include = [
58
+ "src",
59
+ "tests",
60
+ ]
61
+ pythonVersion = "3.10"
62
+ typeCheckingMode = "standard"
63
+
64
+ [tool.pytest.ini_options]
65
+ minversion = "7.0"
66
+ testpaths = ["tests"]
67
+ pythonpath = ["src"]
68
+ addopts = "-ra -q"
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "ci-cd-playground"
3
+ version = "0.1.16"
4
+ description = "A sample Python project with uv, ruff, pyright, and pytest"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "john", email = "96323078+JohnStephens1@users.noreply.github.com" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "fastapi>=0.162.0",
12
+ "uvicorn>=0.54.0",
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["uv_build>=0.12.22,<0.14.0"]
17
+ build-backend = "uv_build"
18
+
19
+ [dependency-groups]
20
+ dev = [
21
+ "httpx2>=2.14.0",
22
+ "pre-commit>=4.6.2",
23
+ "pyright>=1.1.414",
24
+ "pytest>=9.2.0",
25
+ "ruff>=0.17.3",
26
+ "zizmor>=1.30.1",
27
+ ]
28
+
29
+ [[tool.uv.index]]
30
+ name = "testpypi"
31
+ url = "https://test.pypi.org/simple/"
32
+ publish-url = "https://test.pypi.org/legacy/"
33
+ explicit = true
34
+
35
+ [tool.ruff]
36
+ line-length = 88
37
+ target-version = "py310"
38
+ src = ["src"]
39
+
40
+ [tool.ruff.lint]
41
+ select = [
42
+ "E", # pycodestyle errors
43
+ "W", # pycodestyle warnings
44
+ "F", # pyflakes
45
+ "I", # isort
46
+ "B", # flake8-bugbear
47
+ "UP", # pyupgrade
48
+ ]
49
+ ignore = []
50
+
51
+ [tool.ruff.format]
52
+ quote-style = "double"
53
+ indent-style = "space"
54
+
55
+ [tool.pyright]
56
+ include = ["src", "tests"]
57
+ pythonVersion = "3.10"
58
+ typeCheckingMode = "standard"
59
+
60
+ [tool.pytest.ini_options]
61
+ minversion = "7.0"
62
+ testpaths = ["tests"]
63
+ pythonpath = ["src"]
64
+ addopts = "-ra -q"
@@ -0,0 +1,15 @@
1
+ """ci_cd_playground package."""
2
+
3
+ from ci_cd_playground.app import app, create_app
4
+
5
+ __all__ = ["add", "app", "create_app", "hello"]
6
+
7
+
8
+ def hello(name: str = "world") -> str:
9
+ """Return a greeting message."""
10
+ return f"Hello, {name}!"
11
+
12
+
13
+ def add(a: int | float, b: int | float) -> int | float:
14
+ """Add two numbers."""
15
+ return a + b
@@ -0,0 +1,41 @@
1
+ """FastAPI application for ci_cd_playground."""
2
+
3
+ import os
4
+
5
+ from fastapi import FastAPI
6
+
7
+
8
+ def create_app() -> FastAPI:
9
+ """Create and configure the FastAPI application."""
10
+ application = FastAPI(
11
+ title="CI/CD Playground API",
12
+ description="A minimal FastAPI application.",
13
+ version="0.1.1",
14
+ )
15
+
16
+ @application.get("/")
17
+ def read_root() -> dict[str, str]:
18
+ """Root endpoint returning a greeting."""
19
+ return {"message": "Hello, world!"}
20
+
21
+ @application.get("/health")
22
+ def health_check() -> dict[str, str]:
23
+ """Health check endpoint."""
24
+ return {"status": "ok"}
25
+
26
+ @application.get("/version")
27
+ def version() -> dict[str, str]:
28
+ return {
29
+ "version": os.getenv("APP_VERSION", "unknown"),
30
+ "commit": os.getenv("GIT_SHA", "unknown"),
31
+ }
32
+
33
+ @application.get("/hello/{name}")
34
+ def greet(name: str) -> dict[str, str]:
35
+ """Greeting endpoint accepting a path parameter."""
36
+ return {"message": f"Hello, {name}!"}
37
+
38
+ return application
39
+
40
+
41
+ app = create_app()
File without changes