Skip to content

Commit a5e6683

Browse files
authored
Feature/deployments v2 (#97)
ECS deployment workflow
1 parent 7e0886a commit a5e6683

22 files changed

Lines changed: 1326 additions & 159 deletions
Lines changed: 227 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,242 @@
11
name: Reusable Deploy
22

33
on:
4+
workflow_call:
5+
inputs:
6+
environment:
7+
required: true
8+
type: string
9+
description: 'Environment name (preprod or prod)'
10+
aws-region:
11+
required: false
12+
type: string
13+
default: 'eu-west-2'
14+
secrets:
15+
AWS_ROLE_ARN:
16+
required: true
17+
AWS_SECRETS_ARN:
18+
required: true
419
workflow_dispatch:
520
inputs:
621
environment:
722
required: true
823
type: choice
24+
description: 'Environment to deploy to'
925
options:
1026
- preprod
1127
- prod
28+
aws-region:
29+
required: false
30+
type: string
31+
default: 'eu-west-2'
32+
description: 'AWS region'
33+
34+
permissions:
35+
id-token: write
36+
contents: read
1237

1338
jobs:
14-
placeholder:
39+
load-services:
1540
runs-on: ubuntu-latest
41+
outputs:
42+
services: ${{ steps.load.outputs.services }}
1643
steps:
17-
- run: echo "Triggered from feature branch. This placeholder should not run."
44+
- name: Checkout
45+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
46+
47+
- name: Load service mappings
48+
id: load
49+
run: |
50+
services=$(jq -c '.services' infra/services.json)
51+
echo "services=${services}" >> $GITHUB_OUTPUT
52+
echo "Loaded services:"
53+
echo "${services}" | jq '.'
54+
55+
deploy:
56+
needs: load-services
57+
runs-on: ubuntu-latest
58+
environment: ${{ inputs.environment }}
59+
60+
strategy:
61+
matrix:
62+
service: ${{ fromJson(needs.load-services.outputs.services) }}
63+
fail-fast: false
64+
65+
steps:
66+
- name: Checkout
67+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
68+
69+
- name: Configure AWS credentials
70+
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
71+
with:
72+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
73+
aws-region: ${{ inputs.aws-region }}
74+
75+
- name: Login to Amazon ECR
76+
id: login-ecr
77+
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2.1.1
78+
79+
- name: Setup .NET
80+
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
81+
with:
82+
dotnet-version: 10.0.x
83+
84+
- name: Run tests
85+
run: dotnet test --configuration Release
86+
87+
- name: Build and Publish Image
88+
id: build-image
89+
env:
90+
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
91+
REPOSITORY: dms-${{ inputs.environment }}-apps
92+
IMAGE_TAG: ${{ matrix.service.name }}-${{ github.sha }}
93+
run: |
94+
PROJECT_PATH="src/${{ matrix.service.project }}"
95+
96+
if [ ! -f "$PROJECT_PATH" ]; then
97+
echo "Project not found: $PROJECT_PATH - Skipping"
98+
echo "skip=true" >> $GITHUB_OUTPUT
99+
exit 0
100+
fi
101+
102+
echo "Building and publishing: $PROJECT_PATH"
103+
104+
dotnet publish "$PROJECT_PATH" \
105+
--configuration Release \
106+
--target:PublishContainer \
107+
--property:ContainerRegistry=$REGISTRY \
108+
--property:ContainerRepository=$REPOSITORY \
109+
--property:ContainerImageTag=$IMAGE_TAG
110+
111+
IMAGE="${REGISTRY}/${REPOSITORY}:${IMAGE_TAG}"
112+
echo "IMAGE=${IMAGE}" >> $GITHUB_OUTPUT
113+
echo "skip=false" >> $GITHUB_OUTPUT
114+
echo "Published image: ${IMAGE}"
115+
116+
- name: Testing workflow steps
117+
run: echo "The testing / deployment of the image have been skipped for this demo, but the workflow is correctly iterating over the services and would deploy the built image to ECS if those steps were uncommented."
118+
119+
- name: Fetch required secrets
120+
id: fetch-secrets
121+
run: |
122+
SECRET_JSON=$(aws secretsmanager get-secret-value \
123+
--secret-id ${{ secrets.AWS_SECRETS_ARN }} \
124+
--query 'SecretString' \
125+
--output text)
126+
127+
TASK_ROLE=$(echo "$SECRET_JSON" | jq -r '.Task_Role_ARN')
128+
EXEC_ROLE=$(echo "$SECRET_JSON" | jq -r '.Execution_Role_ARN')
129+
EFS_FS_ID=$(echo "$SECRET_JSON" | jq -r '.EFS_File_System_ID')
130+
EFS_AP_ID=$(echo "$SECRET_JSON" | jq -r '.EFS_Access_Point_ID')
131+
132+
echo "task_role=$TASK_ROLE" >> $GITHUB_OUTPUT
133+
echo "exec_role=$EXEC_ROLE" >> $GITHUB_OUTPUT
134+
echo "efs-fs-id=$EFS_FS_ID" >> $GITHUB_OUTPUT
135+
echo "efs-ap-id=$EFS_AP_ID" >> $GITHUB_OUTPUT
136+
137+
- name: Build secrets list for service
138+
id: build-secrets
139+
run: |
140+
SECRETS_JSON='${{ toJson(matrix.service.secrets) }}'
141+
142+
SECRETS_STRING=""
143+
144+
declare -A SECRET_MAP=(
145+
["API_Base_URL"]="API__BaseUrl"
146+
["Authentication_ApiKey"]="Authentication__ApiKey"
147+
["S3_BucketName"]="AWS__S3__BucketName"
148+
["Client_ID"]="AzureAd__ClientId"
149+
["API_Client_ID"]="AzureAd__ClientId"
150+
["Client_Secret"]="AzureAd__ClientSecret"
151+
["AuditDb"]="ConnectionStrings__AuditDb"
152+
["ClusterDb"]="ConnectionStrings__ClusterDb"
153+
["DeliusRunningPictureDb"]="ConnectionStrings__DeliusRunningPictureDb"
154+
["DeliusStagingDb"]="ConnectionStrings__DeliusStagingDb"
155+
["MatchingDb"]="ConnectionStrings__MatchingDb"
156+
["OfflocRunningPictureDb"]="ConnectionStrings__OfflocRunningPictureDb"
157+
["OfflocStagingDb"]="ConnectionStrings__OfflocStagingDb"
158+
["CatsRabbitMQ"]="ConnectionStrings__CatsRabbitMQ"
159+
["RabbitMQ"]="ConnectionStrings__RabbitMQ"
160+
["DMSFilesBasePath"]="DMSFilesBasePath"
161+
["DMSFilesBasePath_DbInteractions"]="DMSFilesBasePath"
162+
["Sentry_Dsn"]="Sentry_Dsn"
163+
)
164+
165+
# Get the full secret ARN
166+
SECRET_ARN="${{ secrets.AWS_SECRETS_ARN }}"
167+
168+
for secret in $(echo "$SECRETS_JSON" | jq -r '.[]'); do
169+
env_var_name="${SECRET_MAP[$secret]}"
170+
if [ -n "$env_var_name" ]; then
171+
SECRETS_STRING+="${env_var_name}=${SECRET_ARN}:${secret}::"$'\n'
172+
fi
173+
done
174+
175+
echo "Built secrets string:"
176+
echo "$SECRETS_STRING"
177+
178+
SECRETS_STRING=$(echo "$SECRETS_STRING" | sed '/^$/d')
179+
echo "secrets<<EOF" >> $GITHUB_OUTPUT
180+
echo "$SECRETS_STRING" >> $GITHUB_OUTPUT
181+
echo "EOF" >> $GITHUB_OUTPUT
182+
183+
- name: Replace EFS placeholders and inject roles in task definition
184+
id: inject-roles
185+
run: |
186+
TASK_DEF_PATH="${{ matrix.service.taskDef }}"
187+
188+
if [ ! -f "$TASK_DEF_PATH" ]; then
189+
echo "Task definition not found: $TASK_DEF_PATH - Skipping"
190+
echo "skip=true" >> $GITHUB_OUTPUT
191+
exit 0
192+
fi
193+
194+
echo "Processing task definition: $TASK_DEF_PATH"
195+
196+
# Step 1: Replace placeholders (EFS, CloudWatch)
197+
LOG_GROUP_NAME="dms-${{ inputs.environment == 'prod' && 'prod' || 'preprod' }}/ecs"
198+
199+
sed -i.bak \
200+
-e 's/EFS_FILE_SYSTEM_ID/${{ steps.fetch-secrets.outputs.efs-fs-id }}/g' \
201+
-e 's/EFS_ACCESS_POINT_ID/${{ steps.fetch-secrets.outputs.efs-ap-id }}/g' \
202+
-e "s|PLACEHOLDER|$LOG_GROUP_NAME|g" \
203+
"$TASK_DEF_PATH"
204+
205+
# Step 2: Inject IAM roles and set family name to match Terraform convention
206+
FAMILY_NAME="dms-${{ inputs.environment == 'prod' && 'prod' || 'preprod' }}-${{ matrix.service.name }}-task"
207+
208+
UPDATED_TASK_DEF=$(jq \
209+
--arg task_role "${{ steps.fetch-secrets.outputs.task_role }}" \
210+
--arg exec_role "${{ steps.fetch-secrets.outputs.exec_role }}" \
211+
--arg family "$FAMILY_NAME" \
212+
'.taskRoleArn = $task_role | .executionRoleArn = $exec_role | .family = $family' \
213+
"$TASK_DEF_PATH")
214+
215+
UPDATED_TASK_DEF_FILE="rendered-task-def.json"
216+
echo "$UPDATED_TASK_DEF" > "$UPDATED_TASK_DEF_FILE"
217+
218+
echo "Updated task definition:"
219+
cat "$UPDATED_TASK_DEF_FILE" | jq '.'
220+
221+
echo "updated_task_def_file=$UPDATED_TASK_DEF_FILE" >> $GITHUB_OUTPUT
222+
echo "skip=false" >> $GITHUB_OUTPUT
223+
224+
- name: Render ECS task definition with secrets
225+
id: render-task-def
226+
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1.8.4
227+
with:
228+
task-definition: ${{ steps.inject-roles.outputs.updated_task_def_file }}
229+
container-name: ${{ matrix.service.container }}
230+
image: ${{ steps.build-image.outputs.IMAGE }}
231+
environment-variables: |
232+
DOTNET_ENVIRONMENT=${{ inputs.environment == 'prod' && 'Production' || 'PreProduction' }}
233+
secrets: ${{ steps.build-secrets.outputs.secrets }}
234+
235+
- name: Deploy ECS service
236+
if: steps.build-image.outputs.skip != 'true'
237+
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2.6.1
238+
with:
239+
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
240+
service: dms-${{ inputs.environment == 'prod' && 'prod' || 'preprod' }}-${{ matrix.service.name }}-service
241+
cluster: dms-${{ inputs.environment == 'prod' && 'prod' || 'preprod' }}-cluster
242+
wait-for-service-stability: true

.github/workflows/deploy.yml

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,30 @@ on:
66
- main
77

88
permissions:
9-
id-token: write # required for OIDC authentication with AWS
9+
id-token: write
10+
contents: read
1011

1112
jobs:
12-
deploy:
13-
runs-on: ubuntu-latest
14-
15-
environment: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}
16-
17-
env:
18-
REPOSITORY: dms_visualiser
19-
IMAGE_TAG: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}-${{ github.sha }}
20-
21-
steps:
22-
- name: Checkout
23-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
24-
25-
- name: Configure AWS credentials
26-
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
27-
with:
28-
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
29-
aws-region: eu-west-2
30-
31-
- name: Login to Amazon ECR
32-
id: login-ecr
33-
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2.1.1
34-
35-
- name: Setup .NET
36-
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
37-
with:
38-
dotnet-version: 10.0.x
39-
40-
- name: Run tests
41-
run: dotnet test --configuration Release
42-
43-
- name: Build and Publish Image
44-
id: build-image
45-
run: |
46-
dotnet publish src/Visualiser/Visualiser.csproj \
47-
--configuration Release \
48-
--target:PublishContainer \
49-
--property:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \
50-
--property:ContainerRepository=$REPOSITORY \
51-
--property:ContainerImageTag=$IMAGE_TAG
52-
53-
IMAGE=${{ steps.login-ecr.outputs.registry }}/${REPOSITORY}:${IMAGE_TAG}
54-
echo "IMAGE=${IMAGE}" >> $GITHUB_OUTPUT
55-
56-
- name: Render ECS task definition
57-
id: render-task-def
58-
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1.8.4
59-
with:
60-
task-definition: infra/dms-visualiser-task-def.json
61-
container-name: dms-visualiser-container
62-
image: ${{ steps.build-image.outputs.image }}
63-
environment-variables: |
64-
VERY_IMPORTANT=DO_NOT_DELETE_ME
65-
DOTNET_ENVIRONMENT=PreProduction
66-
secrets: |
67-
AzureAd__ClientId=${{ secrets.AWS_SECRETS_ARN }}:Client_ID::
68-
AzureAd__ClientSecret=${{ secrets.AWS_SECRETS_ARN }}:Client_Secret::
69-
API__BaseUrl=${{ secrets.AWS_SECRETS_ARN }}:API_Base_URL::
70-
71-
- name: Deploy ECS service
72-
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2.6.1
73-
with:
74-
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
75-
service: dms-visualiser-service-1
76-
cluster: dms-visualiser-cluster
13+
deploy-preprod:
14+
if: github.ref == 'refs/heads/develop'
15+
uses: ./.github/workflows/deploy-reusable.yml
16+
with:
17+
environment: preprod
18+
aws-region: eu-west-2
19+
secrets:
20+
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
21+
AWS_SECRETS_ARN: ${{ secrets.AWS_SECRETS_ARN }}
22+
AWS_TASK_ROLE_ARN: ${{ secrets.AWS_TASK_ROLE_ARN }}
23+
AWS_EXECUTION_ROLE_ARN: ${{ secrets.AWS_EXECUTION_ROLE_ARN }}
24+
25+
deploy-prod:
26+
if: github.ref == 'refs/heads/main'
27+
uses: ./.github/workflows/deploy-reusable.yml
28+
with:
29+
environment: prod
30+
aws-region: eu-west-2
31+
secrets:
32+
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
33+
AWS_SECRETS_ARN: ${{ secrets.AWS_SECRETS_ARN }}
34+
AWS_TASK_ROLE_ARN: ${{ secrets.AWS_TASK_ROLE_ARN }}
35+
AWS_EXECUTION_ROLE_ARN: ${{ secrets.AWS_EXECUTION_ROLE_ARN }}

0 commit comments

Comments
 (0)