Release #113
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a workflow for releasing packages in GitHub and publishing to PyPI. | |
# This workflow is only triggered manually, from the Actions tab. It is limited to those with `write` access | |
# to the repo (e.g., collaborators and orgs, people, teams given write access) | |
# | |
# The release process leans heavily on the Python Semantic Release (PSR) package, which in turn is dependent on | |
# conventional commits to determine release versions. Poetry is used to build the release distributions in order to | |
# use them for "verification" purposes *before* creating a GitHub release and publishing to PyPI. Currently, the | |
# verification process is simply uploading the distributions to TestPyPI and then confirming that the package can be | |
# accessed/used from there. In the future, a more robust end-to-end (E2E) pipeline could be triggered to run instead. | |
# | |
# References: | |
# https://github.community/t/who-has-permission-to-workflow-dispatch/133981 | |
# https://github.community/t/who-can-manually-trigger-a-workflow-using-workflow-dispatch/128592 | |
# https://python-semantic-release.readthedocs.io/en/latest/index.html | |
# https://www.conventionalcommits.org/en/v1.0.0/ | |
--- | |
name: Release | |
concurrency: Production | |
on: | |
workflow_dispatch: | |
inputs: | |
prerelease: | |
description: "Make this a pre-release" | |
type: boolean | |
required: true | |
default: false | |
jobs: | |
release: | |
name: Build, Verify, Release, and Publish | |
if: github.ref_name == 'main' | |
environment: | |
name: Production | |
url: ${{ steps.pypi_release.outputs.url }} | |
runs-on: ubuntu-latest | |
strategy: | |
matrix: | |
# It's only one Python version specified in a "matrix", but on purpose to stay DRY | |
python-version: ["3.12"] | |
defaults: | |
run: | |
shell: bash | |
env: | |
DOCKER_BUILDKIT: 1 | |
steps: | |
- name: Checkout the repo | |
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
with: | |
# `python-semantic-release` needs full history to properly determine the next release version | |
fetch-depth: 0 | |
# `python-semantic-release` needs this personal access token (PAT) in order to push to a protected branch. | |
# This PAT is for the `phylum-bot` account and only has the `public_repo` scope to limit privileges. | |
token: ${{ secrets.GH_RELEASE_PAT }} | |
# This GPG key is for the `phylum-bot` account and used in order to ensure commits and tags are signed/verified | |
- name: Import GPG key for bot account | |
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 | |
with: | |
gpg_private_key: ${{ secrets.PHYLUM_BOT_GPG_PRIVATE_KEY }} | |
passphrase: ${{ secrets.PHYLUM_BOT_GPG_PASSPHRASE }} | |
git_user_signingkey: true | |
git_commit_gpgsign: true | |
git_tag_gpgsign: true | |
- name: Install poetry | |
run: pipx install poetry==1.8.3 | |
- name: Configure poetry | |
run: | | |
poetry config virtualenvs.in-project true | |
poetry config repositories.testpypi https://test.pypi.org/legacy/ | |
- name: Set up Python | |
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 | |
with: | |
python-version: ${{ matrix.python-version }} | |
cache: 'poetry' | |
- name: Install the project with poetry | |
run: | | |
poetry env use python${{ matrix.python-version }} | |
poetry check --lock | |
poetry lock --no-update --no-cache | |
poetry install --verbose --sync --with test,ci | |
- name: Set PHYLUM_ORIGINAL_VER value | |
run: echo "PHYLUM_ORIGINAL_VER=$(poetry version --short)" >> "${GITHUB_ENV}" | |
- name: Set to next version for build | |
run: | | |
next_ver=$(poetry run semantic-release -vv version --print) | |
echo "PSR_EXPECTED_NEXT_VER=${next_ver}" >> "${GITHUB_ENV}" | |
if [ "${{ env.PHYLUM_ORIGINAL_VER }}" = "${next_ver}" ]; then | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
next_ver=$(poetry run semantic-release -vv --strict version --print --as-prerelease --patch) | |
else | |
next_ver=$(poetry run semantic-release -vv --strict version --print --patch) | |
fi | |
else | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
next_ver=$(poetry run semantic-release -vv --strict version --print --as-prerelease) | |
fi | |
fi | |
poetry version "${next_ver}" | |
- name: Set PHYLUM_REL_VER value | |
run: echo "PHYLUM_REL_VER=$(poetry version --short)" >> "${GITHUB_ENV}" | |
# NOTE: Run the tests for the current active Python version, as a sanity check. | |
- name: Run tox via poetry | |
run: poetry run tox | |
- name: Build wheel and source distribution | |
run: poetry build -vvv | |
- name: Publish to TestPyPI | |
# Allow for re-running the workflow, with the same release package version, for situations where that package is | |
# already published to TestPyPI (e.g., failures due to TestPyPI being slow to recognize a published package). | |
continue-on-error: true | |
run: poetry publish -vvv --repository testpypi --username __token__ --password ${{ secrets.TESTPYPI_API_TOKEN }} | |
# This step is currently only verifying that the package uploaded to TestPyPI can be installed and run from there. | |
# This would be a good spot to trigger a more holistic E2E or integration (as it were) level testing suite. It is | |
# also possible to use the `Production` environment here as a manual gating function. That is, the required | |
# "reviewers" for that environment would be notified of the release and could check that the package verification | |
# step(s) worked as intended before approving the release to proceed. | |
- name: Verify the package | |
run: | | |
pipx run \ | |
--index-url https://test.pypi.org/simple/ \ | |
--spec "phylum==${{ env.PHYLUM_REL_VER }}" \ | |
--pip-args="--extra-index-url=https://pypi.org/simple/" \ | |
phylum-init -h | |
pipx run \ | |
--index-url https://test.pypi.org/simple/ \ | |
--spec "phylum==${{ env.PHYLUM_REL_VER }}" \ | |
--pip-args="--extra-index-url=https://pypi.org/simple/" \ | |
phylum-ci -h | |
# This step is needed b/c otherwise the Python Semantic Release `version` cmd would bump the version a 2nd time. | |
- name: Revert to original version | |
run: poetry version ${{ env.PHYLUM_ORIGINAL_VER }} | |
- name: Update script options documentation | |
run: poetry run rich-codex --verbose --skip-git-checks --no-confirm | |
- name: Use Python Semantic Release to create release | |
env: | |
GH_TOKEN: ${{ secrets.GH_RELEASE_PAT }} | |
run: | | |
if [ "${{ env.PHYLUM_ORIGINAL_VER }}" = "${{ env.PSR_EXPECTED_NEXT_VER }}" ]; then | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
poetry run semantic-release -vv --strict version --as-prerelease --patch | |
else | |
poetry run semantic-release -vv --strict version --patch | |
fi | |
else | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
poetry run semantic-release -vv --strict version --as-prerelease | |
else | |
poetry run semantic-release -vv version | |
fi | |
fi | |
- name: Publish to PyPI | |
id: pypi_release | |
# Allow for re-running the workflow, with the same release package version, | |
# for situations where that package is already published to PyPI. | |
continue-on-error: true | |
run: | | |
poetry publish -vvv --username __token__ --password ${{ secrets.PYPI_API_TOKEN }} | |
echo "url=https://pypi.org/project/phylum/${{ env.PHYLUM_REL_VER }}/" >> "${GITHUB_OUTPUT}" | |
- name: Use Python Semantic Release to publish release | |
env: | |
GH_TOKEN: ${{ secrets.GH_RELEASE_PAT }} | |
run: poetry run semantic-release -vv --strict publish --tag "v${{ env.PHYLUM_REL_VER }}" | |
- name: Trigger documentation update | |
if: ${{ !inputs.prerelease }} | |
# Reference: https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GH_RELEASE_PAT }} | |
script: | | |
const response = await github.rest.repos.createDispatchEvent({ | |
owner: "phylum-dev", | |
repo: "documentation", | |
event_type: "trigger-update-submodule", | |
client_payload: { | |
repo_name: context.repo.repo, | |
tag_name: "v${{ env.PHYLUM_REL_VER }}" | |
}, | |
}); | |
console.log(response); | |
- name: Build default docker image | |
run: | | |
docker build \ | |
--tag phylum-ci \ | |
--build-arg PKG_SRC=dist/phylum-*.whl \ | |
--build-arg PKG_NAME=phylum-*.whl \ | |
--build-arg BUILDKIT_INLINE_CACHE=1 \ | |
--build-arg GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \ | |
. | |
- name: Test default docker image with pre-built distributions | |
run: scripts/docker_tests.sh --image phylum-ci | |
- name: Build slim docker image | |
run: | | |
docker build \ | |
--tag phylum-ci-slim \ | |
--build-arg PKG_SRC=dist/phylum-*.whl \ | |
--build-arg PKG_NAME=phylum-*.whl \ | |
--build-arg BUILDKIT_INLINE_CACHE=1 \ | |
--build-arg GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \ | |
--file Dockerfile.slim \ | |
. | |
- name: Test slim docker image with pre-built distributions | |
run: scripts/docker_tests.sh --image phylum-ci-slim --slim | |
- name: Login to Docker Hub | |
run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_TOKEN }} | |
- name: Login to GitHub Container Registry | |
run: docker login --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} ghcr.io | |
- name: Create specific docker tags and push them | |
run: | | |
CLI_REL_VER=$(docker run --rm phylum-ci phylum --version | sed 's/phylum //') | |
docker tag phylum-ci "phylumio/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}" | |
docker tag phylum-ci-slim "phylumio/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}-slim" | |
docker tag phylum-ci "ghcr.io/phylum-dev/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}" | |
docker tag phylum-ci-slim "ghcr.io/phylum-dev/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}-slim" | |
docker push "phylumio/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}" | |
docker push "phylumio/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}-slim" | |
docker push "ghcr.io/phylum-dev/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}" | |
docker push "ghcr.io/phylum-dev/phylum-ci:${{ env.PHYLUM_REL_VER }}-CLI${CLI_REL_VER}-slim" | |
- name: Tag and push latest docker images | |
# Only tag and push `latest` when it's not a phylum-ci pre-release | |
# NOTE: This is an instance where the expression syntax (`${{ }}`) is required for the `if` conditional, | |
# contrary to the GitHub workflow syntax documentation. Do not remove the expression syntax. | |
if: ${{ !inputs.prerelease }} | |
run: | | |
docker tag phylum-ci phylumio/phylum-ci:latest | |
docker tag phylum-ci-slim phylumio/phylum-ci:slim | |
docker tag phylum-ci ghcr.io/phylum-dev/phylum-ci:latest | |
docker tag phylum-ci-slim ghcr.io/phylum-dev/phylum-ci:slim | |
docker push phylumio/phylum-ci:latest | |
docker push phylumio/phylum-ci:slim | |
docker push ghcr.io/phylum-dev/phylum-ci:latest | |
docker push ghcr.io/phylum-dev/phylum-ci:slim | |
- name: Logout of Docker Hub | |
if: always() | |
run: docker logout | |
- name: Logout of GitHub Container Registry | |
if: always() | |
run: docker logout ghcr.io |