name: CI

# Only run when:
#   - PRs are opened against the master branch
#   - the workflow is started from the UI (an optional tag can be passed in via parameter)
#     - If the optional tag parameter is passed in, a new tag will be generated based off the selected branch
on:
  pull_request:
  workflow_dispatch:
    inputs:
      tag:
        description: 'The tag to create (optional)'
        required: false

concurrency:
  group: stacks-blockchain-${{ github.ref }}
  # Only cancel in progress if this is for a PR
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  # Run unit tests with code coverage
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run units tests (with coverage)
        env:
          DOCKER_BUILDKIT: 1
        # Remove .dockerignore file so codecov has access to git info
        run: |
          rm .dockerignore
          docker build -o coverage-output -f ./.github/actions/bitcoin-int-tests/Dockerfile.code-cov .
      - uses: codecov/codecov-action@v2
        with:
          files: ./coverage-output/lcov.info
          name: unit_tests
          fail_ci_if_error: false
  # Build Stacks 2.1 for tests that require stacks-node
  build-stacks-2_1:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          repository: stacks-network/stacks-blockchain
          ref: next
      - name: Build stacks-node 2.1
        env:
          DOCKER_BUILDKIT: 1
        run: |
            rm .dockerignore
            docker build -f ./Dockerfile.stretch -t stacks-blockchain:2.1-stretch .
      - name: Export docker image as tarball
        run: docker save -o stacks-node-image.tar stacks-blockchain:2.1-stretch
      - name: Upload built docker image
        uses: actions/upload-artifact@v2
        with:
          name: stacks-node-image.tar
          path: stacks-node-image.tar
  # Build subnets image for tests that require stacks-node
  build-layer-1-tests:
    runs-on: ubuntu-latest
    needs:
      - build-stacks-2_1
    steps:
      - uses: actions/checkout@v2
      - name: Download Stacks 2.1 image
        uses: actions/download-artifact@v2
        with:
          name: stacks-node-image.tar
      - name: Load Stacks 2.1 image
        run: docker load -i stacks-node-image.tar && rm stacks-node-image.tar
      - name: Build layer-1 integration testing image
        env:
          DOCKER_BUILDKIT: 1
        # Remove .dockerignore file so codecov has access to git info
        run: |
          rm .dockerignore
          docker build -f ./.github/actions/bitcoin-int-tests/Dockerfile.generic.stacks-node -t subnet-node:integrations .
      - name: Export docker image as tarball
        run: docker save -o integration-image.tar subnet-node:integrations
      - name: Upload built docker image
        uses: actions/upload-artifact@v2
        with:
          name: integration-image.tar
          path: integration-image.tar
  compute-layer-1-tests:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v2
      - id: set-matrix
        run: |
          cargo test --workspace --bin=subnet-node -- l --list --format=terse | sed -e 's/: test//g' | jq -ncR '{"test-name": [inputs]}' > test_names.json
          echo "::set-output name=matrix::$(cat test_names.json)"
  # Run the tests that require stacks-node
  layer-1-tests:
    runs-on: ubuntu-latest
    needs:
      - build-layer-1-tests
      - compute-layer-1-tests
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.compute-layer-1-tests.outputs.matrix)}}
    steps:
      - uses: actions/checkout@v2
      - name: Download docker image
        uses: actions/download-artifact@v2
        with:
          name: integration-image.tar
      - name: Load docker image
        run: docker load -i integration-image.tar && rm integration-image.tar
      - name: Run layer 1 tests
        timeout-minutes: 30
        env:
          DOCKER_BUILDKIT: 1
          TEST_NAME: ${{ matrix.test-name }}
        run: docker build -o coverage-output --build-arg test_name=${{ matrix.test-name }} -f ./.github/actions/bitcoin-int-tests/Dockerfile.stacks-node .
      - uses: codecov/codecov-action@v2
        with:
          files: ./coverage-output/lcov.info
          name: ${{ matrix.test-name }}
          fail_ci_if_error: false
  # Run integration tests
  integration-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run units tests (with coverage)
        env:
          DOCKER_BUILDKIT: 1
        # Remove .dockerignore file so codecov has access to git info
        run: |
          rm .dockerignore
          docker build -o coverage-output -f ./.github/actions/bitcoin-int-tests/Dockerfile.integrations .
      - uses: codecov/codecov-action@v2
        with:
          files: ./coverage-output/lcov.info
          name: integration_tests
          fail_ci_if_error: false


  open-api-validation:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run units tests (with coverage)
        env:
          DOCKER_BUILDKIT: 1
        run: docker build -o dist/ -f .github/actions/open-api/Dockerfile.open-api-validate .
      - name: Upload bundled html
        uses: actions/upload-artifact@v2
        with:
          name: open-api-bundle
          path: |
            dist

  # rustfmt checking
  rustfmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run rustfmt check
        env:
          DOCKER_BUILDKIT: 1
        run: docker build -f ./.github/actions/bitcoin-int-tests/Dockerfile.rustfmt .
  build-clarinet-2_1:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          repository: hirosystems/clarinet
          ref: main
          submodules: recursive
      - name: Build Clarinet with 2.1
        run: cargo build -p=clarinet-cli
      - name: Upload Clarinet binary
        uses: actions/upload-artifact@v2
        with:
          name: clarinet
          path: ./target/debug/clarinet
  clarinet-test:
    runs-on: ubuntu-latest
    needs: build-clarinet-2_1
    steps:
      - uses: actions/checkout@v2
      - name: Download built Clarinet 2.1 artifact 
        uses: actions/download-artifact@v2
        with:
          name: clarinet
      - name: "Make Clarinet executable"
        run: chmod +x ./clarinet
      - name: "Execute Clarinet tests"
        run: ./clarinet test --coverage --manifest-path=./core-contracts/Clarinet.toml --import-map=./core-contracts/import_map.json --allow-net
      - name: "Export code coverage"
        uses: codecov/codecov-action@v1
        with:
          files: ./coverage.lcov
          fail_ci_if_error: false
          verbose: true

  # Build docker image, tag it with the git tag and `latest` if running on master branch, and publish under the following conditions
  # Will publish if:
  #   - a tag was passed into this workflow
  #   - a tag was pushed up
  #   - this workflow was invoked against a non-master branch (a Docker image tag with the name of the branch will be published)
  build-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      # Stretch tag will be "<TAG> if a tag was passed in, otherwise "<BRANCH>". If the BRANCH is master, will result in "latest"
      - name: Determine Stretch Tag
        run: |
          if [[ -z ${TAG} ]]; then
              REF=$(echo ${GITHUB_REF#refs/*/} | tr / -)
              if [[ "${REF}" == "master" ]]; then
                  echo "STRETCH_TAG=latest" >> $GITHUB_ENV
              else
                  echo "STRETCH_TAG=${REF}" >> $GITHUB_ENV
              fi
          else
              echo "STRETCH_TAG=${TAG}" >> $GITHUB_ENV
          fi
        env:
          TAG: ${{ github.event.inputs.tag }}

      - name: Set Vars
        run: |
          echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
          echo "GITHUB_REF_SHORT=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

      - name: Build/Tag/Push Image
        uses: docker/build-push-action@v1
        with:
          repository: hirosystems/stacks-subnets
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
          tags: ${{ env.STRETCH_TAG }}
          tag_with_ref: false
          add_git_labels: true
          build_args: SUBNET_NODE_VERSION=${{ github.event.inputs.tag || env.GITHUB_SHA_SHORT }},GIT_BRANCH=${{ env.GITHUB_REF_SHORT }},GIT_COMMIT=${{ env.GITHUB_SHA_SHORT }}
          # Only push if ( we have DockerHub credentials and ((a tag was passed in) or (we're building a non-master branch))
          push: ${{ (secrets.DOCKERHUB_USERNAME != '') && (secrets.DOCKERHUB_PASSWORD != '') && ((github.event.inputs.tag != '') || (github.ref != 'refs/heads/master')) }}

  # Build docker image, tag it with the git tag and `latest` if running on master branch, and publish under the following conditions
  # Will publish if:
  #   - a tag was passed into this workflow
  #   - a tag was pushed up
  #   - this workflow was invoked against a non-master branch (a Docker image tag with the name of the branch will be published)
  build-publish-stretch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      # Stretch tag will be "<TAG>-stretch" if a tag was passed in, otherwise "<BRANCH>-stretch". If the BRANCH is master, will result in "latest-stretch"
      - name: Determine Stretch Tag
        run: |
          if [[ -z ${TAG} ]]; then
              REF=$(echo ${GITHUB_REF#refs/*/} | tr / -)
              if [[ "${REF}" == "master" ]]; then
                  echo "STRETCH_TAG=latest-stretch" >> $GITHUB_ENV
              else
                  echo "STRETCH_TAG=${REF}-stretch" >> $GITHUB_ENV
              fi
          else
              echo "STRETCH_TAG=${TAG}-stretch" >> $GITHUB_ENV
          fi
        env:
          TAG: ${{ github.event.inputs.tag }}

      - name: Set Vars
        run: |
          echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
          echo "GITHUB_REF_SHORT=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

      - name: Build/Tag/Push Image
        uses: docker/build-push-action@v1
        with:
          repository: hirosystems/stacks-subnets
          dockerfile: Dockerfile.stretch
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
          tags: ${{ env.STRETCH_TAG }}
          tag_with_ref: false
          add_git_labels: true
          build_args: SUBNET_NODE_VERSION=${{ github.event.inputs.tag || env.GITHUB_SHA_SHORT }},GIT_BRANCH=${{ env.GITHUB_REF_SHORT }},GIT_COMMIT=${{ env.GITHUB_SHA_SHORT }}
          # Only push if ( we have DockerHub credentials and ((a tag was passed in) or (we're building a non-master branch))
          push: ${{ (secrets.DOCKERHUB_USERNAME != '') && (secrets.DOCKERHUB_PASSWORD != '') && ((github.event.inputs.tag != '') || (github.ref != 'refs/heads/master')) }}
