version: 2.1

orbs:
  win: circleci/windows@5.0.0
  node: circleci/node@5.1.0
  slack: circleci/slack@5
  prodsec: snyk/prodsec-orb@1

parameters:
  run_snapshot_update:
    type: boolean
    default: false

defaults: &defaults
  resource_class: small
  docker:
    - image: cimg/node:22.22
  working_directory: ~/snyk-docker-plugin

windows_defaults: &windows_defaults
  executor:
    name: win/default
    shell: bash.exe
  parameters:
    node_version:
      type: string
      default: ""
  working_directory: ~/snyk-docker-plugin

slack-fail-notify: &slack-fail-notify
  slack/notify:
    event: fail
    channel: team-container-pipeline-info
    branch_pattern: "main"
    template: basic_fail_1

slack-success-notify: &slack-success-notify
  slack/notify:
    event: pass
    channel: team-container-pipeline-info
    branch_pattern: "main"
    template: basic_success_1

slack-snapshot-notify: &slack-snapshot-notify
  slack/notify:
    event: always
    channel: team-container-pipeline-info
    custom: |
      {
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*snyk-docker-plugin*\n\n:camera_with_flash: Snapshot update jobs finished!"
            }
          },
          {
            "type": "divider"
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "Download the patch from each job's *Artifacts* tab:\n\n• <${LINUX_JOB_URL}|Linux snapshots> → `snapshots.patch`\n• <${WINDOWS_JOB_URL}|Windows snapshots> → `snapshots-windows.patch`"
            }
          },
          {
            "type": "markdown",
            "text": "Apply the patches locally with:\n\n```bash\ngit apply snapshots.patch snapshots-windows.patch\n```"
          }
        ]
      }

windows_big: &windows_big
  executor:
    name: win/server-2022
    shell: bash.exe
    size: large
    # we've pinned the version because without it, it uses "current" (at the time of writing, "2023.06.1"),
    # which has a broken Docker installation. See https://discuss.circleci.com/t/build-failures-when-running-docker-on-junes-windows-executor/48605
    # TODO: check if it works again with the next release and unpin the version.
    version: "2023.05.1"
  parameters:
    node_version:
      type: string
      default: ""
  working_directory: ~/snyk-docker-plugin

release_defaults: &release_defaults
  resource_class: small
  docker:
    - image: cimg/node:24.15
  working_directory: ~/snyk-docker-plugin

define: &windows_node_version "20.19.1"

commands:
  setup_npm_user:
    steps:
      - run:
          command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
  install_deps:
    description: Install dependencies
    steps:
      - checkout
      - restore_cache:
          keys:
            - v2-npm-cache-{{ checksum "package-lock.json" }}
            - v2-npm-cache-
      - setup_npm_user
      - run: npm ci
      - save_cache:
          key: v2-npm-cache-{{ checksum "package-lock.json" }}
          paths:
            - ~/.npm
      - persist_to_workspace:
          root: .
          paths:
            - node_modules/
  checkout_and_merge:
    steps:
      - checkout
      - run:
          name: Checkout main
          command: git checkout origin/main
      - run:
          name: Merge test branch
          command: |
            git config user.name "CircleCI"
            git config user.email "noop"
            git merge --no-edit "$CIRCLE_BRANCH"
      - attach_workspace:
          at: ~/snyk-docker-plugin
  install_node_npm:
    description: Install specific Node version
    parameters:
      node_version:
        type: string
        default: ""
    steps:
      - node/install:
          node-version: << parameters.node_version >>
      - run:
          name: Use currently installed node version
          command: nvm list | awk '/<< parameters.node_version >>/ {print $1}' | xargs nvm use

