---
version: 2.1
orbs:
  win: circleci/windows@5.0

executors:
  besu_executor_med: # 2cpu, 4G ram
    docker:
      - image: cimg/openjdk:17.0
    resource_class: medium
    working_directory: ~/project
    environment:
      architecture: "amd64"
      GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=2

  besu_arm64_executor_med: # 2cpu, 8G ram
    machine: #https://circleci.com/developer/machine/image/ubuntu-2204
      image: ubuntu-2204:2022.10.2
    resource_class: arm.medium
    working_directory: ~/project
    environment:
      architecture: "arm64"
      GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=2

  besu_executor_xl: # 8cpu, 16G ram
    docker:
      - image: cimg/openjdk:17.0
    resource_class: xlarge
    working_directory: ~/project
    environment:
      GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=4

  xl_machine_executor:
    machine: #https://circleci.com/developer/machine/image/ubuntu-2204
      image: ubuntu-2204:2022.10.2
    resource_class: xlarge

  trivy_executor:
    docker:
      - image: docker:stable-git
    resource_class: small
    working_directory: ~/project

commands:
  prepare:
    description: "Prepare"
    steps:
      - checkout
      - run:
          name: Install Packages - LibSodium, nssdb
          command: |
            sudo apt-get update
            sudo apt-get install -y libsodium23 libsodium-dev libjemalloc-dev apt-transport-https haveged libnss3-tools
            sudo service haveged restart
            java --version
      - restore_gradle_cache
  restore_gradle_cache:
    description: "Restore Gradle cache"
    steps:
      - restore_cache:
          name: Restore cached gradle dependencies
          keys:
            - deps-{{ checksum "gradle/versions.gradle" }}-{{ .Branch }}-{{ .Revision }}
            - deps-{{ checksum "gradle/versions.gradle" }}
            - deps-

  capture_test_results:
    description: "Capture test results"
    steps:
      - run:
          name: Jacoco
          command: |
            ./gradlew --no-daemon jacocoTestReport
      - run:
          name: Gather test results
          when: always
          command: |
            FILES=`find . -name test-results`
            for FILE in $FILES
            do
              MODULE=`echo "$FILE" | sed -e 's@./\(.*\)/build/test-results@\1@'`
              TARGET="build/test-results/$MODULE"
              mkdir -p "$TARGET"
              cp -rf ${FILE}/*/* "$TARGET"
            done
      - store_test_results:
          path: build/test-results
      - store_artifacts:
          path: besu/build/reports/jacoco

  capture_test_logs:
    description: "Capture test logs"
    steps:
      - store_artifacts:
          path: acceptance-tests/tests/build/acceptanceTestLogs
          destination: acceptance-tests-logs
      - store_artifacts:
          path: acceptance-tests/tests/build/jvmErrorLogs

jobs:
  assemble:
    executor: besu_executor_xl
    steps:
      - prepare
      - run:
          name: Assemble
          command: |
            ./gradlew --no-daemon clean compileJava compileTestJava assemble
      - save_cache:
          name: Caching gradle dependencies
          key: deps-{{ checksum "gradle/versions.gradle" }}-{{ .Branch }}-{{ .Revision }}
          paths:
            - .gradle
            - ~/.gradle
      - persist_to_workspace:
          root: ~/project
          paths:
            - ./
      - store_artifacts:
          name: Distribution artifacts
          path: build/distributions
          destination: distributions
          when: always

  testWindows:
    executor: win/default
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: Unzip Windows build
          no_output_timeout: 20m
          command: |
            cd build/distributions
            unzip besu-*.zip -d besu-tmp
            cd besu-tmp
            mv besu-* ../besu
      - run:
          name: Test Besu Windows executable
          no_output_timeout: 10m
          command: |
            build\distributions\besu\bin\besu.bat --help
            build\distributions\besu\bin\besu.bat --version

  dockerScan:
    executor: trivy_executor
    steps:
      - checkout
      - restore_gradle_cache
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Install trivy
          command: |
            apk add --update-cache --upgrade curl bash
            curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
      - run:
          name: Scan with trivy
          shell: /bin/sh
          command: |
            for FILE in $(ls docker)
            do
              if [[ $FILE == "test.sh" || $FILE == "tests" ]]; then
                continue
              fi
              docker pull -q "hyperledger/besu:develop-$FILE"
              trivy -q image --exit-code 1 --no-progress --severity HIGH,CRITICAL "hyperledger/besu:develop-$FILE"
            done

  unitTests:
    executor: besu_executor_xl
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: Build
          no_output_timeout: 20m
          command: |
            ./gradlew --no-daemon build
      - capture_test_results

  integrationTests:
    executor: xl_machine_executor # needs the machine executor since privacy test uses container tests (docker)
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: IntegrationTests
          command: |
            ./gradlew --no-daemon integrationTest
      - run:
          name: CompileJmh
          command: |
            ./gradlew --no-daemon compileJmh
      - capture_test_results

  referenceTests:
    executor: besu_executor_xl
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: ReferenceTests
          no_output_timeout: 40m
          command: |
            git submodule update --init --recursive
            ./gradlew --no-daemon referenceTest
      - capture_test_results

  acceptanceTests:
    parallelism: 4
    executor: xl_machine_executor # needs the machine executor since privacy test uses container tests (docker)
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: AcceptanceTests (Mainnet)
          no_output_timeout: 20m
          command: |
            CLASSNAMES=$(circleci tests glob "acceptance-tests/tests/src/test/java/**/*.java" \
              | sed 's@.*/src/test/java/@@' \
              | sed 's@/@.@g' \
              | sed 's/.\{5\}$//' \
              | circleci tests split --split-by=timings --timings-type=classname)
            # Format the arguments to "./gradlew test"
            GRADLE_ARGS=$(echo $CLASSNAMES | awk '{for (i=1; i<=NF; i++) print "--tests",$i}')
            ./gradlew --no-daemon acceptanceTestMainnet $GRADLE_ARGS
      - capture_test_results
      - capture_test_logs

  acceptanceTestsCliqueBft:
    executor: besu_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: AcceptanceTests (Non-Mainnet)
          no_output_timeout: 30m
          command: |
            ./gradlew --no-daemon --max-workers=1 acceptanceTestCliqueBft
      - capture_test_results
      - capture_test_logs

  acceptanceTestsPrivacy:
    parallelism: 4
    executor: xl_machine_executor # needs the machine executor since it uses container tests (docker)
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: AcceptanceTests (Non-Mainnet)
          no_output_timeout: 20m
          command: |
            CLASSNAMES=$(circleci tests glob "acceptance-tests/tests/src/test/java/**/*.java" \
              | sed 's@.*/src/test/java/@@' \
              | sed 's@/@.@g' \
              | sed 's/.\{5\}$//' \
              | circleci tests split --split-by=timings --timings-type=classname)
            # Format the arguments to "./gradlew test"
            GRADLE_ARGS=$(echo $CLASSNAMES | awk '{for (i=1; i<=NF; i++) print "--tests",$i}')
            ./gradlew --no-daemon acceptanceTestPrivacy $GRADLE_ARGS
      - capture_test_results
      - capture_test_logs

  acceptanceTestsPermissioning:
    executor: besu_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: AcceptanceTests (Non-Mainnet)
          no_output_timeout: 20m
          command: |
            ./gradlew --no-daemon --max-workers=1 acceptanceTestPermissioning
      - capture_test_results
      - capture_test_logs

  buildDocker:
    executor: besu_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - setup_remote_docker
      - run:
          name: hadoLint_openjdk_17
          command: |
            docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile
      - run:
          name: hadoLint_openjdk_17_debug
          command: |
            docker run --rm -i hadolint/hadolint < docker/openjdk-17-debug/Dockerfile
      - run:
          name: hadoLint_openjdk_latest
          command: |
            docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile
      - run:
          name: hadoLint_graalvm
          command: |
            docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile
      - run:
          name: build image
          command: |
            ./gradlew --no-daemon distDocker
      - run:
          name: test image
          command: |
            mkdir -p docker/reports
            curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.9/goss-linux-amd64 -o ./docker/tests/goss-linux-amd64
            ./gradlew --no-daemon testDocker

  buildArm64Docker:
    executor: besu_arm64_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: hadoLint_openjdk_17
          command: |
            docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile
      - run:
          name: hadoLint_openjdk_latest
          command: |
            docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile
      - run:
          name: hadoLint_graalvm
          command: |
            docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile
      - run:
          name: Java_17
          command: |
            sudo apt install -q --assume-yes openjdk-17-jre-headless openjdk-17-jdk-headless
            sudo update-java-alternatives -a
      - run:
          name: build image
          command: |
            ./gradlew --no-daemon distDocker
      - run:
          name: test image
          command: |
            mkdir -p docker/reports
            curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.9/goss-linux-arm -o ./docker/tests/goss-linux-arm64
            ./gradlew --no-daemon testDocker

  publish:
    executor: besu_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: Publish
          command: |
            ./gradlew --no-daemon artifactoryPublish

  publishDocker:
    executor: besu_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - setup_remote_docker
      - run:
          name: Publish Docker
          command: |
            docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}"
            ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" dockerUpload

  publishArm64Docker:
    executor: besu_arm64_executor_med
    steps:
      - prepare
      - attach_workspace:
          at: ~/project
      - run:
          name: Java_17
          command: |
            sudo apt install -q --assume-yes openjdk-17-jre-headless openjdk-17-jdk-headless
            sudo update-java-alternatives -a
      - run:
          name: Publish Docker
          command: |
            docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}"
            ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" dockerUpload
  manifestDocker:
    executor: besu_executor_med
    steps:
      - prepare
      - setup_remote_docker
      - run:
          name: Create and publish docker manifest
          command: |
            docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}"
            ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" --parallel manifestDocker

