shortener 0.1.1

A simple URL shortener.
Documentation
name: Docker

on:
  push:
    branches:
      - main
    tags:
      - '**'
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions: {}

defaults:
  run:
    shell: bash -xeuo pipefail {0}

env:
  DOCKERHUB_USERNAME: zhongruoyu
  DOCKERHUB_REPOSITORY: zhongruoyu/shortener
  GHCR_USERNAME: zhongruoyu
  GHCR_REPOSITORY: ghcr.io/zhongruoyu/shortener
  BUILD_CACHE_REPOSITORY: ghcr.io/zhongruoyu/shortener/cache

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x86_64
          - aarch64
        include:
          - { arch: x86_64, runs-on: ubuntu-latest }
          - { arch: aarch64, runs-on: ubuntu-24.04-arm }
    runs-on: ${{ matrix.runs-on }}
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
      - name: Login to Docker Hub
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          username: ${{ env.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Login to GitHub Container Registry
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ env.GHCR_USERNAME }}
          password: ${{ github.token }}
      - name: Set up build
        id: setup
        run: |
          cache_from=""
          cache_to=""
          outputs=()
          if [[ "$GITHUB_EVENT_NAME" = "push" ]]; then
            if [[ "$GITHUB_REF_TYPE" = "branch" ]]; then
              cache_from="type=registry,ref=$BUILD_CACHE_REPOSITORY:$ARCH"
              if [[ "$GITHUB_REF" = "refs/heads/main" ]]; then
                cache_to="type=registry,ref=$BUILD_CACHE_REPOSITORY:$ARCH,mode=max"
              fi
            fi
            if [[ "$GITHUB_REF" = "refs/heads/main" || "$GITHUB_REF_TYPE" = "tag" ]]; then
              outputs+=(
                "type=image,name=$DOCKERHUB_REPOSITORY,name-canonical=true,push=true,push-by-digest=true"
                "type=image,name=$GHCR_REPOSITORY,name-canonical=true,push=true,push-by-digest=true"
              )
            fi
          fi
          {
            echo "cache_from=$cache_from"
            echo "cache_to=$cache_to"
            echo "outputs<<OUTPUTS"
            if [[ ${#outputs[@]} -gt 0 ]]; then
              printf '%s\n' "${outputs[@]}"
            fi
            echo "OUTPUTS"
          } >> "$GITHUB_OUTPUT"
        env:
          ARCH: ${{ matrix.arch }}
      - name: Build and push Docker image by digest
        id: build
        uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
        with:
          context: .
          platforms: linux/${{ matrix.arch }}
          cache-from: ${{ steps.setup.outputs.cache_from }}
          cache-to: ${{ steps.setup.outputs.cache_to }}
          outputs: ${{ steps.setup.outputs.outputs }}
      - name: Export digest
        run: |
          mkdir -p "$RUNNER_TEMP"/digests
          echo "${DIGEST#sha256:}" > "$RUNNER_TEMP"/digests/"$ARCH"
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
          ARCH: ${{ matrix.arch }}
      - name: Upload digest
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: digests-${{ matrix.arch }}
          path: ${{ runner.temp }}/digests/*

  merge:
    if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
      - name: Login to Docker Hub
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          username: ${{ env.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Login to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ghcr.io
          username: ${{ env.GHCR_USERNAME }}
          password: ${{ github.token }}
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: ${{ runner.temp }}/digests
          pattern: digests-*
          merge-multiple: true
      - name: Create manifest list and push
        run: |
          tags=()
          case "$GITHUB_REF_TYPE" in
          "branch")
            tags+=(
              "$DOCKERHUB_REPOSITORY:main"
              "$DOCKERHUB_REPOSITORY:$GITHUB_SHA"
              "$GHCR_REPOSITORY:main"
              "$GHCR_REPOSITORY:$GITHUB_SHA"
            )
            ;;
          "tag")
            tags+=(
              "$DOCKERHUB_REPOSITORY:latest"
              "$DOCKERHUB_REPOSITORY:$GITHUB_REF_NAME"
              "$GHCR_REPOSITORY:latest"
              "$GHCR_REPOSITORY:$GITHUB_REF_NAME"
            )
            ;;
          esac
          tag_args=()
          for tag in "${tags[@]}"; do
            tag_args+=(--tag="$tag")
          done
          images=(
            "$GHCR_REPOSITORY@sha256:$(cat "$RUNNER_TEMP"/digests/x86_64)"
            "$GHCR_REPOSITORY@sha256:$(cat "$RUNNER_TEMP"/digests/aarch64)"
          )
          docker buildx imagetools create "${tag_args[@]}" "${images[@]}"