25
25
- web
26
26
- good-job
27
27
default : all
28
+ git_sha_to_deploy :
29
+ description : The git commit SHA to deploy.
30
+ required : false
31
+ type : string
28
32
workflow_call :
29
33
inputs :
30
34
environment :
38
42
required : true
39
43
type : string
40
44
41
- permissions : {}
45
+ permissions : { }
42
46
43
47
concurrency :
44
- group : deploy-application -${{ inputs.environment }}
48
+ group : deploy-mavis -${{ inputs.environment }}
45
49
46
50
env :
47
51
aws-role : ${{ inputs.environment == 'production'
48
52
&& 'arn:aws:iam::820242920762:role/GithubDeployMavisAndInfrastructure'
49
53
|| 'arn:aws:iam::393416225559:role/GithubDeployMavisAndInfrastructure' }}
54
+ web_codedeploy_application : mavis-${{ inputs.environment }}
55
+ web_codedeploy_group : blue-green-group-${{ inputs.environment }}
56
+ web_task_definition : mavis-web-task-definition-${{ inputs.environment }}
57
+ cluster_name : mavis-${{ inputs.environment }}
58
+ good_job_service : mavis-${{ inputs.environment }}-good-job
59
+ good_job_task_definition : mavis-good-job-task-definition-${{ inputs.environment }}
50
60
51
61
jobs :
52
62
prepare-deployment :
@@ -65,102 +75,244 @@ jobs:
65
75
with :
66
76
role-to-assume : ${{ env.aws-role }}
67
77
aws-region : eu-west-2
68
- - name : Install terraform
69
- uses : hashicorp/setup-terraform@v3
70
- with :
71
- terraform_version : 1.11.4
72
- - name : Get terraform output
73
- id : terraform-output
74
- working-directory : terraform/app
78
+ - name : Get image digest from ECR
79
+ id : get-image-digest
80
+ run : |
81
+ # Get AWS account ID and construct repository URI
82
+ AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
83
+ REPOSITORY_URI="${AWS_ACCOUNT_ID}.dkr.ecr.eu-west-2.amazonaws.com/mavis"
84
+
85
+ # Get the image digest for the git SHA
86
+ IMAGE_DIGEST=$(aws ecr describe-images \
87
+ --repository-name mavis/webapp \
88
+ --image-ids imageTag=${{ inputs.git_sha_to_deploy || github.sha }} \
89
+ --query 'imageDetails[0].imageDigest' \
90
+ --output text)
91
+
92
+ NEW_IMAGE_URI="${REPOSITORY_URI}@${IMAGE_DIGEST}"
93
+ echo "new-image-uri=${NEW_IMAGE_URI}" >> $GITHUB_OUTPUT
94
+ echo "New image URI: ${NEW_IMAGE_URI}"
95
+ - name : Get dynamic variables for web
96
+ id : get-dynamic-vars-web
97
+ run : |
98
+ # Fetch SSM parameter for web service
99
+ SSM_OUTPUT=$(aws ssm get-parameter --name /${{ inputs.environment }}/ecs/web/container_variables --query Parameter.Value --output text)
100
+
101
+ # Extract environment variables in the format expected by amazon-ecs-render-task-definition
102
+ ENV_VARS=$(echo "$SSM_OUTPUT" | jq -r '.task_envs | map("\(.name): \(.value)") | join("\n")')
103
+
104
+ # Extract secrets in the format expected by amazon-ecs-render-task-definition
105
+ SECRETS=$(echo "$SSM_OUTPUT" | jq -r '.task_secrets | map("\(.name): \(.valueFrom)") | join("\n")')
106
+
107
+ # Set outputs using multiline format
108
+ echo "environment-variables<<EOF" >> $GITHUB_OUTPUT
109
+ echo "$ENV_VARS" >> $GITHUB_OUTPUT
110
+ echo "EOF" >> $GITHUB_OUTPUT
111
+
112
+ echo "secrets<<EOF" >> $GITHUB_OUTPUT
113
+ echo "$SECRETS" >> $GITHUB_OUTPUT
114
+ echo "EOF" >> $GITHUB_OUTPUT
115
+
116
+ # Also extract role ARNs for potential future use
117
+ EXECUTION_ROLE_ARN=$(echo "$SSM_OUTPUT" | jq -r '.execution_role_arn')
118
+ TASK_ROLE_ARN=$(echo "$SSM_OUTPUT" | jq -r '.task_role_arn')
119
+
120
+ echo "execution-role-arn=$EXECUTION_ROLE_ARN" >> $GITHUB_OUTPUT
121
+ echo "task-role-arn=$TASK_ROLE_ARN" >> $GITHUB_OUTPUT
122
+
123
+ echo "Environment variables:"
124
+ echo "$ENV_VARS"
125
+ echo ""
126
+ echo "Secrets:"
127
+ echo "$SECRETS"
128
+ - name : Get dynamic variables for good-job
129
+ id : get-dynamic-vars-good-job
130
+ if : inputs.server_types == 'good-job' || inputs.server_types == 'all'
131
+ run : |
132
+ # Fetch SSM parameter for good-job service
133
+ SSM_OUTPUT=$(aws ssm get-parameter --name /${{ inputs.environment }}/ecs/good-job/container_variables --query Parameter.Value --output text)
134
+
135
+ # Extract environment variables in the format expected by amazon-ecs-render-task-definition
136
+ ENV_VARS=$(echo "$SSM_OUTPUT" | jq -r '.task_envs | map("\(.name): \(.value)") | join("\n")')
137
+
138
+ # Extract secrets in the format expected by amazon-ecs-render-task-definition
139
+ SECRETS=$(echo "$SSM_OUTPUT" | jq -r '.task_secrets | map("\(.name): \(.valueFrom)") | join("\n")')
140
+
141
+ # Set outputs using multiline format
142
+ echo "environment-variables<<EOF" >> $GITHUB_OUTPUT
143
+ echo "$ENV_VARS" >> $GITHUB_OUTPUT
144
+ echo "EOF" >> $GITHUB_OUTPUT
145
+
146
+ echo "secrets<<EOF" >> $GITHUB_OUTPUT
147
+ echo "$SECRETS" >> $GITHUB_OUTPUT
148
+ echo "EOF" >> $GITHUB_OUTPUT
149
+
150
+ # Also extract role ARNs for potential future use
151
+ EXECUTION_ROLE_ARN=$(echo "$SSM_OUTPUT" | jq -r '.execution_role_arn')
152
+ TASK_ROLE_ARN=$(echo "$SSM_OUTPUT" | jq -r '.task_role_arn')
153
+
154
+ echo "execution-role-arn=$EXECUTION_ROLE_ARN" >> $GITHUB_OUTPUT
155
+ echo "task-role-arn=$TASK_ROLE_ARN" >> $GITHUB_OUTPUT
156
+
157
+ echo "Environment variables:"
158
+ echo "$ENV_VARS"
159
+ echo ""
160
+ echo "Secrets:"
161
+ echo "$SECRETS"
162
+ - name : Create web task definition template
163
+ if : inputs.server_types == 'web' || inputs.server_types == 'all'
164
+ run : |
165
+ # Create a web-specific task definition template
166
+ cp config/task-definition.json.tpl web-task-definition.json.tpl
167
+ sed -i "s/<SERVER_TYPE>/web/g" web-task-definition.json.tpl
168
+ sed -i "s/<ENV>/${{ inputs.environment }}/g" web-task-definition.json.tpl
169
+ - name : Create good-job task definition template
170
+ if : inputs.server_types == 'good-job' || inputs.server_types == 'all'
75
171
run : |
76
- set -e
77
- terraform init -backend-config=env/${{ inputs.environment }}-backend.hcl -reconfigure
78
- terraform output -json | jq -r '
79
- "s3_bucket=" + .s3_bucket.value,
80
- "s3_key=" + .s3_key.value,
81
- "application=" + .codedeploy_application_name.value,
82
- "application_group=" + .codedeploy_deployment_group_name.value,
83
- "cluster_name=" + .ecs_variables.value.cluster_name,
84
- "good_job_service=" + .ecs_variables.value.good_job.service_name,
85
- "good_job_task_definition=" + .ecs_variables.value.good_job.task_definition.arn
86
- ' > ${{ runner.temp }}/DEPLOYMENT_ENVS
87
- - name : Upload Artifact
172
+ # Create a good-job-specific task definition template
173
+ cp config/task-definition.json.tpl good-job-task-definition.json.tpl
174
+ sed -i "s/<SERVER_TYPE>/good-job/g" good-job-task-definition.json.tpl
175
+ sed -i "s/<ENV>/${{ inputs.environment }}/g" good-job-task-definition.json.tpl
176
+ - name : Render web task definition
177
+ if : inputs.server_types == 'web' || inputs.server_types == 'all'
178
+ id : render-web-task-definition
179
+ uses : aws-actions/amazon-ecs-render-task-definition@v1
180
+ with :
181
+ task-definition : web-task-definition.json.tpl
182
+ container-name : application
183
+ image : ${{ steps.get-image-digest.outputs.new-image-uri }}
184
+ environment-variables : ${{ steps.get-dynamic-vars-web.outputs.environment-variables }}
185
+ secrets : ${{ steps.get-dynamic-vars-web.outputs.secrets }}
186
+ - name : Render good-job task definition
187
+ if : inputs.server_types == 'good-job' || inputs.server_types == 'all'
188
+ id : render-good-job-task-definition
189
+ uses : aws-actions/amazon-ecs-render-task-definition@v1
190
+ with :
191
+ task-definition : good-job-task-definition.json.tpl
192
+ container-name : application
193
+ image : ${{ steps.get-image-digest.outputs.new-image-uri }}
194
+ environment-variables : ${{ steps.get-dynamic-vars-good-job.outputs.environment-variables }}
195
+ secrets : ${{ steps.get-dynamic-vars-good-job.outputs.secrets }}
196
+ - name : Upload web task definition artifact
197
+ if : inputs.server_types == 'web' || inputs.server_types == 'all'
198
+ uses : actions/upload-artifact@v4
199
+ with :
200
+ name : web-task-definition
201
+ path : ${{ steps.render-web-task-definition.outputs.task-definition }}
202
+ retention-days : 1
203
+ - name : Upload good-job task definition artifact
204
+ if : inputs.server_types == 'good-job' || inputs.server_types == 'all'
88
205
uses : actions/upload-artifact@v4
89
206
with :
90
- name : DEPLOYMENT_ENVS-${{ inputs.environment }}
91
- path : ${{ runner.temp }}/DEPLOYMENT_ENVS
207
+ name : good-job-task-definition
208
+ path : ${{ steps.render-good-job-task-definition.outputs.task-definition }}
209
+ retention-days : 1
210
+ outputs :
211
+ new-image-uri : ${{ steps.get-image-digest.outputs.new-image-uri }}
212
+ web-environment-variables : ${{ steps.get-dynamic-vars-web.outputs.environment-variables }}
213
+ web-secrets : ${{ steps.get-dynamic-vars-web.outputs.secrets }}
214
+ web-execution-role-arn : ${{ steps.get-dynamic-vars-web.outputs.execution-role-arn }}
215
+ web-task-role-arn : ${{ steps.get-dynamic-vars-web.outputs.task-role-arn }}
216
+ good-job-environment-variables : ${{ steps.get-dynamic-vars-good-job.outputs.environment-variables }}
217
+ good-job-secrets : ${{ steps.get-dynamic-vars-good-job.outputs.secrets }}
218
+ good-job-execution-role-arn : ${{ steps.get-dynamic-vars-good-job.outputs.execution-role-arn }}
219
+ good-job-task-role-arn : ${{ steps.get-dynamic-vars-good-job.outputs.task-role-arn }}
92
220
93
- create -web-deployment :
94
- name : Create web deployment
221
+ deploy -web :
222
+ name : Deploy web service
95
223
runs-on : ubuntu-latest
96
- needs : prepare-deployment
97
224
if : inputs.server_types == 'web' || inputs.server_types == 'all'
225
+ needs : prepare-deployment
226
+ environment : ${{ inputs.environment }}
98
227
permissions :
99
228
id-token : write
100
229
steps :
101
- - name : Download artifact
102
- uses : actions/download-artifact@v4
103
- with :
104
- name : DEPLOYMENT_ENVS-${{ inputs.environment }}
105
- path : ${{ runner.temp }}
106
230
- name : Configure AWS Credentials
107
231
uses : aws-actions/configure-aws-credentials@v4
108
232
with :
109
233
role-to-assume : ${{ env.aws-role }}
110
234
aws-region : eu-west-2
111
- - name : Trigger CodeDeploy deployment
235
+ - name : Download web task definition artifact
236
+ uses : actions/download-artifact@v4
237
+ with :
238
+ name : web-task-definition
239
+ path : ./artifacts
240
+ - name : Register new task definition
241
+ run : |
242
+ # Find the task definition file in the artifacts directory
243
+ TASK_DEF_FILE=$(find ./artifacts -name "*.json" -type f | head -1)
244
+ aws ecs register-task-definition --cli-input-json file://$TASK_DEF_FILE
245
+ - name : Deploy with CodeDeploy
112
246
run : |
113
- set -e
114
- source ${{ runner.temp }}/DEPLOYMENT_ENVS
247
+ # Create CodeDeploy deployment for blue-green deployment
115
248
deployment_id=$(aws deploy create-deployment \
116
- --application-name "$application" --deployment-group-name "$application_group" \
117
- --s3-location bucket="$s3_bucket",key="$s3_key",bundleType=yaml | jq -r .deploymentId)
118
- echo "Deployment started: $deployment_id"
249
+ --application-name ${{ env.web_codedeploy_application }} \
250
+ --deployment-group-name ${{ env.web_codedeploy_group }} \
251
+ --deployment-config-name CodeDeployDefault.ECSBlueGreenCanary10Percent5Minutes \
252
+ --description "Deployment from GitHub Actions" \
253
+ | jq -r .deploymentId)
254
+
255
+ echo "CodeDeploy deployment started: $deployment_id"
119
256
echo "deployment_id=$deployment_id" >> $GITHUB_ENV
120
- - name : Wait up to 30 minutes for deployment to complete
257
+ - name : Wait for deployment to complete
121
258
run : |
122
- set -e
259
+ echo "Waiting for CodeDeploy deployment $deployment_id to complete..."
123
260
aws deploy wait deployment-successful --deployment-id "$deployment_id"
124
261
echo "Deployment successful"
125
262
126
- create -good-job-deployment :
127
- name : Create good-job deployment
263
+ deploy -good-job :
264
+ name : Deploy good-job service
128
265
runs-on : ubuntu-latest
129
- needs : prepare-deployment
130
266
if : inputs.server_types == 'good-job' || inputs.server_types == 'all'
267
+ needs : prepare-deployment
268
+ environment : ${{ inputs.environment }}
131
269
permissions :
132
270
id-token : write
133
271
steps :
134
- - name : Download Artifact
135
- uses : actions/download-artifact@v4
136
- with :
137
- name : DEPLOYMENT_ENVS-${{ inputs.environment }}
138
- path : ${{ runner.temp }}
139
272
- name : Configure AWS Credentials
140
273
uses : aws-actions/configure-aws-credentials@v4
141
274
with :
142
275
role-to-assume : ${{ env.aws-role }}
143
276
aws-region : eu-west-2
144
- - name : Trigger ECS Deployment
277
+ - name : Download good-job task definition artifact
278
+ uses : actions/download-artifact@v4
279
+ with :
280
+ name : good-job-task-definition
281
+ path : ./artifacts
282
+ - name : Register new task definition
145
283
run : |
146
- set -e
147
- source ${{ runner.temp }}/DEPLOYMENT_ENVS
148
- DEPLOYMENT_ID=$(aws ecs update-service --cluster $cluster_name --service $good_job_service \
149
- --task-definition $good_job_task_definition --force-new-deployment \
150
- --query 'service.deployments[?rolloutState==`IN_PROGRESS`].[id][0]' --output text)
151
- echo "Deployment started: $DEPLOYMENT_ID"
284
+ # Find the task definition file in the artifacts directory
285
+ TASK_DEF_FILE=$(find ./artifacts -name "*.json" -type f | head -1)
286
+ TASK_DEFINITION_ARN=$(aws ecs register-task-definition --cli-input-json file://$TASK_DEF_FILE --query 'taskDefinition.taskDefinitionArn' --output text)
287
+ echo "New task definition registered: $TASK_DEFINITION_ARN"
288
+ echo "task_definition_arn=$TASK_DEFINITION_ARN" >> $GITHUB_ENV
289
+ - name : Update ECS service
290
+ run : |
291
+ # Update the good-job service with the new task definition
292
+ DEPLOYMENT_ID=$(aws ecs update-service \
293
+ --cluster ${{ env.cluster_name }} \
294
+ --service ${{ env.good_job_service }} \
295
+ --task-definition "$task_definition_arn" \
296
+ --force-new-deployment \
297
+ --query 'service.deployments[?rolloutState==`IN_PROGRESS`].[id][0]' \
298
+ --output text)
299
+
300
+ echo "ECS deployment started: $DEPLOYMENT_ID"
152
301
echo "deployment_id=$DEPLOYMENT_ID" >> $GITHUB_ENV
153
302
- name : Wait for deployment to complete
154
303
run : |
155
- set -e
156
- source ${{ runner.temp }}/DEPLOYMENT_ENVS
304
+ echo "Waiting for ECS deployment $deployment_id to complete..."
157
305
DEPLOYMENT_STATE=IN_PROGRESS
158
306
while [ "$DEPLOYMENT_STATE" == "IN_PROGRESS" ]; do
159
- echo "Waiting for deployment to complete ..."
307
+ echo "Checking deployment status ..."
160
308
sleep 30
161
- DEPLOYMENT_STATE="$(aws ecs describe-services --cluster $cluster_name --services $good_job_service \
162
- --query "services[0].deployments[?id == \`$deployment_id\`].[rolloutState][0]" --output text)"
309
+ DEPLOYMENT_STATE=$(aws ecs describe-services \
310
+ --cluster ${{ env.cluster_name }} \
311
+ --services ${{ env.good_job_service }} \
312
+ --query "services[0].deployments[?id == \`$deployment_id\`].[rolloutState][0]" \
313
+ --output text)
163
314
done
315
+
164
316
if [ "$DEPLOYMENT_STATE" != "COMPLETED" ]; then
165
317
echo "Deployment failed with state: $DEPLOYMENT_STATE"
166
318
exit 1
0 commit comments