name: ci

on:
  push:
    branches: [main, master]

  pull_request:
    branches: [main, master]

env:
  NEW_HSTREAM_IMAGE: new_hstream_image

jobs:
  pre-build:
    runs-on: ubuntu-latest
    name: prepare pre-build environment for tests
    outputs:
      ghc: ${{ steps.parser.outputs.ghc }}
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "recursive"

      - id: parser
        run: |
          pkgcabal="hstream/hstream.cabal"
          GHCS=$(cat ${pkgcabal} | grep tested-with | python3 -c 'import sys, re, json; print(re.findall(r"(\d+\.\d+\.\d+)", sys.stdin.read()))')
          echo "Set ghc versions: $GHCS..."
          echo "ghc=$GHCS" >> $GITHUB_OUTPUT

      - name: run stylish-haskell
        run: |
          # install stylish-haskell
          PACKAGE=stylish-haskell
          URL=$(\
            curl --silent https://api.github.com/repos/haskell/$PACKAGE/releases/latest \
            | grep "browser_download_url.*linux-x86_64\.tar\.gz" \
            | cut -d ':' -f 2,3 \
            | tr -d \" \
          )
          VERSION=$(echo $URL | sed -e 's/.*-\(v[\.0-9]\+-linux-x86_64\)\.tar\.gz/\1/')
          TEMP=$(mktemp --directory)
          curl --progress-bar --location -o$TEMP/$PACKAGE.tar.gz $URL
          tar -xzf $TEMP/$PACKAGE.tar.gz -C$TEMP
          chmod +x $TEMP/$PACKAGE-$VERSION/$PACKAGE
          # check all sources
          echo "Run script/format.sh with latest stylish-haskell..."
          FORMATER_BIN=$TEMP/$PACKAGE-$VERSION/$PACKAGE bash script/format.sh ci && git diff-index --exit-code HEAD

  # NOTE: hstream-admin-store requires ghc8.10 to build.
  # Also there is a WIP ghc9 support for hsthrift:
  # https://github.com/facebookincubator/hsthrift/pull/107
  build-hstream-admin-store:
    needs: pre-build
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "recursive"

      - name: set env
        run: |
          docker pull docker.io/hstreamdb/haskell:8.10
          echo "CABAL=python3 script/dev-tools cabal --no-interactive --no-services-required \
                --check -i docker.io/hstreamdb/haskell:8.10 -- \
                --project-file=cabal.project.hadmin.store" >> $GITHUB_ENV
          echo "SHELL=python3 script/dev-tools shell --no-interactive --no-services-required \
                --check -i docker.io/hstreamdb/haskell:8.10" >> $GITHUB_ENV

      - name: cabal freeze
        run: |
          ${{ env.CABAL }} update
          ${{ env.CABAL }} freeze

      - name: cache
        uses: actions/cache@v3
        with:
          path: |
            ~/.cabal/packages
            ~/.cabal/store
            dist-newstyle
          key: ${{ runner.os }}-8.10-v2-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project*') }}
          restore-keys: |
            ${{ runner.os }}-8.10-v2

      - name: install
        run: |
          ${{ env.CABAL }} update
          ${{ env.SHELL }} "'make clean'"
          ${{ env.SHELL }} "'make thrift'"
          ${{ env.CABAL }} install hadmin-store

      - name: run hadmin-store
        run: ${{ env.CABAL }} exec -- hadmin-store --help

  build:
    needs: pre-build
    runs-on: ubuntu-latest
    name: build-ghc-${{ matrix.ghc }}
    strategy:
      fail-fast: false
      matrix:
        ghc: ${{ fromJson(needs.pre-build.outputs.ghc) }}
    steps:
      - name: Free disk space
        run: |
          echo "Before..."
          df -h
          sudo rm -rf \
            /usr/share/dotnet /usr/local/lib/android /opt/ghc \
            /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup \
            "/usr/local/share/boost" \
            /usr/lib/jvm || true
          echo "After..."
          df -h

      - uses: actions/checkout@v3
        with:
          submodules: "recursive"

      - name: CPU info
        run: |
          sudo apt-get install cpuid
          cpuid

      - name: set env
        run: |
          docker pull docker.io/hstreamdb/haskell:${{ matrix.ghc }}
          GHC_MAJOR_VER="$(echo ${{ matrix.ghc }} | cut -d'.' -f1)"
          echo "GHC_MAJOR_VER=$GHC_MAJOR_VER" >> $GITHUB_ENV
          if [ "$GHC_MAJOR_VER" = "8" ]; then
            echo "EXTRA_CABAL_ARGS='--project-file=cabal.project.ghc810'" >> $GITHUB_ENV
          else
            echo "EXTRA_CABAL_ARGS=''" >> $GITHUB_ENV
          fi
          echo "CABAL=python3 script/dev-tools cabal --check --no-interactive --no-services-required \
                -i docker.io/hstreamdb/haskell:${{ matrix.ghc }} -- " >> $GITHUB_ENV
          echo "SHELL=python3 script/dev-tools shell --check --no-interactive --no-services-required \
                -i docker.io/hstreamdb/haskell:${{ matrix.ghc }}" >> $GITHUB_ENV

      - name: cabal freeze
        run: |
          ${{ env.CABAL }} update
          ${{ env.CABAL }} ${{ env.EXTRA_CABAL_ARGS }} freeze

      - name: cache
        uses: actions/cache@v3
        with:
          path: |
            ~/.cabal/packages
            ~/.cabal/store
            dist-newstyle
          key: ${{ runner.os }}-${{ matrix.ghc }}-v2-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project*') }}
          restore-keys: |
            ${{ runner.os }}-${{ matrix.ghc }}-v2-

      - name: build
        run: |
          ${{ env.CABAL }} update
          ${{ env.SHELL }} "'make clean'"
          ${{ env.SHELL }} make
          ${{ env.CABAL }} ${{ env.EXTRA_CABAL_ARGS }} build --enable-tests --enable-benchmarks all
          ${{ env.CABAL }} ${{ env.EXTRA_CABAL_ARGS }} install hstream

      # TODO: move to "test" job
      - name: test
        run: |
          ${{ env.SHELL }} "'make syntax-test-run'"
          ${{ env.SHELL }} "'make plan-test-run'"

      # NOTE: The quick-build-dev-image relies on the "hstreamdb/hstream" base image.
      # If you have installed any additional libraries in the builder image (hstreamdb/haskell),
      # and these libraries are required (e.g., if a lib.so file is needed), you may encounter a
      # linking error during the integration tests that follow. In such cases, you will need to
      # publish a new version of the hstreamdb/hstream image first, which includes the necessary
      # libraries.
      - name: quick build new hstream image
        run: |
          mkdir -p ~/data
          if [ "${{ env.GHC_MAJOR_VER }}" = "8" ]; then
            python3 script/dev-tools quick-build-dev-image \
              --builder-image docker.io/hstreamdb/haskell:${{ matrix.ghc }} \
              --project-file cabal.project.ghc810 \
              --only-hstream \
              -t $NEW_HSTREAM_IMAGE
          else
            python3 script/dev-tools quick-build-dev-image \
              --builder-image docker.io/hstreamdb/haskell:${{ matrix.ghc }} \
              --only-hstream \
              -t $NEW_HSTREAM_IMAGE
          fi

          docker save -o ~/data/new_hstream_image.tar $NEW_HSTREAM_IMAGE

      - uses: actions/upload-artifact@v3
        with:
          name: image-testing-${{ matrix.ghc }}
          path: ~/data/new_hstream_image.tar
          retention-days: 2

      - name: tar tests
        run: |
          mkdir -p ~/data
          rm -f ~/data/hstream_tests.tar
          find dist-newstyle/build -type f \( \
            -name "*-test" -o \
            -name "hstream-server" -o \
            -name "hstream-kafka-server" \) \
            -exec tar -rvf ~/data/hstream_tests.tar {} \;

      - uses: actions/upload-artifact@v3
        with:
          name: hstream-tests-${{ matrix.ghc }}
          path: ~/data/hstream_tests.tar
          retention-days: 2

  test:
    needs: [pre-build, build]
    runs-on: ubuntu-latest
    name: test-ghc-${{ matrix.ghc }}
    strategy:
      fail-fast: false
      matrix:
        ghc: ${{ fromJson(needs.pre-build.outputs.ghc) }}

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: "recursive"

      - name: set env
        run: |
          docker pull docker.io/hstreamdb/haskell:${{ matrix.ghc }}
          echo "CABAL=python3 script/dev-tools cabal --check --no-interactive \
                -i docker.io/hstreamdb/haskell:${{ matrix.ghc }} -- " >> $GITHUB_ENV
          echo "SHELL=python3 script/dev-tools shell --check --no-interactive \
                -i docker.io/hstreamdb/haskell:${{ matrix.ghc }}" >> $GITHUB_ENV
          echo "TEST_CONTAINER_NAME=test-hstream-server" >> $GITHUB_ENV

      - name: retrieve saved tests
        uses: actions/download-artifact@v3
        with:
          name: hstream-tests-${{ matrix.ghc }}
          path: ~/data

      - name: untar tests
        run: tar -xf ~/data/hstream_tests.tar

      - name: start required services
        run: python3 script/dev-tools start-services

      - name: start hstream server
        run: |
          export CONTAINER_NAME=$TEST_CONTAINER_NAME
          export IMAGE="docker.io/hstreamdb/haskell:${{ matrix.ghc }}"
          export EXTRA_OPTS="--check --no-interactive --detach"
          export COMMAND=" "
          export EXE=$(find dist-newstyle -name "hstream-server" -type f)
          ./script/start-server.sh
          sleep 5
          docker logs --tail 100 $TEST_CONTAINER_NAME

      - name: run tests
        run: ${{ env.SHELL }} "'find dist-newstyle/build -type f -name \"*-test\" -exec {} \;'"

      - name: collect hserver logs
        if: ${{ success() }} || ${{ failure() }}
        run: |
          rm -rf hserver.log
          docker logs $TEST_CONTAINER_NAME &> hserver.log

      - name: upload hserver logs
        uses: actions/upload-artifact@v3
        if: ${{ success() }} || ${{ failure() }}
        with:
          name: hserver-logs-${{ matrix.ghc }}
          path: hserver.log
          retention-days: 7

      # Due to an [cabal bug](https://github.com/haskell/cabal/issues/7423),
      # `cabal check` will emit a warning even if the `-O2` option is just
      # an flag. This is disabled until the problem is fixed.
      #- name: check
      #  run: |
      #    python3 script/dev-tools cabal --check --no-interactive -i docker.io/hstreamdb/haskell:${{ matrix.ghc }} -- sdist all

      #    # unfortunately, there is no `cabal check all`
      #    #log_info "Run all cabal check..."
      #    # Note that we ignore hstream-store package to run cabal check, because there
      #    # is an unexpected warning:
      #    #   ...
      #    #   Warning: 'cpp-options': -std=c++17 is not portable C-preprocessor flag
      #    #   Warning: Hackage would reject this package.
      #    for dir in hstream-sql hstream-processing hstream; do
      #      python3 script/dev-tools shell --check --no-interactive -i docker.io/hstreamdb/haskell:${{ matrix.ghc }} "'cd $dir && cabal check'"
      #    done

      # -------------------------------------------------------------------------------

      - name: stop all started services
        run: docker rm -f $(docker ps -a -q)

  integration-tests:
    needs: [pre-build, build]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        ghc: ${{ fromJson(needs.pre-build.outputs.ghc) }}
        test:
          - integration-tests
          - integration-tests-rqlite
          - integration-tests-kafka
          - integration-tests-kafka-rqlite
          - distributed-tests
        include:
          - test: integration-tests
            repo: hstreamdb/integration-tests
            command: |
              ./gradlew test --info --fail-fast -Dtag='basicTest'
          - test: integration-tests-rqlite
            repo: hstreamdb/integration-tests
            command: |
              export HSTREAM_META_STORE=RQLITE
              ./gradlew test --info --fail-fast -Dtag='basicTest'
          - test: integration-tests-kafka
            repo: hstreamdb/kafka-tests
            command: |
              ./gradlew test --info --fail-fast
          - test: integration-tests-kafka-rqlite
            repo: hstreamdb/kafka-tests
            command: |
              export HSTREAM_META_STORE=RQLITE
              ./gradlew test --info --fail-fast
          - test: distributed-tests
            repo: hstreamdb/distributed-tests
            command: |
              ./gradlew test --info --fail-fast

    name: ${{ matrix.test }}-${{ matrix.ghc }}

    steps:
      - name: Free disk space
        run: |
          echo "Before..."
          df -h
          sudo rm -rf \
            /usr/share/dotnet /usr/local/lib/android /opt/ghc \
            /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup \
            "/usr/local/share/boost" \
            /usr/lib/jvm || true
          echo "After..."
          df -h

      - name: retrieve saved docker image
        uses: actions/download-artifact@v3
        with:
          name: image-testing-${{ matrix.ghc }}
          path: ~/data

      - name: docker load
        run: |
          docker load -i ~/data/new_hstream_image.tar
          docker run -t --rm $NEW_HSTREAM_IMAGE /usr/local/bin/hstream-server +RTS --info
          docker run -t --rm $NEW_HSTREAM_IMAGE /usr/local/bin/hstream-kafka-server +RTS --info

      - name: fetch tests source code
        uses: actions/checkout@v3
        with:
          repository: ${{ matrix.repo }}
          submodules: "recursive"
          path: integration-tests

      - uses: actions/setup-java@v3
        with:
          distribution: "adopt"
          java-version: 11
          cache: "gradle"

      - uses: gradle/wrapper-validation-action@v1

      - name: run tests
        run: |
          cd integration-tests
          export HSTREAM_IMAGE_NAME=$NEW_HSTREAM_IMAGE
          ${{ matrix.command }}

      - name: collect tests reports
        if: ${{ success() }} || ${{ failure() }}
        run: |
          rm -rf ci_artifact && mkdir ci_artifact
          mv integration-tests/.logs ci_artifact/logs
          mv integration-tests/app/build/reports ci_artifact/reports

      - name: upload tests-reports
        uses: actions/upload-artifact@v3
        if: ${{ success() }} || ${{ failure() }}
        with:
          name: reports-${{ matrix.test }}-${{ matrix.ghc }}
          path: ci_artifact
          retention-days: 7

  # hstream-io-tests:
  #   needs: [pre-build, build]
  #   runs-on: ubuntu-latest
  #   name: hstream-io-tests-ghc-${{ matrix.ghc }}
  #   strategy:
  #     fail-fast: false
  #     matrix:
  #       ghc: ${{ fromJson(needs.pre-build.outputs.ghc) }}

  #   steps:
  #     - name: retrieve saved docker image
  #       uses: actions/download-artifact@v3
  #       with:
  #         name: image-testing-${{ matrix.ghc }}
  #         path: ~/data

  #     - name: docker load
  #       run: |
  #         docker load -i ~/data/new_hstream_image.tar
  #         docker run -t --rm $NEW_HSTREAM_IMAGE /usr/local/bin/hstream-server +RTS --info

  #     - name: fetch hstream io tests source code
  #       uses: actions/checkout@v3
  #       with:
  #         repository: "hstreamdb/hstream-connectors"
  #         submodules: "recursive"
  #         path: hstream-connectors

  #     - uses: actions/setup-java@v3
  #       with:
  #         distribution: "adopt"
  #         java-version: 11
  #         cache: "gradle"

  #     - uses: gradle/gradle-build-action@v2

  #     - name: build and pull images
  #       run: |
  #         cd hstream-connectors
  #         export CONNECTOR_IMAGE_VERSION=v0.2.3
  #         make pull_images
  #         make build_images

  #     - name: run hstream io tests
  #       run: |
  #         cd hstream-connectors/integration_tests
  #         export HSTREAM_IMAGE_NAME=$NEW_HSTREAM_IMAGE
  #         export HSTREAM_IO_USE_DEFAULT_IMAGES=true
  #         ./gradlew test --info --fail-fast

  #     - name: collect tests reports
  #       if: ${{ success() }} || ${{ failure() }}
  #       run: |
  #         rm -rf ci_artifact && mkdir ci_artifact
  #         mv hstream-connectors/integration_tests/.logs ci_artifact/logs
  #         mv hstream-connectors/integration_tests/app/build/reports ci_artifact/reports
  #         export U=$(id -u); export G=$(id -g); sudo chown -R $U:$G /tmp/io
  #         mv /tmp/io/tasks ci_artifact/tasks

  #     - name: upload tests-reports
  #       uses: actions/upload-artifact@v3
  #       if: ${{ success() }} || ${{ failure() }}
  #       with:
  #         name: hstream-io-logs-${{ matrix.ghc }}
  #         path: ci_artifact
  #         retention-days: 7

  deploy-k8s:
    # disabled due to a lack of hardware resources of the github action
    if: false
    needs: [pre-build, build]
    runs-on: ubuntu-latest
    name: deploy-to-minikube-ghc-${{ matrix.ghc }}
    strategy:
      fail-fast: true
      matrix:
        ghc: ${{ fromJson(needs.pre-build.outputs.ghc) }}

    steps:
      - name: Start minikube
        uses: medyagh/setup-minikube@master

      - name: Try the cluster !
        run: kubectl get pods -A

      - name: retrieve saved docker image
        uses: actions/download-artifact@v3
        with:
          name: image-testing-${{ matrix.ghc }}
          path: ~/data

      - name: Load image
        run: |
          export SHELL=/bin/bash
          eval $(minikube -p minikube docker-env)
          docker load -i ~/data/new_hstream_image.tar
          docker tag $NEW_HSTREAM_IMAGE hstreamdb/hstream
          echo -n "verifying images:"
          docker images

      - uses: actions/checkout@v3
      - name: Deploy to minikube by helm
        run: |
          cd deploy/chart/hstream
          helm repo add bitnami https://charts.bitnami.com/bitnami
          helm repo update
          helm dependency build .
          helm install my-hstream .

      - name: Waiting for cluster ready
        run: |
          timeout=600    # seconds
          until ( \
              kubectl exec -it my-hstream-0 -- hadmin server status |grep "100.*Running" && \
              kubectl exec -it my-hstream-0 -- hadmin server status |grep "101.*Running" && \
              kubectl exec -it my-hstream-0 -- hadmin server status |grep "102.*Running"
              ) >/dev/null 2>&1;
          do
            >&2 echo "Waiting for cluster ready ..."
            kubectl get pods
            sleep 10
            timeout=$((timeout - 10))
            if [ $timeout -le 0 ]; then
              echo "Timeout!" && \
              kubectl logs --tail 100 my-hstream-0 ; \
              kubectl logs --tail 100 my-hstream-1 ; \
              kubectl logs --tail 100 my-hstream-2 ; \
              kubectl exec -it my-hstream-0 -- hadmin server status ; \
              exit 1;
            fi
          done

          sleep 2
          kubectl exec -it my-hstream-0 -- hadmin store --host my-hstream-logdevice-admin-server status
          kubectl exec -it my-hstream-0 -- hadmin server status

      - name: Fetch bench source code
        uses: actions/checkout@v3
        with:
          repository: "hstreamdb/bench"
          submodules: "recursive"
          path: a_bench

      - name: Run simple appends
        run: |
          eval $(minikube -p minikube docker-env)
          cd a_bench
          echo -e "FROM openjdk:11\nCOPY . /srv\nWORKDIR /srv" | docker build -t tmp_bench -f - .
          ADDR=$(kubectl get pods my-hstream-0 --template '{{.status.podIP}}')
          docker run -t --rm tmp_bench \
            ./gradlew writeBench --args="\
               --bench-time=30 \
               --warmup=1 \
               --service-url=$ADDR:6570 \
               --thread-count=1 \
               --stream-count=2 \
               --shard-count=1 \
               --stream-backlog-duration=60 \
               --stream-replication-factor=1 \
               --record-size=1024 \
               --batch-bytes-limit=2048 \
               --batch-age-limit=500 \
               --rate-limit=100 \
               --total-bytes-limit=8192"