From 27446a5ddc7f488b8b73da97586a9458a405e025 Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Tue, 26 Nov 2019 12:05:14 -0600 Subject: [PATCH 1/4] chore: fix/update CI config - newer style - use CI scripts - remove snyk checks Signed-off-by: Eric Dobbertin --- .circleci/bin/docker-tags | 50 -------- .circleci/config.yml | 252 ++++++++++++-------------------------- 2 files changed, 76 insertions(+), 226 deletions(-) delete mode 100755 .circleci/bin/docker-tags diff --git a/.circleci/bin/docker-tags b/.circleci/bin/docker-tags deleted file mode 100755 index 453e77d9d..000000000 --- a/.circleci/bin/docker-tags +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# outputs each tag we're attaching to this docker image -set -e - -SHA=$1 -BRANCH=$2 -SHA1=$(git rev-parse --verify "${SHA}") - -# Echo to stderr -echoerr() { echo "$@" 1>&2; } - -# Ensure that git SHA was provided -if [[ -x "${SHA1}" ]]; then - echoerr "Error, no git SHA provided" - exit 1; -fi - -# tag with the branch -if [[ -n "${BRANCH}" ]]; then - echo "${BRANCH}" -fi - -# Tag with each git tag -git show-ref --tags -d | grep "^${SHA1}" | sed -e 's,.* refs/tags/,,' -e 's/\^{}//' 2> /dev/null \ - | xargs -I % \ - echo "%" - -# Tag with latest if certain conditions are met -if [[ "$BRANCH" == "master" ]]; then - # Check to see if we have a valid `vX.X.X` tag and assign to CURRENT_TAG - CURRENT_TAG=$( \ - git show-ref --tags -d \ - | grep "^${SHA1}" \ - | sed -e 's,.* refs/tags/,,' -e 's/\^{}//' \ - | grep "^v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$" \ - | sort \ - ) - - # Find the highest tagged version number - HIGHEST_TAG=$(git --no-pager tag | grep "^v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$" | sort -r | head -n 1) - - # We tag :latest only if - # 1. We have a current tag - # 2. The current tag is equal to the highest tag, OR the highest tag does not exist - if [[ -n "${CURRENT_TAG}" ]]; then - if [[ "${CURRENT_TAG}" == "${HIGHEST_TAG}" ]] || [[ -z "${HIGHEST_TAG}" ]]; then - echo "latest" - fi - fi -fi diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d1d0d3ee..61ffd6d92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,3 @@ -# This CircleCI configuration uses workflows to fan-out to multiple jobs. The -# workflow is Dockerized. The first job builds the Docker image which is used -# in all future steps. -# -# Assumes that the Docker image is published to Docker Hub. version: 2 # The following stanza defines a map named defaults with a variable that may be @@ -10,187 +5,96 @@ version: 2 # typing. See http://yaml.org/type/merge.html for details. defaults: &defaults environment: - - DOCKER_REPOSITORY: "reactioncommerce/styleguide" - - DOCKER_NAMESPACE: "reactioncommerce" - - DOCKER_NAME: "styleguide" - + CI_SCRIPTS: 'npx --quiet --package @reactioncommerce/ci-scripts@1.6.2' + DOCKER_REPOSITORY: "reactioncommerce/styleguide" + DOCKER_NAMESPACE: "reactioncommerce" + DOCKER_NAME: "styleguide" docker: - - image: circleci/node:8-stretch + - image: circleci/node:8.11.4-stretch jobs: - docker-build: + install-dependencies: <<: *defaults steps: - checkout - - setup_remote_docker - - run: - name: Discover Docker Tags - command: | - mkdir -p docker-cache - .circleci/bin/docker-tags "$CIRCLE_SHA1" "$CIRCLE_BRANCH" \ - > docker-cache/docker-tags.txt - cat docker-cache/docker-tags.txt + - restore_cache: + keys: + - node-modules-v3-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} + - node-modules-v3-{{ .Branch }} - run: - name: Docker Build - command: | - docker build \ - --build-arg "BUILD_COMPARE_URL=$CIRCLE_COMPARE_URL" \ - --build-arg "BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \ - --build-arg "BUILD_ENV=test" \ - --build-arg "BUILD_NUMBER=$CIRCLE_BUILD_NUM" \ - --build-arg "BUILD_PLATFORM=circleci" \ - --build-arg "BUILD_PLATFORM_PROJECT_REPONAME=$CIRCLE_PROJECT_REPONAME" \ - --build-arg "BUILD_PLATFORM_PROJECT_USERNAME=$CIRCLE_PROJECT_USERNAME" \ - --build-arg "BUILD_PULL_REQUESTS=$CI_PULL_REQUESTS" \ - --build-arg "BUILD_TRIGGERED_BY_TAG=$CIRCLE_TAG" \ - --build-arg "BUILD_URL=$CIRCLE_BUILD_URL" \ - --build-arg "CIRCLE_WORKFLOW_ID=$CIRCLE_WORKFLOW_ID" \ - --build-arg "CIRCLE_WORKFLOW_JOB_ID=$CIRCLE_WORKFLOW_JOB_ID" \ - --build-arg "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS=$CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" \ - --build-arg "CIRCLE_WORKSPACE_ID=$CIRCLE_WORKSPACE_ID" \ - --build-arg "GIT_REPOSITORY_URL=$CIRCLE_REPOSITORY_URL" \ - --build-arg "GIT_SHA1=$CIRCLE_SHA1" \ - --build-arg "LICENSE=Apache-2.0" \ - --build-arg "VCS_REF=$CIRCLE_SHA1" \ - --build-arg "VENDOR=Reaction Commerce" \ - -t "$DOCKER_REPOSITORY:$CIRCLE_SHA1" . - mkdir -p docker-cache - docker save \ - -o docker-cache/docker-image.tar \ - "$DOCKER_REPOSITORY:$CIRCLE_SHA1" + name: Install Style Guide NPM dependencies + command: yarn install - run: - name: Save Test .env for Workspace Jobs - command: cp .env.example docker-cache/.env - - persist_to_workspace: - root: docker-cache + name: Install Package NPM dependencies + command: cd package && yarn install + - save_cache: + key: node-modules-v3-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} + paths: + - node_modules + - save_cache: + key: node-modules-v3-{{ .Branch }} paths: - - docker-image.tar - - docker-tags.txt - - .env + - node_modules + - save_cache: + key: node-modules-package-v3-{{ checksum "package/package.json" }}-{{ checksum "package/yarn.lock" }} + paths: + - package/node_modules + - save_cache: + key: node-modules-package-v3-{{ .Branch }} + paths: + - package/node_modules - docker-push: + dockerfile-lint: <<: *defaults steps: - - setup_remote_docker - - attach_workspace: - at: docker-cache - - run: - name: Load Docker Image - command: | - docker load < docker-cache/docker-image.tar - - run: - name: Tag Docker Image - command: | - cat docker-cache/docker-tags.txt \ - | xargs -t -I % \ - docker tag \ - "$DOCKER_REPOSITORY:$CIRCLE_SHA1" \ - "$DOCKER_REPOSITORY:%" - - run: - # Creates a new Docker repository. This is not strictly required if - # the Docker Hub defaults are set appropriately. - name: Create Private Docker Hub Repository - command: | - # Fetch a login token from environment credentials. - TOKEN=$(curl \ - -H "Content-Type: application/json" \ - -X POST \ - -d "{\"username\":\"$DOCKER_USER\",\"password\":\"$DOCKER_PASS\"}" \ - -s \ - https://hub.docker.com/v2/users/login/ \ - | jq -r .token) - - # Try to create the private repo. It exits with success on fail. - curl \ - -H "Authorization: JWT $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"namespace\":\"$DOCKER_NAMESPACE\", - \"name\":\"$DOCKER_NAME\", - \"description\":\"$DESCRIPTION\", - \"full_description\":\"\", - \"is_private\":false}" \ - https://hub.docker.com/v2/repositories/ + - checkout + - setup_remote_docker: + docker_layer_caching: true - run: - name: Docker Push - command: | - docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" - docker push "$DOCKER_REPOSITORY:$CIRCLE_SHA1" - cat docker-cache/docker-tags.txt \ - | xargs -t -I % \ - docker push "$DOCKER_REPOSITORY:%" - + name: Lint Dockerfiles + command: ${CI_SCRIPTS} lint-dockerfiles - lint: + eslint: <<: *defaults steps: - - setup_remote_docker - - attach_workspace: - at: docker-cache - - run: - name: Load Docker Image - command: | - docker load < docker-cache/docker-image.tar + - checkout + - restore_cache: + keys: + - node-modules-v3-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} + - node-modules-v3-{{ .Branch }} + - restore_cache: + keys: + - node-modules-package-v3-{{ checksum "package/package.json" }}-{{ checksum "package/yarn.lock" }} + - node-modules-package-v3-{{ .Branch }} - run: - name: Lint - command: | - docker run \ - --env-file docker-cache/.env \ - --name reactionapp_web_1 \ - "$DOCKER_REPOSITORY:$CIRCLE_SHA1" \ - yarn run lint + name: Lint JavaScript + command: yarn run lint test: <<: *defaults steps: - - setup_remote_docker - - attach_workspace: - at: docker-cache - - run: - name: Load Docker Image - command: | - docker load < docker-cache/docker-image.tar - - run: - name: Test - command: | - docker run \ - --env-file docker-cache/.env \ - --name reactionapp_web_1 \ - "$DOCKER_REPOSITORY:$CIRCLE_SHA1" \ - yarn run test + - checkout + - restore_cache: + keys: + - node-modules-v3-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} + - node-modules-v3-{{ .Branch }} + - restore_cache: + keys: + - node-modules-package-v3-{{ checksum "package/package.json" }}-{{ checksum "package/yarn.lock" }} + - node-modules-package-v3-{{ .Branch }} - run: - name: Copy test artifacts from Remote Docker - command: | - docker cp \ - reactionapp_web_1:/usr/local/src/reaction-app/reports \ - reports - - store_test_results: - path: reports/junit - - store_artifacts: - path: reports + name: Run Unit Tests + command: yarn run test - snyk-security: + docker-build-push: <<: *defaults steps: - - setup_remote_docker - - attach_workspace: - at: docker-cache - - run: - name: Load Docker Image - command: | - docker load < docker-cache/docker-image.tar + - checkout + - setup_remote_docker: + docker_layer_caching: true - run: - name: Snyk - command: | - # Snyk doesn't look up the directory tree for node_modules as - # NodeJS does so we have to take some extra measures to test in the - # Docker image. Copy package.json up a directory so that it is a - # sibling to node_modules, then run snyk test. - docker run \ - --env-file docker-cache/.env \ - -e "SNYK_TOKEN=$SNYK_TOKEN" \ - --name reactionapp_web_1 \ - "$DOCKER_REPOSITORY:$CIRCLE_SHA1" \ - sh -c "snyk test" + name: Build and push production Docker image + command: ${CI_SCRIPTS} docker-build-tag-push . ${DOCKER_REPOSITORY} publish-npm-package: docker: @@ -212,30 +116,26 @@ workflows: version: 2 build_and_test: jobs: - - docker-build: - context: reaction-build-read - - docker-push: - context: reaction-publish-docker - requires: - - docker-build - - lint: - context: reaction-validation + - install-dependencies + - dockerfile-lint + - eslint: requires: - - docker-build + - install-dependencies - test: - context: reaction-validation requires: - - docker-build - - snyk-security: - context: reaction-validation + - install-dependencies + - docker-build-push: + context: reaction-publish-docker requires: - - docker-build + - dockerfile-lint + - eslint + - test - publish-npm-package: context: reaction-publish-semantic-release requires: - - lint + - dockerfile-lint + - eslint - test - - snyk-security filters: branches: only: master From 524936227b1abfbb57e1bc5805cdc3b261c7d46d Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Tue, 26 Nov 2019 12:17:10 -0600 Subject: [PATCH 2/4] chore: fix Dockerfile lint Signed-off-by: Eric Dobbertin --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index b955d6a05..6b73345f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,6 +76,7 @@ COPY --chown=node . $APP_SOURCE_DIR # The project directory will be mounted during development. Therefore, we'll # install dependencies into an external directory (one level up.) This works # because Node traverses up the fs to find node_modules. +# hadolint ignore=DL3003 RUN set -ex; \ if [ "$BUILD_ENV" = "production" ]; then \ yarn install \ From 22bc458df60bb8fdebc0baca55582bafc391ba81 Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Tue, 26 Nov 2019 15:01:49 -0600 Subject: [PATCH 3/4] chore: run Jest tests in single thread Signed-off-by: Eric Dobbertin --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61ffd6d92..b3480c34f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,7 +84,7 @@ jobs: - node-modules-package-v3-{{ .Branch }} - run: name: Run Unit Tests - command: yarn run test + command: yarn run test -w 1 docker-build-push: <<: *defaults From 88868af4bafa430b22d9a4ef7db929fd8e59efb5 Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Tue, 26 Nov 2019 15:12:49 -0600 Subject: [PATCH 4/4] chore: fix Babel config for tests Signed-off-by: Eric Dobbertin --- babel.config.js | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/babel.config.js b/babel.config.js index 9ca1b9795..e3903bc3f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,6 +2,22 @@ module.exports = function (api) { const isTest = api.env("test"); + // We set this in the `build:modules` package.json script + const esmodules = process.env.BABEL_MODULES === "1"; + + const plugins = [ + "babel-plugin-styled-components", + ["@babel/plugin-proposal-decorators", { legacy: true }], + ["@babel/plugin-proposal-class-properties", { loose: true }], + "@babel/plugin-syntax-dynamic-import", + [ + "@babel/plugin-transform-runtime", + { + useESModules: esmodules + } + ] + ]; + // Config for when running Jest tests if (isTest) { return { @@ -23,18 +39,10 @@ module.exports = function (api) { ], "@babel/preset-react" ], - plugins: [ - "babel-plugin-styled-components", - ["@babel/plugin-proposal-decorators", { legacy: true }], - ["@babel/plugin-proposal-class-properties", { loose: true }], - "@babel/plugin-syntax-dynamic-import" - ] + plugins }; } - // We set this in the `build:modules` package.json script - const esmodules = process.env.BABEL_MODULES === "1"; - const presets = [ [ "@babel/env", @@ -63,19 +71,6 @@ module.exports = function (api) { "@babel/preset-react" ]; - const plugins = [ - "babel-plugin-styled-components", - ["@babel/plugin-proposal-decorators", { legacy: true }], - ["@babel/plugin-proposal-class-properties", { loose: true }], - "@babel/plugin-syntax-dynamic-import", - [ - "@babel/plugin-transform-runtime", - { - useESModules: esmodules - } - ] - ]; - let ignore; if (process.env.NODE_ENV === "production") { ignore = [