---
name: Docker

on:
  # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
  workflow_dispatch:

  # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
  push:
    branches:
      - dev
      - staging
    tags:
      - "*"

  # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
  pull_request:
    types:
      - opened
      - reopened
      - synchronize

jobs:
  lint:
    name: hadolint
    runs-on: ubuntu-latest

    permissions:
      contents: read

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Docker Lint
        uses: hadolint/hadolint-action@v3.1.0
        with:
          dockerfile: Dockerfile
          failure-threshold: error

  shellcheck:
    name: ShellCheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        uses: ludeeus/action-shellcheck@master
        env:
          SHELLCHECK_OPTS: --shell=bash --external-sources
        with:
          version: v0.9.0
          additional_files: "*.envsh .env .env.docker .env.example .env.testing"

  bats:
    name: Bats Testing
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run bats
        run: docker run -v "$PWD:/var/www" bats/bats:latest /var/www/tests/bats

  build:
    name: Build, Test, and Push
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

      # See: https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
      matrix:
        php_version:
          - 8.2
          - 8.3
        target_runtime:
          - apache
          - fpm
          - nginx
        php_base:
          - apache
          - fpm

        # See: https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#excluding-matrix-configurations
        # See: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixexclude
        exclude:
          # targeting [apache] runtime with [fpm] base type doesn't make sense
          - target_runtime: apache
            php_base: fpm

          # targeting [fpm] runtime with [apache] base type doesn't make sense
          - target_runtime: fpm
            php_base: apache

          # targeting [nginx] runtime with [apache] base type doesn't make sense
          - target_runtime: nginx
            php_base: apache

    # See: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-concurrency-and-the-default-behavior
    concurrency:
      group: docker-build-${{ github.ref }}-${{ matrix.php_base }}-${{ matrix.php_version }}-${{ matrix.target_runtime }}
      cancel-in-progress: true

    permissions:
      contents: read
      packages: write

    env:
      # Set the repo variable [DOCKER_HUB_USERNAME] to override the default
      # at https://github.com/<user>/<project>/settings/variables/actions
      DOCKER_HUB_USERNAME: ${{ vars.DOCKER_HUB_USERNAME || 'pixelfed' }}

      # Set the repo variable [DOCKER_HUB_ORGANISATION] to override the default
      # at https://github.com/<user>/<project>/settings/variables/actions
      DOCKER_HUB_ORGANISATION: ${{ vars.DOCKER_HUB_ORGANISATION || 'pixelfed' }}

      # Set the repo variable [DOCKER_HUB_REPO] to override the default
      # at https://github.com/<user>/<project>/settings/variables/actions
      DOCKER_HUB_REPO: ${{ vars.DOCKER_HUB_REPO || 'pixelfed' }}

      # For Docker Hub pushing to work, you need the secret [DOCKER_HUB_TOKEN]
      # set to your Personal Access Token at https://github.com/<user>/<project>/settings/secrets/actions
      #
      # ! NOTE: no [login] or [push] will happen to Docker Hub until this secret is set!
      HAS_DOCKER_HUB_CONFIGURED: ${{ secrets.DOCKER_HUB_TOKEN != '' }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        id: buildx
        with:
          version: v0.12.0 # *or* newer, needed for annotations to work

        # See: https://github.com/docker/login-action?tab=readme-ov-file#github-container-registry
      - name: Log in to the GitHub Container registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

        # See: https://github.com/docker/login-action?tab=readme-ov-file#docker-hub
      - name: Login to Docker Hub registry (conditionally)
        if: ${{ env.HAS_DOCKER_HUB_CONFIGURED == true }}
        uses: docker/login-action@v3
        with:
          username: ${{ env.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}

      - name: Docker meta
        uses: docker/metadata-action@v5
        id: meta
        with:
          images: |
            name=ghcr.io/${{ github.repository }},enable=true
            name=${{ env.DOCKER_HUB_ORGANISATION }}/${{ env.DOCKER_HUB_REPO }},enable=${{ env.HAS_DOCKER_HUB_CONFIGURED }}
          flavor: |
            latest=auto
            suffix=-${{ matrix.target_runtime }}-${{ matrix.php_version }}
          tags: |
            type=raw,value=dev,enable=${{ github.ref == format('refs/heads/{0}', 'dev') }}
            type=raw,value=staging,enable=${{ github.ref == format('refs/heads/{0}', 'staging') }}
            type=pep440,pattern={{raw}}
            type=pep440,pattern=v{{major}}.{{minor}}
            type=ref,event=branch,prefix=branch-
            type=ref,event=pr,prefix=pr-
            type=ref,event=tag
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

      - name: Docker meta (Cache)
        uses: docker/metadata-action@v5
        id: cache
        with:
          images: |
            name=ghcr.io/${{ github.repository }}-cache,enable=true
            name=${{ env.DOCKER_HUB_ORGANISATION }}/${{ env.DOCKER_HUB_REPO }}-cache,enable=${{ env.HAS_DOCKER_HUB_CONFIGURED }}
          flavor: |
            latest=auto
            suffix=-${{ matrix.target_runtime }}-${{ matrix.php_version }}
          tags: |
            type=raw,value=dev,enable=${{ github.ref == format('refs/heads/{0}', 'dev') }}
            type=raw,value=staging,enable=${{ github.ref == format('refs/heads/{0}', 'staging') }}
            type=pep440,pattern={{raw}}
            type=pep440,pattern=v{{major}}.{{minor}}
            type=ref,event=branch,prefix=branch-
            type=ref,event=pr,prefix=pr-
            type=ref,event=tag
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: Dockerfile
          target: ${{ matrix.target_runtime }}-runtime
          platforms: linux/amd64,linux/arm64
          builder: ${{ steps.buildx.outputs.name }}
          tags: ${{ steps.meta.outputs.tags }}
          annotations: ${{ steps.meta.outputs.annotations }}
          push: true
          sbom: true
          provenance: true
          build-args: |
            PHP_VERSION=${{ matrix.php_version }}
            PHP_BASE_TYPE=${{ matrix.php_base }}
          cache-from: |
            type=gha,scope=${{ matrix.target_runtime }}-${{ matrix.php_base }}-${{ matrix.php_version }}
          cache-to: |
            type=gha,mode=max,scope=${{ matrix.target_runtime }}-${{ matrix.php_base }}-${{ matrix.php_version }}
            ${{ steps.cache.outputs.tags }}

      # goss validate the image
      #
      # See: https://github.com/goss-org/goss
      - uses: e1himself/goss-installation-action@v1
        with:
          version: "v0.4.4"
      - name: Execute Goss tests
        run: |
          dgoss run \
            -v "./.env.testing:/var/www/.env" \
            -e "EXPECTED_PHP_VERSION=${{ matrix.php_version }}" \
            -e "PHP_BASE_TYPE=${{ matrix.php_base }}" \
            ${{ steps.meta.outputs.tags }}