version: 2.1

########################################################################################################################
#                                                      EXECUTORS                                                       #
########################################################################################################################

executors:
  # CircleCI base Node + Headless browsers + Clojure CLI - big one
  # Maildev runs by default with all Cypress tests

  builder:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-8-clj-1.10.3.929-07-27-2021-node-browsers

  tester:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-8-clj-1.10.3.929-07-27-2021-node-browsers
      - image: maildev/maildev:1.1.0
      - image: metabase/qa-databases:postgres-sample-12
      - image: metabase/qa-databases:mongo-sample-4.0
      - image: metabase/qa-databases:mysql-sample-8

  java-8:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-8-clj-1.10.3.929-07-27-2021-node-browsers

  # Java 11 tests also test Metabase with the at-rest encryption enabled. See
  # https://metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html for an explanation of
  # what this means.
  java-11:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_ENCRYPTION_SECRET_KEY: Orw0AAyzkO/kPTLJRxiyKoBHXa/d6ZcO+p+gpZO/wSQ=

  java-16:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-16-clj-1.10.3.929-07-27-2021-node-browsers

  postgres-9-6:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: postgres
          MB_DB_PORT: 5432
          MB_DB_HOST: localhost
          MB_DB_DBNAME: circle_test
          MB_DB_USER: circle_test
          MB_POSTGRESQL_TEST_USER: circle_test
      - image: circleci/postgres:9.6-alpine
        environment:
          POSTGRES_USER: circle_test
          POSTGRES_DB: circle_test

  postgres-latest:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: postgres
          MB_DB_PORT: 5432
          MB_DB_HOST: localhost
          MB_DB_DBNAME: metabase_test
          MB_DB_USER: metabase_test
          MB_POSTGRESQL_TEST_USER: metabase_test
      - image: circleci/postgres:latest
        environment:
          POSTGRES_USER: metabase_test
          POSTGRES_DB: metabase_test
          POSTGRES_HOST_AUTH_METHOD: trust

  mysql-5-7:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: mysql
          MB_DB_HOST: localhost
          MB_DB_PORT: 3306
          MB_DB_DBNAME: circle_test
          MB_DB_USER: root
          MB_MYSQL_TEST_USER: root
      - image: circleci/mysql:5.7.23

  mysql-latest:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: mysql
          MB_DB_HOST: localhost
          MB_DB_PORT: 3306
          MB_DB_DBNAME: circle_test
          MB_DB_USER: root
          MB_MYSQL_TEST_USER: root
      - image: circleci/mysql:latest

  mariadb-10-2:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: mysql
          MB_DB_HOST: localhost
          MB_DB_PORT: 3306
          MB_DB_DBNAME: circle_test
          MB_DB_USER: root
          MB_MYSQL_TEST_USER: root
      - image: circleci/mariadb:10.2.23

  mariadb-latest:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_DB_TYPE: mysql
          MB_DB_HOST: localhost
          MB_DB_PORT: 3306
          MB_DB_DBNAME: circle_test
          MB_DB_USER: root
          MB_MYSQL_TEST_USER: root
      - image: circleci/mariadb:latest
        environment:
          # MYSQL_DATABASE: metabase_test
          # MYSQL_USER: root
          # MYSQL_ALLOW_EMPTY_PASSWORD: yes

  mongo-4-0:
     working_directory: /home/circleci/metabase/metabase/
     docker:
       - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
       - image: circleci/mongo:4.0

  mongo-latest:
     working_directory: /home/circleci/metabase/metabase/
     docker:
       - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
       - image: circleci/mongo:latest

  presto-186:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
      - image: metabase/presto-mb-ci:0.186
        environment:
          JAVA_TOOL_OPTIONS: "-Xmx2g"
    # Run instance with 8GB or RAM instead of the default 4GB for medium instances. The Presto Docker image runs
    # OOM sometimes with the default medium size.
    resource_class: large

  presto-jdbc-env:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
      - image: metabase/presto-mb-ci:latest # version 0.254
        environment:
          JAVA_TOOL_OPTIONS: "-Xmx2g"
          MB_PRESTO_JDBC_TEST_CATALOG: test_data
          MB_PRESTO_JDBC_TEST_HOST: localhost
          MB_PRESTO_JDBC_TEST_PORT: 8443
          MB_PRESTO_JDBC_TEST_SSL: true
          MB_PRESTO_JDBC_TEST_USER: metabase
          MB_PRESTO_JDBC_TEST_PASSWORD: metabase
          MB_ENABLE_PRESTO_JDBC_DRIVER: true
          MB_PRESTO_JDBC_TEST_ADDITIONAL_OPTIONS: >
            SSLTrustStorePath=/tmp/cacerts-with-presto-ssl.jks&SSLTrustStorePassword=changeit
    # (see above)
    resource_class: large

  sparksql:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
      - image: metabase/spark:3.2.1
    resource_class: large

  vertica:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
      - image: sumitchawla/vertica

  sqlserver:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
        environment:
          MB_SQLSERVER_TEST_HOST: localhost
          MB_SQLSERVER_TEST_PASSWORD: 'P@ssw0rd'
          MB_SQLSERVER_TEST_USER: SA
      - image: mcr.microsoft.com/mssql/server:2017-latest
        environment:
          ACCEPT_EULA: Y
          SA_PASSWORD: 'P@ssw0rd'
          MSSQL_MEMORY_LIMIT_MB: 1024

  druid:
    working_directory: /home/circleci/metabase/metabase/
    docker:
      - image: metabase/ci:circleci-java-11-clj-1.10.3.929-07-27-2021-node-browsers
      - image: metabase/druid:0.20.2
        environment:
          CLUSTER_SIZE: nano-quickstart
    # Run Docker images with 8GB or RAM instead of the default 4GB for medium instances. The Druid Docker image runs
    # OOM all the time with the default medium size.
    resource_class: large

