-
Notifications
You must be signed in to change notification settings - Fork 22
487 lines (432 loc) · 19.8 KB
/
deploy-changed-cf.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
name: Deploy changed CloudFormation templates
on:
workflow_dispatch:
inputs:
environment_name:
type: string
description: Select the environment name to run the actions on
required: true
default: all
environment_type:
type: choice
description: Select environment type
options:
- staging
- production
- staging and production
default: staging
push:
branches:
- master
paths:
- 'cf/**.yaml'
- 'cf/**.yml'
pull_request:
branches:
- master
paths:
- 'cf/**.yaml'
- 'cf/**.yml'
permissions:
id-token: write
contents: read
jobs:
load-config:
uses: ./.github/workflows/load-config.yaml
with:
environment_name: ${{ github.event.inputs.environment_name }}
environment_type: ${{ github.event.inputs.environment_type }}
check-secrets:
name: Check secrets
needs: load-config
runs-on: ubuntu-20.04
if: github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch'
strategy:
matrix:
environment_name: ${{fromJson(needs.load-config.outputs.environment_names)}}
environment: ${{ matrix.environment_name }}
steps:
- id: check-secrets
name: Check if necessary secrets are installed.
run: |-
if [ -z "${{ secrets.AWS_ACCOUNT_ID }}" ]
then
echo "This workflow requires AWS_ACCOUNT_ID defined in this repository secrets to complete."
echo "The secrets can be set/rotated by running 'rotate-ci' from 'cellenics-utils'."
ERROR=true
fi
if [ ! -z "$ERROR" ]
then
echo
echo This workflow requires some secrets to complete.
echo Please make they are created by adding/rotating them manually.
exit 1
fi
get-modified-templates:
name: Fetch paths to modified CloudFormation templates
runs-on: ubuntu-20.04
needs: load-config
outputs:
files: ${{ steps.exclude-services.outputs.files }}
num-files: ${{ steps.exclude-services.outputs.num-files }}
steps:
- id: checkout
name: Check out current branch source code
uses: actions/checkout@v3
with:
fetch-depth: 0
- id: validate-workflow-dispatch
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'
name: Validate workflow dispatch
run: |-
echo Deploying the master branch via workflow_dispatch is not supported.
echo To deploy master branch, raise a PR with changes to the CF files
echo that needs to be deployed and merge the PR.
exit 1
- id: get-changed-files-on-pr-merge
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
name: Get changed files on PR merge to master
uses: lots0logs/[email protected]
with:
token: ${{ secrets.GITHUB_TOKEN }}
- id: get-changed-files-on-branch
if: github.ref != 'refs/heads/master'
name: Get changed files on branch
run: |
echo "[]" > empty.json
# Get name of changed files
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT remotes/origin/master ${{ github.sha }})
# If there are no changed files, create an empty array
# else concatenate into an array
if [ -z "$CHANGED_FILES" ]; then
echo "[]" > ${HOME}/files.json
else
echo "$CHANGED_FILES" | xargs -I 'value' jq --arg filename "value" '. |= . + [$filename]' empty.json | jq -s 'add' > ${HOME}/files.json
fi
echo "All changed files"
cat ${HOME}/files.json
- id: check-number-of-cf-files
name: Check CloudFormation templates
run: |-
# Select those that are CF templates (path starts with `cf/`)
jq '[.[] | select(match("^cf/"))]' ${HOME}/files.json > ${HOME}/changed_cf_files.json
- id: exclude-services
name: Exclude services
run: |-
echo "Excluding services: $EXCLUDED_SERVICES"
# Select files that are not in excluded_services
jq --argjson excluded_services "$EXCLUDED_SERVICES" 'map(select(. as $in | $excluded_services | any(. == $in) | not))' ${HOME}/changed_cf_files.json > ${HOME}/filtered_cf_files.json
# Set as output the minified JSON.
echo "Filtered CF files"
cat ${HOME}/filtered_cf_files.json
echo "num-files=$(jq '. | length' ${HOME}/filtered_cf_files.json)" >> $GITHUB_OUTPUT
echo "files=$(jq -c . < ${HOME}/filtered_cf_files.json)" >> $GITHUB_OUTPUT
env:
EXCLUDED_SERVICES: ${{ needs.load-config.outputs.excluded_services }}
lint-templates:
name: Lint template files
runs-on: ubuntu-20.04
needs: get-modified-templates
if: needs.get-modified-templates.outputs.num-files > 0
strategy:
matrix:
template: ${{fromJson(needs.get-modified-templates.outputs.files)}}
steps:
- id: checkout
name: Check out source code
uses: actions/checkout@v3
- id: lint
name: Lint template
uses: scottbrenner/[email protected]
with:
args: ${{ matrix.template }}
deploy-templates:
name: Deploy changed CloudFormation template
runs-on: ubuntu-20.04
needs: [load-config, check-secrets, get-modified-templates, lint-templates]
if: github.ref == 'refs/heads/master' || github.event_name == 'workflow_dispatch'
outputs:
deploy-rds: ${{ steps.set-name.outputs.deploy-rds }}
strategy:
max-parallel: 1
matrix:
environment: ${{fromJson(needs.load-config.outputs.deployment_matrix)}}
template: ${{fromJson(needs.get-modified-templates.outputs.files)}}
environment: ${{ matrix.environment.name }}
env:
CLUSTER_ENV: ${{ matrix.environment.type }}
TEMPLATES_WITH_EXTRA_PARAMS: ("cf/sns.yaml" "cf/ses.yaml" "cf/post-register-lambda.yaml", "cf/canaries.yaml", "cf/cognito-case-insensitive.yaml", "cf/batch-job-definition.yaml")
steps:
- id: checkout
name: Check out source code
uses: actions/checkout@v3
# - name: Get parameters
# id: get-params
# run: echo "::set-output name=params::$(./get-params-for-template.sh ${{ matrix.template }})"
# shell: bash
- id: set-up-creds
name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ci-iac-role
aws-region: ${{ secrets.AWS_REGION }}
- id: set-name
name: Set name of the CloudFormation stack
run: |-
echo "Ref is $GITHUB_REF"
BASE_NAME=$(basename $FILE_NAME | sed "s/\..*//")
STACK_NAME=biomage-$BASE_NAME-$CLUSTER_ENV
echo "stack-name=$STACK_NAME" >> $GITHUB_OUTPUT
if [ "$BASE_NAME" == 'rds' ]; then
echo "deploy-rds=true" >> $GITHUB_OUTPUT
fi
env:
FILE_NAME: ${{ matrix.template }}
- id: using-self-signed-certificate
name: Get config for whether deployment is using self-signed certificate
uses: mikefarah/yq@master
with:
cmd: yq '.[env(ENVIRONMENT_NAME)].selfSignedCertificate' 'infra/config/github-environments-config.yaml'
env:
ENVIRONMENT_NAME: ${{ matrix.environment.name }}
## TODO - GET THE "parameter-overrides" dynamically, to use one deploy action for all templates
- id: deploy-template
name: Deploy CloudFormation template
if: ${{ !contains(matrix.template, 'irsa-') && !contains(env.TEMPLATES_WITH_EXTRA_PARAMS, matrix.template) }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-canaries-template
name: Deploy CloudFormation PostRegisterLambda template
if: ${{ matrix.template == 'cf/canaries.yaml' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }},SupportEmail=${{ needs.load-config.outputs.support_email }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-batch-job-definition-template
name: Deploy CloudFormation batch-job-definition template
if: ${{ matrix.template == 'cf/batch-job-definition.yaml' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: >-
Environment=${{ matrix.environment.type }},
ImageAccountId=${{ needs.load-config.outputs.image_account_id }},
ImageAccountRegion=${{ needs.load-config.outputs.image_account_region }}
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-cognito-case-insensitive
name: Deploy CloudFormation cognito-case-insensitive template
if: ${{ matrix.template == 'cf/cognito-case-insensitive.yaml' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: >-
Environment=${{ matrix.environment.type }},
ReplyEmail=${{ needs.load-config.outputs.reply_email }},
UserPoolDomainName=${{ needs.load-config.outputs.user_pool_domain_name }}
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-post-register-lambda-template
name: Deploy CloudFormation PostRegisterLambda template
if: ${{ matrix.template == 'cf/post-register-lambda.yaml' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }},UsingSelfSignedCert=${{ steps.using-self-signed-certificate.outputs.result }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-sns-template
name: Deploy CloudFormation SNS template
if: ${{ matrix.template == 'cf/sns.yaml' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }},UsingSelfSignedCert=${{ steps.using-self-signed-certificate.outputs.result }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
- id: deploy-ses-template
name: Deploy CloudFormation SES template
if: ${{ matrix.template == 'cf/ses.yaml' && needs.load-config.outputs.should_deploy_ses == 'true' }}
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }},DomainName=${{ secrets.DOMAIN_NAME }},PrimaryDomainName=${{ secrets.PRIMARY_DOMAIN_NAME }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
# The following steps are only necessary for IAM Service Account roles.
- id: get-oidc
if: ${{ contains(matrix.template, 'irsa-') }}
name: Get OIDC provider information for IRSA role
run: |-
OIDC_PROVIDER=$(aws eks describe-cluster --name "biomage-$CLUSTER_ENV" --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
echo "oidc-provider=$OIDC_PROVIDER" >> $GITHUB_OUTPUT
- id: deploy-irsa-template
if: ${{ contains(matrix.template, 'irsa-') }}
name: Deploy IRSA CloudFormation template
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
parameter-overrides: "Environment=${{ matrix.environment.type }},OIDCProvider=${{ steps.get-oidc.outputs.oidc-provider }}"
name: ${{ steps.set-name.outputs.stack-name }}
template: ${{ matrix.template }}
no-fail-on-empty-changeset: "1"
capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM"
setup-rds-roles:
name: Setup RDS roles for default resources
runs-on: ubuntu-20.04
needs: [check-secrets, deploy-templates, load-config]
if: ${{ needs.deploy-templates.outputs.deploy-rds == 'true' }}
strategy:
max-parallel: 1
matrix:
environment: ${{ fromJson(needs.load-config.outputs.deployment_matrix) }}
environment: ${{ matrix.environment.name }}
env:
CLUSTER_ENV: ${{ matrix.environment.type }}
steps:
- id: set-up-creds
name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ci-iac-role
aws-region: ${{ secrets.AWS_REGION }}
# This step is needed to change the environment name (e.g. staging) into its uppercase form.
# This is required because the action that is used in get-rds-secrets store the secret in environment variables,
# with uppercase letters (see action ref). Therefore, we need the uppercased form of the environment
# Which is output in .outputs.uppercase of this step.
- id: setup-rds-env
name: Uppercase environment name
uses: ASzc/change-string-case-action@v2
with:
string: ${{ matrix.environment.type }}
- id: get-rds-secrets
name: Export RDS secrets into environment variables
uses: abhilash1in/[email protected]
with:
secrets: aurora-${{ matrix.environment.type }}
parse-json: true
- id: check-rds-secrets
name: Check RDS secrets are fetched
run: |-
# These checks if the RDS username and passwords are set
if [ -z $AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_USERNAME ]; then
echo "RDS username not provided"
exit 1
fi
if [ -z $AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_PASSWORD ]; then
echo "RDS password not provided"
exit 1
fi
- id: setup-rds-roles
name: Setup RDS roles for default RDS instances
run: |-
INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=rds-${CLUSTER_ENV}-ssm-agent" \
--output text \
--query 'Reservations[*].Instances[*].InstanceId')
if [ -z $INSTANCE_ID ]; then
echo "Can not connect to RDS agent: No instances found for $CLUSTER_ENV"
exit 1
fi
CLUSTER_NAME=aurora-cluster-${CLUSTER_ENV}-default
RDSHOST=$(aws rds describe-db-cluster-endpoints \
--region $REGION \
--db-cluster-identifier $CLUSTER_NAME \
--filter Name=db-cluster-endpoint-type,Values='writer' \
--query 'DBClusterEndpoints[0].Endpoint' \
--output text)
if [ -z $RDSHOST ]; then
echo "Failed getting RDS host with name $CLUSTER_NAME"
exit 1
fi
ENSURE_PSQL_INSTALLED_COMMAND="sudo yum -y install postgresql"
aws ssm send-command --instance-ids "$INSTANCE_ID" \
--document-name AWS-RunShellScript \
--parameters "commands='$ENSURE_PSQL_INSTALLED_COMMAND'"
SETUP_ROLES_CMD="
PGPASSWORD=\'${AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_PASSWORD}\' psql \
--host=${RDSHOST} \
--port=5432 \
--username=${AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_USERNAME} \
--dbname=aurora_db <<EOF
CREATE ROLE api_role WITH LOGIN;
CREATE ROLE dev_role WITH LOGIN;
GRANT USAGE ON SCHEMA public TO api_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public to api_role;
GRANT dev_role TO ${AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_USERNAME};
ALTER DEFAULT PRIVILEGES FOR USER dev_role IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO api_role;
ALTER DEFAULT PRIVILEGES FOR USER dev_role IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO api_role;
REVOKE dev_role FROM ${AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_USERNAME};
GRANT rds_iam TO api_role;
GRANT rds_iam, ${AURORA_${{ steps.setup-rds-env.outputs.uppercase }}_USERNAME} TO dev_role;
EOF"
aws ssm send-command --instance-ids "$INSTANCE_ID" \
--document-name AWS-RunShellScript \
--parameters "commands='$SETUP_ROLES_CMD'"
env:
REGION: ${{ secrets.AWS_REGION }}
- id: install-crowdstrike-on-rds
name: Install CrowdStrike Sensor for default RDS instances
run: |-
if [[ -n "${{ secrets.FALCON_CID }}" ]];
then
INSTANCE_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=rds-${CLUSTER_ENV}-ssm-agent" \
--output text \
--query 'Reservations[*].Instances[*].InstanceId')
if [ -z $INSTANCE_ID ]; then
echo "Can not connect to RDS agent: No instances found for $CLUSTER_ENV"
exit 1
fi
CLUSTER_NAME=aurora-cluster-${CLUSTER_ENV}-default
RDSHOST=$(aws rds describe-db-cluster-endpoints \
--region $REGION \
--db-cluster-identifier $CLUSTER_NAME \
--filter Name=db-cluster-endpoint-type,Values='writer' \
--query 'DBClusterEndpoints[0].Endpoint' \
--output text)
if [ -z $RDSHOST ]; then
echo "Failed getting RDS host with name $CLUSTER_NAME"
exit 1
fi
INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.6.0/bash/install/falcon-linux-install.sh"
INSTALL_FALCON_COMMAND="
export FALCON_CLIENT_ID=${{ secrets.FALCON_CLIENT_ID }} && \
export FALCON_CLIENT_SECRET=${{ secrets.FALCON_CLIENT_SECRET }} && \
curl -O ${INSTALL_SCRIPT_URL} && \
bash falcon-linux-install.sh
"
aws ssm send-command --instance-ids "$INSTANCE_ID" \
--document-name AWS-RunShellScript \
--parameters "commands='$INSTALL_FALCON_COMMAND'"
else
echo "CrowdStrike CID missing, skipping falcon sensor setup"
fi
env:
REGION: ${{ secrets.AWS_REGION }}
report-if-failed:
name: Report if workflow failed
runs-on: ubuntu-20.04
needs: [check-secrets, lint-templates, deploy-templates, setup-rds-roles]
if: failure() && github.ref == 'refs/heads/master'
steps:
- id: send-to-slack
name: Send failure notification to Slack on failure
env:
SLACK_BOT_TOKEN: ${{ secrets.WORKFLOW_STATUS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: workflow-failures
status: FAILED
color: danger