Skip to content

Commit 1114958

Browse files
authored
Github actions support (#31)
* CIVendor option: Github actions
1 parent 82aea64 commit 1114958

6 files changed

Lines changed: 285 additions & 3 deletions

File tree

Makefile

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,27 @@
55

66
GITHUB_ORG := $(shell echo ${REPOSITORY} | cut -d "/" -f 2)
77
GITHUB_REPO := $(shell echo ${REPOSITORY} | cut -d "/" -f 3)
8+
REPOSITORY := ${REPOSITORY}
9+
RANDOM_SEED := ${randomSeed}
10+
REGION := ${region}
11+
PROJECT_NAME := ${PROJECT_NAME}
812

9-
run:
13+
.EXPORT_ALL_VARIABLES:
14+
15+
run: ci_setup
16+
@echo "\nDone"
17+
18+
ci_setup:
19+
# Conditionally setup GitHub Action OR CircleCI's environment for CI
20+
ifeq ($(CIVendor), github-actions)
21+
ci_setup: github_actions_setup
22+
endif
23+
ifeq ($(CIVendor), circleci)
24+
ci_setup: circle_ci_setup
25+
endif
26+
27+
28+
circle_ci_setup:
1029
@echo "Set CIRCLECI environment variables\n"
1130
export AWS_ACCESS_KEY_ID=$(shell aws secretsmanager get-secret-value --region ${region} --secret-id=${PROJECT_NAME}-ci-user-aws-keys${randomSeed} | jq -r '.SecretString'| jq -r .access_key_id)
1231
export AWS_SECRET_ACCESS_KEY=$(shell aws secretsmanager get-secret-value --region ${region} --secret-id=${PROJECT_NAME}-ci-user-aws-keys${randomSeed} | jq -r '.SecretString'| jq -r .secret_key)
@@ -15,7 +34,9 @@ run:
1534
curl -X POST --header "Content-Type: application/json" -d '{"name":"AWS_SECRET_ACCESS_KEY", "value":"${AWS_SECRET_ACCESS_KEY}"}' https://circleci.com/api/v1.1/project/github/${GITHUB_ORG}/${GITHUB_REPO}/envvar?circle-token=${CIRCLECI_API_KEY}
1635
@echo "\nFollow CIRCLECI project"
1736
curl -X POST https://circleci.com/api/v1.1/project/github/${GITHUB_ORG}/${GITHUB_REPO}/follow?circle-token=${CIRCLECI_API_KEY}
18-
@echo "\nDone"
37+
38+
github_actions_setup:
39+
sh scripts/gha-setup.sh
1940

2041
summary:
2142
@echo "zero-deployable-react-frontend:"

scripts/gha-setup.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
3+
# In order to set project env-vars, we must encrypt secrets
4+
# Using gh client allows us to set the secret without installing another
5+
# binary just to encrypt the secrets
6+
7+
# Login GH client
8+
# GITHUB_ACCESS_TOKEN is injected when zero apply runs
9+
gh auth login --with-token <<EOF
10+
$GITHUB_ACCESS_TOKEN
11+
EOF
12+
13+
gh auth status
14+
15+
AWS_KEY_PAIR=$(aws secretsmanager get-secret-value --region ${REGION} --secret-id=${PROJECT_NAME}-ci-user-aws-keys${RANDOM_SEED})
16+
AWS_ACCESS_KEY_ID=$(echo ${AWS_KEY_PAIR} | jq -r '.SecretString'| jq -r .access_key_id)
17+
AWS_SECRET_ACCESS_KEY=$(echo ${AWS_KEY_PAIR} | jq -r '.SecretString'| jq -r .secret_key)
18+
19+
## IMPORTANT: Set secret operates on the nearest .git repo even if you specify a different repository
20+
pushd $PROJECT_DIR && \
21+
gh secret set AWS_ACCESS_KEY_ID --repos="$GITHUB_REPO" --body="$AWS_ACCESS_KEY_ID" && \
22+
gh secret set AWS_SECRET_ACCESS_KEY --repos="$GITHUB_REPO" --body="$AWS_SECRET_ACCESS_KEY" && \
23+
popd
24+
25+
## Branch Protect for PRs
26+
## By default we setup Pull-request checks of [lint, unit-test] in `.github/workflows/pull-request.yml`
27+
## And we will enforce both the checks pass before PR can be merged into default branch
28+
DEFAULT_BRANCH=master
29+
curl -XPUT "https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/branches/$DEFAULT_BRANCH/protection" \
30+
--header "Authorization: token $GITHUB_ACCESS_TOKEN" \
31+
--header 'Content-Type: application/json' \
32+
--data '{
33+
"required_status_checks": {
34+
"strict": false,
35+
"contexts": ["lint","unit-test"]
36+
},
37+
"enforce_admins": false,
38+
"required_pull_request_reviews": null,
39+
"restrictions": null
40+
}'
41+
42+
echo "Github actions environment variables setup successfully."