########################################################################################################################
#                                             MAP FRAGMENTS AND CACHE KEYS                                             #
########################################################################################################################

# `default_parameters` isn't a key that CircleCI uses, but this form lets us reuse parameter definitions
default_parameters: &Params
  edition:
    type: enum
    enum: ["oss", "ee"]
    default: "oss"

# .BACKEND-CHECKSUMS, .FRONTEND-CHECKSUMS, and .MODULE-CHECKSUMS are created during the checkout step; see that step
# for exact details as to what they contain.
#
# To support cache busting, we create a file named .CACHE-PREFIX in the checkout step and use its checksum as the
# prefix for every cache key. If the commit message DOES NOT include [ci nocache], we create an empty file; the
# checksum will always be the same for this file. If the commit message DOES include [ci nocache], we'll write the
# unique ID of the current pipeline to .CACHE-PREFIX which will effectively bust our caches whenever it's used.

### Deps Keys ###

# Why don't we use fallback keys for backend/frontend deps? We used to, but it allowed the cache to grow
# uncontrollably since old deps would continue to accumulate. Restoring big caches is really slow in Circle. It's
# actually faster to recreate the deps cache from scratch whenever we need to which keeps the size down.
cache-key-backend-deps: &CacheKeyBackendDeps
  # TODO -- this should actually include the Java source files and the Spark SQL AOT source files as well since we now
  # compile those as part of this step. FIXME
  key: v5-{{ checksum ".CACHE-PREFIX" }}-be-deps-{{ checksum "deps.edn" }}-{{ checksum ".SCRIPTS-DEPS-CHECKSUMS" }}

cache-key-frontend-deps: &CacheKeyFrontendDeps
  key: v5-{{ checksum ".CACHE-PREFIX" }}-fe-deps-{{ checksum "yarn.lock" }}

# Key used for implementation of run-on-change -- this is the cache key that contains the .SUCCESS dummy file
# By default the key ALWAYS includes the name of the test job itself ($CIRCLE_STAGE) so you don't need to add that yourself.
cache-key-run-on-change: &CacheKeyRunOnChange
  key: v5-{{ checksum ".CACHE-PREFIX" }}-run-on-change-{{ .Environment.CIRCLE_STAGE }}-<< parameters.checksum >>

# Key for the local maven installation of metabase-core (used by build-uberjar-drivers)
cache-key-metabase-core: &CacheKeyMetabaseCore
  key: v5-{{ checksum ".CACHE-PREFIX" }}-metabase-core-{{ checksum ".BACKEND-CHECKSUMS" }}

# Key for the drivers built by build-uberjar-drivers
cache-key-drivers: &CacheKeyDrivers
  key: v5-{{ checksum ".CACHE-PREFIX" }}-drivers-<< parameters.edition >>-{{ checksum ".MODULES-CHECKSUMS" }}-{{ checksum ".BACKEND-CHECKSUMS" }}-<< parameters.edition >>

# This is also used by the uberjar-build-drivers step; this is a unique situation because the build-drivers script has
# logic to determine whether to rebuild drivers or not that is quite a bit more sophisticated that the run-on-change
# stuff in this file. e.g. if I only change the bigquery driver, the script is smart enough to not rebuild the
# redshift driver.
cache-keys-drivers-with-fallback-keys: &CacheKeyDrivers_WithFallbackKeys
  keys:
    - v5-{{ checksum ".CACHE-PREFIX" }}-drivers-<< parameters.edition >>-{{ checksum ".MODULES-CHECKSUMS" }}-{{ checksum ".BACKEND-CHECKSUMS" }}
    - v5-{{ checksum ".CACHE-PREFIX" }}-drivers-<< parameters.edition >>-{{ checksum ".MODULES-CHECKSUMS" }}
    - v5-{{ checksum ".CACHE-PREFIX" }}-drivers-<< parameters.edition >>-

# Key for frontend client built by uberjar-build-frontend step
cache-key-frontend: &CacheKeyFrontend
  key: v5-{{ checksum ".CACHE-PREFIX" }}-frontend-<< parameters.edition >>-{{ checksum ".FRONTEND-CHECKSUMS" }}

# Key for uberjar built by build-uberjar
cache-key-uberjar: &CacheKeyUberjar
  key: v5-{{ checksum ".CACHE-PREFIX" }}-uberjar-<< parameters.edition >>-{{ checksum ".BACKEND-CHECKSUMS" }}-{{ checksum ".FRONTEND-CHECKSUMS" }}

cache-key-snowplow-deps: &CacheKeySnowplowDeps
  key: v5-{{ checksum ".CACHE-PREFIX" }}-snowplow-deps


########################################################################################################################
#                                                       COMMANDS                                                       #
########################################################################################################################

