Skip to content
Latchkey

Release workflow (powerapi-ng/powerapi)

The Release workflow from powerapi-ng/powerapi, explained and optimized by Latchkey.

D

CI health: D - needs work

Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: powerapi-ng/powerapi.github/workflows/release.ymlLicense BSD-3-ClauseView source

What it does

This is the Release workflow from the powerapi-ng/powerapi repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: read

jobs:
  pre-checks:
    name: Release Pre-Checks
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

      - name: Check if package version corresponds to git tag
        shell: python
        env:
          PYTHONPATH: ${{ github.workspace }}/src
        run: |
          import os
          import sys
          from powerapi import __version__

          git_tag = os.environ['GITHUB_REF_NAME'].removeprefix('v')
          pkg_version = __version__

          if git_tag != pkg_version:
            title = 'Invalid version'
            file = 'src/powerapi/__init__.py'
            msg = f'Version mismatch between python package ({pkg_version}) and git tag ({git_tag})'
            print(f'::error title={title},file={file}::{msg}')
            sys.exit(1)

  build-python-package:
    name: Build Python Package
    runs-on: ubuntu-latest
    needs: [pre-checks]
    outputs:
      dist-hashes: ${{ steps.dist-hashes.outputs.hash }}
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

      - name: Set up Python
        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          python-version: "3.x"

      - name: Install uv
        uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0

      - name: Build sdist and wheel
        run: |
          uv build --sdist --wheel --out-dir dist/

      - name: Compute SHA256 hashes of build artifacts
        id: dist-hashes
        shell: bash
        run: |
          cd ./dist && echo "hash=$(sha256sum -- * | base64 -w0)" >> $GITHUB_OUTPUT

      - name: Upload build artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: "python-build-dist"
          path: ./dist
          if-no-files-found: error

  publish-python-package:
    name: Publish Pypi Package
    runs-on: ubuntu-latest
    needs: [build-python-package]
    permissions:
      contents: read
      id-token: write

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: "python-build-dist"
          path: dist

      - name: Publish package
        uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
        with:
          print-hash: true
          attestations: true

  docker-image:
    name: Publish Docker image
    runs-on: ubuntu-latest
    needs: [pre-checks, publish-python-package]
    permissions:
      contents: read
      packages: write

    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0

      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

      - name: Log in to Docker Hub
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          username: ${{ vars.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
        with:
          images: |
            docker.io/powerapi/powerapi
            ghcr.io/powerapi-ng/powerapi
          tags: |
            type=pep440,pattern={{version}}

      - name: Build and push Docker image
        uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
        id: build-and-push
        with:
          push: true
          provenance: false
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            POWERAPI_INSTALL_METHOD=pypi
            POWERAPI_VERSION=${{ steps.meta.outputs.version }}

  generate-release-notes:
    name: Generate release notes
    runs-on: ubuntu-latest
    needs: [pre-checks]
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0

      - name: Generate version changelog
        uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
        with:
          config: .cliff.toml
          args: --no-exec --latest
        env:
          OUTPUT: CHANGELOG.md
          GITHUB_REPO: ${{ github.repository }}

      - name: Upload release notes artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: "release-notes"
          path: CHANGELOG.md
          if-no-files-found: error

  github-release:
    name: Publish GitHub release
    runs-on: ubuntu-latest
    needs: [publish-python-package, docker-image, generate-release-notes]
    permissions:
      contents: write

    steps:
      - name: Download release notes artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: "release-notes"

      - name: Create GitHub release
        uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
        with:
          body_path: CHANGELOG.md

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: Release
 
on:
  push:
    tags:
      - "v*"
 
permissions:
  contents: read
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  pre-checks:
    timeout-minutes: 30
    name: Release Pre-Checks
    runs-on: latchkey-small
    permissions:
      contents: read
 
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
 
      - name: Check if package version corresponds to git tag
        shell: python
        env:
          PYTHONPATH: ${{ github.workspace }}/src
        run: |
          import os
          import sys
          from powerapi import __version__
 
          git_tag = os.environ['GITHUB_REF_NAME'].removeprefix('v')
          pkg_version = __version__
 
          if git_tag != pkg_version:
            title = 'Invalid version'
            file = 'src/powerapi/__init__.py'
            msg = f'Version mismatch between python package ({pkg_version}) and git tag ({git_tag})'
            print(f'::error title={title},file={file}::{msg}')
            sys.exit(1)
 
  build-python-package:
    timeout-minutes: 30
    name: Build Python Package
    runs-on: latchkey-small
    needs: [pre-checks]
    outputs:
      dist-hashes: ${{ steps.dist-hashes.outputs.hash }}
    permissions:
      contents: read
 
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
 
      - name: Set up Python
        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          cache: 'pip'
          python-version: "3.x"
 
      - name: Install uv
        uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
 
      - name: Build sdist and wheel
        run: |
          uv build --sdist --wheel --out-dir dist/
 
      - name: Compute SHA256 hashes of build artifacts
        id: dist-hashes
        shell: bash
        run: |
          cd ./dist && echo "hash=$(sha256sum -- * | base64 -w0)" >> $GITHUB_OUTPUT
 
      - name: Upload build artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: "python-build-dist"
          path: ./dist
          if-no-files-found: error
 
  publish-python-package:
    timeout-minutes: 30
    name: Publish Pypi Package
    runs-on: latchkey-small
    needs: [build-python-package]
    permissions:
      contents: read
      id-token: write
 
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: "python-build-dist"
          path: dist
 
      - name: Publish package
        uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
        with:
          print-hash: true
          attestations: true
 
  docker-image:
    timeout-minutes: 30
    name: Publish Docker image
    runs-on: latchkey-small
    needs: [pre-checks, publish-python-package]
    permissions:
      contents: read
      packages: write
 
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
 
      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
 
      - name: Log in to Docker Hub
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          username: ${{ vars.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}
 
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
        with:
          images: |
            docker.io/powerapi/powerapi
            ghcr.io/powerapi-ng/powerapi
          tags: |
            type=pep440,pattern={{version}}
 
      - name: Build and push Docker image
        uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
        id: build-and-push
        with:
          push: true
          provenance: false
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            POWERAPI_INSTALL_METHOD=pypi
            POWERAPI_VERSION=${{ steps.meta.outputs.version }}
 
  generate-release-notes:
    timeout-minutes: 30
    name: Generate release notes
    runs-on: latchkey-small
    needs: [pre-checks]
    permissions:
      contents: read
 
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
 
      - name: Generate version changelog
        uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
        with:
          config: .cliff.toml
          args: --no-exec --latest
        env:
          OUTPUT: CHANGELOG.md
          GITHUB_REPO: ${{ github.repository }}
 
      - name: Upload release notes artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: "release-notes"
          path: CHANGELOG.md
          if-no-files-found: error
 
  github-release:
    timeout-minutes: 30
    name: Publish GitHub release
    runs-on: latchkey-small
    needs: [publish-python-package, docker-image, generate-release-notes]
    permissions:
      contents: write
 
    steps:
      - name: Download release notes artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: "release-notes"
 
      - name: Create GitHub release
        uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
        with:
          body_path: CHANGELOG.md
 

What changed

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:

This workflow runs 6 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow