on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  release:
    types:
      - published
name: build-test

permissions: read-all

jobs:
  build-test-arch:
    name: Build and test ZOT
    permissions:
      contents: write
      packages: write
    runs-on: ubuntu-latest
    strategy:
      matrix:
        os: [linux, darwin, freebsd]
        arch: [amd64, arm64]
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
      - uses: ./.github/actions/clean-runner
      - name: Install go
        uses: actions/setup-go@v4
        with:
          cache: false
          go-version: 1.20.x
      - name: Cache go dependencies
        id: cache-go-dependencies
        uses: actions/cache@v3
        with:
          path: |
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-mod-
      - name: Cache go build output
        id: cache-go-build
        uses: actions/cache@v3
        with:
          path: |
            ~/.cache/go-build
          key: ${{ matrix.os }}-${{ matrix.arch }}-go-build-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ matrix.os }}-${{ matrix.arch }}-go-build-
      - name: Install go dependencies
        if: steps.cache-go-dependencies.outputs.cache-hit != 'true'
        run: |
          cd $GITHUB_WORKSPACE
          go mod download
      - name: Install other dependencies
        run: |
          cd $GITHUB_WORKSPACE
          go install github.com/swaggo/swag/cmd/swag@v1.8.12
          sudo apt-get update
          sudo apt-get install rpm
          sudo apt-get install snapd
          sudo apt-get install libgpgme-dev libassuan-dev libbtrfs-dev libdevmapper-dev pkg-config
          git clone https://github.com/containers/skopeo -b v1.12.0 $GITHUB_WORKSPACE/src/github.com/containers/skopeo
          cd $GITHUB_WORKSPACE/src/github.com/containers/skopeo && \
            make bin/skopeo && \
            sudo cp bin/skopeo /usr/bin && \
            rm -rf $GITHUB_WORKSPACE/src/github.com/containers/skopeo
          cd $GITHUB_WORKSPACE
          curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v1.0.0-rc.4/notation_1.0.0-rc.4_linux_amd64.tar.gz
          sudo tar xvzf notation.tar.gz -C /usr/bin notation
          rm -f notation.tar.gz
          go install github.com/wadey/gocovmerge@latest

      - if: matrix.os == 'linux' && matrix.arch == 'amd64'
        name: Setup localstack service
        run: |
            pip install localstack                    # Install LocalStack cli
            docker pull localstack/localstack:1.3     # Make sure to pull the latest version of the image
            localstack start -d                       # Start LocalStack in the background

            echo "Waiting for LocalStack startup..."  # Wait 30 seconds for the LocalStack container
            localstack wait -t 30                     # to become ready before timing out
            echo "Startup complete"

            aws --endpoint-url=http://localhost:4566 s3api create-bucket --bucket zot-storage --region us-east-2 --create-bucket-configuration="{\"LocationConstraint\": \"us-east-2\"}"
            aws dynamodb --endpoint-url http://localhost:4566 --region "us-east-2" create-table --table-name BlobTable --attribute-definitions AttributeName=Digest,AttributeType=S --key-schema AttributeName=Digest,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5
        env:
          AWS_ACCESS_KEY_ID: fake
          AWS_SECRET_ACCESS_KEY: fake

      - name: Check disk space before build
        run: |
          cd $GITHUB_WORKSPACE
          set -x
          df -h
          sudo ls -lRh /tmp/* || true
          sudo du -sh /tmp || true
          sudo du -sh /tmp/* || true
          sudo find /tmp/ -size +5M | sudo xargs ls -lh
          du -sh ./* || true
          find ./ -size +5M   | xargs ls -lh
          sudo du -sh /var/
          sudo du -sh /var/lib/docker/
          du -sh /home/runner/work/
          set +x

      - name: Run build and test
        timeout-minutes: 80
        run: |
          echo "Building for $OS:$ARCH"
          cd $GITHUB_WORKSPACE
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            make
            sudo env "PATH=$PATH" make privileged-test
          else
            make binary binary-minimal binary-debug cli bench exporter-minimal
          fi
        env:
          S3MOCK_ENDPOINT: localhost:4566
          DYNAMODBMOCK_ENDPOINT: http://localhost:4566
          AWS_ACCESS_KEY_ID: fake
          AWS_SECRET_ACCESS_KEY: fake
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}

      - if: matrix.os == 'linux' && matrix.arch == 'amd64'
        name: Stop localstack
        run: |
            localstack stop

      - name: Check disk space after build
        if: always()
        run: |
          cd $GITHUB_WORKSPACE
          set -x
          df -h
          sudo ls -lRh /tmp/* || true
          sudo du -sh /tmp || true
          sudo du -sh /tmp/* || true
          sudo find /tmp/ -size +5M | sudo xargs ls -lh
          du -sh ./* || true
          find ./ -size +5M   | xargs ls -lh
          sudo du -sh /var/
          sudo du -sh /var/lib/docker/
          du -sh /home/runner/work/
          set +x

      - name: Upload code coverage
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

      - name: Generate GraphQL Introspection JSON on Release
        if: github.event_name == 'release' && github.event.action == 'published' && matrix.os == 'linux' && matrix.arch == 'amd64'
        run: |
          bin/zot-linux-amd64 serve examples/config-search.json &
          sleep 10
          curl -X POST -H "Content-Type: application/json" -d @.pkg/debug/githubWorkflows/introspection-query.json http://localhost:5000/v2/_zot/ext/search | jq > bin/zot-gql-introspection-result.json
          pkill zot

      - if: github.event_name == 'release' && github.event.action == 'published'
        name: Publish artifacts on releases
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: bin/z*
          tag: ${{ github.ref }}
          overwrite: true
          file_glob: true

  push-image:
    if: github.event_name == 'release' && github.event.action== 'published'
    name: Push OCI images to GitHub Packages
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
      packages: write
    strategy:
      matrix:
        os: [linux, darwin]
        arch: [amd64, arm64]
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4
      - name: Log in to GitHub Docker Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push zot container image
        uses: project-stacker/stacker-build-push-action@main
        with:
          file: 'build/stacker.yaml'
          build-args: |
            RELEASE_TAG=${{ github.event.release.tag_name }}
            COMMIT=${{ github.event.release.tag_name }}-${{ github.sha }}
            OS=${{ matrix.os }}
            ARCH=${{ matrix.arch }}
            REPO_NAME=zot-${{ matrix.os }}-${{ matrix.arch }}
          url: docker://ghcr.io/${{ github.repository_owner }}
          tags: ${{ github.event.release.tag_name }} latest
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Run zot container image with docker
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            docker run -d -p 5000:5000 ghcr.io/${{ github.repository_owner }}/zot-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5000/v2/'
            docker kill $(docker ps -q)
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Run zot container image with podman
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            podman run -d -p 5000:5000 ghcr.io/${{ github.repository_owner }}/zot-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5000/v2/'
            podman kill --all
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Build and push zot-minimal container image
        uses: project-stacker/stacker-build-push-action@main
        with:
          file: 'build/stacker-minimal.yaml'
          build-args: |
            RELEASE_TAG=${{ github.event.release.tag_name }}
            COMMIT=${{ github.event.release.tag_name }}-${{ github.sha }}
            OS=${{ matrix.os }}
            ARCH=${{ matrix.arch }}
            EXT=-minimal
            REPO_NAME=zot-minimal-${{ matrix.os }}-${{ matrix.arch }}
          url: docker://ghcr.io/${{ github.repository_owner }}
          tags: ${{ github.event.release.tag_name }} latest
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Run zot-minimal container image with docker
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            docker run -d -p 5000:5000 ghcr.io/${{ github.repository_owner }}/zot-minimal-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5000/v2/'
            docker kill $(docker ps -q)
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Run zot-minimal container image with podman
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            podman run -d -p 5000:5000 ghcr.io/${{ github.repository_owner }}/zot-minimal-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5000/v2/'
            podman kill --all
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Build and push zot-exporter container image
        uses: project-stacker/stacker-build-push-action@main
        with:
          file: 'build/stacker-zxp.yaml'
          build-args: |
            RELEASE_TAG=${{ github.event.release.tag_name }}
            COMMIT=${{ github.event.release.tag_name }}-${{ github.sha }}
            OS=${{ matrix.os }}
            ARCH=${{ matrix.arch }}
            REPO_NAME=zxp-${{ matrix.os }}-${{ matrix.arch }}
          url: docker://ghcr.io/${{ github.repository_owner }}
          tags: ${{ github.event.release.tag_name }} latest
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Run zot-exporter container image with docker
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            docker run -d -p 5001:5001 ghcr.io/${{ github.repository_owner }}/zxp-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5001/metrics'
            docker kill $(docker ps -q)
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Run zot-exporter container image with podman
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            podman run -d -p 5001:5001 ghcr.io/${{ github.repository_owner }}/zxp-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}
            sleep 2
            curl --connect-timeout 5 \
              --max-time 10 \
              --retry 12 \
              --retry-max-time 360 \
              --retry-connrefused \
              'http://localhost:5001/metrics'
            podman kill --all
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Build and push zb container image
        uses: project-stacker/stacker-build-push-action@main
        with:
          file: 'build/stacker-zb.yaml'
          build-args: |
            RELEASE_TAG=${{ github.event.release.tag_name }}
            COMMIT=${{ github.event.release.tag_name }}-${{ github.sha }}
            OS=${{ matrix.os }}
            ARCH=${{ matrix.arch }}
            REPO_NAME=zb-${{ matrix.os }}-${{ matrix.arch }}
          url: docker://ghcr.io/${{ github.repository_owner }}
          tags: ${{ github.event.release.tag_name }} latest
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Run zb container image with docker
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            docker run ghcr.io/${{ github.repository_owner }}/zb-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }} --help
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Run zb container image with podman
        run: |
          if [[ $OS == "linux" && $ARCH == "amd64" ]]; then
            podman run ghcr.io/${{ github.repository_owner }}/zb-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }} --help
          fi
        env:
          OS: ${{ matrix.os }}
          ARCH: ${{ matrix.arch }}
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/${{ github.repository }}-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
        env:
          TRIVY_USERNAME: ${{ github.actor }}
          TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
      - name: Run Trivy vulnerability scanner (minimal)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/${{ github.repository }}-minimal-${{ matrix.os }}-${{ matrix.arch }}:${{ github.event.release.tag_name }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
        env:
          TRIVY_USERNAME: ${{ github.actor }}
          TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
      - name: Upload Trivy scan results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v2.21.5
        with:
          sarif_file: 'trivy-results.sarif'

  update-helm-chart:
    if: github.event_name == 'release' && github.event.action== 'published'
    needs: push-image
    name: Update Helm Chart
    permissions:
      contents: write
      packages: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: '0'

      - name: Checkout project-zot/helm-charts
        uses: actions/checkout@v4
        with:
          repository: project-zot/helm-charts
          ref: main
          fetch-depth: '0'
          token: ${{ secrets.HELM_PUSH_TOKEN }}
          path: ./helm-charts

      - name: Configure Git
        run: |
          git config --global user.name 'github-actions'
          git config --global user.email 'github-actions@users.noreply.github.com'
      - name: Update appVersion
        uses: mikefarah/yq@master
        with:
          cmd: yq -i '.appVersion = "${{ github.event.release.tag_name }}"' 'helm-charts/charts/zot/Chart.yaml'
      - name: Update image tag
        uses: mikefarah/yq@master
        with:
          cmd: yq -i '.image.tag = "${{ github.event.release.tag_name }}"' 'helm-charts/charts/zot/values.yaml'
      - name: Update version
        run: |
          sudo apt-get install pip
          pip install pybump
          pybump bump --file helm-charts/charts/zot/Chart.yaml --level patch
      - name: Push changes to project-zot/helm-charts
        run: |
          cd ./helm-charts
          git commit -am "build: automated update of Helm Chart"
          git push