commands:
  attach-workspace:
    steps:
      - attach_workspace:
          at: /home/circleci/

  # For the restore-deps-cache commands below, only restore the cache if there's an exact match. This means whatever
  # is in the cache will be exactly what's used and the cache won't keep growing uncontrollably going forward.

  restore-be-deps-cache:
    steps:
      - restore_cache:
          name: Restore cached backend dependencies
          <<: *CacheKeyBackendDeps

  restore-fe-deps-cache:
    steps:
      - restore_cache:
          name: Restore cached frontend dependencies
          <<: *CacheKeyFrontendDeps

  restore-snowplow-deps-cache:
    steps:
      - restore_cache:
          name: Restore Snowplow Micro JAR
          <<: *CacheKeySnowplowDeps

  # run-on-change lets you only run steps if changes have happened to relevant files since the last time it was run
  # successfully. Uses a cache key to record successful runs -- cache key should be unique for job and relevant source
  # files -- use a checksum! It works like this:
  #
  # 1. Calculate a cache key using a checksum of relevant files for the step in question, e.g. a backend linter step
  #    might use a checksum of all .clj files.
  #
  # 2. When the step completes successfully, create a dummy file .SUCCESS and cache it with that cache key.
  #
  # 3. On subsequent runs:
  #
  #    a. Attempt to restore the cache using an exact match for this cache key
  #
  #    b. If we have a cache entry for that key, .SUCCESS will get restored
  #
  #    c. If this command has the skip-job-if-commit-message-includes-ci-quick option enabled, and commit message includes
  #       [ci quick], create a dummy file .SUCCESS if not already present. Ignored for master/release branches.
  #
  #    d. If commit message includes [ci noskip], delete .SUCCESS so the job will be forced to run.
  #
  #    e. If .SUCCESS is present, we can skip the rest of the job, including potentially slow steps like restoring
  #       dependency caches or the like. This logs a link to the last successful (not skipped) run of the job
  #
  #       Important! If this step is skipped because no changes have happened, the entire JOB will halt with a success
  #       status -- no steps that happen AFTER run-on-change will be ran. Keep this in mind!
  #
  #   f. If .SUCCESS is not present, proceed as normal, and create and cache .SUCCESS if the job succeeds
  run-on-change:
    parameters:
      checksum:
        type: string
        default: ""
      steps:
        type: steps
      # Whether to skip the rest of the job if commit message includes [ci quick]
      skip-job-if-commit-message-includes-ci-quick:
        type: boolean
        default: false
    steps:
      - restore_cache:
          name: Restore dummy file .SUCCESS if it exists for cache key << parameters.checksum >>
          <<: *CacheKeyRunOnChange
      - when:
          condition: << parameters.skip-job-if-commit-message-includes-ci-quick >>
          steps:
            - run:
                name: "Skip tests (create dummy file .SUCCESS) if commit message contains [ci quick] and branch isn't a master/release branch"
                command: |
                  if [[ "$CIRCLE_BRANCH" =~ ^master|release-.+$ ]]; then
                      echo "branch '$CIRCLE_BRANCH' is a master or release branch: ignoring [ci quick]"
                  elif [[ `cat .COMMIT` == *"[ci quick]"* ]]; then
                      echo 'Commit message includes [ci quick]. Creating dummy file .SUCCESS'
                      touch .SUCCESS
                  else
                      echo 'Commit message does not include [ci quick]'
                  fi
      - run:
          name: "Force test run (delete dummy file .SUCCESS) if commit message includes [ci noskip]"
          command: |
            if [[ `cat .COMMIT` == *"[ci noskip]"* ]]; then
                echo 'Commit message includes [ci noskip] -- forcing test run (delete .SUCCESS)'
                rm -f .SUCCESS
            else
                echo 'Commit message does not include [ci noskip]'
            fi
      - run:
          name: Skip rest of job if .SUCCESS exists
          command: |
            if [ -f .SUCCESS ]; then
                echo '.SUCCESS is present: skipping rest of job.'
                echo "Link to last successful run (if available): $(cat .SUCCESS)"
                circleci-agent step halt
            fi
      - steps: << parameters.steps >>
      - run:
          name: Create dummy file .SUCCESS
          command: |
            echo "$CIRCLE_BUILD_URL" > .SUCCESS
      - save_cache:
          name: Persist dummy file .SUCCESS to cache with key << parameters.checksum >>
          <<: *CacheKeyRunOnChange
          paths:
            - /home/circleci/metabase/metabase/.SUCCESS
      - run:
          name: Delete dummy file .SUCCESS so subsequent steps don't see it
          command: rm /home/circleci/metabase/metabase/.SUCCESS

  # Creates a file that contains checksums for all the files found using the find command with supplied arguments.
  # You can use a checksum of the checksum file for cache keys including run-on-change cache keys.
  create-checksum-file:
    parameters:
      filename:
        type: string
      find-args:
        type: string
    steps:
      - run:
          name: Create << parameters.filename >> checksum file
          command: |
            for file in `find << parameters.find-args >> | sort`; do
                echo `md5sum "$file"` >> "<< parameters.filename >>"
            done
            if [ ! -f "<< parameters.filename >>" ]; then
                echo 'Error: no matching files. Did you remember to attach the workspace?'
                exit 1
            fi
            echo "Created checksums for $(cat << parameters.filename >> | wc -l) files"

  run-clojure-command:
    parameters:
      before-steps:
        type: steps
        default: []
      clojure-args:
        type: string
      after-steps:
        type: steps
        default: []
      <<: *Params
    steps:
      - restore-be-deps-cache
      - steps: << parameters.before-steps >>
      - run:
          name: clojure << parameters.clojure-args >>:<< parameters.edition >>:<< parameters.edition >>-dev
          command: |
            clojure << parameters.clojure-args >>:<< parameters.edition >>:<< parameters.edition >>-dev
          no_output_timeout: 15m
      - steps: << parameters.after-steps >>
      - store_test_results:
          path: /home/circleci/metabase/metabase/target/junit

  run-yarn-command:
    parameters:
      command-name:
        type: string
      command:
        type: string
      before-steps:
        type: steps
        default: []
      after-steps:
        type: steps
        default: []
      skip-when-no-change:
        type: boolean
        default: false
    steps:
      - when:
          condition: << parameters.skip-when-no-change >>
          steps:
            - run-on-change:
                checksum: '{{ checksum ".FRONTEND-CHECKSUMS" }}'
                steps:
                  - restore-fe-deps-cache
                  - steps: << parameters.before-steps >>
                  - run:
                      name: << parameters.command-name >>
                      command: yarn << parameters.command >>
                      no_output_timeout: 15m
                  - steps: << parameters.after-steps >>
      - unless:
          condition: << parameters.skip-when-no-change >>
          steps:
            - restore-fe-deps-cache
            - steps: << parameters.before-steps >>
            - run:
                name: << parameters.command-name >>
                command: yarn << parameters.command >>
                no_output_timeout: 15m
            - steps: << parameters.after-steps >>

  wait-for-port:
    parameters:
      port:
        type: integer
    steps:
      - run:
          name: Wait for port << parameters.port >> to be ready
          command: |
            while ! nc -z localhost << parameters.port >>; do sleep 0.1; done
          no_output_timeout: 15m

  wait-for-databases:
    steps:
      - wait-for-port:
          port: 3306 # mysql
      - wait-for-port:
          port: 5432 # postgres
      - wait-for-port:
          port: 27017 # mongo

  fetch-jdbc-driver:
    parameters:
      source:
        type: string
      dest:
        type: string
    steps:
      - run:
          name: Make plugins dir
          command: mkdir /home/circleci/metabase/metabase/plugins
      - run:
          name: Download JDBC driver JAR << parameters.dest >>
          command: |
            wget --output-document=plugins/<< parameters.dest >> ${<< parameters.source >>}
          no_output_timeout: 15m

  run-command:
    parameters:
      command:
        type: string
    steps:
      - run:
          name: Run command
          command: << parameters.command >>

  run-snowplow-micro:
    steps:
      - restore-snowplow-deps-cache
      - run:
          name: Run Snowplow Micro
          command: |
            java -cp snowplow-micro.jar:snowplow com.snowplowanalytics.snowplow.micro.Main --collector-config snowplow/micro.conf --iglu snowplow/iglu.json
          background: true
      - wait-for-port:
          port: 9090