jobs:
  security-scans:
    <<: *defaults
    steps:
      - checkout
      - install_deps
      - attach_workspace:
          at: ~/snyk-docker-plugin
      - prodsec/security_scans:
          mode: auto
          open-source-additional-arguments: --exclude=test
          iac-scan: disabled
  install:
    <<: *defaults
    steps:
      - install_deps
  lint:
    <<: *defaults
    steps:
      - checkout
      - attach_workspace:
          at: ~/snyk-docker-plugin
      - run: npm run lint
  test_unit:
    <<: *defaults
    resource_class: medium
    steps:
      - checkout
      - attach_workspace:
          at: ~/snyk-docker-plugin
      - run:
          command: npm run test:unit
      - store_test_results:
          path: ./test/reports
  test_system:
    <<: *defaults
    steps:
      - checkout
      - setup_remote_docker
      - attach_workspace:
          at: ~/snyk-docker-plugin
      - run:
          command: npm run test:system
          no_output_timeout: 20m
      - store_test_results:
          path: ./test/reports
  test_windows_with_docker:
    <<: *windows_big
    steps:
      - checkout
      - install_node_npm:
          node_version: << parameters.node_version >>
      - setup_npm_user
      - restore_cache:
          keys:
            - v1-windows-npm-cache-{{ checksum "package-lock.json" }}
            - v1-windows-npm-cache-
      - run: npm ci
      - save_cache:
          key: v1-windows-npm-cache-{{ checksum "package-lock.json" }}
          paths:
            - ~/AppData/Roaming/npm-cache
      - run: docker version
      - run:
          command: npm run test:windows:docker
          no_output_timeout: 20m
      - store_test_results:
          path: ./test/reports
  test_windows_no_docker:
    <<: *windows_big
    steps:
      - checkout
      - install_node_npm:
          node_version: << parameters.node_version >>
      - setup_npm_user
      - restore_cache:
          keys:
            - v1-windows-npm-cache-{{ checksum "package-lock.json" }}
            - v1-windows-npm-cache-
      - run: npm ci
      - save_cache:
          key: v1-windows-npm-cache-{{ checksum "package-lock.json" }}
          paths:
            - ~/AppData/Roaming/npm-cache
        # make docker appear to be broken.
      - run: "function docker() { return 1; }"
      - run:
          command: npm run test:windows
          no_output_timeout: 20m
      - store_test_results:
          path: ./test/reports
  build:
    <<: *defaults
    steps:
      - checkout_and_merge
      - setup_npm_user
      - restore_cache:
          keys:
            - v2-npm-cache-{{ checksum "package-lock.json" }}
            - v2-npm-cache-
      - run: npm ci
      - run: npm run build
  build_cli:
    <<: *defaults
    resource_class: medium
    steps:
      - checkout_and_merge
      - setup_npm_user
      - run:
          name: Build Snyk CLI with latest changes
          command: ./.circleci/build-cli.sh
  build_and_test_latest_go_binary:
    <<: *defaults
    resource_class: medium
    steps:
      - setup_remote_docker
      - checkout_and_merge
      - run:
          name: Build a Go binary with latest Go version
          command: ./.circleci/build-go-binary-latest.sh
      - run:
          name: Run Go binaries unit test
          command: npx jest test/unit/go-binaries.spec.ts
  update_snapshots:
    <<: *defaults
    resource_class: medium
    steps:
      - install_deps
      - setup_remote_docker
      - run:
          name: Regenerate Jest snapshots
          command: npx jest -u --colors --selectProjects unit system
          no_output_timeout: 20m
      - run:
          name: Build snapshot patch
          when: always
          command: |
            git add -A -- '*.snap'
            git diff --cached -- '*.snap' > snapshots.patch
            git diff --cached --stat -- '*.snap'
      - store_artifacts:
          path: snapshots.patch
          destination: snapshots.patch
      - run:
          name: Record job URL
          when: always
          command: |
            mkdir -p snapshot-meta
            echo "$CIRCLE_BUILD_URL" > snapshot-meta/linux-job-url.txt
      - persist_to_workspace:
          when: always
          root: .
          paths:
            - snapshot-meta/linux-job-url.txt
  update_snapshots_windows:
    <<: *windows_big
    steps:
      - checkout
      - install_node_npm:
          node_version: << parameters.node_version >>
      - setup_npm_user
      - restore_cache:
          keys:
            - v1-windows-npm-cache-{{ checksum "package-lock.json" }}
            - v1-windows-npm-cache-
      - run: npm ci
      - run:
          name: Regenerate Windows Jest snapshots
          command: npx jest -u --selectProjects windows
          no_output_timeout: 20m
      - run:
          name: Build snapshot patch
          when: always
          command: |
            git add -A -- '*.snap'
            git diff --cached -- '*.snap' > snapshots-windows.patch
            git diff --cached --stat -- '*.snap'
      - store_artifacts:
          path: snapshots-windows.patch
          destination: snapshots-windows.patch
      - run:
          name: Record job URL
          when: always
          command: |
            mkdir -p snapshot-meta
            echo "$CIRCLE_BUILD_URL" > snapshot-meta/windows-job-url.txt
      - persist_to_workspace:
          when: always
          root: .
          paths:
            - snapshot-meta/windows-job-url.txt
  notify_snapshots:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/snyk-docker-plugin
      - run:
          name: Load job URLs into environment
          command: |
            echo "export LINUX_JOB_URL=$(cat snapshot-meta/linux-job-url.txt)" >> "$BASH_ENV"
            echo "export WINDOWS_JOB_URL=$(cat snapshot-meta/windows-job-url.txt)" >> "$BASH_ENV"
      - *slack-snapshot-notify
  release:
    <<: *release_defaults
    steps:
      - checkout
      - run: npm ci
      - run: npm run build
      - run:
          name: Release on GitHub
          command: |
            export NPM_ID_TOKEN=$(circleci run oidc get --claims '{"aud": "npm:registry.npmjs.org"}')
            npx semantic-release@25

