Publish Release workflow (jorisroovers/gitlint)
The Publish Release workflow from jorisroovers/gitlint, explained and optimized by Latchkey.
CI health: C - fair
Run this on Latchkey for self-healing, caching, and up to 58% lower cost.
Grade your own workflow free or run it on Latchkey →What it does
This is the Publish Release workflow from the jorisroovers/gitlint repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Publish Release
run-name: "Publish Release (environment=${{ inputs.environment }}, repo_release_ref=${{ inputs.repo_release_ref }})"
on:
# Trigger release workflow from other workflows (e.g. release dev build as part of CI)
workflow_call:
inputs:
environment:
description: "Target environment"
type: string
required: true
repo_release_ref:
description: "Gitlint git reference to publish release for"
type: string
default: "main"
docker_image_tag:
description: "Docker image tag"
required: true
type: string
default: "latest_dev"
outputs:
gitlint_version:
description: "Gitlint version that was published"
value: ${{ jobs.publish.outputs.gitlint_version }}
# Manually trigger a release
workflow_dispatch:
inputs:
environment:
description: "Target environment"
type: environment
required: true
repo_release_ref:
description: "Gitlint git reference to publish release for"
type: string
default: "main"
docker_image_tag:
description: "Docker image tag"
required: true
type: choice
options:
- "latest_dev"
- "latest"
- "Use $gitlint_version"
default: "latest_dev"
jobs:
publish:
timeout-minutes: 15
runs-on: "ubuntu-latest"
environment: ${{ inputs.environment }}
permissions:
# Required for trusted publishing to PyPI
id-token: write
outputs:
gitlint_version: ${{ steps.set_version.outputs.gitlint_version }}
steps:
- name: Setup python
uses: actions/setup-python@v4.7.0
with:
python-version: "3.11"
- name: Install pypa/build
run: python -m pip install build==0.10.0
- name: Install Hatch
run: python -m pip install hatch==1.7.0
- uses: actions/checkout@v3.3.0
with:
ref: ${{ inputs.repo_release_ref }}
fetch-depth: 0 # checkout all history, needed for hatch versioning
# Run hatch version once to avoid additional output ("Setting up build environment for missing dependencies")
# during the next step
- name: Hatch version
run: hatch version
# Hatch versioning is based on git (using hatch-vcs). If there is no explicit tag for the commit we're trying to
# publish, hatch versioning strings will have this format: 0.19.0.dev52+g9f7dc7d
# With the string after '+' being the 'g<short-sha>' of the commit.
#
# However, PyPI doesn't allow '+' in version numbers (no PEP440 local versions allowed on PyPI).
# To work around this, we override the version string by setting the SETUPTOOLS_SCM_PRETEND_VERSION env var
# to the version string without the '+' and everything after it.
# We do this by setting the `gitlint_version` step output here and re-using it later to
# set SETUPTOOLS_SCM_PRETEND_VERSION.
#
# We only actually publish such releases on the main branch to guarantee the dev numbering scheme remains
# unique.
# Note that when a tag *is* present (i.e. v0.19.0), hatch versioning will return the tag name (i.e. 0.19.0)
# and this step has no effect, ie. SETUPTOOLS_SCM_PRETEND_VERSION will be the same as `hatch version`.
- name: Set SETUPTOOLS_SCM_PRETEND_VERSION
id: set_version
run: |
echo "gitlint_version=$(hatch version | cut -d+ -f1)" >> $GITHUB_OUTPUT
- name: Build (gitlint-core)
run: python -m build
working-directory: ./gitlint-core
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}
- name: Build (gitlint)
run: python -m build
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}
- name: Publish gitlint-core ππ¦ to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: gitlint-core/dist/
repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
- name: Publish gitlint ππ¦ to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
# Wait for gitlint package to be available in PyPI for installation
wait-for-package:
environment: ${{ inputs.environment }}
needs:
- publish
runs-on: "ubuntu-latest"
steps:
- name: Install gitlint
uses: nick-fields/retry@v2.8.3
with:
timeout_minutes: 1
max_attempts: 10
# We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy,
# because gitlint's dependencies are not available on Test PyPI
command: |
python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }}
- name: gitlint --version
run: |
gitlint --version
[ "$(gitlint --version)" == "gitlint, version ${{ needs.publish.outputs.gitlint_version }}" ]
# Unfortunately, it's not because the newly published package installation worked once that replication
# has finished amongst all PyPI servers (subsequent installations might still fail). We sleep for 10 min here
# to increase the odds that replication has finished.
- name: Sleep
run: sleep 600
test-release:
needs:
- publish
- wait-for-package
uses: ./.github/workflows/test-release.yml
with:
gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
environment: ${{ inputs.environment }}
repo_test_ref: ${{ inputs.repo_release_ref }}
publish-docker:
needs:
- publish
- test-release
uses: ./.github/workflows/publish-docker.yml
secrets: inherit # pass all secrets (required to access secrets in a called workflow)
with:
gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
docker_image_tag: ${{ inputs.docker_image_tag }}
push_to_dockerhub: true
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Publish Release run-name: "Publish Release (environment=${{ inputs.environment }}, repo_release_ref=${{ inputs.repo_release_ref }})" on: # Trigger release workflow from other workflows (e.g. release dev build as part of CI) workflow_call: inputs: environment: description: "Target environment" type: string required: true repo_release_ref: description: "Gitlint git reference to publish release for" type: string default: "main" docker_image_tag: description: "Docker image tag" required: true type: string default: "latest_dev" outputs: gitlint_version: description: "Gitlint version that was published" value: ${{ jobs.publish.outputs.gitlint_version }} # Manually trigger a release workflow_dispatch: inputs: environment: description: "Target environment" type: environment required: true repo_release_ref: description: "Gitlint git reference to publish release for" type: string default: "main" docker_image_tag: description: "Docker image tag" required: true type: choice options: - "latest_dev" - "latest" - "Use $gitlint_version" default: "latest_dev" jobs: publish: timeout-minutes: 15 runs-on: "ubuntu-latest" environment: ${{ inputs.environment }} permissions: # Required for trusted publishing to PyPI id-token: write outputs: gitlint_version: ${{ steps.set_version.outputs.gitlint_version }} steps: - name: Setup python uses: actions/setup-python@v4.7.0 with: cache: 'pip' python-version: "3.11" - name: Install pypa/build run: python -m pip install build==0.10.0 - name: Install Hatch run: python -m pip install hatch==1.7.0 - uses: actions/checkout@v3.3.0 with: ref: ${{ inputs.repo_release_ref }} fetch-depth: 0 # checkout all history, needed for hatch versioning # Run hatch version once to avoid additional output ("Setting up build environment for missing dependencies") # during the next step - name: Hatch version run: hatch version # Hatch versioning is based on git (using hatch-vcs). If there is no explicit tag for the commit we're trying to # publish, hatch versioning strings will have this format: 0.19.0.dev52+g9f7dc7d # With the string after '+' being the 'g<short-sha>' of the commit. # # However, PyPI doesn't allow '+' in version numbers (no PEP440 local versions allowed on PyPI). # To work around this, we override the version string by setting the SETUPTOOLS_SCM_PRETEND_VERSION env var # to the version string without the '+' and everything after it. # We do this by setting the `gitlint_version` step output here and re-using it later to # set SETUPTOOLS_SCM_PRETEND_VERSION. # # We only actually publish such releases on the main branch to guarantee the dev numbering scheme remains # unique. # Note that when a tag *is* present (i.e. v0.19.0), hatch versioning will return the tag name (i.e. 0.19.0) # and this step has no effect, ie. SETUPTOOLS_SCM_PRETEND_VERSION will be the same as `hatch version`. - name: Set SETUPTOOLS_SCM_PRETEND_VERSION id: set_version run: | echo "gitlint_version=$(hatch version | cut -d+ -f1)" >> $GITHUB_OUTPUT - name: Build (gitlint-core) run: python -m build working-directory: ./gitlint-core env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }} - name: Build (gitlint) run: python -m build env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }} - name: Publish gitlint-core ππ¦ to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: gitlint-core/dist/ repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment - name: Publish gitlint ππ¦ to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment # Wait for gitlint package to be available in PyPI for installation wait-for-package: timeout-minutes: 30 environment: ${{ inputs.environment }} needs: - publish runs-on: "ubuntu-latest" steps: - name: Install gitlint uses: nick-fields/retry@v2.8.3 with: timeout_minutes: 1 max_attempts: 10 # We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy, # because gitlint's dependencies are not available on Test PyPI command: | python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }} - name: gitlint --version run: | gitlint --version [ "$(gitlint --version)" == "gitlint, version ${{ needs.publish.outputs.gitlint_version }}" ] # Unfortunately, it's not because the newly published package installation worked once that replication # has finished amongst all PyPI servers (subsequent installations might still fail). We sleep for 10 min here # to increase the odds that replication has finished. - name: Sleep run: sleep 600 test-release: timeout-minutes: 30 needs: - publish - wait-for-package uses: ./.github/workflows/test-release.yml with: gitlint_version: ${{ needs.publish.outputs.gitlint_version }} environment: ${{ inputs.environment }} repo_test_ref: ${{ inputs.repo_release_ref }} publish-docker: timeout-minutes: 30 needs: - publish - test-release uses: ./.github/workflows/publish-docker.yml secrets: inherit # pass all secrets (required to access secrets in a called workflow) with: gitlint_version: ${{ needs.publish.outputs.gitlint_version }} docker_image_tag: ${{ inputs.docker_image_tag }} push_to_dockerhub: true
What changed
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
2 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.