jobs:

########################################################################################################################
#                                                    CHECKOUT ETC.                                                     #
########################################################################################################################

  checkout:
    executor: builder
    steps:
      - checkout
      - attach-workspace
      # .BACKEND-CHECKSUMS is every Clojure source file as well as dependency files like deps.edn and plugin manifests
      - create-checksum-file:
          filename: .BACKEND-CHECKSUMS
          find-args: ". -type f -name '*.clj' -or -name '*.cljc' -or -name '*.java' -or -name '*.edn' -or -name '*.yaml' -or -name sample-dataset.db.mv.db"
      # .SCRIPTS-DEPS-CHECKSUMS is all the deps.edn files inside ./bin
      - create-checksum-file:
          filename: .SCRIPTS-DEPS-CHECKSUMS
          find-args: "bin -type f -name 'deps.edn'"
      # .FRONTEND-CHECKSUMS is every JavaScript source file as well as dependency files like yarn.lock (sans all frontend test files)
      - create-checksum-file:
          filename: .FRONTEND-CHECKSUMS
          find-args: ". -type f '(' -name '*.js' -or -name '*.jsx' -or -name '*.ts' -or -name '*.tsx' -or -name '*.cljc' -or -name '*.cljs' -or -name '*.json' -or -name yarn.lock -or -name sample-dataset.db.mv.db ')' ! -path '*/frontend/test/*'"
      # .E2E-TESTS-CHECKSUMS is every `*.cy.spes.js` file as well as e2e support JavaScript files
      - create-checksum-file:
          filename: .E2E-TESTS-CHECKSUMS
          find-args: "./frontend/test/ -name '*.cy.*' -or -type f -path '*/__support__/e2e/*'"
      # .MODULES-CHECKSUMS is every Clojure source file in the modules/ directory as well as plugin manifests
      - create-checksum-file:
          filename: .MODULES-CHECKSUMS
          find-args: "./modules -type f -name '*.clj' -or -name metabase-plugin.yaml"
      - run:
          name: Save last git commit message to .COMMIT
          command: git log -1 > .COMMIT
      - run:
          name: Determine what to do to .git directory
          command: |
            if [[ $CIRCLE_BRANCH == release* ]]; then
              echo 'This is a release branch; preserving .git directory to determine version'
            else
              echo 'This is not a release branch; removing .git directory (not needed for tests)'
              rm -rf /home/circleci/metabase/metabase/.git
            fi

      - run:
          name: Remove ./OSX directory (not needed for tests)
          command: rm -rf /home/circleci/metabase/metabase/OSX
      # .CACHE-PREFIX is described above in the Cache Keys section of this file
      - run:
          name: 'Create cache key prefix .CACHE-PREFIX to bust caches if commit message includes [ci nocache]'
          command: |
            if [[ `cat .COMMIT` == *"[ci nocache]"* ]]; then
                echo 'Commit message includes [ci nocache]; using cache-busting prefix'
                echo '<< pipeline.id >>' > .CACHE-PREFIX
            else
               echo '' > .CACHE-PREFIX
            fi
      - run:
          name: 'Check for branch name to bust caches if it is a release branch'
          command: |
            if [[ $CIRCLE_BRANCH == release* || $CIRCLE_BRANCH == master  ]]; then
              echo 'This is a release or master branch; using cache-busting prefix'
                echo '<< pipeline.id >>' > .CACHE-PREFIX
            fi
      - run-yarn-command:
          command-name: Create static visualization js bundle
          command: build-static-viz
      - persist_to_workspace:
          root: /home/circleci/
          paths:
            - metabase/metabase

