diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 3d7f4ae39..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,104 +0,0 @@ -version: '2.1' -jobs: - lint: - docker: - - image: cimg/node:16.13.1 - environment: - NODE_PATH: "./src" - REACT_APP_API_URL: https://learning-admin-staging.herokuapp.com - steps: - - checkout - - node/install-packages: - pkg-manager: yarn - - run: - name: eslint - command: yarn run lint - - stylelint: - docker: - - image: cimg/node:16.13.1 - environment: - NODE_PATH: ./src - REACT_APP_API_URL: https://learning-admin-staging.herokuapp.com - steps: - - checkout - - node/install-packages: - pkg-manager: yarn - - run: - name: stylelint - command: yarn run stylelint - - test: - docker: - - image: cimg/node:16.13.1-browsers - environment: - NODE_PATH: ./src - steps: - - checkout - - restore_cache: - keys: - - v1-deps-{{ .Branch }}-{{ checksum "package.json" }} - - v1-deps-{{ .Branch }} - - v1-deps - - run: - name: Install Dependencies - command: yarn install - - save_cache: - key: v1-deps-{{ .Branch }}-{{ checksum "package.json" }} - # cache NPM modules and the folder with the Cypress binary - paths: - - ~/project/node_modules - - ~/.cache - - run: - command: yarn run test --coverage --maxWorkers=4 --reporters=default --reporters=jest-junit - environment: - JEST_JUNIT_OUTPUT_DIR: ./coverage/ - - run: - name: Start server - command: yarn run start - background: true - environment: - PORT: 3000 - - run: - name: Start web component server - command: yarn run start:wc - environment: - PUBLIC_URL: http://localhost:3000 - background: true - - run: - name: Wait for services to be ready - command: dockerize -wait http://localhost:3000 -wait http://localhost:3001 -timeout 120s - - run: - name: Cypress integration tests - command: yarn exec cypress run - - store_test_results: - path: ./coverage - - store_artifacts: - path: ./coverage - - store_artifacts: - path: ./cypress/videos - - run: - name: Post test coverage to Github - command: bash -ue .circleci/record_coverage - when: always - -orbs: - node: circleci/node@5 - -workflows: - test: - jobs: - - test: - context: raspberrypigithubbot -# code_quality: -# jobs: -# - stylelint: -# filters: -# branches: -# ignore: -# - main -# - lint: -# filters: -# branches: -# ignore: -# - main diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml new file mode 100644 index 000000000..e7120890b --- /dev/null +++ b/.github/workflows/build-and-deploy.yml @@ -0,0 +1,84 @@ +name: Build and Upload to S3 + +on: + workflow_call: + inputs: + cookiebot_domain_group_id: + required: false + default: "1e9a6bdd-5870-4d54-8e5f-adcf6b5c5499" + type: string + deploy_dir: + required: true + type: string + environment: + required: true + type: string + public_url: + required: true + type: string + react_app_api_endpoint: + required: false + default: "https://staging-editor-api.raspberrypi.org" + type: string + react_app_authentication_client_id: + required: false + default: editor-api + type: string + react_app_authentication_url: + required: false + default: "https://staging-auth-v1.raspberrypi.org" + type: string + react_app_login_enabled: + required: false + default: "true" + type: string + secrets: + AWS_ACCESS_KEY_ID: + required: true + AWS_REGION: + required: true + AWS_S3_BUCKET: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + +jobs: + build-deploy: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + url: ${{ inputs.public_url }} + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Cache dependencies + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Install code + run: yarn install --frozen-lock-file + + - name: Build site and WC bundle + run: | + yarn build + yarn build:wc + env: + COOKIEBOT_DOMAIN_GROUP_ID: ${{ inputs.cookiebot_domain_group_id }} + PUBLIC_URL: ${{ inputs.public_url }} + REACT_APP_API_ENDPOINT: ${{ inputs.react_app_api_endpoint }} + REACT_APP_AUTHENTICATION_CLIENT_ID: ${{ inputs.react_app_authentication_client_id }} + REACT_APP_AUTHENTICATION_URL: ${{ inputs.react_app_authentication_url }} + REACT_APP_LOGIN_ENABLED: ${{ inputs.react_app_login_enabled }} + + - name: Deploy site to S3 bucket + run: aws s3 sync ./build/ s3://${{ secrets.AWS_S3_BUCKET }}/${{ inputs.deploy_dir }} --delete diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fc35d0947..af26910e0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,7 +45,14 @@ jobs: run: yarn install --frozen-lock-file - name: Run tests - run: yarn test + run: yarn run test --coverage --maxWorkers=4 --reporters=default --reporters=jest-junit --reporters=jest-github-actions-reporter + env: + JEST_JUNIT_OUTPUT_DIR: ./coverage/ + + - name: Record coverage + run: ./.github/workflows/record_coverage + env: + GITHUB_TOKEN: ${{ github.token }} test-cypress: runs-on: ubuntu-latest @@ -82,68 +89,37 @@ jobs: cypress/screenshots cypress/videos - build-deploy: + + build-and-deploy-release: + if: github.ref_type == 'tag' needs: - test - # Disabling test-cypress while it is flaky - # - test-cypress - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Cache dependencies - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'yarn' - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: eu-west-2 - - - name: Set bucket - run: | - if [ ${{ github.ref_type }} == 'tag' ]; then - echo "Deploying to production bucket" - echo "bucket=python-editor-dist-test" >> $GITHUB_ENV - else - echo "Deploying to staging/preview bucket" - echo "bucket=python-editor-dist-test" >> $GITHUB_ENV - fi - shell: bash - - - name: Set deploy directory - run: | - if [ ${{ github.ref_type }} == 'tag' ]; then - echo "Deploying tagged release ${{ github.ref_name }}" - echo "deploy_dir=${{ github.ref_name }}" >> $GITHUB_ENV - elif [ ${{ github.ref }} == 'refs/head/main' ]; then - echo "Deploying staging release" - echo "deploy_dir=staging" >> $GITHUB_ENV - else - echo "Deploying preview release ${{ github.ref_name }}" - echo "deploy_dir=previews/${{ github.ref_name }}" >> $GITHUB_ENV - fi - shell: bash - - - name: Set PUBLIC_URL - run: | - public_url=https://${{ env.bucket }}.s3.eu-west-2.amazonaws.com/${{ env.deploy_dir }} - echo "Setting PUBLIC_URL to $public_url" - echo PUBLIC_URL=$public_url >> $GITHUB_ENV - shell: bash - - - name: Install code - run: yarn install --frozen-lock-file - - - name: Build site and WC bundle - run: | - yarn build - yarn build:wc + uses: ./.github/workflows/build-and-deploy.yml + with: + deploy_dir: ${{ github.ref_name }} + environment: production + public_url: https://editor.raspberrypi.org/${{ github.ref_name }} + react_app_api_endpoint: https://editor-api.raspberrypi.org + react_app_authentication_url: https://auth-v1.raspberrypi.org + secrets: inherit + + build-and-deploy-staging: + if: github.ref == 'refs/heads/main' + needs: + - test + uses: ./.github/workflows/build-and-deploy.yml + with: + deploy_dir: ${{ github.ref_name }} + environment: staging + public_url: https://staging-editor.raspberrypi.org/${{ github.ref_name }} + secrets: inherit + + build-and-deploy-preview: + if: github.ref_type == 'branch' && github.ref != 'refs/heads/main' + uses: ./.github/workflows/build-and-deploy.yml + with: + deploy_dir: previews/${{ github.ref_name }} + environment: previews/${{ github.ref_name }} + public_url: http://python-editor-dist-test.s3-website.eu-west-2.amazonaws.com/previews/${{ github.ref_name }} + secrets: inherit - - name: Deploy site to S3 bucket - run: aws s3 sync ./build/ s3://${{ env.bucket }}/${{ env.deploy_dir }} --delete diff --git a/.circleci/record_coverage b/.github/workflows/record_coverage old mode 100644 new mode 100755 similarity index 60% rename from .circleci/record_coverage rename to .github/workflows/record_coverage index b0a2207bd..344524f28 --- a/.circleci/record_coverage +++ b/.github/workflows/record_coverage @@ -1,18 +1,11 @@ -#!/bin/bash -ueo pipefail +#!/bin/bash -eu # Record coverage # -# This script uses the Circle and Github APIs to poke a comment into a PR about test coverage. +# This script uses the Github APIs to poke a comment into a PR about test coverage. # -# To work, the GITHUB_TOKEN and CIRCLE_TOKEN vars must be in the environment, -# with appropriate API tokens from GH and Circle. +# To work, the GITHUB_TOKEN var must be in the environment # -# Also to get the magic link to your test coverage, you'll want to store the -# `coverage/` directory. -#``` -# - store_artifacts: -# path: coverage -#``` CURL_ARGS="-s -S -f" @@ -43,9 +36,10 @@ fi sudo apt update -qq sudo apt install -qq --no-install-recommends -y xmlstarlet which jq > /dev/null || sudo apt-get install -y jq +which curl > /dev/null || sudo apt-get install -y curl # This is the message that makes it into github -msg="* CircleCI build [#${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL})\n" +msg="* Github [Run ${GITHUB_RUN_ID}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)\n" msg="$msg* Test coverage: " statements=$(xmlstarlet sel -t -v '/coverage/project[@name="All files"]/metrics/@statements' $clover_xml) @@ -58,21 +52,13 @@ if [ "${coverage}" = "null" ] ; then exit 0 fi -artifacts_response=$(curl $CURL_ARGS -H "Circle-Token: $CIRCLE_TOKEN" https://circleci.com/api/v1.1/project/gh/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/artifacts) -coverage_url=$(echo ${artifacts_response} | jq -r '. | map(select(.path == "coverage/lcov-report/index.html"))[0].url') - -if ! [ "${coverage_url}" = "null" ] ; then - msg="$msg [$coverage%]($coverage_url)\n\n" -else - msg="$msg $coverage%\n\n" - msg="$msg > CircleCI didn't store the coverage index (maybe the store_artifacts step is missing?)" -fi +msg="$msg $coverage%\n\n" # Find associated PR. *NB* we're assuming that the first, open PR is the one # to comment on. q="query { - repository(name: \"${CIRCLE_PROJECT_REPONAME}\", owner: \"${CIRCLE_PROJECT_USERNAME}\") { - ref(qualifiedName: \"${CIRCLE_BRANCH}\") { + repository(name: \"${GITHUB_REPOSITORY##*/}\", owner: \"${GITHUB_REPOSITORY%%/*}\") { + ref(qualifiedName: \"${GITHUB_REF_NAME}\") { associatedPullRequests(first: 1) { nodes { id diff --git a/CHANGELOG.md b/CHANGELOG.md index 8197bc9b8..4c1bdc699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Changed +- Update build workflow with a reusable job to update preview, staging, and prod (#176) + ## [0.3.0] ### Added diff --git a/README.md b/README.md index cc733a51d..6cda53562 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,18 @@ It is possible to add query strings to control how the web component is configur For example, to load the page with the Sense Hat always showing, add [`?sense_hat_always_enabled` to the URL](http://localhost:3001?sense_hat_always_enabled) -## Review apps +## Deployment + +Deployment is managed through Giithub actions. The UI is deployed to staging and production environments via an S3 bucket. This requires the following environment variables to be set + +* `AWS_ACCESS_KEY_ID` +* `AWS_REGION` +* `AWS_S3_BUCKET` +* `AWS_SECRET_ACCESS_KEY` + +Other variables that pertain to the app, rather than its deployment are set with defaults in the [build-and-deploy workflow](./.github/workflows/build-and-deploy.yml). These are also in `.env.example`. + +### Review apps Currently the build is deployed to both S3 and Heroku. The PR should get updated with the Heroku URL, and the web component demo is at `/web-component.html` on the Heroku review app domain. + diff --git a/package.json b/package.json index 7f430f2b0..ea82af374 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "html-webpack-plugin": "4.5.0", "jest": "26.6.0", "jest-circus": "26.6.0", + "jest-github-actions-reporter": "^1.0.3", "jest-junit": "^13.0.0", "jest-resolve": "26.6.0", "jest-watch-typeahead": "0.6.1", diff --git a/yarn.lock b/yarn.lock index 864a6280c..4cfc123ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,21 @@ # yarn lockfile v1 +"@actions/core@^1.2.0": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.9.1.tgz#97c0201b1f9856df4f7c3a375cdcdb0c2a2f750b" + integrity sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA== + dependencies: + "@actions/http-client" "^2.0.1" + uuid "^8.3.2" + +"@actions/http-client@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.0.1.tgz#873f4ca98fe32f6839462a6f046332677322f99c" + integrity sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw== + dependencies: + tunnel "^0.0.6" + "@allmarkedup/fang@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@allmarkedup/fang/-/fang-2.0.0.tgz#c61b0ad9e487fca6f1af4639d7813a91e41d8107" @@ -9876,6 +9891,13 @@ jest-get-type@^28.0.2: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== +jest-github-actions-reporter@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/jest-github-actions-reporter/-/jest-github-actions-reporter-1.0.3.tgz#6aa2b3a6599352e1043bbe42628a7f73a1ce48c2" + integrity sha512-IwLAKLSWLN8ZVfcfEEv6rfeWb78wKDeOhvOmH9KKXayKsKLSCwceopBcB+KUtwxfB5wYnT8Y9s2eZ+WdhA5yng== + dependencies: + "@actions/core" "^1.2.0" + jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" @@ -16956,6 +16978,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"