Skip to content

Release

Release #113

Workflow file for this run

# 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