########################################################################################################################
#                                                       BACKEND                                                        #
########################################################################################################################

  be-deps:
    executor: builder
    parameters:
      <<: *Params
    steps:
      - attach-workspace
      # This step is pretty slow, even with the cache, so only run it if deps.edn has changed
      - run-on-change:
          checksum: 'v5-{{ checksum "deps.edn" }}-{{ checksum ".SCRIPTS-DEPS-CHECKSUMS" }}'
          steps:
            - restore-be-deps-cache
            - run:
                name: Compile Java source file(s)
                command: clojure -X:deps prep
            - run:
                name: Fetch dependencies
                command: clojure -P -X:dev:ci:ee:ee-dev:drivers:drivers-dev
            - run:
                name: Fetch dependencies (./bin/build/build-mb)
                command: cd /home/circleci/metabase/metabase/bin/build-mb && clojure -P -M:test
            # Not sure why this is needed since you would think build-mb would fetch this stuff as well. It doesn't
            # seem to fetch everything tho. :shrug:
            - run:
                name: Fetch dependencies (./bin/build/build-drivers)
                command: cd /home/circleci/metabase/metabase/bin/build-drivers && clojure -P -M:test
            - save_cache:
                name: Cache backend dependencies
                <<: *CacheKeyBackendDeps
                paths:
                  - /home/circleci/.m2
                  - /home/circleci/.gitlibs
                  - /home/circleci/metabase/metabase/java/target/classes
                  - /home/circleci/metabase/metabase/modules/drivers/sparksql/target/classes

  clojure:
    parameters:
      e:
        type: executor
        default: builder
      before-steps:
        type: steps
        default: []
      clojure-args:
        type: string
      after-steps:
        type: steps
        default: []
      skip-when-no-change:
        type: boolean
        default: false
      java-version:
        type: string
        default: ""
      version:
        type: string
        default: ""
      <<: *Params
    executor: << parameters.e >>
    steps:
      - attach-workspace
      - when:
          condition: << parameters.skip-when-no-change >>
          steps:
            - run-on-change:
                checksum: '{{ checksum ".BACKEND-CHECKSUMS" }}'
                steps:
                  - run-clojure-command:
                      before-steps: << parameters.before-steps >>
                      clojure-args: << parameters.clojure-args >>
                      after-steps: << parameters.after-steps >>
                      edition: << parameters.edition >>
      - unless:
          condition: << parameters.skip-when-no-change >>
          steps:
            - run-clojure-command:
                before-steps: << parameters.before-steps >>
                clojure-args: << parameters.clojure-args >>
                after-steps: << parameters.after-steps >>
                edition: << parameters.edition >>

  be-linter-reflection-warnings:
    executor: builder
    steps:
      - attach-workspace
      - run-on-change:
          checksum: '{{ checksum ".BACKEND-CHECKSUMS" }}-{{ checksum "bin/reflection-linter" }}'
          steps:
            - restore-be-deps-cache
            - run:
                name: Run reflection warnings checker
                command: ./bin/reflection-linter
                no_output_timeout: 15m

  test-driver:
    parameters:
      e:
        type: executor
        default: builder
      driver:
        type: string
      timeout:
        type: string
        default: 20m
      before-steps:
        type: steps
        default: []
      after-steps:
        type: steps
        default: []
      description:
        type: string
        default: ""
      extra-env:
        type: string
        default: ""
      test-args:
        type: string
        default: ""
      version:
        type: string
        default: ""
    executor: << parameters.e >>
    steps:
      - attach-workspace
      - run-on-change:
          checksum: '{{ checksum ".BACKEND-CHECKSUMS" }}'
          skip-job-if-commit-message-includes-ci-quick: true
          steps:
            - restore-be-deps-cache
            - steps: << parameters.before-steps >>
            - run:
                name: Test << parameters.driver >> driver << parameters.description >>
                environment:
                  DRIVERS: << parameters.driver >>
                command: >
                  << parameters.extra-env >> clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test
                  << parameters.test-args >>
                no_output_timeout: << parameters.timeout >>
            - store_test_results:
                path: /home/circleci/metabase/metabase/target/junit
            - steps: << parameters.after-steps >>


