Skip to content

Commit 5af336e

Browse files
[CUTL-90] 🚀 (CD) Configure and document deploys to GCP and Sentry release management (#21)
* [CUTL-90] 🚀 (CD) Configure and document deploys to GCP and Sentry release management * [CUTL-90] ⚙️ (CI) Explicitly configure `CYPRESS_API_BASE_URL` for Cypress testing steps * [CUTL-90] 🚀 (CD) Attempt to fix workspace persist config * [CUTL-90] 🔊 (CI/CD) Add Slack notifications for build steps * [CUTL-90] 🧹 (CD) Clean up CircleCI config comments; enable enforcement of deploys on master/staging/prod only * [CUTL-90] 🔄 (Firebase) Ignore cache files; remove previously tracked cache file
1 parent 3a5de8e commit 5af336e

12 files changed

+3157
-300
lines changed

.circleci/config.yml

+173-22
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,56 @@
1-
# Javascript Node CircleCI 2.0 configuration file
2-
#
3-
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
4-
#
1+
### ------------------------------------------------------------------------------------------------
2+
### To use this configuration, ensure that the following environment variables are configured in
3+
### the CircleCI project settings:
4+
###
5+
### For all projects, set:
6+
### - $DEV_API_BASE_URL (becomes $API_BASE_URL based on env)
7+
### - $STAGING_API_BASE_URL (becomes $API_BASE_URL based on env)
8+
### - $PROD_API_BASE_URL (becomes $API_BASE_URL based on env)
9+
### - $SENTRY_AUTH_TOKEN
10+
### - $SENTRY_DSN
11+
### - $SENTRY_ENABLED
12+
### - $SENTRY_ORG
13+
### - $SENTRY_PROJECT
14+
### - $CYPRESS_RECORD_KEY
15+
### - $SLACK_HOOK, configure via https://cuttlesoft.slack.com/apps/A0F7XDUAZ-incoming-webhooks
16+
###
17+
### If deploying to Google Cloud Platform (GCP), create a token via `firebase login:ci` and set:
18+
### - $FIREBASE_TOKEN
19+
###
20+
### If deploying to AWS, you must configure the built-in settings (only available on the old UI,
21+
### this is being deprecated), set:
22+
### - $DEV_S3_BUCKET_NAME (becomes $S3_BUCKET_NAME based on env)
23+
### - $DEV_DISTRIBUTION_ID (becomes $DISTRIBUTION_ID based on env)
24+
### - $STAGING_S3_BUCKET_NAME (becomes $S3_BUCKET_NAME based on env)
25+
### - $STAGING_DISTRIBUTION_ID (becomes $DISTRIBUTION_ID based on env)
26+
### - $PROD_S3_BUCKET_NAME (becomes $S3_BUCKET_NAME based on env)
27+
### - $PROD_DISTRIBUTION_ID (becomes $DISTRIBUTION_ID based on env)
28+
version: 2.1
529

6-
defaults: &defaults
7-
working_directory: ~/repo
8-
docker:
9-
- image: cypress/browsers:node12.18.0-chrome83-ff77
30+
executors:
31+
docker-executor:
32+
working_directory: ~/repo
33+
docker:
34+
- image: cypress/browsers:node12.18.0-chrome83-ff77
1035

11-
version: 2
1236
jobs:
13-
build:
14-
<<: *defaults
37+
setup-lint:
38+
executor: docker-executor
1539
steps:
1640
- checkout
1741

1842
# find compatible cache from previous build,
1943
# it should have same dependencies installed from package.json checksum
2044
- restore_cache:
2145
keys:
22-
- cache-{{ .Branch }}-{{ checksum "package.json" }}
46+
- cache-{{ checksum "package.json" }}
2347

2448
- run:
2549
name: Install Dependencies
2650
command: npm install
2751

2852
- save_cache:
29-
key: cache-{{ .Branch }}-{{ checksum "package.json" }}
53+
key: cache-{{ checksum "package.json" }}
3054
paths:
3155
- ~/.npm
3256
- ~/.cache
@@ -40,7 +64,7 @@ jobs:
4064
command: npm run format:test
4165

4266
# all other test jobs will run AFTER this build job finishes
43-
# to avoid reinstalling dependencies, we persist the source folder "app"
67+
# to avoid reinstalling dependencies, we persist the source folder "repo"
4468
# and the Cypress binary to workspace, which is the fastest way
4569
# for Circle jobs to pass files
4670
- persist_to_workspace:
@@ -50,45 +74,172 @@ jobs:
5074
- .cache/Cypress
5175

5276
unittests:
53-
<<: *defaults
77+
executor: docker-executor
5478
steps:
5579
- attach_workspace:
5680
at: ~/
5781
- run: npm test -- --maxWorkers=4
5882

5983
integration-chrome:
60-
<<: *defaults
84+
executor: docker-executor
6185
steps:
6286
- attach_workspace:
6387
at: ~/
88+
- run:
89+
name: Configure cypress variables
90+
command: ./.circleci/configure-cypress-vars.sh
91+
- run:
92+
name: Configure build variables
93+
command: ./.circleci/configure-build-vars.sh
6494
- run:
6595
command: npm start
6696
background: true
6797
- run: npm run e2e:record -- --group $CIRCLE_JOB --browser chrome
6898

6999
integration-firefox:
70-
<<: *defaults
100+
executor: docker-executor
71101
steps:
72102
- attach_workspace:
73103
at: ~/
104+
- run:
105+
name: Configure cypress variables
106+
command: ./.circleci/configure-cypress-vars.sh
107+
- run:
108+
name: Configure build variables
109+
command: ./.circleci/configure-build-vars.sh
74110
- run:
75111
command: npm start
76112
background: true
77113
- run: npm run e2e:record -- --group $CIRCLE_JOB --browser firefox
78114

115+
build:
116+
executor: docker-executor
117+
steps:
118+
- attach_workspace:
119+
at: ~/
120+
- run:
121+
name: Configure build variables
122+
command: ./.circleci/configure-build-vars.sh
123+
# Ensure that the build completes before considering the build as 'Passed'
124+
- run:
125+
name: Run build
126+
command: npm run build
127+
- persist_to_workspace:
128+
root: ~/
129+
paths:
130+
- repo
131+
- .cache/Cypress
132+
- build
133+
134+
deploy-s3:
135+
executor: docker-executor
136+
steps:
137+
- checkout
138+
- attach_workspace:
139+
at: ~/
140+
- run:
141+
name: Configure deploy variables
142+
command: ./.circleci/configure-deploy-vars.sh
143+
- run:
144+
name: Install AWS CLI
145+
command: sudo apt-get -y -qq install awscli
146+
- run:
147+
name: Enable CloudFront
148+
command: aws configure set preview.cloudfront true
149+
- run:
150+
name: Upload to S3
151+
command: aws s3 sync ./build s3://${S3_BUCKET_NAME} --delete --cache-control max-age=31536000,public
152+
153+
- run:
154+
name: Update file cache headers
155+
command: aws s3 cp s3://${S3_BUCKET_NAME}/index.html s3://${S3_BUCKET_NAME}/index.html --metadata-directive REPLACE --cache-control max-age=0,no-cache,no-store,must-revalidate --content-type text/html --acl public-read
156+
- run:
157+
name: Invalidate CloudFront distribution
158+
command: aws cloudfront create-invalidation --distribution-id ${DISTRIBUTION_ID} --paths "/index.html"
159+
160+
deploy-gcp:
161+
executor: docker-executor
162+
steps:
163+
- checkout
164+
- attach_workspace:
165+
at: ~/
166+
- run:
167+
name: Notify via Slack
168+
command: curl -X POST -H 'Content-type:\ application/json' --data "{'text':'📦 Build successful. Deploying!'}" $SLACK_HOOK
169+
- run:
170+
name: Configure deploy variables
171+
command: ./.circleci/configure-deploy-vars.sh
172+
- run:
173+
name: Firebase Deploy
174+
command: ./node_modules/.bin/firebase deploy --token "$FIREBASE_TOKEN"
175+
- run:
176+
name: Notify via Slack
177+
command: curl -X POST -H 'Content-type:\ application/json' --data "{'text':'🍰 Deploy successful.'}" $SLACK_HOOK
178+
179+
180+
notify-sentry-deploy:
181+
executor: docker-executor
182+
steps:
183+
- checkout
184+
- attach_workspace:
185+
at: ~/
186+
- run:
187+
name: Create release and notify Sentry of deploy
188+
command: |
189+
curl -sL https://sentry.io/get-cli/ | bash
190+
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
191+
sentry-cli releases new -p $SENTRY_PROJECT $SENTRY_RELEASE
192+
sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps ./build
193+
sentry-cli releases finalize $SENTRY_RELEASE
194+
sentry-cli releases deploys $SENTRY_RELEASE new -e $CIRCLE_BRANCH
195+
- run:
196+
name: Notify via Slack
197+
command: curl -X POST -H 'Content-type:\ application/json' --data "{'text':'🦾 Sentry Release successful.'}" $SLACK_HOOK
198+
199+
79200
workflows:
80201
version: 2
81202
build_and_test:
82203
jobs:
83-
- build
84-
# after installing dependencies in the "build" job
85-
# run all tests in several groups
204+
- setup-lint
86205
- unittests:
87206
requires:
88-
- build
207+
- setup-lint
89208
- integration-chrome:
90209
requires:
91-
- build
210+
- setup-lint
92211
- integration-firefox:
212+
requires:
213+
- setup-lint
214+
- build:
215+
requires:
216+
- unittests
217+
- integration-chrome
218+
- integration-firefox
219+
# Enable this and configure branches as needed if deploying to AWS S3
220+
# - deploy-s3:
221+
# requires:
222+
# - build
223+
# filters:
224+
# branches:
225+
# only:
226+
# - master
227+
# - staging
228+
- deploy-gcp:
93229
requires:
94230
- build
231+
filters:
232+
branches:
233+
only:
234+
- master
235+
- staging
236+
- prod
237+
- notify-sentry-deploy:
238+
requires:
239+
- deploy-gcp
240+
filters:
241+
branches:
242+
only:
243+
- master
244+
- staging
245+
- prod

.circleci/configure-build-vars.sh

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
if [ "$CIRCLE_BRANCH" == "prod" ]; then
4+
echo "Setting production URLS..."
5+
echo "export API_BASE_URL=$PROD_API_BASE_URL" >> $BASH_ENV
6+
echo "export SENTRY_ENVIRONMENT=$CIRCLE_BRANCH" >> $BASH_ENV
7+
elif [ "$CIRCLE_BRANCH" == "staging" ]; then
8+
echo "Setting staging URLS..."
9+
echo "export API_BASE_URL=$STAGING_API_BASE_URL" >> $BASH_ENV
10+
echo "export SENTRY_ENVIRONMENT=$CIRCLE_BRANCH" >> $BASH_ENV
11+
else
12+
echo "Setting dev URLS..."
13+
echo "export API_BASE_URL=$DEV_API_BASE_URL" >> $BASH_ENV
14+
echo "export SENTRY_ENVIRONMENT=dev" >> $BASH_ENV
15+
fi
16+
17+
# Always set the current git hash and short hash used for release
18+
echo "export SENTRY_RELEASE_GIT_HASH=$(git rev-parse --short HEAD)" >> $BASH_ENV
19+
echo "export SHORT_GIT_HASH=$(echo $CIRCLE_SHA1 | cut -c -7)" >> $BASH_ENV

.circleci/configure-cypress-vars.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
if [ "$CIRCLE_BRANCH" == "prod" ]; then
4+
echo "Setting production URLS..."
5+
echo "export CYPRESS_API_BASE_URL=$PROD_API_BASE_URL" >> $BASH_ENV
6+
elif [ "$CIRCLE_BRANCH" == "staging" ]; then
7+
echo "Setting staging URLS..."
8+
echo "export CYPRESS_API_BASE_URL=$STAGING_API_BASE_URL" >> $BASH_ENV
9+
else
10+
echo "Setting dev URLS..."
11+
echo "export CYPRESS_API_BASE_URL=$DEV_API_BASE_URL" >> $BASH_ENV
12+
fi

.circleci/configure-deploy-vars.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
if [ "$CIRCLE_BRANCH" == "staging" ]; then
4+
echo "Setting staging URLS..."
5+
echo "export DISTRIBUTION_ID=$STAGING_DISTRIBUTION_ID" >> $BASH_ENV
6+
echo "export S3_BUCKET_NAME=$STAGING_S3_BUCKET_NAME" >> $BASH_ENV
7+
else
8+
echo "Setting dev URLS..."
9+
echo "export DISTRIBUTION_ID=$DEV_DISTRIBUTION_ID" >> $BASH_ENV
10+
echo "export S3_BUCKET_NAME=$DEV_S3_BUCKET_NAME" >> $BASH_ENV
11+
fi

.firebaserc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"default": "react-boilerplate-ac567"
4+
}
5+
}