workflows:
  version: 2
  default:
    jobs:
      - assemble
      - unitTests:
           requires:
            - assemble
      - testWindows:
          requires:
            - assemble
      - referenceTests:
          requires:
            - assemble
      - integrationTests:
          requires:
            - assemble
      - acceptanceTests:
          requires:
            - assemble
      - acceptanceTestsCliqueBft:
          requires:
            - assemble
            - acceptanceTests
      - acceptanceTestsPermissioning:
          requires:
            - assemble
      - buildDocker:
          requires:
            - assemble
      - buildArm64Docker:
          requires:
            - buildDocker
            - assemble
      - publish:
          filters:
            branches:
              only:
                - main
                - /^release-.*/
          requires:
            - assemble
            - integrationTests
            - unitTests
            - acceptanceTests
            - referenceTests
            - buildDocker
      - publishDocker:
          filters:
            branches:
              only:
                - main
                - /^release-.*/
          requires:
            - assemble
            - integrationTests
            - unitTests
            - acceptanceTests
            - referenceTests
            - buildDocker
          context:
            - besu-dockerhub-rw
      - publishArm64Docker:
          filters:
            branches:
              only:
                - main
                - /^release-.*/
          requires:
            - integrationTests
            - unitTests
            - acceptanceTests
            - referenceTests
            - buildArm64Docker
          context:
            - besu-dockerhub-rw
      - manifestDocker:
          filters:
            branches:
              only:
                - main
                - /^release-.*/
          requires:
            - publishDocker
            - publishArm64Docker
          context:
            - besu-dockerhub-rw

  nightly:
    triggers:
      - schedule:
          cron: "0 19 * * *"
          filters:
            branches:
              only:
                - main
    jobs:
      - assemble
      - dockerScan
