ministryofjustice/Claim-for-Crown-Court-Defence

View on GitHub
.circleci/config.yml

Summary

Maintainability
Test Coverage
version: 2.1

orbs:
  aws-cli: circleci/aws-cli@4.0.0
  browser-tools: circleci/browser-tools@1.4.8
  slack: circleci/slack@3.4.1

references:
  _attach-tmp-workspace: &attach-tmp-workspace
      attach_workspace:
        at: ~/repo/tmp

  _create-tmp-dir: &create-tmp-dir
    run:
      name: Create workspace temporary directories
      command: |
        mkdir -p tmp/
        mkdir -p tmp/coverage/
        mkdir -p /tmp/test-results/rspec
        mkdir -p /tmp/test-results/cucumber

  _install-codeclimate: &install-codeclimate
    run:
      name: Install Code Climate test-reporter
      command: |
        curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > tmp/cc-test-reporter
        chmod +x tmp/cc-test-reporter

  _persist-codeclimate: &persist-codeclimate
    persist_to_workspace:
      root: tmp
      paths:
        - cc-test-reporter

  _wait-for-db: &wait-for-db
    run:
      name: Wait for DB
      command: dockerize -wait tcp://localhost:5432 -timeout 1m

  _load-db: &load-db
    run:
      name: Database setup
      command: bin/rails db:schema:load --trace

  _script-build-app-container: &script-build-app-container
    run:
      name: Build and push cccd docker image
      command: |
        .circleci/build.sh

# ------------------
# EXECUTORS
# ------------------
executors:
  basic-executor:
    resource_class: small
    docker:
      - image: cimg/base:2020.01
        environment:
          GITHUB_TEAM_NAME_SLUG: laa-get-paid
          REPO_NAME: cccd

  cloud-platform-executor:
    resource_class: small
    docker:
    - image: ministryofjustice/cloud-platform-tools
      environment:
        GITHUB_TEAM_NAME_SLUG: laa-get-paid
        REPO_NAME: cccd

  smoke-test-executor:
    resource_class: medium
    working_directory: /usr/src/app
    parameters:
      tag:
        description: Image tag to use for test
        default: app-latest
        type: string
    docker:
      - image: ${ECR_ENDPOINT}/laa-get-paid/cccd:<<parameters.tag>>
        aws_auth:
          oidc_role_arn: $ECR_ROLE_TO_ASSUME
        environment:
          BASH: true
          RAILS_ENV: test
          ADVOCATE_PASSWORD: just-be-present
          CASE_WORKER_PASSWORD: just-be-present
          ADMIN_PASSWORD: just-be-present
          SECRET_KEY_BASE: just-be-present
          SUPERADMIN_USERNAME: superadmin@circleci.com
          SUPERADMIN_PASSWORD: just-be-present
          DATABASE_URL: postgres://postgres:circleci@127.0.0.1:5432/cccd_smoke_test
          TZ: Europe/London
          GITHUB_TEAM_NAME_SLUG: laa-get-paid
          REPO_NAME: cccd
          LIVE1_DB_TASK: none
      - image: cimg/postgres:13.3
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: "circleci"
          POSTGRES_DB: cccd_smoke_test

  test-executor:
    working_directory: ~/repo
    docker:
      - image: cimg/ruby:3.3.5-browsers
        environment:
          RAILS_ENV: test
          DATABASE_URL: postgres://postgres:circleci@127.0.0.1:5432/cccd_test
          TZ: Europe/London
          GITHUB_TEAM_NAME_SLUG: laa-get-paid
          REPO_NAME: cccd
      - image: cimg/postgres:13.3
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: "circleci"
          POSTGRES_DB: cccd_test

