name: Release Docker Image
concurrency:
  group: "release-image-${{ github.ref }}"

on:
  push:
    paths-ignore:
      - "*.md"
      - "**/*.md"
      - ".gitlab-ci.yml"
      - ".gitignore"
      - "renovate.json"
      - "debian/**"
      - "docker/**"
      - "docs/**"
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

env:
  BUILTIN_REGISTRY: forgejo.ellis.link
  BUILTIN_REGISTRY_ENABLED: "${{ ((vars.BUILTIN_REGISTRY_USER && secrets.BUILTIN_REGISTRY_PASSWORD) || (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)) && 'true' || 'false' }}"

jobs:
  define-variables:
    runs-on: ubuntu-latest

    outputs:
      images: ${{ steps.var.outputs.images }}
      images_list: ${{ steps.var.outputs.images_list }}
      build_matrix: ${{ steps.var.outputs.build_matrix }}

    steps:
      - name: Setting variables
        uses: https://github.com/actions/github-script@v7
        id: var
        with:
          script: |
            const githubRepo = '${{ github.repository }}'.toLowerCase()
            const repoId = githubRepo.split('/')[1]

            core.setOutput('github_repository', githubRepo)
            const builtinImage = '${{ env.BUILTIN_REGISTRY }}/' + githubRepo
            let images = []
            if (process.env.BUILTIN_REGISTRY_ENABLED === "true") {
              images.push(builtinImage)
            }
            core.setOutput('images', images.join("\n"))
            core.setOutput('images_list', images.join(","))
            const platforms = ['linux/amd64', 'linux/arm64']
            core.setOutput('build_matrix', JSON.stringify({
              platform: platforms,
              include: platforms.map(platform => { return {
                platform,
                slug: platform.replace('/', '-')
              }})
            }))

  build-image:
    runs-on: dind
    needs: define-variables
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    strategy:
      matrix:
        {
          "include":
            [
              { "platform": "linux/amd64", "slug": "linux-amd64" },
              { "platform": "linux/arm64", "slug": "linux-arm64" },
            ],
          "platform": ["linux/amd64", "linux/arm64"],
        }
    steps:
      - name: Echo strategy
        run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}'
      - name: Echo matrix
        run: echo '${{ toJSON(matrix) }}'

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: Install rust
        id: rust-toolchain
        uses: ./.forgejo/actions/rust-toolchain

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
      - name: Login to builtin registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.BUILTIN_REGISTRY }}
          username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
          password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}

      # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
      - name: Extract metadata (labels, annotations) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{needs.define-variables.outputs.images}}
          # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

      # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
      # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
      # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
      # It will not push images generated from a pull request
      - name: Get short git commit SHA
        id: sha
        run: |
          calculatedSha=$(git rev-parse --short ${{ github.sha }})
          echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
      - name: Get Git commit timestamps
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV

      - uses: ./.forgejo/actions/timelord
        with:
          key: timelord-v0
          path: .

      - name: Cache Rust registry
        uses: actions/cache@v3
        with:
          path: |
            .cargo/git
            .cargo/git/checkouts
            .cargo/registry
            .cargo/registry/src
          key: rust-registry-image-${{hashFiles('**/Cargo.lock') }}
      - name: Cache cargo target
        id: cache-cargo-target
        uses: actions/cache@v3
        with:
          path: |
            cargo-target-${{ matrix.slug }}
          key: cargo-target-${{ matrix.slug }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
      - name: Cache apt cache
        id: cache-apt
        uses: actions/cache@v3
        with:
          path: |
            var-cache-apt-${{ matrix.slug }}
          key: var-cache-apt-${{ matrix.slug }}
      - name: Cache apt lib
        id: cache-apt-lib
        uses: actions/cache@v3
        with:
          path: |
            var-lib-apt-${{ matrix.slug }}
          key: var-lib-apt-${{ matrix.slug }}
      - name: inject cache into docker
        uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.1.0
        with:
          cache-map: |
            {
              ".cargo/registry": "/usr/local/cargo/registry",
              ".cargo/git/db": "/usr/local/cargo/git/db",
              "cargo-target-${{ matrix.slug }}": {
                "target": "/app/target",
                "id": "cargo-target-${{ matrix.platform }}"
              },
              "var-cache-apt-${{ matrix.slug }}": "/var/cache/apt",
              "var-lib-apt-${{ matrix.slug }}": "/var/lib/apt"
            }
          skip-extraction: ${{ steps.cache.outputs.cache-hit }}

      - name: Build and push Docker image by digest
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          file: "docker/Dockerfile"
          build-args: |
            GIT_COMMIT_HASH=${{ github.sha }})
            GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }})
            GIT_REMOTE_URL=${{github.event.repository.html_url }}
            GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
          platforms: ${{ matrix.platform }}
          labels: ${{ steps.meta.outputs.labels }}
          annotations: ${{ steps.meta.outputs.annotations }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          sbom: true
          outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true
        env:
          SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}

      # For publishing multi-platform manifests
      - name: Export digest
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digest
        uses: forgejo/upload-artifact@v4
        with:
          name: digests-${{ matrix.slug }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    runs-on: dind
    needs: [define-variables, build-image]
    steps:
      - name: Download digests
        uses: forgejo/download-artifact@v4
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true
      # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
      - name: Login to builtin registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.BUILTIN_REGISTRY }}
          username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
          password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Extract metadata (tags) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          tags: |
            type=semver,pattern=v{{version}}
            type=semver,pattern=v{{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }}
            type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
            type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
            type=ref,event=pr
            type=sha,format=long
          images: ${{needs.define-variables.outputs.images}}
          # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index

      - name: Create manifest list and push
        working-directory: /tmp/digests
        env:
          IMAGES: ${{needs.define-variables.outputs.images}}
        shell: bash
        run: |
          IFS=$'\n'
          IMAGES_LIST=($IMAGES)
          ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS)
          TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS)
          for REPO in "${IMAGES_LIST[@]}"; do
              docker buildx imagetools create \
                $(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \
                $(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \
                $(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done)
          done

      - name: Inspect image
        env:
          IMAGES: ${{needs.define-variables.outputs.images}}
        shell: bash
        run: |
          IMAGES_LIST=($IMAGES)
          for REPO in "${IMAGES_LIST[@]}"; do
            docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }}
          done