templates/.github/workflows/ci.yml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: CI Pipeline
2+
on:
3+
push:
4+
branches: [master, main]
5+
jobs:
6+
unit-test:
7+
runs-on: ubuntu-latest
8+
env:
9+
CI: true
10+
build_env: production
11+
steps:
12+
- uses: actions/checkout@v2
13+
- name: Run Unit Tests
14+
uses: actions/setup-node@v1
15+
with:
16+
node-version: 14.x
17+
- run: |
18+
yarn
19+
yarn test --watchAll=false
20+
build:
21+
needs: unit-test
22+
runs-on: ubuntu-latest
23+
env:
24+
CI: true
25+
strategy:
26+
matrix:
27+
include:
28+
- env: stage
29+
bucket: <% index .Params `stagingFrontendSubdomain` %><% index .Params `stagingHostRoot` %>
30+
- env: production
31+
bucket: <% index .Params `productionFrontendSubdomain` %><% index .Params `productionHostRoot` %>
32+
outputs:
33+
needs-approval: "${{ steps.needs-approval.outputs.needs-approval }}"
34+
approved-deploy: "${{ steps.approved-deploy.outputs.approved-deploy }}"
35+
steps:
36+
- uses: actions/checkout@v2
37+
- name: Build Static Site
38+
uses: actions/setup-node@v1
39+
with:
40+
node-version: 14.x
41+
- run: |
42+
yarn
43+
yarn build
44+
env:
45+
REACT_APP_CONFIG: ${{ matrix.env }}
46+
- name: Upload build artifact to Github
47+
uses: actions/upload-artifact@v1
48+
with:
49+
name: build-artifact-${{ matrix.env }}
50+
path: build/
51+
## This sets `step.outputs` is picked up by `jobs.outputs`, then when the step deploy reads `jobs.outputs`
52+
## this provides data and set matrix for the deploy steps. This allows us to specify the data once
53+
## and have the subsequence step can pass along the information created during this step.
54+
# the Deploy step will have a strategy matrix as follows:
55+
# {
56+
# "include": [{
57+
# "env": "stage",
58+
# "buildName": "build-artifact-stage",
59+
# "bucket": "app.mydomain.com"
60+
# }]
61+
# }
62+
##
63+
- name: needs-approval
64+
id: needs-approval
65+
if: ${{ matrix.env == 'production' }}
66+
run: echo "::set-output name=needs-approval::{\"include\":[{\"env\":\"${{ matrix.env }}\",\"buildName\":\"build-artifact-${{ matrix.env }}\",\"bucket\":\"${{ matrix.bucket }}\"}]}"
67+
- name: approved-deploy
68+
id: approved-deploy
69+
if: ${{ matrix.env == 'stage' }}
70+
run: echo "::set-output name=approved-deploy::{\"include\":[{\"env\":\"${{ matrix.env }}\",\"buildName\":\"build-artifact-${{ matrix.env }}\",\"bucket\":\"${{ matrix.bucket }}\"}]}"
71+
deploy:
72+
name: Deploy
73+
runs-on: ubuntu-latest
74+
needs: build
75+
strategy:
76+
# with `needs` declared, the job will have access to the output of the needed step
77+
# and in the needed step(`build`) we have the output of what should be instantly deployed
78+
matrix: ${{ fromJSON(needs.build.outputs.approved-deploy) }}
79+
env:
80+
region: <% index .Params `region` %>
81+
steps:
82+
# Once github action supports nested composite actions (anything `uses` is a composite action)
83+
# Therefore we cannot reuse the code as a separate composite action until it supports it,
84+
# current the deploy logic is in this file twice because of it
85+
## https://github.com/actions/runner/issues/862
86+
- uses: actions/checkout@v2
87+
- name: Configure AWS credentials for S3 sync
88+
uses: aws-actions/configure-aws-credentials@v1
89+
with:
90+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
91+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
92+
aws-region: <% index .Params `region` %>
93+
- name: Download build artifact from Github
94+
uses: actions/download-artifact@v1
95+
with:
96+
name: ${{ matrix.buildName }}
97+
path: build/
98+
- name: Sync with S3
99+
shell: bash
100+
run: |
101+
cd build
102+
aws s3 sync . s3://${{ matrix.bucket }}
103+
- name: Invalidate Cloudfront
104+
shell: bash
105+
run: |
106+
export DIST_ID=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[?@=='${{ matrix.bucket }}']].Id | [0]" | tr -d '"')
107+
aws cloudfront create-invalidation --distribution-id ${DIST_ID} --paths "/*"
108+
integration-test:
109+
needs: [deploy]
110+
runs-on: ubuntu-latest
111+
name: integration-test
112+
steps:
113+
## Example of smoke test against staging env before deploying to production
114+
## To be enhanced to more sophisicated checks
115+
- run: echo "TEST_RESPONSE_COD=$(curl -o /dev/null -s -w \"%{http_code}\" https://<% index .Params `stagingFrontendSubdomain` %><% index .Params `stagingHostRoot` %>)" >> $GITHUB_ENV
116+
- if: ${{env.TEST_RESPONSE_COD != '"200"'}}
117+
run: exit 1
118+
production-deploy:
119+
needs: [build, integration-test]
120+
runs-on: ubuntu-latest
121+
name: production-deploy
122+
env:
123+
region: <% index .Params `region` %>
124+
# with `needs` declared, the job will have access to the output of the needed step
125+
# and in the needed step(`build`) we have the output of what should be instantly deployed
126+
strategy:
127+
matrix: ${{ fromJSON(needs.build.outputs.needs-approval) }}
128+
steps:
129+
- uses: actions/checkout@v2
130+
- name: Configure AWS credentials for S3 sync
131+
uses: aws-actions/configure-aws-credentials@v1
132+
with:
133+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
134+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
135+
aws-region: ${{ env.region }}
136+
- name: Download build artifact from Github
137+
uses: actions/download-artifact@v1
138+
with:
139+
name: ${{ matrix.buildName }}
140+
path: build/
141+
- name: Sync with S3
142+
run: |
143+
cd build
144+
aws s3 sync . s3://${{ matrix.bucket }}
145+
- name: Invalidate Cloudfront
146+
run: |
147+
export DIST_ID=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[?@=='${{ matrix.bucket }}']].Id | [0]" | tr -d '"')
148+
aws cloudfront create-invalidation --distribution-id ${DIST_ID} --paths "/*"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Pull request
2+
on:
3+
pull_request:
4+
branches: ['*']
5+
# Jobs
6+
jobs:
7+
lint:
8+
runs-on: ubuntu-latest
9+
env:
10+
CI: true
11+
build_env: production
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Run Lint
15+
uses: actions/setup-node@v1
16+
with:
17+
node-version: 14.x
18+
- run: |
19+
yarn
20+
yarn lint
21+
unit-test:
22+
runs-on: ubuntu-latest
23+
env:
24+
CI: true
25+
build_env: production
26+
steps:
27+
- uses: actions/checkout@v2
28+
- name: Run Unit Tests
29+
uses: actions/setup-node@v1
30+
with:
31+
node-version: 14.x
32+
- run: |
33+
yarn
34+
yarn test --watchAll=false