workflows:
  version: 2
  test_and_release:
    when:
      and:
        # do not run on a pipeline schedule
        - not:
            equal: [scheduled_pipeline, << pipeline.trigger_source >>]
        # do not run on graphite temporary branches
        - not:
            matches:
              pattern: "^graphite-base/.*"
              value: << pipeline.git.branch >>
        # do not run when manually triggered to update snapshots
        - equal: [false, << pipeline.parameters.run_snapshot_update >>]
    jobs:
      - prodsec/secrets-scan:
          name: Scan repository for secrets
          context:
            - snyk-bot-slack
          channel: snyk-vuln-alerts-container
          filters:
            branches:
              ignore: main
      - install:
          name: Install
          context:
            - nodejs-install
      - lint:
          name: Lint
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Install
          post-steps:
            - *slack-fail-notify
      - build:
          name: Build
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Lint
          post-steps:
            - *slack-fail-notify
      - security-scans:
          name: Security Scans
          context: infrasec_container
          post-steps:
            - *slack-fail-notify
      - test_unit:
          name: Unit Test
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Build
          post-steps:
            - *slack-fail-notify
      - test_system:
          name: System Test
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Build
          post-steps:
            - *slack-fail-notify
      - test_windows_with_docker:
          name: Test Windows with Docker
          context:
            - nodejs-install
            - snyk-bot-slack
          node_version: *windows_node_version
          requires:
            - Build
          post-steps:
            - *slack-fail-notify
      - test_windows_no_docker:
          name: Test Windows no Docker
          context:
            - nodejs-install
            - snyk-bot-slack
          node_version: *windows_node_version
          requires:
            - Build
          post-steps:
            - *slack-fail-notify
      - build_cli:
          name: Build CLI with changes
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Build
          post-steps:
            - *slack-fail-notify
      - release:
          name: Release to GitHub
          context:
            - nodejs-install
            - github-release
            - snyk-bot-slack
          filters:
            branches:
              only:
                - main
          requires:
            - Security Scans
            - Build CLI with changes
            - Unit Test
            - System Test
            - Test Windows with Docker
            - Test Windows no Docker
          post-steps:
            - *slack-fail-notify
            - *slack-success-notify
  go_regression_test:
    when:
      and:
        - equal: [scheduled_pipeline, << pipeline.trigger_source >>]
        - equal: ["Build and test Go binaries", << pipeline.schedule.name >>]
    jobs:
      - install:
          name: Install
          context:
            - nodejs-install
          post-steps:
            - *slack-fail-notify
      - build_and_test_latest_go_binary:
          name: Build Go binary
          context:
            - nodejs-install
          requires:
            - Install
          post-steps:
            - *slack-fail-notify
  update_snapshots:
    when: << pipeline.parameters.run_snapshot_update >>
    jobs:
      - update_snapshots:
          name: Update Snapshots
          context:
            - nodejs-install
      - update_snapshots_windows:
          name: Update Windows Snapshots
          node_version: *windows_node_version
          context:
            - nodejs-install
      - notify_snapshots:
          name: Notify Snapshots
          context:
            - nodejs-install
            - snyk-bot-slack
          requires:
            - Update Snapshots: terminal
            - Update Windows Snapshots: terminal
