name: Docker

on:
  pull_request:
  push:
    branches:
      - main
      - master
  merge_group:
  release:
    types:
      - published

permissions:
  contents: read

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

env:
  # odeprecated: remove 20.04 image in Homebrew >=4.7
  VERSIONS: '["20.04", "22.04", "24.04"]'

jobs:
  generate-tags:
    if: github.repository_owner == 'Homebrew'
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.attributes.outputs.matrix }}
      tags: ${{ steps.attributes.outputs.tags }}
      labels: ${{ steps.attributes.outputs.labels }}
      push: ${{ steps.attributes.outputs.push }}
      merge: ${{ steps.attributes.outputs.merge }}
    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Fetch origin/HEAD from Git
        run: git fetch origin HEAD

      - name: Determine build attributes
        id: attributes
        run: |
          date="$(date --rfc-3339=seconds --utc)"
          brew_version="$(git describe --tags --dirty --abbrev=7)"

          DELIMITER="END_LABELS_$(uuidgen)"
          cat <<EOS | tee -a "${GITHUB_OUTPUT}"
          labels<<${DELIMITER}
          org.opencontainers.image.created=${date}
          org.opencontainers.image.url=https://brew.sh
          org.opencontainers.image.documentation=https://docs.brew.sh
          org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}
          org.opencontainers.image.version=${brew_version}
          org.opencontainers.image.revision=${GITHUB_SHA}
          org.opencontainers.image.vendor=${GITHUB_REPOSITORY_OWNER}
          org.opencontainers.image.licenses=BSD-2-Clause
          ${DELIMITER}
          EOS

          typeset -A tag_hash
          typeset -A push_hash
          matrix=()
          merge=false
          while IFS=$'\n' read -r version; do
            tags=()
            if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
              tags+=(
                "ghcr.io/homebrew/ubuntu${version}:${brew_version}"
                "ghcr.io/homebrew/ubuntu${version}:latest"
                "homebrew/ubuntu${version}:${brew_version}"
                "homebrew/ubuntu${version}:latest"
              )
              if [[ "${version}" == "22.04" ]]; then
                tags+=(
                  "ghcr.io/homebrew/brew:${brew_version}"
                  "ghcr.io/homebrew/brew:latest"
                  "homebrew/brew:${brew_version}"
                  "homebrew/brew:latest"
                )
              fi
            elif [[ "${GITHUB_EVENT_NAME}" == "push" &&
                    ("${GITHUB_REF}" == "refs/heads/master" || "${GITHUB_REF}" == "refs/heads/main") &&
                    "${version}" == "22.04" ]]; then
              tags+=(
                "ghcr.io/homebrew/brew:main"
                "ghcr.io/homebrew/brew:master"
                "ghcr.io/homebrew/ubuntu${version}:main"
                "ghcr.io/homebrew/ubuntu${version}:master"
                "homebrew/brew:main"
                "homebrew/brew:master"
                "homebrew/ubuntu${version}:main"
                "homebrew/ubuntu${version}:master"
              )
            fi

            if [[ "${#tags[@]}" -ne 0 ]]; then
              tags_as_json_array="$(
                jq --null-input --compact-output '$ARGS.positional' --args "${tags[@]}"
              )"
              tag_hash["${version}"]="${tags_as_json_array}"
              push_hash["${version}"]=true
              merge=true
              matrix+=("${version}")
            else
              push_hash["${version}"]=false
            fi
          done <<<"$(jq --raw-output '.[]' <<<"${VERSIONS}")"

          # Transform the `matrix` variable into a JSON array.
          echo "matrix=$(jq --null-input --compact-output '$ARGS.positional' --args "${matrix[@]}")" >>"${GITHUB_OUTPUT}"
          echo "merge=${merge}" >>"${GITHUB_OUTPUT}"

          {
            DELIMITER="END_TAGS_$(uuidgen)"
            has_previous=
            echo "tags<<${DELIMITER}"
            printf '{'
            for version in "${!tag_hash[@]}"; do
              [[ -n "${has_previous:-}" ]] && printf ','
              printf '"%s": %s' "${version}" "${tag_hash[$version]}"
              has_previous=1
            done
            echo '}'
            echo "${DELIMITER}"
          } | tee -a "${GITHUB_OUTPUT}"

          {
            DELIMITER="END_PUSH_$(uuidgen)"
            has_previous=
            echo "push<<${DELIMITER}"
            printf '{'
            for version in "${!push_hash[@]}"; do
              [[ -n "${has_previous:-}" ]] && printf ','
              printf '"%s": %s' "${version}" "${push_hash[$version]}"
              has_previous=1
            done
            echo '}'
            echo "${DELIMITER}"
          } | tee -a "${GITHUB_OUTPUT}"

  build:
    needs: generate-tags
    if: github.repository_owner == 'Homebrew'
    name: docker (${{ matrix.arch }} Ubuntu ${{ matrix.version }})
    runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
    strategy:
      fail-fast: false
      matrix:
        # odeprecated: remove 20.04 image in Homebrew >=4.7
        version: ["20.04", "22.04", "24.04"]
        arch: ["x86_64", "arm64"]
        exclude:
          # odeprecated: remove 20.04 image in Homebrew >=4.7
          - version: "20.04"
            arch: "arm64"
    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Fetch origin/HEAD from Git
        run: git fetch origin HEAD

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
        with:
          cache-binary: false

      - name: Retrieve build attributes
        id: attributes
        env:
          VERSION: ${{ matrix.version }}
          PUSH: ${{ needs.generate-tags.outputs.push }}
        run: |
          # odeprecated: remove 20.04 image in Homebrew >=4.7
          if [[ "${VERSION}" == "20.04" ]]; then
            echo "The homebrew/ubuntu20.04 image is deprecated and will soon be retired. Use homebrew/ubuntu24.04 or homebrew/brew." > .docker-deprecate
          fi

          filter="$(printf '.["%s"]' "${VERSION}")"
          echo "push=$(jq --raw-output "${filter}" <<<"${PUSH}")" >>"${GITHUB_OUTPUT}"

      - name: Log in to GitHub Packages (github-actions[bot])
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ghcr.io
          username: github-actions[bot]
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build Docker image
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          load: true
          tags: brew
          cache-from: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache
          build-args: version=${{ matrix.version }}
          labels: ${{ needs.generate-tags.outputs.labels }}

      - name: Set environment variables
        run: |
          if [[ "${ARCH}" == "arm64" ]]; then
            echo "HOMEBREW_ARM64_TESTING=1" >> "$GITHUB_ENV"
          fi

          # odeprecated: remove 20.04 in Homebrew >=4.7
          if [[ "${VERSION}" == "20.04" ]]; then
            echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV"
          fi
        env:
          VERSION: ${{ matrix.version }}
          ARCH: ${{ matrix.arch }}

      - name: Run brew test-bot --only-setup
        run: docker run --env HOMEBREW_ARM64_TESTING --env HOMEBREW_GLIBC_TESTING --rm brew brew test-bot --only-setup

      - name: Log in to GitHub Packages (BrewTestBot)
        if: fromJSON(steps.attributes.outputs.push)
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ghcr.io
          username: BrewTestBot
          password: ${{ secrets.HOMEBREW_BREW_GITHUB_PACKAGES_TOKEN }}

      - name: Deploy the Docker image by digest
        id: digest
        if: fromJSON(steps.attributes.outputs.push)
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          cache-from: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache
          cache-to: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache,mode=max
          build-args: version=${{ matrix.version }}
          labels: ${{ needs.generate-tags.outputs.labels }}
          outputs: type=image,name=ghcr.io/homebrew/ubuntu${{ matrix.version }},name-canonical=true,push=true,push-by-digest=true

      - name: Export the Docker image digest
        if: fromJSON(steps.attributes.outputs.push)
        run: |
          mkdir -p "${RUNNER_TEMP}"/digests
          echo "${DIGEST#sha256:}" >"${RUNNER_TEMP}/digests/${VERSION}-${ARCH}"
        env:
          DIGEST: ${{ steps.digest.outputs.digest }}
          VERSION: ${{ matrix.version }}
          ARCH: ${{ matrix.arch }}

      - name: Upload the Docker image digest
        if: fromJSON(steps.attributes.outputs.push)
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: digest-${{ matrix.version }}-${{ matrix.arch }}
          path: ${{ runner.temp }}/digests/*

  merge:
    needs: [generate-tags, build]
    if: github.repository_owner == 'Homebrew' && fromJSON(needs.generate-tags.outputs.merge)
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        version: ${{ fromJSON(needs.generate-tags.outputs.matrix) }}
    steps:
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
        with:
          cache-binary: false

      - name: Download Docker image digests
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          path: ${{ runner.temp }}/digests
          pattern: digest-${{ matrix.version }}-*
          merge-multiple: true

      - name: Log in to Docker Hub
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          username: brewtestbot
          password: ${{ secrets.HOMEBREW_BREW_DOCKER_TOKEN }}

      - name: Log in to GitHub Packages (BrewTestBot)
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ghcr.io
          username: BrewTestBot
          password: ${{ secrets.HOMEBREW_BREW_GITHUB_PACKAGES_TOKEN }}

      - name: Merge and push Docker image
        env:
          TAGS: ${{ needs.generate-tags.outputs.tags }}
          VERSION: ${{ matrix.version }}
        run: |
          filter="$(printf '.["%s"].[]' "${VERSION}")"
          tag_args=()
          while IFS=$'\n' read -r tag; do
            [[ -n "${tag}" ]] || continue
            tag_args+=("--tag=${tag}")
          done <<<"$(jq --raw-output "${filter}" <<<"${TAGS}")"

          image_args=("ghcr.io/homebrew/ubuntu${VERSION}@sha256:$(<"${RUNNER_TEMP}/digests/${VERSION}-x86_64")")
          # odeprecated: remove 20.04 image in Homebrew >=4.7
          if [[ "${VERSION}" != 20.04 ]]; then
            image_args+=("ghcr.io/homebrew/ubuntu${VERSION}@sha256:$(<"${RUNNER_TEMP}/digests/${VERSION}-arm64")")
          fi

          attempts=0
          until docker buildx imagetools create "${tag_args[@]}" "${image_args[@]}"; do
            attempts=$((attempts + 1))
            if [[ $attempts -ge 3 ]]; then
              echo "[$(date -u)] ERROR: Failed after 3 attempts." >&2
              exit 1
            fi
            delay=$((2 ** attempts))
            if [[ $delay -gt 15 ]]; then delay=15; fi
            echo "Push failed (attempt $attempts). Retrying in ${delay} seconds..."
            sleep ${delay}
          done
