name: Package Release

on:
  workflow_dispatch:
    inputs:
      tag:
        description: 'v-tag to package, for example v0.2.2'
        required: true
        type: string
      publish:
        description: 'Upload assets to the GitHub release after packaging and validation'
        required: true
        default: false
        type: boolean
  push:
    tags:
      - 'v*'
  pull_request:
    paths:
      - '.github/workflows/package-release.yml'
      - 'Cargo.toml'
      - 'Cargo.lock'
      - 'Makefile'
      - 'expected/**'
      - 'scripts/package-deb.sh'
      - 'scripts/validate-deb-package.sh'
      - 'sql/**'

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
  CARGO_PGRX_VERSION: 0.16.1
  PACKAGE_HTTP_FEATURE: http-allow-azure-domains

concurrency:
  group: package-release-${{ inputs.tag || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  validate-inputs:
    name: Validate package inputs
    runs-on: ubuntu-latest

    steps:
      - name: Require v-tag for manual packaging
        if: github.event_name == 'workflow_dispatch'
        run: |
          case "${{ inputs.tag }}" in
            v*) ;;
            *)
              echo "::error::tag input must be a v-tag, for example v0.2.2"
              exit 1
              ;;
          esac

  build-packages:
    name: Build PG${{ matrix.pg_version }} ${{ matrix.platform.type }} package
    needs: validate-inputs
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        pg_version: [17, 18]
        platform:
          - type: amd64
            docker_platform: linux/amd64
            file_pattern: x86-64

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}

      # Release tooling (scripts, packaging helpers) must come from the commit
      # that defines this workflow, not from the target tag. For
      # workflow_dispatch, this allows a PR branch to run its updated pipeline
      # while the primary checkout above is pinned to the v-tag being packaged.
      # github.sha tracks the workflow's own commit for workflow_dispatch and
      # push:tags, keeping YAML and scripts in sync with the branch that ran it.
      - name: Checkout release tooling
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.sha }}
          path: _release_tooling

      - name: Set version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            echo "VERSION=${{ inputs.tag }}" >> "$GITHUB_ENV"
          elif [ "${{ github.event_name }}" = "pull_request" ]; then
            crate_version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
            echo "VERSION=${crate_version}~pr${{ github.event.pull_request.number }}+${GITHUB_SHA::7}" >> "$GITHUB_ENV"
          else
            echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV"
          fi

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

      - name: Build pgrx package in Docker
        run: |
          docker run --rm \
            --platform "${{ matrix.platform.docker_platform }}" \
            -e VERSION \
            -v "$PWD:/work" \
            -w /work \
            --user root \
            rust:bookworm \
            bash -euxo pipefail -c '
              export DEBIAN_FRONTEND=noninteractive
              apt-get update
              apt-get install -y \
                ca-certificates \
                curl \
                gnupg \
                lsb-release \
                pkg-config \
                libssl-dev \
                libclang-dev \
                clang \
                build-essential \
                bison \
                flex \
                libreadline-dev \
                zlib1g-dev \
                libxml2-dev \
                libxslt1-dev \
                libicu-dev \
                file

              install -d -m 0755 /usr/share/keyrings
              curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
                | gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg
              echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
                > /etc/apt/sources.list.d/pgdg.list
              apt-get update
              apt-get install -y \
                postgresql-${{ matrix.pg_version }} \
                postgresql-server-dev-${{ matrix.pg_version }}

              export PG_CONFIG="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config"
              export PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin:$PATH"
              cargo install cargo-pgrx --version "${{ env.CARGO_PGRX_VERSION }}" --locked
              cargo pgrx init --pg${{ matrix.pg_version }} "$PG_CONFIG"

              make -f _release_tooling/Makefile package \
                PG_CONFIG="$PG_CONFIG" \
                EXTRA_FEATURES="${{ env.PACKAGE_HTTP_FEATURE }}"

              STAGE_DIR="$PWD/target/source-install-stage-pg${{ matrix.pg_version }}"
              make -f _release_tooling/Makefile install \
                PG_CONFIG="$PG_CONFIG" \
                PGRX_PACKAGE_DIR="$PWD/target/release/pg_durable-pg${{ matrix.pg_version }}" \
                DESTDIR="$STAGE_DIR"
              test -f "$STAGE_DIR/usr/lib/postgresql/${{ matrix.pg_version }}/lib/pg_durable.so"
              test -f "$STAGE_DIR/usr/share/postgresql/${{ matrix.pg_version }}/extension/pg_durable.control"
              compgen -G "$STAGE_DIR/usr/share/postgresql/${{ matrix.pg_version }}/extension/pg_durable--*.sql" >/dev/null
              compgen -G "$STAGE_DIR/usr/share/postgresql/${{ matrix.pg_version }}/extension/pg_durable--*--*.sql" >/dev/null

              _release_tooling/scripts/package-deb.sh \
                "$VERSION" \
                "$PWD/target/release/pg_durable-pg${{ matrix.pg_version }}" \
                "${{ matrix.platform.type }}" \
                "${{ matrix.pg_version }}"
            '

      - name: Verify Debian package
        run: |
          sudo chown -R "$USER:$USER" dist target || true
          deb_file=$(ls dist/pg-durable-postgresql-${{ matrix.pg_version }}_*_${{ matrix.platform.type }}.deb)
          echo "Checking $deb_file"
          dpkg-deb --info "$deb_file"
          dpkg-deb --contents "$deb_file" | grep "usr/lib/postgresql/${{ matrix.pg_version }}/lib/pg_durable.so"
          dpkg-deb --contents "$deb_file" | grep "usr/share/postgresql/${{ matrix.pg_version }}/extension/pg_durable.control"
          rm -rf check-package
          mkdir check-package
          dpkg-deb -x "$deb_file" check-package
          file "check-package/usr/lib/postgresql/${{ matrix.pg_version }}/lib/pg_durable.so" | grep -E "${{ matrix.platform.file_pattern }}"

      - name: Upload package artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: pg-durable-package-${{ github.run_id }}-pg${{ matrix.pg_version }}-${{ matrix.platform.type }}
          path: |
            dist/*.deb
          retention-days: 30

  validate-packages:
    name: Validate PG${{ matrix.pg_version }} ${{ matrix.platform.type }} package
    needs: build-packages
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        pg_version: [17, 18]
        platform:
          - type: amd64
            docker_platform: linux/amd64

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}

      # See build-packages: validation tooling must come from the workflow's
      # own commit (github.sha), not the target tag, so a tag predating these
      # scripts still validates correctly.
      - name: Checkout release tooling
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.sha }}
          path: _release_tooling

      - name: Download package artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: pg-durable-package-${{ github.run_id }}-pg${{ matrix.pg_version }}-${{ matrix.platform.type }}
          path: dist

      - name: Validate Debian package in PostgreSQL
        run: |
          docker run --rm \
            --platform "${{ matrix.platform.docker_platform }}" \
            -v "$PWD:/work" \
            -w /work \
            --user root \
            debian:bookworm \
            bash -euxo pipefail -c '_release_tooling/scripts/validate-deb-package.sh "${{ matrix.pg_version }}" "${{ matrix.platform.type }}"'

      - name: Upload validation diagnostics on failure
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: pg-durable-validation-diagnostics-${{ github.run_id }}-pg${{ matrix.pg_version }}-${{ matrix.platform.type }}
          path: |
            package-validation-logs/**
            regression.out
            regression.diffs
          if-no-files-found: ignore

  build-source:
    name: Build source archives
    needs: validate-inputs
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}

      - name: Set version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            echo "VERSION=${{ inputs.tag }}" >> "$GITHUB_ENV"
          elif [ "${{ github.event_name }}" = "pull_request" ]; then
            crate_version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
            echo "VERSION=${crate_version}~pr${{ github.event.pull_request.number }}+${GITHUB_SHA::7}" >> "$GITHUB_ENV"
          else
            echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV"
          fi

      - name: Create source archives
        run: |
          clean_version="${VERSION#v}"
          mkdir -p dist
          git archive --format=tar --prefix="pg_durable-${clean_version}/" HEAD -o "dist/pg_durable-${clean_version}.tar"
          gzip -c "dist/pg_durable-${clean_version}.tar" > "dist/pg_durable-${clean_version}.tar.gz"
          bzip2 -k "dist/pg_durable-${clean_version}.tar"
          rm "dist/pg_durable-${clean_version}.tar"

      - name: Upload source artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: pg-durable-source-${{ github.run_id }}
          path: dist/pg_durable-*.tar.*
          retention-days: 30

  release:
    name: Upload release assets
    needs: [validate-packages, build-source]
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || inputs.publish == true
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}

      - name: Set version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            echo "VERSION=${{ inputs.tag }}" >> "$GITHUB_ENV"
          else
            echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV"
          fi

      - name: Download artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: artifacts

      - name: Upload assets
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if ! gh release view "$VERSION" >/dev/null 2>&1; then
            gh release create "$VERSION" \
              --title "Release $VERSION" \
              --notes "Release $VERSION" \
              --draft
          fi

          mkdir -p release-assets
          find artifacts -type f \( -name '*.deb' -o -name '*.tar.gz' -o -name '*.tar.bz2' \) \
            -exec cp -t release-assets {} +

          ( cd release-assets && sha256sum -- * > SHA256SUMS )

          find release-assets -type f -print0 \
            | xargs -0 -I {} gh release upload "$VERSION" "{}" --clobber
