Release #119
Workflow file for this run
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, publishing to PyPI, and building/pushing new Docker images. | |
# 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 | |
env: | |
PYTHON_VERSION: "3.12" | |
POETRY_VERSION: "1.8.3" | |
jobs: | |
build_dist: | |
name: Build and verify Python distribution | |
runs-on: ubuntu-latest | |
outputs: | |
phylum_rel_ver: ${{ steps.get_vers.outputs.phylum_rel_ver }} | |
phylum_rel_ver_nuitka: ${{ steps.get_vers.outputs.phylum_rel_ver_nuitka }} | |
psr_expected_next_ver: ${{ steps.get_vers.outputs.psr_expected_next_ver }} | |
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 | |
- name: Install poetry | |
run: pipx install poetry==${{ env.POETRY_VERSION }} | |
- 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@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
cache: 'poetry' | |
- name: Install the project with poetry | |
run: | | |
poetry env use python${{ env.PYTHON_VERSION }} | |
poetry check --lock | |
poetry lock --no-update --no-cache | |
poetry install --verbose --sync --with test,ci | |
- name: Set to next version for build | |
id: get_vers | |
run: | | |
curr_ver=$(poetry version --short) | |
next_ver=$(poetry run semantic-release -v version --print) | |
echo "psr_expected_next_ver=${next_ver}" >> "${GITHUB_OUTPUT}" | |
if [ "${curr_ver}" = "${next_ver}" ]; then | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
next_ver=$(poetry run semantic-release -v --strict version --print --as-prerelease --patch) | |
else | |
next_ver=$(poetry run semantic-release -v --strict version --print --patch) | |
fi | |
else | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
next_ver=$(poetry run semantic-release -v --strict version --print --as-prerelease) | |
fi | |
fi | |
poetry version "${next_ver}" | |
phylum_rel_ver=$(poetry version --short) | |
phylum_rel_ver_nuitka=${phylum_rel_ver//-rc/} | |
echo "phylum_rel_ver=${phylum_rel_ver}" >> "${GITHUB_OUTPUT}" | |
echo "phylum_rel_ver_nuitka=${phylum_rel_ver_nuitka}" >> "${GITHUB_OUTPUT}" | |
# Run the tests as a sanity check. | |
- name: Run tox via poetry | |
run: poetry run tox | |
- name: Build wheel and source distribution | |
run: poetry build -vvv | |
- name: Upload build artifacts | |
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 | |
with: | |
name: dist | |
path: ./dist/ | |
if-no-files-found: error | |
- name: Publish to TestPyPI | |
if: github.ref_name == 'main' | |
# 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 | |
if: github.ref_name == 'main' | |
run: | | |
pipx run \ | |
--index-url https://test.pypi.org/simple/ \ | |
--spec "phylum==${{ steps.get_vers.outputs.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==${{ steps.get_vers.outputs.phylum_rel_ver }}" \ | |
--pip-args="--extra-index-url=https://pypi.org/simple/" \ | |
phylum-ci -h | |
build_windows: | |
name: Build Windows standalone binary | |
needs: build_dist | |
runs-on: windows-latest | |
steps: | |
- name: Checkout the repo | |
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
# Nuitka needs the packaged form and not the editable install Poetry provides | |
# Ref: https://github.com/Nuitka/Nuitka/issues/2965 | |
- name: Download build artifacts | |
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
- name: Set up Python | |
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install poetry | |
run: pipx install --python ${{ env.PYTHON_VERSION }} poetry==${{ env.POETRY_VERSION }} | |
- name: Configure poetry | |
run: poetry config virtualenvs.in-project true | |
- name: Install the project with poetry | |
run: | | |
poetry check --lock | |
poetry lock --no-update --no-cache | |
poetry install --verbose --no-root --sync --with compile | |
poetry run python -m pip install --find-links dist --no-index phylum | |
- name: Compile binary with Nuitka | |
env: | |
PHYLUM_REL_VER: ${{ needs.build_dist.outputs.phylum_rel_ver_nuitka }} | |
run: | | |
poetry run python -m nuitka ` | |
--onefile ` | |
--output-dir=build ` | |
--output-filename="phylum-ci.exe" ` | |
--include-package=phylum ` | |
--include-package-data=phylum ` | |
--include-distribution-metadata=phylum ` | |
--onefile-tempdir-spec="{CACHE_DIR}/{PRODUCT}/{VERSION}" ` | |
--product-name=phylum-ci ` | |
--product-version=${env:PHYLUM_REL_VER} ` | |
--file-version=${env:GITHUB_RUN_NUMBER} ` | |
--company-name="Phylum, Inc." ` | |
--copyright="Copyright (C) 2024 Phylum, Inc." ` | |
--file-description="Analyze dependencies in CI with Phylum" ` | |
--windows-icon-from-ico="docs/img/favicon.ico" ` | |
--warn-implicit-exceptions ` | |
--warn-unusual-code ` | |
--assume-yes-for-downloads ` | |
--report=nuitka-compilation-report.xml ` | |
--deployment ` | |
src/phylum/ci/cli.py | |
- name: Confirm operation of binary | |
env: | |
PHYLUM_API_KEY: ${{ secrets.PHYLUM_TOKEN }} | |
PHYLUM_BYPASS_CI_DETECTION: true | |
run: | | |
./build/phylum-ci.exe -h | |
./build/phylum-ci.exe -vvaf | |
- name: Upload standalone binary | |
if: always() | |
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 | |
with: | |
name: phylum-ci.exe | |
path: ./build/phylum-ci.exe | |
if-no-files-found: error | |
- name: Upload compilation report | |
if: always() | |
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 | |
with: | |
name: nuitka-compilation-report.xml | |
path: ./nuitka-compilation-report.xml | |
if-no-files-found: warn | |
# Nuitka will create a crash report with a static name when there are failures | |
- name: Upload crash report | |
if: always() | |
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 | |
with: | |
name: nuitka-crash-report.xml | |
path: ./nuitka-crash-report.xml | |
if-no-files-found: ignore | |
release: | |
name: Release and publish artifacts | |
needs: [build_dist, build_windows] | |
if: github.ref_name == 'main' | |
environment: | |
name: Production | |
url: ${{ steps.pypi_release.outputs.url }} | |
runs-on: ubuntu-latest | |
env: | |
DOCKER_BUILDKIT: 1 | |
PHYLUM_REL_VER: ${{ needs.build_dist.outputs.phylum_rel_ver }} | |
PSR_EXPECTED_NEXT_VER: ${{ needs.build_dist.outputs.psr_expected_next_ver }} | |
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: Download build artifacts | |
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
- name: Install poetry | |
run: pipx install poetry==${{ env.POETRY_VERSION }} | |
- name: Configure poetry | |
run: poetry config virtualenvs.in-project true | |
- name: Set up Python | |
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
cache: 'poetry' | |
- name: Install the project with poetry | |
run: | | |
poetry env use python${{ env.PYTHON_VERSION }} | |
poetry check --lock | |
poetry lock --no-update --no-cache | |
poetry install --verbose --sync --with ci | |
- 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: | | |
curr_ver=$(poetry version --short) | |
if [ "${curr_ver}" = "${{ env.PSR_EXPECTED_NEXT_VER }}" ]; then | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
poetry run semantic-release -v --strict version --as-prerelease --patch | |
else | |
poetry run semantic-release -v --strict version --patch | |
fi | |
else | |
if [ "${{ inputs.prerelease }}" = "true" ]; then | |
poetry run semantic-release -v --strict version --as-prerelease | |
else | |
poetry run semantic-release -v version | |
fi | |
fi | |
- name: Use Python Semantic Release to publish release | |
env: | |
GH_TOKEN: ${{ secrets.GH_RELEASE_PAT }} | |
run: poetry run semantic-release -v --strict publish --tag "v${{ env.PHYLUM_REL_VER }}" | |
- 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}" | |
# This is the safety net for the previous step allowing "continue-on-error" | |
- name: Verify PyPI release | |
run: | | |
pipx run --spec "phylum==${{ env.PHYLUM_REL_VER }}" phylum-init -h | |
pipx run --spec "phylum==${{ env.PHYLUM_REL_VER }}" phylum-ci -h | |
- name: Trigger documentation update | |
# 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 }} | |
# 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` and `slim` images 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 |