templates/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ ___
1212

1313
Your application is deployed to an AWS S3 bucket through CircleCi.
1414

15-
## Circle CI
15+
<%if eq (index .Params `CIVendor`) "circleci" %>## Circle CI
1616

1717
Your repository comes with a end-to-end CI/CD pipeline, which includes the following steps:
1818

@@ -29,6 +29,27 @@ The *Deploy* step does a:
2929
- Cloudfront Invalidation
3030

3131
To learn more your pipeline checkout your [CircleCi config file](.circleci/config.yml)
32+
<% else if eq (index .Params `CIVendor`) "github-actions" %>## Github actions
33+
Your repository comes with a end-to-end CI/CD pipeline, which includes the following steps:
34+
1. Checkout
35+
2. Unit Tests
36+
3. Build frontend (stage / prod)
37+
4. Deploy Staging
38+
5. Integration tests
39+
6. Deploy Production
40+
41+
**Note**: you can add a approval step using Github Environments(Available for Public repos/Github pro), you can configure an environment in the Settings tab then simply add the follow to your ci manifest (`./github/workflows/ci.yml`)
42+
```yml
43+
deploy-production: # or any step you would like to require Explicit approval
44+
enviroments:
45+
name: <env-name>
46+
```
47+
### Branch Protection
48+
Your repository comes with a protected branch `master` and you can edit Branch protection in **Branches** tab of Github settings. This ensures code passes tests before getting merged into your default branch.
49+
By default it requires `[lint, unit-test]` to be passing to allow Pull requests to merge.
50+
51+
<% end %>
52+
3253
___
3354

3455

zero-module.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ parameters:
7474
options:
7575
- "yes"
7676
- "no"
77+
- field: CIVendor
78+
label: Using either circleCI or github Actions to build / test your repository
79+
default: "circleci"
80+
options:
81+
- "circleci"
82+
- "github-actions"
7783

7884
conditions:
7985
- action: ignoreFile
@@ -88,3 +94,13 @@ conditions:
8894
- src/pages/Logout.js
8995
- src/api/
9096
- src/context/
97+
- action: ignoreFile
98+
matchField: CIVendor
99+
whenValue: "circleci"
100+
data:
101+
- .github/
102+
- action: ignoreFile
103+
matchField: CIVendor
104+
whenValue: "github-actions"
105+
data:
106+
- .circleci/

0 commit comments

Comments
 (0)