git-cache-proxy 0.1.9

Read-only caching proxy for Git: serves clones/fetches from an in-region mirror, pulling only deltas from upstream.
Documentation
name: Release

on:
  push:
    branches: [main]

permissions:
  contents: read

# One release at a time; do not cancel one mid-flight.
concurrency:
  group: release
  cancel-in-progress: false

env:
  # Derive the image name from the repository so it tracks a rename automatically
  # instead of drifting (the hardcoded name once lagged a rename and published under
  # the old name). `github.repository` is `owner/repo`, lowercase as GHCR requires.
  IMAGE: ghcr.io/${{ github.repository }}
  # OCI namespace for the Helm chart, shared across this owner's charts. The chart is
  # pushed as `<owner>/charts/git-cache-proxy`.
  CHART_REPO: oci://ghcr.io/${{ github.repository_owner }}/charts

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    permissions:
      contents: write
    outputs:
      released: ${{ steps.release.outputs.released }}
      version: ${{ steps.release.outputs.version }}
    steps:
      # The version-bump commit is pushed directly to `main`, which is protected by a
      # branch ruleset that requires status checks. The built-in GITHUB_TOKEN cannot be
      # granted a ruleset bypass, so mint a token for a GitHub App that is on the
      # ruleset's bypass list and push with that instead.
      - name: Mint a token for the release app
        id: app-token
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ secrets.RELEASE_APP_ID }}
          private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
      - uses: actions/checkout@v7
        with:
          # Full history and tags so knope can read the commits since the last release.
          fetch-depth: 0
          # Push the release commit as the app so it bypasses the branch ruleset.
          token: ${{ steps.app-token.outputs.token }}
      - uses: knope-dev/action@v2.1.2
        with:
          version: 0.23.0
      - name: Configure the release identity
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
      # knope bumps the version, updates the changelog, tags, and creates the GitHub
      # release from the conventional commits since the last tag. The push uses the app
      # token, so - unlike the built-in token - it retriggers this workflow; that rerun
      # finds no releasable commits and no-ops via `no_release`, so the publish job runs
      # only in the run that actually released. `no_release` is a normal no-op, not a
      # failure.
      - name: Release
        id: release
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          set +e
          out="$(knope release 2>&1)"
          code=$?
          echo "$out"
          if [ "$code" -eq 0 ]; then
            # Read the version knope just bumped in the working tree. Do NOT derive it
            # from `git tag`: with [github] configured, knope creates the tag via the
            # GitHub API, so it never appears as a local tag in this checkout, and
            # `git tag | head` would return the *previous* release instead.
            version="$(grep -m1 '^version = ' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"
            echo "released=true" >> "$GITHUB_OUTPUT"
            echo "version=${version}" >> "$GITHUB_OUTPUT"
            echo "::notice::Released v${version}."
          elif echo "$out" | grep -q 'no_release'; then
            echo "::notice::No releasable commits since the last release; nothing to do."
            echo "released=false" >> "$GITHUB_OUTPUT"
          else
            exit "$code"
          fi

  # Build and push the versioned image to GHCR, in the same run that cut the release, at
  # the tag knope just created. The published GHCR package starts private; make it public
  # once to allow anonymous `docker pull`.
  publish:
    name: Publish image
    needs: release
    if: needs.release.outputs.released == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v7
        with:
          ref: v${{ needs.release.outputs.version }}
      - uses: docker/setup-buildx-action@v4
      - uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push the versioned image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: Dockerfile
          push: true
          tags: |
            ${{ env.IMAGE }}:${{ needs.release.outputs.version }}
            ${{ env.IMAGE }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Publish the crate to crates.io (which feeds docs.rs) in the same run that cut the
  # release, at the tag knope just created. Uses crates.io trusted publishing: GitHub's
  # OIDC token is exchanged for a short-lived crates.io token, so no API token secret is
  # stored. One-time bootstrap before this can work: publish once manually to claim the
  # name (`cargo publish`), then add a GitHub Actions trusted publisher on the crate's
  # crates.io settings for this repo and the Release workflow.
  publish-crate:
    name: Publish crate
    needs: release
    if: needs.release.outputs.released == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v7
        with:
          ref: v${{ needs.release.outputs.version }}
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Authenticate to crates.io
        id: auth
        uses: rust-lang/crates-io-auth-action@v1
      - name: Publish to crates.io
        run: cargo publish --locked
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

  # Package and push the Helm chart to GHCR as an OCI artifact, in the same run that
  # cut the release, at the tag knope just created. The published GHCR package starts
  # private; make it public once to allow anonymous `helm pull`.
  publish-chart:
    name: Publish chart
    needs: release
    if: needs.release.outputs.released == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v7
        with:
          ref: v${{ needs.release.outputs.version }}
      - uses: azure/setup-helm@v5
      # Version the chart in lockstep with the app: package at the released version
      # (overriding the static Chart.yaml version) with the matching appVersion, so a
      # pulled chart deploys the image it was cut with.
      - name: Package and push the chart
        run: |
          helm package chart -d dist \
            --version "${{ needs.release.outputs.version }}" \
            --app-version "${{ needs.release.outputs.version }}"
          echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin
          helm push dist/git-cache-proxy-*.tgz "${{ env.CHART_REPO }}"