########################################################################################################################
#                                                       FRONTEND                                                       #
########################################################################################################################

  fe-deps:
    executor: builder
    steps:
      - attach-workspace
      # This step is *really* slow, so we can skip it if yarn.lock hasn't changed since last time we ran it
      - run-on-change:
          checksum: '{{ checksum "yarn.lock" }}'
          steps:
            - restore-fe-deps-cache
            - run:
                name: Run yarn to install deps
                command: rm -rf node_modules/ && yarn --frozen-lockfile;
                no_output_timeout: 15m
            - save_cache:
                name: Cache frontend dependencies
                <<: *CacheKeyFrontendDeps
                paths:
                  - /home/circleci/.yarn
                  - /home/circleci/.yarn-cache
                  - /home/circleci/metabase/metabase/node_modules
                  - /home/circleci/.cache/Cypress

  # Unlike the other build-uberjar steps, this step should be run once overall and the results can be shared between
  # OSS and EE uberjars.
  build-uberjar-drivers:
    executor: builder
    parameters:
      <<: *Params
    steps:
      - attach-workspace
      - run-on-change:
          # .MODULES-CHECKSUMS is a subset of .BACKEND-CHECKSUMS.
          #
          # We have both versions so we can try to load cached drivers that match MODULES-CHECKSUMS but not
          # BACKEND-CHECKSUMS as a whole.
          #
          # The build-drivers script is smart enough to only rebuild drivers if needed -- there's a chance we won't
          # need to rebuild them.
          checksum: '{{ checksum ".MODULES-CHECKSUMS" }}-{{ checksum ".BACKEND-CHECKSUMS" }}'
          steps:
            - restore-be-deps-cache            #
            - restore_cache:
                <<: *CacheKeyMetabaseCore
            - restore_cache:
                name: Restore cached drivers uberjars from previous runs
                <<: *CacheKeyDrivers_WithFallbackKeys
            - run:
                name: Build << parameters.edition >> drivers if needed
                command: ./bin/build-drivers.sh << parameters.edition >>
                no_output_timeout: 15m
            - save_cache:
                name: Cache local Maven installation of metabase-core
                <<: *CacheKeyMetabaseCore
                paths:
                  - /home/circleci/.m2/repository/metabase-core
            - save_cache:
                name: Cache the built drivers
                <<: *CacheKeyDrivers
                paths:
                  - /home/circleci/metabase/metabase/resources/modules

  # Build the frontend client. parameters.edition determines whether we build the OSS or EE version.
  build-uberjar-frontend:
    parameters:
      <<: *Params
    executor: builder
    resource_class: large
    steps:
      - attach-workspace
      - run-on-change:
          checksum: '{{ checksum ".FRONTEND-CHECKSUMS" }}'
          steps:
            - restore-fe-deps-cache
            - restore-be-deps-cache
            - run:
                name: Build frontend
                environment:
                  MB_EDITION: << parameters.edition >>
                command: ./bin/build version frontend
                no_output_timeout: 15m
            - save_cache:
                name: Cache the built frontend
                <<: *CacheKeyFrontend
                paths:
                  - /home/circleci/metabase/metabase/resources/frontend_client

  # Build the uberjar. parmeters.edition determines whether we build the OSS or EE version.
  build-uberjar:
    parameters:
      <<: *Params
    executor: builder
    resource_class: large
    steps:
      - attach-workspace
      - run-on-change:
          checksum: '{{ checksum ".BACKEND-CHECKSUMS" }}-{{ checksum ".FRONTEND-CHECKSUMS" }}'
          steps:
            - restore_cache:
                name: Restore cached uberjar from previous runs
                <<: *CacheKeyUberjar
            - run:
                name: Skip rest of job if uberjar already exists
                command: |
                   if [ -f './target/uberjar/metabase.jar' ]; then
                       circleci-agent step halt
                   fi
            - restore-be-deps-cache
            - restore_cache:
                name: Restore cached drivers built by previous step
                <<: *CacheKeyDrivers
            - restore_cache:
                name: Restore cached FE built by previous step
                <<: *CacheKeyFrontend
            - run:
                name: Build uberjar
                environment:
                  # INTERACTIVE=false will tell the clojure build scripts not to do interactive retries etc.
                  INTERACTIVE: "false"
                  MB_EDITION: << parameters.edition >>
                command: |
                  if [[ $CIRCLE_BRANCH == release* || $CIRCLE_BRANCH == master  ]]; then
                    echo 'This is a release or master branch; building a complete Uberjar'
                    ./bin/build
                  else
                    ./bin/build version uberjar
                  fi
                no_output_timeout: 15m
            - store_artifacts:
                path: /home/circleci/metabase/metabase/target/uberjar/metabase.jar
            - store_artifacts:
                path: /home/circleci/metabase/metabase/resources/version.properties
            - save_cache:
                name: Cache the built uberjar & version.properties
                <<: *CacheKeyUberjar
                paths:
                  - /home/circleci/metabase/metabase/target/uberjar/metabase.jar
                  - /home/circleci/metabase/metabase/resources/version.properties

  fe-tests-cypress:
    parameters:
      e:
        type: executor
        default: tester
      cypress-group:
        type: string
      source-folder:
        type: string
        default: ""
      folder:
        type: string
        default: ""
      test-files:
        type: string
        default: ""
      qa-db:
        type: boolean
        default: false
      snowplow:
        type: boolean
        default: false
      before-steps:
        type: steps
        default: []
      <<: *Params
    executor: << parameters.e >>
    environment:
      MB_EDITION: << parameters.edition >>
      CYPRESS_GROUP:  << parameters.cypress-group >>
      QA_DB_ENABLED: << parameters.qa-db >>
      DISPLAY: ""
      MB_SNOWPLOW_AVAILABLE: << parameters.snowplow >>
    steps:
      - attach-workspace
      - run:
          command: |
            mkdir -p /home/circleci/metabase/metabase/target/uberjar/
            mkdir -p /home/circleci/metabase/metabase/resources/
      - run-on-change:
          checksum: '{{ checksum ".BACKEND-CHECKSUMS" }}-{{ checksum ".FRONTEND-CHECKSUMS" }}-{{ checksum ".E2E-TESTS-CHECKSUMS" }}'
          steps:
            - run-yarn-command:
                command-name: Run Cypress tests
                before-steps:
                  - restore_cache:
                      name: Restore cached uberjar built in previous step
                      <<: *CacheKeyUberjar
                  - steps: << parameters.before-steps >>
                  # Make both `test-files`,  `source-folder` and `currents-record` parameters optional. Translates to: if `parameter` => run associated flag (`--spec`, `--folder` and `--key $CURRENTS_KEY --record` respectively)
                command: |
                  run test-cypress-run \
                  <<# parameters.test-files >> --spec << parameters.test-files >> <</ parameters.test-files >> \
                  <<# parameters.source-folder >> --folder << parameters.source-folder >> <</ parameters.source-folder >>
                after-steps:
                  - store_artifacts:
                      path: /home/circleci/metabase/metabase/cypress
                  - store_test_results:
                      path: cypress/results

