chore: setup release, readthedocs#186
Conversation
There was a problem hiding this comment.
Pull request overview
Sets up the project's release tooling: a manually-triggered GitHub Actions release pipeline that uses semantic-release to determine the next version, publishes to TestPyPI then PyPI via OIDC/Trusted Publishing, runs a post-publish round-trip validation against AWS, and creates a GitHub release. Also adds Sphinx-based ReadTheDocs configuration and a docs optional-dependency extra so RTD can build the API reference.
Changes:
- New
release.ymlworkflow (determine-version → test → build → publish-testpypi → validate-testpypi → publish-pypi → validate-pypi → create-release) plus.releaserc.cjsconfiguring conventional-commits-driven versioning. - New
release-validation/validate.pyperforming a KMS-keyring put/get round-trip against S3 using the installed wheel. - New Sphinx docs scaffolding (
docs/conf.py,docs/index.rst,docs/api.rst),.readthedocs.yaml, and adocsextra inpyproject.toml.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/release.yml | New 7-job release pipeline gated on workflow_dispatch with optional version override / dry-run. |
| .releaserc.cjs | semantic-release config: commit-analyzer, release notes, sed-based pyproject version bump, changelog, git, github. |
| release-validation/validate.py | Post-publish smoke test that encrypts/decrypts a unique S3 object using the installed package. |
| .readthedocs.yaml | RTD build config pinning Ubuntu 22.04 + Python 3.11 and installing the docs extra. |
| docs/conf.py | Sphinx config with autodoc, napoleon (Google docstrings), intersphinx, RTD theme. |
| docs/index.rst | Landing page with project blurb, getting-started snippet, and toctree to api. |
| docs/api.rst | autodoc references for client, materials, keyring, and exceptions modules. |
| pyproject.toml | Adds docs optional-dependency group (sphinx + sphinx-rtd-theme). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| path: dist/ | ||
| - uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org/legacy/ |
| # Manual override: create a simple GitHub release | ||
| gh release create "v${{ needs.determine-version.outputs.version }}" \ | ||
| --title "v${{ needs.determine-version.outputs.version }}" \ |
| import boto3 | ||
|
|
||
| from s3_encryption import S3EncryptionClient, S3EncryptionClientConfig | ||
| from s3_encryption._utils import _PACKAGE_VERSION |
| # Put | ||
| print(f" Encrypting and uploading to s3://{BUCKET}/{key}") | ||
| s3ec.put_object(Bucket=BUCKET, Key=key, Body=plaintext) | ||
|
|
||
| # Get | ||
| print(f" Downloading and decrypting from s3://{BUCKET}/{key}") | ||
| response = s3ec.get_object(Bucket=BUCKET, Key=key) | ||
| result = response["Body"].read() | ||
|
|
||
| assert result == plaintext, f"Round-trip failed: expected {plaintext!r}, got {result!r}" | ||
|
|
||
| # Cleanup | ||
| s3_client.delete_object(Bucket=BUCKET, Key=key) | ||
|
|
| test: | ||
| name: Run Tests | ||
| needs: determine-version | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: "3.10" | ||
| - run: pip install uv | ||
| - run: make install | ||
| - uses: aws-actions/configure-aws-credentials@v6 | ||
| with: | ||
| role-to-assume: arn:aws:iam::370957321024:role/S3EC-Python-Github-test-role | ||
| aws-region: us-west-2 | ||
| - name: Unit tests | ||
| run: make test-unit | ||
| - name: Integration tests | ||
| run: make test-integration | ||
| env: | ||
| CI_S3_BUCKET: ${{ vars.CI_S3_BUCKET }} | ||
| CI_KMS_KEY_ALIAS: ${{ vars.CI_KMS_KEY_ALIAS }} | ||
| CI_MRK_KEY_ID_PRIMARY: ${{ vars.CI_MRK_KEY_ID_PRIMARY }} | ||
| CI_MRK_KEY_ID_REPLICA: ${{ vars.CI_MRK_KEY_ID_REPLICA }} | ||
| CI_S3_STATIC_TEST_BUCKET: ${{ vars.CI_S3_STATIC_TEST_BUCKET }} | ||
| CI_KMS_KEY_STATIC_TESTS: ${{ vars.CI_KMS_KEY_STATIC_TESTS }} | ||
| - name: Example tests | ||
| run: make test-examples |
| echo "No release needed based on commits" | ||
| exit 1 | ||
| fi | ||
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | ||
| echo "Semantic release determined version: $VERSION" | ||
| fi | ||
|
|
||
| test: | ||
| name: Run Tests | ||
| needs: determine-version |
lucasmcdonald3
left a comment
There was a problem hiding this comment.
I'm pretty sure we can make the docs private for now if we want to publish right? I think it'd be good to make sure the build works and docs look good before release but not strictly necessary
| persist-credentials: true | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "20" |
|
|
||
| import boto3 | ||
|
|
||
| from s3_encryption import S3EncryptionClient, S3EncryptionClientConfig |
There was a problem hiding this comment.
For validate scripts we should just need a smoke test that the package is usable -- i.e. this import line is sufficient. My concern about a real integ test here is that it needs permissions which could be annoying to maintain. But just a personal preference and nbd
There was a problem hiding this comment.
I think this is a bit easier to maintain in GHA since we have the same identity/permissions as CI. I'd rather have the coverage that something within the runtime isn't broken.
| if [ -n "${{ inputs.version_override }}" ]; then | ||
| # Manual override: create a simple GitHub release | ||
| gh release create "v${{ needs.determine-version.outputs.version }}" \ | ||
| --title "v${{ needs.determine-version.outputs.version }}" \ | ||
| --generate-notes | ||
| else | ||
| npx semantic-release | ||
| fi |
There was a problem hiding this comment.
I don't fully understand this in either path --
- If we specify a version then the script will automatically publish a release right? Do we do this anywhere else? I thought we prefer to have the bot make a draft then humans review and click publish
- What does running
npx semantic-releasedo here?
There was a problem hiding this comment.
The Github releases can be a draft, I'll fix that in the next revision.
npx semantic-release does this. In short it commits the version change and creates a Github release.
The branch exists because if we give a manual override, we can't use semantic-release so we have to do the various steps manually. Notably, there is no CHANGELOG update when this happens, I'll make a note to mention this in the release process.
| [ | ||
| "@semantic-release/git", | ||
| { | ||
| assets: ["pyproject.toml", "CHANGELOG.md"], |
There was a problem hiding this comment.
Will this include the source code by default?
There was a problem hiding this comment.
If you mean the zip of source code in Github releases, that is done automatically by Github whenever a git tag exists.
| "sphinx>=7.0", | ||
| "sphinx-rtd-theme>=2.0", |
There was a problem hiding this comment.
I might suggest pinning to major version here, I've been burned before
…python into kessplas/release
Issue #, if available:
Description of changes: Adds release workflow to publish to TestPyPi and PyPi. This uses OIDC; we will need to configure OIDC/TrustedPublishing in PyPi for this to work. Also configures ReadTheDocs, which also needs external action to setup.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.