name: Continuous Delivery
on:
  push:
    branches:
      - main

env:
  # mark:automatic-version
  VERSION: "1.0.0"

jobs:
  ci:
    uses: ./.github/workflows/ci.yml
    secrets: inherit

  deploy-staging:
    runs-on: ubuntu-22.04
    # if: ${{ github.event.workflow_run.conclusion == 'success' }}
    permissions:
      contents: write
    # Cancel old workflow runs if new code is pushed
    concurrency:
      group: "staging-deploy-${{ github.workflow }}-${{ github.ref }}"
      cancel-in-progress: false
    needs:
      - ci
    env:
      TF_CLOUD_ORGANIZATION: "firezone"
      TF_API_TOKEN: "${{ secrets.TF_API_TOKEN }}"
      TF_WORKSPACE: "staging"
    steps:
      - name: Get Terraform Version
        run: |
          TERRAFORM_VERSION=$(cat .tool-versions | grep terraform | awk '{ print $NF; }')
          echo "TERRAFORM_VERSION=${TERRAFORM_VERSION}" >> $GITHUB_ENV
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TERRAFORM_VERSION }}
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.workflow_run.head_branch }}
      - name: Upload Configuration
        uses: hashicorp/tfc-workflows-github/actions/upload-configuration@v1.1.1
        id: apply-upload
        with:
          workspace: ${{ env.TF_WORKSPACE }}
          # Subdirectory is set in the project settings:
          # https://app.terraform.io/app/firezone/workspaces/staging/settings/general
          directory: "./"
      - name: Create Plan Run
        uses: hashicorp/tfc-workflows-github/actions/create-run@v1.1.1
        id: apply-run
        env:
          TF_VAR_image_tag: '"${{ env.VERSION }}-${{ github.sha }}"'
        with:
          workspace: ${{ env.TF_WORKSPACE }}
          configuration_version:
            ${{ steps.apply-upload.outputs.configuration_version_id }}
      - name: Apply
        uses: hashicorp/tfc-workflows-github/actions/apply-run@v1.1.1
        if: fromJSON(steps.apply-run.outputs.payload).data.attributes.actions.IsConfirmable
        id: apply
        with:
          run: ${{ steps.apply-run.outputs.run_id }}
          comment: "Apply Run from GitHub Actions CI ${{ github.sha }}"

  update-release-draft:
    needs: deploy-staging
    runs-on: ubuntu-22.04
    permissions:
      # write permission is required to create a github release
      contents: write
      # autolabeler
      pull-requests: write
    concurrency:
      group: "staging-draft-release-${{ github.workflow }}-${{ github.ref }}"
      cancel-in-progress: true
    steps:
      - uses: release-drafter/release-drafter@v5
        id: update-release-draft
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          prerelease: true
          include-pre-releases: true
          version: ${{ env.VERSION }}
      - name: Delete stale artifacts
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -xe

          gh release view ${{ steps.update-release-draft.outputs.tag_name }} \
            --repo ${{ github.repository }} \
            --json assets \
            --jq '.assets[] | .name' \
            | xargs -I{} gh release delete-asset ${{ steps.update-release-draft.outputs.tag_name }} {} --repo ${{ github.repository }} --yes
    outputs:
      name: ${{ steps.update-release-draft.outputs.name }}
      tag_name: ${{ steps.update-release-draft.outputs.tag_name }}
      upload_url: ${{ steps.update-release-draft.outputs.upload_url }}

  build-push-linux-release-artifacts:
    permissions:
      id-token: write
      contents: write
    needs: update-release-draft
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./rust
    strategy:
      fail-fast: false
      matrix:
        arch:
          - target: x86_64-unknown-linux-musl
            shortname: x64
            platform: linux/amd64
          - target: aarch64-unknown-linux-musl # E.g. AWS Graviton
            shortname: arm64
            platform: linux/arm64
          - target: armv7-unknown-linux-musleabihf # E.g. Raspberry Pi
            platform: linux/arm/v7
            shortname: arm
          # Requires ring v0.17 which a number of our dependencies don't yet support
          # - target: mips64-unknown-linux-muslabi64 # E.g. UniFi Routers
          #   platform: linux/mips64le
        name:
          - package: firezone-linux-client
            artifact: linux-client
            image_name: client
          - package: firezone-relay
            artifact: relay
            image_name: relay
          - package: firezone-gateway
            artifact: gateway
            image_name: gateway
    env:
      BINARY_DEST_PATH: ${{ matrix.name.artifact }}-${{ matrix.arch.shortname }}
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-rust
        with:
          targets: aarch64-unknown-linux-musl armv7-unknown-linux-musleabihf
      - uses: taiki-e/install-action@v2
        with:
          tool: cross
      - name: Build release binaries
        run: |
          set -xe

          cross build --release -p ${{ matrix.name.package }} --target ${{ matrix.arch.target }}

          # Used for release artifact
          cp target/${{ matrix.arch.target }}/release/${{ matrix.name.package }} $BINARY_DEST_PATH

          # Used for Docker images
          cp target/${{ matrix.arch.target }}/release/${{ matrix.name.package }} ${{ matrix.name.package }}
          sha256sum $BINARY_DEST_PATH > $BINARY_DEST_PATH.sha256sum.txt

          ls -la $BINARY_DEST_PATH
          ls -la $BINARY_DEST_PATH.sha256sum.txt
      - name: Upload Release Assets
        if: ${{ matrix.name.artifact != 'relay' }}  # Hide relay binary from public releases
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -xe

          gh release upload ${{ needs.update-release-draft.outputs.tag_name }} \
            ${{ env.BINARY_DEST_PATH }} \
            ${{ env.BINARY_DEST_PATH }}.sha256sum.txt \
            --clobber \
            --repo ${{ github.repository }}
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          # We are overriding the default buildkit version being used by Buildx. We need buildkit >= 12.0 and currently BuildX
          # supports v0.11.6 https://github.com/docker/buildx/blob/b8739d74417f86aa8fc9aafb830a8ba656bdef0e/Dockerfile#L9.
          # We should for any updates on buildx and on the setup-buildx-action itself.
          driver-opts: |
            image=moby/buildkit:v0.12.0
      - uses: ./.github/actions/gcp-docker-login
        id: login
        with:
          project: firezone-staging
      - name: Build Version Tags
        run: |
          set -xe
          MAJOR_VERSION="${VERSION%%.*}"
          MAJOR_MINOR_VERSION="${VERSION%.*}"

          echo "MAJOR_VERSION=${MAJOR_VERSION}" >> $GITHUB_ENV
          echo "MAJOR_MINOR_VERSION=${MAJOR_MINOR_VERSION}" >> $GITHUB_ENV
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images:
            ${{ steps.login.outputs.registry }}/firezone/${{matrix.name.image_name }}
          tags: |
            type=raw,value=${{ github.sha }}
            type=raw,value=${{ env.VERSION }}
            type=raw,value=${{ env.MAJOR_VERSION }}
            type=raw,value=${{ env.MAJOR_MINOR_VERSION }}
      - name: Build and push release Docker images by digest
        id: build
        uses: docker/build-push-action@v5
        with:
          platforms: ${{ matrix.arch.platform }}
          build-args: |
            PACKAGE=${{ matrix.name.package }}
            TARGET=${{ matrix.arch.target }}
          context: rust
          cache-from: |
            type=registry,ref=${{ steps.login.outputs.registry }}/cache/${{ matrix.name.image_name }}:main
          cache-to: |
            type=registry,ref=${{ steps.login.outputs.registry }}/cache/${{ matrix.name.image_name }}:main,mode=max
          target: release
          outputs:
            type=image,name=${{ steps.login.outputs.registry }}/firezone/${{ matrix.name.image_name }},push-by-digest=true,name-canonical=true,push=true
      - name: Export digest
        run: |
          mkdir -p /tmp/digests/${{ matrix.name.image_name }}
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${{ matrix.name.image_name }}/${digest#sha256:}"
      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.name.image_name }}-${{ matrix.arch.shortname }}-digest
          path: /tmp/digests/${{ matrix.name.image_name }}
          if-no-files-found: error
          retention-days: 1

  # Build for Windows
  build-push-windows-release-artifacts:
    permissions:
      id-token: write
      contents: write
    needs: update-release-draft
    runs-on: windows-2019
    defaults:
      run:
        working-directory: ./rust
    strategy:
      fail-fast: false
      # The matrix is 1x1 to match the style of build-push-linux-release-artifacts
      # In the future we could try to cross-compile aarch64-windows here.
      matrix:
        name:
          - package: firezone-windows-client
            artifact: windows-client
    env:
      BINARY_DEST_PATH: ${{ matrix.name.artifact }}
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-rust
        with:
          targets: x86_64-pc-windows-msvc
      - name: Build release binaries
        run: |
          cargo install tauri-cli

          # Tauri build already defaults to '--release'
          cargo tauri build

          # Used for release artifact
          # This should match 'build-tauri' in _rust.yml
          cp "target/release/Firezone Windows Client.exe" "${{ env.BINARY_DEST_PATH }}-x64.exe"

          pwd
          ls *.exe

          # There's an MSI and NSIS installer here but their names include the version so I'll try those later.


      - name: Upload Release Assets
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # TODO: Calculate sha256sum for this binary and attach to release
          gh release upload ${{ needs.update-release-draft.outputs.tag_name }} `
            ${{ env.BINARY_DEST_PATH }}-x64.exe `
            --clobber `
            --repo ${{ github.repository }}

  merge-release-artifacts:
    permissions:
      id-token: write
    needs: build-push-linux-release-artifacts
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false
      matrix:
        include:
          - image_name: relay
          - image_name: gateway
          - image_name: client
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/gcp-docker-login
        id: login
        with:
          project: firezone-staging
      - name: Download digests
        uses: actions/download-artifact@v4
        with:
          pattern: ${{ matrix.image_name }}-*-digest
          merge-multiple: true
          path: /tmp/digests/${{ matrix.image_name }}
      - name: Display structure of downloaded artifacts
        run: ls -R /tmp/digests
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Build Version Tags
        run: |
          set -xe
          MAJOR_VERSION="${VERSION%%.*}"
          MAJOR_MINOR_VERSION="${VERSION%.*}"

          echo "MAJOR_VERSION=${MAJOR_VERSION}" >> $GITHUB_ENV
          echo "MAJOR_MINOR_VERSION=${MAJOR_MINOR_VERSION}" >> $GITHUB_ENV
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images:
            ${{ steps.login.outputs.registry }}/firezone/${{ matrix.image_name }}
          tags: |
            type=raw,value=${{ github.sha }}
            type=raw,value=${{ env.VERSION }}
            type=raw,value=${{ env.MAJOR_VERSION }}
            type=raw,value=${{ env.MAJOR_MINOR_VERSION }}
      - name: Create manifest list and push
        working-directory: /tmp/digests/${{ matrix.image_name }}
        run: |
          tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
          sources=$(printf '${{ steps.login.outputs.registry }}/firezone/${{ matrix.image_name }}@sha256:%s ' *)
          echo "$sources"
          docker buildx imagetools create $tags $sources
          docker buildx imagetools inspect "${{ steps.login.outputs.registry }}/firezone/${{ matrix.image_name }}"
