.github/workflows/deployment.yml
1name: Deploy backend
2on:
3 push:
4 branches:
5 - "release/*/*"
6jobs:
7 deployment:
8 runs-on: ubuntu-latest
9 environment: deployment
10 env:
11 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
12 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
13 REGION: ap-southeast-2
14 steps:
15 - name: Get code
16 uses: actions/checkout@v4
17 - name: Run custom action to deploy aws fargate
18 uses: ./.github/actions/fargate-deployment
19 with:
20 branch_name: ${{ github.ref_name }}
21 image-registry: "798404461798.dkr.ecr.ap-southeast-2.amazonaws.com"
22 stage: poc
23 image-name: billie-v3-poc
24 task-family: billie-chat-poc
25 cluster-name: billie-chat-poc
26 service-name: billie-chat-poc
27 region: ap-southeast-2
.github/actions/fargate-deployment/action.yml
1name: "Deploy to AWS Fargate"
2description: "Build and Deploy an image to AWS fargate"
3
4inputs:
5 branch_name:
6 description: The target branch being deployed
7 required: true
8 stage:
9 description: uat, poc or prod
10 required: true
11 image-registry:
12 description: "Image registry"
13 required: true
14 image-name:
15 description: "Image name"
16 required: true
17 task-family:
18 description: "Task family"
19 required: true
20 cluster-name:
21 description: "Cluster name"
22 required: true
23 service-name:
24 description: "Service name"
25 required: true
26 region:
27 description: "region"
28 required: true
29
30runs:
31 using: "node20"
32 main: "main.js"
.github/actions/fargate-deployment/main.js
1npm install @actions/core @actions/github @actions/exec
1const core = require("@actions/core");
2const github = require("@actions/github");
3const exec = require("@actions/exec");
4const fs = require("fs");
5const { v4: uuid } = require("uuid");
6
7const cmd = async (...commands) => {
8 const filepath = uuid() + ".sh";
9 let commandStr = "";
10 for (const command of commands) {
11 commandStr += "\n" + command;
12 }
13 fs.writeFileSync(filepath, commandStr, { encoding: "utf-8" });
14 await exec.exec(`sh ${filepath}`);
15};
16
17async function run() {
18 const branchName = core.getInput("branch_name", { required: true });
19 const stage = core.getInput("stage", { required: true });
20 const IMAGE_REGISTRY = core.getInput("image-registry", { required: true });
21 const IMAGE_NAME = core.getInput("image-name", { required: true });
22 const TASK_FAMILY = core.getInput("task-family", { required: true });
23 const REGION = core.getInput("region", { required: true });
24 const CLUSTER_NAME = core.getInput("cluster-name", { required: true });
25 const SERVICE_NAME = core.getInput("service-name", { required: true });
26
27 core.notice(`I am working on branch ${branchName}`);
28
29 const context = github.context;
30 const runNumber = context.runNumber;
31 const splitData = branchName.split("/"); // ["release", "v3", "uat"];
32 const version = splitData[1];
33 const newTag = `${stage}-${version}-${runNumber}`;
34
35 await cmd(
36 `aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${IMAGE_REGISTRY}`,
37 `docker build -t ${IMAGE_REGISTRY}/${IMAGE_NAME}:${newTag} -f Dockerfile.${stage} .`,
38 `docker push ${IMAGE_REGISTRY}/${IMAGE_NAME}:${newTag}`
39 );
40
41 const tmpTaskDefinitionPath = "latest_task_definition_cicd.json";
42
43 await cmd(
44 `latest_task_definition=$(aws ecs describe-task-definition --task-definition ${TASK_FAMILY} --query 'taskDefinition' --region ${REGION})`,
45 `echo $latest_task_definition > "${tmpTaskDefinitionPath}"`
46 );
47 await exec.exec("ls");
48 const jsonString = fs.readFileSync(tmpTaskDefinitionPath, {
49 encoding: "utf-8",
50 });
51 core.notice(`I am working on jsonString ${jsonString}`);
52 const taskDefinition = JSON.parse(jsonString);
53 const containerDefinition = taskDefinition.containerDefinitions[0];
54 const imageUri = containerDefinition.image;
55 const imguriTagRegex = /(?<=:).*?$/g; // should be only 1 occurrence
56 const newimageUri = imageUri.replace(imguriTagRegex, (tag) => newTag); // replace
57
58 // https://github.com/aws/aws-sdk/issues/406
59 await cmd(
60 `TASK_DEFINITION=$(aws ecs describe-task-definition --task-definition ${TASK_FAMILY} --region ${REGION})`,
61 `NEW_TASK_DEFINTIION=$(echo $TASK_DEFINITION | jq --arg IMAGE ${newimageUri} '.taskDefinition | .containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.requiresAttributes) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)')`,
62 `aws ecs register-task-definition --region ${REGION} --cli-input-json "$NEW_TASK_DEFINTIION"`
63 );
64
65 await cmd(
66 `TASK_DEFINITION=$(aws ecs describe-task-definition --task-definition ${TASK_FAMILY} --region ${REGION})`,
67 `echo $TASK_DEFINITION`,
68 `revision=$(echo $TASK_DEFINITION | jq '.taskDefinition.revision')`,
69 `aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE_NAME} --region ${REGION} --task-definition ${TASK_FAMILY}:$revision`
70 );
71}
72
73run();