-
Notifications
You must be signed in to change notification settings - Fork 0
344 lines (291 loc) · 13.9 KB
/
ci-cd.yml
File metadata and controls
344 lines (291 loc) · 13.9 KB
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
name: CI-CD
on:
workflow_dispatch:
inputs:
env:
description: 'Please select your command'
type: choice
options:
- SCHEMA_CHANGED
- JUST_INDEX
- MIGRATE_UP
- MIGRATE_DOWN_UP
build:
description: 'Build new image'
type: boolean
default: false
env:
REGION: eu-central-1
PLATFORM: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}
REPO_NAME: ${{ github.event.repository.name }}
ECR_REPOSITORY: ${{ github.event.repository.name }}-${{ github.ref_name == 'main' && 'prod' || 'dev' }}
AWS_ACCOUNT_ID: ${{ github.ref_name == 'main' && secrets.ACCOUNT_ID_PROD || secrets.ACCOUNT_ID_DEV }}
permissions:
id-token: write
contents: read
jobs:
validate-branch:
runs-on: ubuntu-latest
steps:
- name: Check branch name
run: |
if [[ "${{ github.ref_name }}" != "main" && "${{ github.ref_name }}" != "dev" ]]; then
echo "Error: Workflow can only run on 'main' or 'dev' branches"
exit 1
fi
build:
name: Build
runs-on: ubuntu-latest
environment: ${{ github.ref_name == 'main' && 'production' || 'development' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{env.AWS_ACCOUNT_ID}}:role/platform-github-action-role-${{env.PLATFORM}}
aws-region: ${{ env.REGION }}
role-duration-seconds: 3600
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Check out code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set ECR environment variables
run: |
echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
echo "IMAGE_TAG=$PLATFORM-${GITHUB_SHA::8}" >> $GITHUB_ENV
- name: Check if image exists
run: |
if aws ecr describe-images --repository-name $ECR_REPOSITORY --image-ids imageTag=$IMAGE_TAG 2>/dev/null; then
echo "IMAGE_EXISTS=true" >> $GITHUB_ENV
else
echo "IMAGE_EXISTS=false" >> $GITHUB_ENV
fi
- name: If skip build or image exists, set image tag to latest remote
id: get-latest-image-tag
if: inputs.build == false || env.IMAGE_EXISTS == 'true'
run: |
LATEST_TAG=$(aws ecr describe-images --repository-name $ECR_REPOSITORY --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]' --output text)
if [ -n "$LATEST_TAG" ]; then
echo "IMAGE_TAG=$LATEST_TAG" >> $GITHUB_ENV
fi
- name: Build, tag, and push image to Amazon ECR
id: build-image
if: env.IMAGE_EXISTS == 'false' && inputs.build == true
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- uses: act10ns/slack@v1
if: always() && inputs.build == true
with:
status: ${{ job.status }}
channel: 'a-deployments'
config: .github/config/slack.yml
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
outputs:
image_tag: ${{ env.IMAGE_TAG }}
deploy:
runs-on: ubuntu-latest
needs: build
environment: ${{ github.ref_name == 'main' && 'production' || 'development' }}
if: always() && !cancelled()
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{env.AWS_ACCOUNT_ID}}:role/platform-github-action-role-${{env.PLATFORM}}
aws-region: ${{ env.REGION }}
role-duration-seconds: 3600
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Set ECR environment variables
run: |
echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV
- name: Get Latest Image ARN
id: get-image-arn
run: |
IMAGE_URI="$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
echo "Verifying image: $IMAGE_URI"
if ! aws ecr describe-images --repository-name $ECR_REPOSITORY --image-ids imageTag=$IMAGE_TAG 2>/dev/null; then
echo "Image verification failed: $IMAGE_URI not found in repository $ECR_REPOSITORY"
exit 1
fi
echo "Image verification successful"
echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV
- name: Create Task Definition
id: task-def
run: |
MIGRATE_SETUP='node -e "require('\''./generated/src/db/Migrations.res.js'\'').runUpMigrations(true, true)"'
MIGRATE_UP='node -e "require('\''./generated/src/db/Migrations.res.js'\'').runUpMigrations(true)"'
MIGRATE_DOWN='node -e "require('\''./generated/src/db/Migrations.res.js'\'').runDownMigrations(true)"'
GRANT_PERMISSIONS='pnpm ts-node scripts/grant-aggregate-permissions.ts'
RUN_INDEXER='TUI_OFF=true pnpm ts-node generated/src/Index.res.js'
case "${{ inputs.env }}" in
"SCHEMA_CHANGED")
CMD="$MIGRATE_SETUP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"MIGRATE_UP")
CMD="$MIGRATE_UP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"MIGRATE_DOWN_UP")
CMD="$MIGRATE_DOWN && $MIGRATE_UP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"JUST_INDEX")
CMD="$GRANT_PERMISSIONS && $RUN_INDEXER"
;;
*)
echo "Invalid command type" && exit 1
;;
esac
aws ecs describe-task-definition --task-definition indexer-${{ env.PLATFORM }} --query 'taskDefinition' > task-definition-full.json
jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' task-definition-full.json > task-definition.json
RUNNING_TASK_ARN=$(aws ecs list-tasks --cluster ${{env.PLATFORM}}-services-cluster --service-name ${{ env.REPO_NAME }}-${{env.PLATFORM}}-svc --desired-status RUNNING --query 'taskArns[0]' --output text)
if [[ "$RUNNING_TASK_ARN" != "None" && -n "$RUNNING_TASK_ARN" ]]; then
RUNNING_TASK_DEF_ARN=$(aws ecs describe-tasks --cluster ${{env.PLATFORM}}-services-cluster --tasks $RUNNING_TASK_ARN --query 'tasks[0].taskDefinitionArn' --output text)
if [[ "$RUNNING_TASK_DEF_ARN" != "None" && -n "$RUNNING_TASK_DEF_ARN" ]]; then
aws ecs describe-task-definition --task-definition indexer-${{ env.PLATFORM }} --query 'taskDefinition' > running-task-definition.json
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' running-task-definition.json | grep -o 'blue\|green')
else
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' task-definition.json | grep -o 'blue\|green')
fi
else
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' task-definition.json | grep -o 'blue\|green')
fi
if [[ "${{ inputs.env }}" == "SCHEMA_CHANGED" || "${{ inputs.env }}" == "MIGRATE_DOWN_UP" ]]; then
NEW_DB=$([ "$CURRENT_DB" = "blue" ] && echo "green" || echo "blue")
echo "Switching database to $NEW_DB from $CURRENT_DB"
else
NEW_DB=$CURRENT_DB
echo "Keeping current database $CURRENT_DB as chosen command is not SCHEMA_CHANGED or MIGRATE_DOWN_UP"
fi
jq --arg cmd "$CMD" --arg img "$IMAGE_URI" --arg new_db "$NEW_DB" '
(.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value) |= sub("/(blue|green)\\?"; "/\($new_db)?") |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .environment[] | select(.name == "ENVIO_PG_DATABASE") | .value) = $new_db |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .command) = ["sh", "-c", $cmd] |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .image) = $img
' task-definition.json > updated-task-definition.json
mv updated-task-definition.json task-definition.json
echo "TASK_DEFINITION_ARN=$(aws ecs register-task-definition --cli-input-json file://task-definition.json --query 'taskDefinition.taskDefinitionArn' --output text)" >> $GITHUB_ENV
- name: Create CodeDeploy Deployment
timeout-minutes: 45
run: |
cat > appspec.json << EOF
{
"version": "0.0",
"Resources": [
{
"TargetService": {
"Type": "AWS::ECS::Service",
"Properties": {
"TaskDefinition": "${{ env.TASK_DEFINITION_ARN }}",
"LoadBalancerInfo": {
"ContainerName": "graphql-engine",
"ContainerPort": 8080
},
"PlatformVersion": "LATEST"
}
}
}
]
}
EOF
APPSPEC_CONTENT=$(cat appspec.json | jq -c . | jq -R .)
cat > revision.json << EOF
{
"revisionType": "AppSpecContent",
"appSpecContent": {
"content": ${APPSPEC_CONTENT}
}
}
EOF
echo "Revision JSON content:"
cat revision.json
aws deploy create-deployment \
--application-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }} \
--deployment-group-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }}-dg \
--revision file://revision.json \
--deployment-config-name CodeDeployDefault.ECSAllAtOnce \
--region ${{ env.REGION }} \
--description "Deploying ${{ env.REPO_NAME }}-${{ env.PLATFORM }} with task definition ${{ env.TASK_DEFINITION_ARN }}"
DEPLOYMENT_ID=$(aws deploy list-deployments \
--application-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }} \
--deployment-group-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }}-dg \
--query 'deployments[0]' \
--output text)
echo "Waiting for deployment $DEPLOYMENT_ID to complete..."
while true; do
STATUS=$(aws deploy get-deployment \
--deployment-id $DEPLOYMENT_ID \
--query 'deploymentInfo.status' \
--output text)
if [[ "$STATUS" == "Succeeded" ]]; then
echo "Deployment completed successfully!"
break
elif [[ "$STATUS" == "Failed" || "$STATUS" == "Stopped" ]]; then
echo "Deployment failed with status: $STATUS"
aws deploy get-deployment \
--deployment-id $DEPLOYMENT_ID \
--query 'deploymentInfo.errorInformation' \
--output json
exit 1
fi
echo "Deployment status: $STATUS. Waiting..."
sleep 30
done
- name: Update HTTPS Target Group
run: |
# Define the ECS cluster, service, and region
# Get ECS service load balancer information
SERVICE_INFO=$(aws ecs describe-services \
--cluster ${{env.PLATFORM}}-services-cluster \
--services ${{ env.REPO_NAME }}-${{env.PLATFORM}}-svc \
--query 'services[0].loadBalancers' \
--region ${{ env.REGION }} \
--output json)
# Extract the target group ARN used by the service on HTTP port (port 80 or custom port)
TARGET_GROUP_ARN=$(echo $SERVICE_INFO | jq -r '.[] | select(.containerPort == 8080) | .targetGroupArn')
if [ -z "$TARGET_GROUP_ARN" ]; then
echo "Error: Could not find HTTP target group"
exit 1
fi
echo "Found HTTP Target Group ARN: $TARGET_GROUP_ARN"
# Get Load Balancer ARN associated with the target group
LB_ARN=$(aws elbv2 describe-target-groups \
--target-group-arns "$TARGET_GROUP_ARN" \
--query 'TargetGroups[0].LoadBalancerArns[0]' \
--output text \
--region ${{ env.REGION }})
if [ -z "$LB_ARN" ]; then
echo "Error: Could not find Load Balancer ARN"
exit 1
fi
echo "Found Load Balancer ARN: $LB_ARN"
# Get the HTTPS Listener ARN (port 443) from the Load Balancer
HTTPS_LISTENER_ARN=$(aws elbv2 describe-listeners \
--load-balancer-arn "$LB_ARN" \
--query 'Listeners[?Port==`443`].ListenerArn' \
--output text \
--region ${{ env.REGION }})
if [ -z "$HTTPS_LISTENER_ARN" ]; then
echo "Error: Could not find HTTPS listener (port 443)"
exit 1
fi
echo "Found HTTPS Listener ARN: $HTTPS_LISTENER_ARN"
# Modify the HTTPS listener to forward traffic to the HTTP target group
aws elbv2 modify-listener \
--listener-arn "$HTTPS_LISTENER_ARN" \
--default-actions Type=forward,TargetGroupArn="$TARGET_GROUP_ARN" \
--region ${{ env.REGION }}
echo "Successfully updated HTTPS listener to forward to HTTP target group"
- name: Slack Notification
uses: act10ns/slack@v1
with:
status: ${{ job.status }}
channel: 'a-deployments'
config: .github/config/slack.yml
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
steps: ${{ toJson(steps) }}
if: always()