########################################################################################################################
#                                                       SNOWPLOW                                                       #
########################################################################################################################

  snowplow-deps:
    executor: builder
    steps:
      - attach-workspace
      - restore-snowplow-deps-cache
      - run:
          name: Check if Snowplow Micro JAR exists
          command: |
            if [ -f snowplow-micro.jar ]; then
                echo 'snowplow-micro.jar is present, skipping the rest of the job.'
                circleci-agent step halt
            fi
      - run:
          name: Download Snowplow Micro JAR
          command: |
            wget --output-document=snowplow-micro.jar $SNOWPLOW_MICRO_JAR
          no_output_timeout: 15m
      - save_cache:
          name: Cache Snowplow Micro JAR
          <<: *CacheKeySnowplowDeps
          paths:
            - /home/circleci/metabase/metabase/snowplow-micro.jar

########################################################################################################################
#                                                      WORKFLOWS                                                       #
########################################################################################################################

# `default_matrix` isn't a key that CircleCI uses, but this form lets us reuse the matrix: block
default_matrix: &Matrix
  matrix:
    parameters:
      edition: ["ee", "oss"]

workflows:
  version: 2
  build:
    jobs:
      - checkout

      - be-deps:
          requires:
            - checkout

      - clojure:
          matrix:
            parameters:
              edition: ["ee", "oss"]
              java-version: ["java-8", "java-11", "java-16"]
          name: be-tests-<< matrix.java-version >>-<< matrix.edition >>
          requires:
            - be-deps
          e: << matrix.java-version >>
          clojure-args: -X:dev:ci:test
          skip-when-no-change: true

      - clojure:
          name: be-linter-cloverage
          requires:
            - be-deps
          # TODO FIXME
          clojure-args: -X:dev:ee:ee-dev:test:cloverage
          after-steps:
            - run:
                name: Upload code coverage to codecov.io
                command: bash <(curl -s https://codecov.io/bash) -F back-end

          skip-when-no-change: true

      - test-driver:
          matrix:
            parameters:
              driver: ["bigquery-cloud-sdk", "googleanalytics", "sqlite"]
          name: be-tests-<< matrix.driver >>-ee
          requires:
            - be-tests-java-8-ee
          driver: << matrix.driver >>

      - test-driver:
          matrix:
            parameters:
              driver: ["sqlserver", "druid"]
          name: be-tests-<< matrix.driver >>-ee
          requires:
            - be-tests-java-8-ee
          e: << matrix.driver >>
          driver: << matrix.driver >>

      - test-driver:
          name: be-google-related-drivers-classpath-test
          requires:
            - be-tests-java-8-ee
          driver: googleanalytics,bigquery-cloud-sdk
          test-args: >-
            :only "[metabase.query-processor-test.expressions-test metabase.driver.google-test
                    metabase.driver.googleanalytics-test]"

      - test-driver:
          matrix:
            parameters:
              version: ["mongo-4-0", "mongo-latest"]
          name: be-tests-<< matrix.version >>-ee
          description: "(<< matrix.version >>)"
          requires:
            - be-tests-java-8-ee
          e: << matrix.version >>
          driver: mongo

      - test-driver:
          matrix:
            parameters:
              version: ["mysql-5-7", "mariadb-10-2", "mariadb-latest"]
          name: be-tests-<< matrix.version >>-ee
          description: "(<< matrix.version >>)"
          requires:
            - be-tests-java-8-ee
          e:
            name: << matrix.version >>
          driver: mysql

      - test-driver:
          name: be-tests-mysql-latest-ee
          description: "(MySQL latest)"
          requires:
            - be-tests-java-8-ee
          e:
            name: mysql-latest
          driver: mysql
          # set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
          # they are deliberately given a different name to prevent them from affecting the regular test run against
          # the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
          # that overrides the MB_MYSQL_TEST_* values with them
          # the MYSQL_RDS_SSL_INSTANCE vars are secret and/or changeable, so they are defined in the CircleCI settings
          extra-env: >-
            MB_MYSQL_SSL_TEST_HOST=$MYSQL_RDS_SSL_INSTANCE_HOST
            MB_MYSQL_SSL_TEST_SSL=true
            MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS='verifyServerCertificate=true'
            MB_MYSQL_SSL_TEST_SSL_CERT="$(cat /home/circleci/metabase/metabase/resources/certificates/rds-combined-ca-bundle.pem)"
            MB_MYSQL_SSL_TEST_USER=metabase
            MB_MYSQL_SSL_TEST_PASSWORD=$MYSQL_RDS_SSL_INSTANCE_PASSWORD

      - test-driver:
          name: be-tests-oracle-ee
          requires:
            - be-tests-java-8-ee
          before-steps:
            - fetch-jdbc-driver:
                source: ORACLE_JDBC_JAR
                dest: ojdbc8.jar
            - run:
                name: Ensure truststore file
                command: ls /home/circleci/metabase/metabase/resources/certificates/rds_root_ca_truststore.jks
          driver: oracle
          extra-env: >-
            MB_ORACLE_SSL_TEST_SSL=true
            MB_ORACLE_SSL_TEST_PORT=2484
            MB_ORACLE_SSL_TEST_SSL_USE_TRUSTSTORE=true
            MB_ORACLE_SSL_TEST_SSL_TRUSTSTORE_PATH=/home/circleci/metabase/metabase/resources/certificates/rds_root_ca_truststore.jks
            MB_ORACLE_SSL_TEST_SSL_TRUSTSTORE_OPTIONS=local
            MB_ORACLE_SSL_TEST_SSL_TRUSTSTORE_PASSWORD_VALUE=metabase

      - test-driver:
          name: be-tests-postgres-ee
          description: "(9.6)"
          requires:
            - be-tests-java-8-ee
          e: postgres-9-6
          driver: postgres

      - test-driver:
          name: be-tests-postgres-latest-ee
          description: "(Latest)"
          requires:
            - be-tests-java-8-ee
          e: postgres-latest
          driver: postgres
          extra-env: >-
            MB_POSTGRES_SSL_TEST_SSL=true
            MB_POSTGRES_SSL_TEST_SSL_MODE=verify-full
            MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH=/home/circleci/metabase/metabase/test-resources/certificates/us-east-2-bundle.pem

      - test-driver:
          name: be-tests-presto-ee
          requires:
            - be-tests-java-8-ee
          e: presto-186
          before-steps:
            - wait-for-port:
                port: 8080
          driver: presto

      - test-driver:
          name: be-tests-presto-jdbc-ee
          requires:
            - be-tests-java-8-ee
          e: presto-jdbc-env # specific env for running Presto JDBC tests (newer Presto version, SSL, etc.)
          before-steps:
            - wait-for-port:
                port: 8443
            - run:
                name: Create temp cacerts file based on bundled JDK one
                command: cp $JAVA_HOME/lib/security/cacerts /tmp/cacerts-with-presto-ssl.jks
            - run:
                name: Capture Presto server self signed CA
                command: |
                  while [[ ! -s /tmp/presto-ssl-ca.pem ]];
                    do echo "Waiting to capture SSL CA" \
                      && openssl s_client -connect localhost:8443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/presto-ssl-ca.pem \
                      && sleep 1; done
            - run:
                name: Convert Presto CA from PEM to DER
                command: openssl x509 -outform der -in /tmp/presto-ssl-ca.pem -out /tmp/presto-ssl-ca.der
            - run:
                name: Add write permission on cacerts file
                command: chmod u+w /tmp/cacerts-with-presto-ssl.jks
            - run:
                name: Import Presto CA into temp cacerts file
                command: |
                  keytool -noprompt -import -alias presto -keystore /tmp/cacerts-with-presto-ssl.jks \
                   -storepass changeit -file /tmp/presto-ssl-ca.der -trustcacerts
          after-steps:
            - run:
                name: Capture max memory usage
                command: cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
                when: always
          driver: presto-jdbc

      - test-driver:
          name: be-tests-redshift-ee
          requires:
            - be-tests-java-8-ee
          driver: redshift
          timeout: 15m

      - test-driver:
          name: be-tests-snowflake-ee
          requires:
            - be-tests-java-8-ee
          driver: snowflake
          timeout: 115m

      - test-driver:
          name: be-tests-sparksql-ee
          requires:
            - be-tests-java-8-ee
          e: sparksql
          before-steps:
            - wait-for-port:
                port: 10000
          driver: sparksql

      - test-driver:
          name: be-tests-vertica-ee
          requires:
            - be-tests-java-8-ee
          e: vertica
          before-steps:
            - fetch-jdbc-driver:
                source: VERTICA_JDBC_JAR
                dest: vertica-jdbc-7.1.2-0.jar
          driver: vertica

      - build-uberjar-drivers:
          name: build-uberjar-drivers-<< matrix.edition >>
          requires:
            - be-deps
          <<: *Matrix

      - build-uberjar-frontend:
          name: build-uberjar-frontend-<< matrix.edition >>
          requires:
            - fe-deps
          <<: *Matrix

      - build-uberjar:
          name: build-uberjar-<< matrix.edition >>
          requires:
            - build-uberjar-drivers-<< matrix.edition >>
            - build-uberjar-frontend-<< matrix.edition >>
          <<: *Matrix

      - fe-deps:
          requires:
            - checkout

      - fe-tests-cypress:
          matrix:
            parameters:
              edition: ["ee", "oss"]
              folder:
                [
                  "admin",
                  "collections",
                  "dashboard",
                  "embedding",
                  "filters",
                  "models",
                  "native",
                  "native-filters",
                  "onboarding",
                  "organization",
                  "permissions",
                  "question",
                  "sharing",
                  "smoketest",
                  "visualizations",
                ]
          name: e2e-tests-<< matrix.folder >>-<< matrix.edition >>
          requires:
            - build-uberjar-<< matrix.edition >>
            - snowplow-deps
          cypress-group: "<< matrix.folder >>-<< matrix.edition >>"
          source-folder: << matrix.folder >>
          qa-db: true
          snowplow: true
          before-steps:
            - run-snowplow-micro
            - wait-for-databases

      - fe-tests-cypress:
          name: percy-visual-tests-<< matrix.edition >>
          requires:
            - build-uberjar-<< matrix.edition >>
          cypress-group: "percy-visual-<< matrix.edition >>"
          test-files: "./frontend/test/metabase-visual/**/*.cy.spec.js"
          <<: *Matrix

      - snowplow-deps:
          requires:
            - checkout
