|
63 | 63 | || 'arn:aws:iam::393416225559:role/GithubDeployECSService' }} |
64 | 64 | aws_account_id: ${{ inputs.environment == 'production' && '820242920762' || '393416225559' }} |
65 | 65 | cluster_name: mavis-${{ inputs.environment }} |
| 66 | + git_ref_to_deploy: ${{ inputs.git_ref_to_deploy }} |
66 | 67 | app_version: ${{ inputs.app_version || inputs.git_ref_to_deploy || 'unknown' }} |
67 | 68 |
|
68 | 69 | concurrency: |
69 | 70 | group: deploy-application-${{ inputs.environment }} |
70 | 71 |
|
71 | 72 | jobs: |
| 73 | + notify-deployment-start: |
| 74 | + if: ${{ inputs.environment == 'production' }} |
| 75 | + runs-on: ubuntu-latest |
| 76 | + steps: |
| 77 | + - name: Notify deployment start |
| 78 | + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a |
| 79 | + with: |
| 80 | + webhook: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 81 | + webhook-type: incoming-webhook |
| 82 | + payload: | |
| 83 | + text: "Deployment started :rocket:" |
| 84 | + blocks: |
| 85 | + - type: "section" |
| 86 | + text: |
| 87 | + type: "mrkdwn" |
| 88 | + text: "Deployment of *${{ env.app_version }}* started" |
| 89 | + - type: "section" |
| 90 | + fields: |
| 91 | + - type: "mrkdwn" |
| 92 | + text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>" |
| 93 | + - type: "mrkdwn" |
| 94 | + text: "*Triggered by:*\n${{ github.actor }}" |
| 95 | +
|
72 | 96 | determine-git-sha: |
73 | 97 | runs-on: ubuntu-latest |
74 | 98 | permissions: { } |
@@ -142,17 +166,145 @@ jobs: |
142 | 166 | name: ${{ inputs.environment }}-${{ matrix.service }}-task-definition |
143 | 167 | path: ${{ runner.temp }}/${{ matrix.service }}-task-definition.json |
144 | 168 |
|
| 169 | + notify-approval-required: |
| 170 | + name: Notify approval required |
| 171 | + if: ${{ inputs.environment == 'production' }} |
| 172 | + runs-on: ubuntu-latest |
| 173 | + needs: prepare-deployment |
| 174 | + steps: |
| 175 | + - name: Notify approval required |
| 176 | + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a |
| 177 | + with: |
| 178 | + webhook: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 179 | + webhook-type: incoming-webhook |
| 180 | + payload: | |
| 181 | + text: ":hourglass: Approval required :hourglass:" |
| 182 | + blocks: |
| 183 | + - type: "section" |
| 184 | + text: |
| 185 | + type: "mrkdwn" |
| 186 | + text: "${{ github.workflow }} requires approval" |
| 187 | + - type: "section" |
| 188 | + fields: |
| 189 | + - type: "mrkdwn" |
| 190 | + text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>" |
| 191 | +
|
145 | 192 | approve-deployments: |
146 | 193 | name: Wait for approval if required |
147 | 194 | runs-on: ubuntu-latest |
148 | 195 | needs: prepare-deployment |
149 | 196 | environment: ${{ inputs.environment }} |
150 | 197 | steps: |
151 | 198 | - run: echo "Proceeding with deployment to $environment" |
| 199 | + |
| 200 | + run-migrations: |
| 201 | + name: Run migrations |
| 202 | + runs-on: ubuntu-latest |
| 203 | + needs: [ approve-deployments ] |
| 204 | + if: ${{ inputs.server_types == 'all' || inputs.server_types == 'ops' }} |
| 205 | + permissions: |
| 206 | + id-token: write |
| 207 | + steps: |
| 208 | + - name: Checkout code |
| 209 | + uses: actions/checkout@v6 |
| 210 | + - name: Configure AWS Credentials |
| 211 | + uses: aws-actions/configure-aws-credentials@v5 |
| 212 | + with: |
| 213 | + role-to-assume: ${{ env.aws_role }} |
| 214 | + aws-region: eu-west-2 |
| 215 | + - name: Download ops task definition artifact |
| 216 | + uses: actions/download-artifact@v6 |
| 217 | + with: |
| 218 | + path: ${{ runner.temp }} |
| 219 | + name: ${{ inputs.environment }}-ops-task-definition |
| 220 | + - name: Register migration task definition |
| 221 | + id: register-migration-task-definition |
| 222 | + run: | |
| 223 | + family_name="mavis-migration-task-definition-$environment" |
| 224 | + file_path="${{ runner.temp }}/migration-task-definition.json" |
| 225 | + echo "$(jq --arg f "$family_name" '.family = $f' "${{ runner.temp }}/ops-task-definition.json")" > "$file_path" |
| 226 | + task_definition_arn=$(aws ecs register-task-definition \ |
| 227 | + --cli-input-json file://$file_path \ |
| 228 | + --query 'taskDefinition.taskDefinitionArn' \ |
| 229 | + --output text |
| 230 | + ) |
| 231 | + echo "task_definition_arn=$task_definition_arn" >> $GITHUB_OUTPUT |
| 232 | + - name: Run schema migrations |
| 233 | + id: run-schema-migrations |
| 234 | + env: |
| 235 | + SLACK_MAVIS_RELEASES_WEBHOOK_URL: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 236 | + run: | |
| 237 | + TASK_DEFINITION_ARN=${{ steps.register-migration-task-definition.outputs.task_definition_arn }} |
| 238 | + SUBNET_ID=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=private-subnet-$environment-a --query 'Subnets[0].SubnetId' --output text) |
| 239 | + SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=ops-service-$environment --query 'SecurityGroups[0].GroupId' --output text) |
| 240 | + |
| 241 | + MAX_ATTEMPTS=3 |
| 242 | + ATTEMPT=1 |
| 243 | +
|
| 244 | + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do |
| 245 | + TASK_ARN=$(aws ecs run-task \ |
| 246 | + --cluster $cluster_name \ |
| 247 | + --task-definition $TASK_DEFINITION_ARN \ |
| 248 | + --launch-type FARGATE \ |
| 249 | + --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_ID],securityGroups=[$SECURITY_GROUP_ID]}" \ |
| 250 | + --overrides '{ |
| 251 | + "containerOverrides": [{ |
| 252 | + "name": "application", |
| 253 | + "command": ["bin/rails","db:migrate"] |
| 254 | + }] |
| 255 | + }' \ |
| 256 | + --query 'tasks[0].taskArn' \ |
| 257 | + --output text) |
| 258 | + |
| 259 | + echo "Waiting for task to complete: $TASK_ARN" |
| 260 | + TASK_ID=$(sed 's:^.*/::' <<< $TASK_ARN) |
| 261 | + AWS_CONSOLE_URL="https://eu-west-2.console.aws.amazon.com/ecs/v2/clusters/$cluster_name/tasks/$TASK_ID/logs" |
| 262 | + |
| 263 | + echo "View logs in AWS Console: $AWS_CONSOLE_URL" |
| 264 | + if [ $environment = 'production' ]; then |
| 265 | + ./.github/send_slack_notification.sh "${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }}" "$AWS_CONSOLE_URL" "Running schema migrations attempt $ATTEMPT/$MAX_ATTEMPTS" |
| 266 | + fi |
| 267 | +
|
| 268 | + aws ecs wait tasks-stopped \ |
| 269 | + --cluster $cluster_name \ |
| 270 | + --tasks $TASK_ID |
| 271 | + |
| 272 | + EXIT_CODE=$(aws ecs describe-tasks \ |
| 273 | + --cluster $cluster_name \ |
| 274 | + --tasks $TASK_ARN \ |
| 275 | + --query 'tasks[0].containers[0].exitCode' \ |
| 276 | + --output text) |
| 277 | + |
| 278 | + echo "Container exit code: $EXIT_CODE" |
| 279 | + |
| 280 | + if [ "$EXIT_CODE" = "0" ]; then |
| 281 | + echo "Migrations completed" |
| 282 | + break |
| 283 | + else |
| 284 | + echo "ECS task failed with exit code: $EXIT_CODE" |
| 285 | + if [ "$ATTEMPT" = "$MAX_ATTEMPTS" ]; then |
| 286 | + exit 1 |
| 287 | + fi |
| 288 | + ATTEMPT=$((ATTEMPT+1)) |
| 289 | + fi |
| 290 | + done |
| 291 | + - name: Notify migrations completed |
| 292 | + if: ${{ env.environment == 'production' }} |
| 293 | + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a |
| 294 | + with: |
| 295 | + webhook: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 296 | + webhook-type: incoming-webhook |
| 297 | + payload: | |
| 298 | + text: "Schema migrations finished successfully :white_check_mark:" |
| 299 | + blocks: |
| 300 | + - type: "section" |
| 301 | + text: |
| 302 | + type: "mrkdwn" |
| 303 | + text: "Schema migrations finished successfully :white_check_mark:" |
152 | 304 | deploy-service: |
153 | 305 | name: Deploy service |
154 | 306 | runs-on: ubuntu-latest |
155 | | - needs: [ prepare-deployment, approve-deployments ] |
| 307 | + needs: [ prepare-deployment, approve-deployments, run-migrations ] |
156 | 308 | permissions: |
157 | 309 | id-token: write |
158 | 310 | strategy: |
@@ -194,3 +346,41 @@ jobs: |
194 | 346 | echo "Task definition arns don't match, likely due to a rollback to the previous version. Deployment failed." |
195 | 347 | exit 1 |
196 | 348 | fi |
| 349 | + notify-deployment-complete: |
| 350 | + name: Notify deployment status |
| 351 | + runs-on: ubuntu-latest |
| 352 | + needs: [ deploy-service, run-migrations ] |
| 353 | + if: ${{ !cancelled() }} |
| 354 | + steps: |
| 355 | + - name: Notify deployment success |
| 356 | + if: ${{ env.environment == 'production' && needs.deploy-service.result == 'success' }} |
| 357 | + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a |
| 358 | + with: |
| 359 | + webhook: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 360 | + webhook-type: incoming-webhook |
| 361 | + payload: | |
| 362 | + text: "*${{ env.app_version }}* is deployed :ship:" |
| 363 | + blocks: |
| 364 | + - type: "section" |
| 365 | + text: |
| 366 | + type: "mrkdwn" |
| 367 | + text: "*${{ env.app_version }}* is deployed :ship:" |
| 368 | + - name: Notify deployment failure |
| 369 | + if: ${{ env.environment == 'production' && (needs.deploy-service.result == 'failure' || needs.run-migrations.result == 'failure') }} |
| 370 | + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a |
| 371 | + with: |
| 372 | + webhook: ${{ secrets.SLACK_MAVIS_RELEASES_WEBHOOK_URL }} |
| 373 | + webhook-type: incoming-webhook |
| 374 | + payload: | |
| 375 | + text: "Deployment of *${{ env.app_version }}* failed :x:" |
| 376 | + blocks: |
| 377 | + - type: "section" |
| 378 | + text: |
| 379 | + type: "mrkdwn" |
| 380 | + text: "Deployment of *${{ env.app_version }}* failed :x:" |
| 381 | + - type: "section" |
| 382 | + fields: |
| 383 | + - type: "mrkdwn" |
| 384 | + text: "*Failed job:*\n${{ needs.deploy-service.result == 'failure' && 'deploy-service' || 'run-migrations' }}" |
| 385 | + - type: "mrkdwn" |
| 386 | + text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>" |
0 commit comments