.gitignore

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
# Don't check auto-generated stuff into git
1+
# Dependencies
2+
node_modules
3+
4+
# One-Offs / Generated
25
coverage
36
build
4-
node_modules
57
stats.json
8+
npm-debug.log
69

7-
# Cruft
10+
# Mac meta file
811
.DS_Store
9-
npm-debug.log
10-
.idea
1112

13+
# Configuration
1214
.env
15+
cypress.env.json
1316

17+
# Cypress intermediate assets
1418
cypress/videos
15-
cypress/screenshots
19+
cypress/screenshots
20+
21+
# Firebase hosting cache files
22+
.firebase/

cypress.env.example.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"API_BASE_URL": "http://0.0.0.0:8000/api/v1/"
3+
}

cypress/integration/login.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('Log In', () => {
9595
// Mock the `login` API endpoint with an usuccessful response
9696
cy.route({
9797
method: 'POST',
98-
url: 'http://0.0.0.0:8000/api/v1/login/',
98+
url: `${Cypress.env('API_BASE_URL')}/login/`,
9999
status: 400,
100100
response: 'fixture:login-invalid-credentials.json',
101101
})
@@ -115,7 +115,7 @@ describe('Log In', () => {
115115
// Mock the `login` API endpoint with a successful response
116116
cy.route({
117117
method: 'POST',
118-
url: 'http://0.0.0.0:8000/api/v1/login/',
118+
url: `${Cypress.env('API_BASE_URL')}/login/`,
119119
status: 200,
120120
response: 'fixture:login-valid-credentials.json',
121121
})

cypress/support/commands.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Cypress.Commands.add('login', (email = 'engineering@cuttlesoft.com', password =
3232
cy.server()
3333
cy.route({
3434
method: 'POST',
35-
url: 'http://0.0.0.0:8000/api/v1/login/',
35+
url: `${Cypress.env('API_BASE_URL')}/login/`,
3636
status: 200,
3737
response: 'fixture:login-valid-credentials.json',
3838
})

0 commit comments

Comments
 (0)