# ------------------
# COMMANDS
# ------------------
commands:
  aws-cli-setup:
    steps:
     - aws-cli/setup:
        role_arn: $ECR_ROLE_TO_ASSUME
        region: $ECR_REGION

  install-chrome:
    steps:
      - browser-tools/install-chrome
      - browser-tools/install-chromedriver
  precompile-assets:
    description: >
      Precompile assets
    steps:
      - run:
          name: Precompile assets
          command: bundle exec rails assets:precompile

  run-rubocop:
    description: >
      Run rubocop
    steps:
      - run:
          name: Run rubocop
          command: bundle exec rubocop

  run-brakeman:
    description: >
      Run brakeman
    steps:
      - run:
          name: Run brakeman
          command: bundle exec brakeman

  run-standardjs:
    description: >
      Run standardJS
    steps:
      - run:
          name: Run standardJS
          command: yarn run validate:js

  run-stylelint:
    description: >
      Run stylelint
    steps:
      - run:
          name: Run stylelint
          command: yarn run validate:scss

  run-jasmine:
    description: >
      Run jasmine tests
    steps:
      - install-chrome
      - precompile-assets
      - run:
          name: Run jasmine
          command: |
            bundle exec rails jasmine:run

  run-rspec:
    description: >
      Run rspec tests and store results
    steps:
      - *attach-tmp-workspace
      - precompile-assets
      - run:
          name: Run rspec tests
          command: |
            tmp/cc-test-reporter before-build
            TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings --timings-type=filename)

            bundle exec rspec --format progress \
            --format RspecJunitFormatter \
            -o /tmp/test-results/rspec/rspec.xml \
            -- ${TESTFILES}

            tmp/cc-test-reporter format-coverage -t simplecov -o "tmp/coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
      - persist_to_workspace:
          root: tmp
          paths:
            - coverage/codeclimate.*.json
      - store_artifacts:
          path: tmp/coverage
      - store_test_results:
          path: /tmp/test-results/rspec

  run-cucumber:
    description: >
      Run cucumber tests and store results
    steps:
      - precompile-assets
      - run:
          name: Run cucumber tests
          command: |
            circleci tests glob "features/**/*.feature" \
              | circleci tests run --command="xargs bundle exec cucumber \
                                                      --format pretty \
                                                      --format junit,fileattribute=true \
                                                      --out /tmp/test-results/cucumber" \
                                   --split-by=timings \
                                   --timings-type=filename
      - store_artifacts:
          path: tmp/capybara
      - store_test_results:
          path: /tmp/test-results/cucumber

  install-gem-dependencies:
    description: >
      Install, or restore from cache, ruby gem dependencies
    steps:
      - restore_cache:
          keys:
            - v5-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v5-dependencies-
      - run:
          name: Install ruby gem dependencies
          command: |
            bundler_version=$(cat Gemfile.lock | tail -1 | tr -d " ")
            gem install bundler -v $bundler_version
            bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          key: v5-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle

  install-js-dependencies:
    description: >
      Install, or restore from cache, javacsript package dependencies
    steps:
      - restore_cache:
          keys:
            - v2-js-dependencies-{{ checksum "yarn.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v2-js-dependencies
      - run:
          name: Install javascript package dependencies
          command: |
            yarn install --frozen-lockfile
      - save_cache:
          key: v2-js-dependencies-{{ checksum "yarn.lock" }}
          paths:
            - node_modules

  build-base:
    steps:
      - install-gem-dependencies
      - install-js-dependencies

  deploy-to:
    description: >
      Deploy CCCD to the specified environment
    parameters:
      environment:
        description: destination environment
        type: string
    steps:
      - checkout
      - setup_remote_docker
      - aws-cli-setup
      - run:
          name: deploying to << parameters.environment >> namespace
          command: |
            .circleci/deploy.sh << parameters.environment >>
      - slack/status:
          success_message: ":tada: deploy of <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH> to << parameters.environment >> successful!"
          failure_message: ":red_circle: deploy of <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH> to << parameters.environment >> failed!"

  ui-smoke-test:
    description: >
      Check web UI is ok
    parameters:
      smoke-url:
        description: url of web ui to test
        type: string
    steps:
      - run:
          name: Test status OK for << parameters.smoke-url >>
          command: |
            STATUS=$(curl -s -o /dev/null -w "%{http_code}" << parameters.smoke-url >>)
            if [ $STATUS -eq 200 ]; then
              circleci-agent step halt
            fi
      - slack/notify:
          title: ":smoke_it: UI Smoke test"
          channel: laa-cccd-alerts
          message: ":no_smoking: UI smoke test << parameters.smoke-url >> failed!"
          color: "#FF0000"
      - run:
          name: UI smoke test failed
          command:
            exit 1

  hold-notification:
    description: >
      Display a slack notification
    parameters:
      message:
        description: slack message
        type: string
    steps:
      - run:
          name: Set slack notification options
          command: |
            if [[ $CIRCLE_BRANCH == "master" ]]; then
              echo 'export CUSTOM_SLACK_COLOR="#FF8C00"' >> $BASH_ENV
            else
              echo 'export CUSTOM_SLACK_COLOR="#3AA3E3"' >> $BASH_ENV
            fi
      - slack/approval:
          color: $CUSTOM_SLACK_COLOR
          message: << parameters.message >>

  smoke-test:
     steps:
      - run:
          name: Persistence - prepare result storage
          command: mkdir -p /tmp/smoke_test
      - run:
          name: Run db schema load
          command: bundle exec rails db:schema:load
      - run:
          name: Run app server
          command: bundle exec puma -p 3000
          background: true
      - run:
          name: Run smoke test
          command: |
            if ./runtests.sh; then
              echo 'true' > /tmp/smoke_test/success
            else
              echo 'false' > /tmp/smoke_test/success
            fi
      - persist_to_workspace:
          root: /tmp/smoke_test
          paths:
            - success

  smoke-test-notification:
    steps:
      - attach_workspace:
          at: /tmp/smoke_test
      - run:
          name: Setting - determine success of smoke test
          command: |
            if [[ `cat /tmp/smoke_test/success` == "true" ]]; then
              echo "Smoke test succeeded!";
              echo 'export CUSTOM_SLACK_MESSAGE=":tada: smoke test of <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH> successful!"' >> $BASH_ENV
              echo 'export CUSTOM_SLACK_COLOR="#008000"' >> $BASH_ENV
              exit 0
            else
              echo 'export CUSTOM_SLACK_MESSAGE=":no_smoking: smoke test <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH> failed!"' >> $BASH_ENV
              echo 'export CUSTOM_SLACK_COLOR="#FF0000"' >> $BASH_ENV
              echo "Smoke test failed!";
            fi
      - slack/notify:
          title: ":smoke_it: Smoke test"
          channel: laa-claim-for-payment-development
          message: $CUSTOM_SLACK_MESSAGE
          color: $CUSTOM_SLACK_COLOR

# ------------------
# JOBS
# ------------------
jobs:
  build-test-container:
    executor: test-executor
    steps:
      - checkout
      - setup_remote_docker
      - *create-tmp-dir
      - *install-codeclimate
      - *persist-codeclimate
      - build-base

  smoke-test:
    executor: smoke-test-executor
    steps:
      - smoke-test

  smoke-test-app:
    executor:
      name: smoke-test-executor
      tag: app-${CIRCLE_SHA1}
    steps:
      - smoke-test

  smoke-test-notification:
    executor: basic-executor
    steps:
      - smoke-test-notification

  rspec-tests:
    executor: test-executor
    resource_class: large
    parallelism: 6
    steps:
      - checkout
      - build-base
      - *wait-for-db
      - *load-db
      - run-rspec

  cucumber-tests:
    executor: test-executor
    resource_class: small
    parallelism: 6
    steps:
      - checkout
      - run:
          name: Halt if no cucumber tests to run
          command: |
            mkdir -p ./tmp && \
            >./tmp/tests.txt && \
            circleci tests glob "features/**/*.feature" | circleci tests run --command ">./tmp/tests.txt xargs echo" --split-by=timings
            [ -s tmp/tests.txt ] || circleci-agent step halt
      - build-base
      - *wait-for-db
      - *load-db
      - run-cucumber

  other-tests:
    executor: test-executor
    resource_class: medium
    steps:
      - checkout
      - build-base
      - run-rubocop
      - run-brakeman
      - run-jasmine
      - run-standardjs
      - run-stylelint

  upload-coverage:
    executor: test-executor
    resource_class: small
    steps:
      - *attach-tmp-workspace
      - run:
          name: Upload coverage results to Code Climate
          command: |
            tmp/cc-test-reporter sum-coverage --output - --parts 6 tmp/coverage/codeclimate.*.json | tmp/cc-test-reporter upload-coverage --input -

  build-app-container:
    executor: cloud-platform-executor
    steps:
      - checkout
      - setup_remote_docker
      - aws-cli-setup
      - *script-build-app-container

  hold-build-notification:
    executor: basic-executor
    steps:
      - hold-notification:
          message: "Do you want to build <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH>?"

  hold-deploy-notification:
    executor: basic-executor
    steps:
      - hold-notification:
          message: "Deployment of <$CIRCLE_BUILD_URL|$CIRCLE_BRANCH> pending approval"

  deploy-dev:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: dev

  deploy-dev-lgfs:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: dev-lgfs

  deploy-staging:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: staging

  deploy-api-sandbox:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: api-sandbox

  deploy-production:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: production

  auto-deploy-dev:
    executor: cloud-platform-executor
    steps:
      - deploy-to:
          environment: dev

  ui-smoke-test-dev:
    executor: basic-executor
    steps:
      - ui-smoke-test:
          smoke-url: https://dev.claim-crown-court-defence.service.justice.gov.uk

# ------------------
# WORKFLOWS
# ------------------
workflows:
  version: 2
  test-build-deploy-master:
    jobs:
      - build-test-container:
          filters:
            branches:
              only:
                - master
      - other-tests:
          requires:
            - build-test-container
      - rspec-tests:
          requires:
            - build-test-container
      - cucumber-tests:
          requires:
            - build-test-container
      - upload-coverage:
          requires:
            - rspec-tests
            - cucumber-tests
            - other-tests
      - build-app-container:
          requires:
            - upload-coverage
          context:
            - cccd-live-base
      - smoke-test:
          requires:
            - build-app-container
          context:
            - cccd-live-base
      - auto-deploy-dev:
          requires:
            - smoke-test
          context:
            - cccd-live-base
            - cccd-live-dev
      - ui-smoke-test-dev:
          requires:
            - auto-deploy-dev
      - hold-deploy-notification:
          requires:
            - ui-smoke-test-dev
      - hold-api-sandbox:
          type: approval
          requires:
            - ui-smoke-test-dev
      - deploy-api-sandbox:
          requires:
            - hold-api-sandbox
          context:
            - cccd-live-base
            - cccd-live-api-sandbox
      - hold-staging:
          type: approval
          requires:
            - ui-smoke-test-dev
      - deploy-staging:
          requires:
            - hold-staging
          context:
            - cccd-live-base
            - cccd-live-staging
      - hold-production:
          type: approval
          requires:
            - ui-smoke-test-dev
      - deploy-production:
          requires:
            - hold-production
          context:
            - cccd-live-base
            - cccd-live-production

  test-branch:
    jobs:
      - build-test-container:
          filters:
            branches:
              ignore:
                - master
      - other-tests:
          requires:
            - build-test-container
      - rspec-tests:
          requires:
            - build-test-container
      - cucumber-tests:
          requires:
            - build-test-container
      - upload-coverage:
          requires:
            - rspec-tests
            - cucumber-tests
            - other-tests

  build-deploy-branch:
    jobs:
      - hold-build-notification:
          filters:
            branches:
              ignore:
                - master
      - hold-branch-build:
          type: approval
          filters:
            branches:
              ignore:
                - master
      - build-app-container:
          requires:
            - hold-branch-build
          context:
            - cccd-live-base
      - smoke-test-app:
          requires:
            - build-app-container
          context:
            - cccd-live-base
      - hold-dev:
          type: approval
          requires:
            - build-app-container
      - hold-dev-lgfs:
          type: approval
          requires:
            - build-app-container
      - hold-staging:
          type: approval
          requires:
            - build-app-container
      - hold-api-sandbox:
          type: approval
          requires:
            - build-app-container
      - deploy-dev:
          requires:
            - hold-dev
          context:
            - cccd-live-base
            - cccd-live-dev
      - deploy-dev-lgfs:
          requires:
            - hold-dev-lgfs
          context:
            - cccd-live-base
            - cccd-live-dev-lgfs
      - deploy-staging:
          requires:
            - hold-staging
          context:
            - cccd-live-base
            - cccd-live-staging
      - deploy-api-sandbox:
          requires:
            - hold-api-sandbox
          context:
            - cccd-live-base
            - cccd-live-api-sandbox
      - ui-smoke-test-dev:
          requires:
            - deploy-dev

  scheduled-smoke-test:
    triggers:
      - schedule:
          cron: "5 8 * * *"
          filters:
            branches:
              only:
                - master
    jobs:
      - smoke-test:
          context:
            - cccd-live-base
      - smoke-test-notification:
          requires:
            - smoke-test