From f0bf4c5842f1ed8dd213161c83ccf33191f85ef9 Mon Sep 17 00:00:00 2001
From: "Jordan K. Wilson" <j.wilson@gns.cri.nz>
Date: Thu, 18 Jul 2024 14:19:52 +1200
Subject: [PATCH] feat: add ability to create new task revisions, and
 optionally deploy

Allows a workflow to update a task definition with updated container,
and deploy new task revision.
---
 .github/workflows/reusable-aws-deploy.yml | 115 ++++++++++++++++++++++
 README.md                                 |  87 ++++++++++++++++
 2 files changed, 202 insertions(+)
 create mode 100644 .github/workflows/reusable-aws-deploy.yml

diff --git a/.github/workflows/reusable-aws-deploy.yml b/.github/workflows/reusable-aws-deploy.yml
new file mode 100644
index 0000000..35851bf
--- /dev/null
+++ b/.github/workflows/reusable-aws-deploy.yml
@@ -0,0 +1,115 @@
+name: Reusable AWS Deploy
+on:
+  workflow_call:
+    inputs:
+      aws-role-arn-to-assume:
+        type: string
+        required: false
+        description: |
+          see reusable docker build workflow
+      image:
+        required: true
+        type: string
+        description: |
+          uri of the image to deploy
+      service:
+        required: false
+        type: string
+        description: |
+          name of service if deploying
+      cluster:
+        required: false
+        type: string
+        description: |
+          name of cluster if deploying
+      rule-name:
+        required: false
+        type: string
+        description: |
+          name of EventBridge rule to update, if deploying
+      container:
+        required: true
+        type: string
+        description: |
+          name of container
+      task-name:
+        required: true
+        type: string
+        description: |
+          name of task definition
+      deployment-type:
+        required: false
+        type: string
+        description: |
+          type of deployment, valid values are
+          ecs, eventbridge, or empty for no deployment
+        default: ''
+      deployment-tag-param-name:
+        required: false
+        type: string
+        description: |
+          name of AWS System Store Parameter to save tag
+    outputs:
+      task-definition:
+        value: ${{ jobs.new-task-revision.outputs.task-definition }}
+
+permissions:
+  id-token: write
+
+jobs:
+  deploy-task-revision:
+    runs-on: ubuntu-latest
+    outputs:
+      task-definition: ${{ steps.task-def.outputs.task-definition }}
+    steps:
+      - if: ${{ startsWith(github.repository, 'GeoNet/') == false }}
+        name: require GeoNet org
+        run: |
+          exit 1
+      - name: Configure AWS Credentials
+        uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
+        with:
+          aws-region: ap-southeast-2
+          role-to-assume: ${{ inputs.aws-role-arn-to-assume }}
+      - name: Download task definition
+        run: |
+          aws ecs describe-task-definition \
+            --task-definition ${{ inputs.task-name }} \
+            --query taskDefinition > task-definition.json
+      - name: Update task definition
+        id: task-def
+        uses: aws-actions/amazon-ecs-render-task-definition@5f07eab76e1851cbd4e07dea0f3ed53b304475bd # v1.3.0
+        with:
+          task-definition: task-definition.json
+          container-name: ${{ inputs.container }}
+          image: ${{ inputs.image }}
+      - name: Deploy task definition
+        id: task-deploy
+        uses: aws-actions/amazon-ecs-deploy-task-definition@69e7aed9b8acdd75a6c585ac669c33831ab1b9a3 # v1.5.0
+        with:
+          task-definition: ${{ steps.task-def.outputs.task-definition }}
+          # if service is empty, task revision will be created, but not deployed
+          service: ${{ inputs.deployment-type == 'ecs' && inputs.service || '' }}
+          cluster: ${{ inputs.deployment-type == 'ecs' && inputs.cluster || '' }}
+          wait-for-service-stability: true
+      - name: Update EventBridge target
+        run: |
+          # get target
+          aws events list-targets-by-rule \
+            --rule ${{ inputs.rule-name }} > rule.json
+
+          # update target
+          cat rule.json | jq '.Targets[0].EcsParameters.TaskDefinitionArn = "${{ steps.task-deploy.outputs.task-definition-arn }}"' > rule-updated-target.json
+
+          # write target to aws
+          aws events put-targets \
+            --rule ${{ inputs.rule-name }} \
+            --cli-input-json file://rule-updated-target.json
+      - name: Save deployment information
+        if: inputs.deployment-type != ''
+        run: |
+          IMAGE_TAG=$(echo ${{ inputs.image }} | cut -d':' -f 2)
+          aws ssm put-parameter \
+            --name ${{ inputs.deployment-tag-param-name }} \
+            --value $(jq -cn --arg image-tag $IMAGE_TAG --arg task-arn ${{ steps.task-deploy.outputs.task-definition-arn }} '$ARGS.named') \
+            --overwrite
diff --git a/README.md b/README.md
index d3b5d25..1fcf9cc 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@
     - [Copy to S3](#copy-to-s3)
     - [Clean container versions](#clean-container-versions)
     - [ESLint](#eslint)
+    - [AWS deploy](#aws-deploy)
   - [Composite Actions](#composite-actions)
     - [Tagging](#tagging)
   - [Other documentation](#other-documentation)
@@ -1117,6 +1118,92 @@ jobs:
 ```
 
 
+### AWS deploy
+
+STATUS: beta
+
+CICD-driven container image deployment, using AWS ECR and ECS.
+
+This workflow supports:
+
+- ECS service deployments
+- EventBridge rule target updates
+
+No deployment can also be specified, allowing the newly created task revision to be deployed via other mechanisms.
+
+Example:
+
+```yaml
+name: build-and-deploy
+
+permissions:
+  contents: write
+  id-token: write
+
+jobs:
+  # build image
+  build:
+    uses: GeoNet/Actions/.github/workflows/reusable-docker-build.yml@main
+
+  # example 1: deploy - ECS service
+  deploy:
+    needs: build
+    uses: GeoNet/Actions/.github/workflows/reusable-aws-deploy.yml@main
+    with:
+      aws-role-arn-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-geonet-deploy-ROLE_NAME
+
+      # update task definition with new container image uri
+      task-name: my_task_name
+      container: my_task_container_name
+      image: ${{ needs.build.output.image }}
+
+      # deploy
+      deployment-type: ecs
+      service: my_service
+      cluster: my_cluster
+
+      # save deployment information
+      deployment-tag-param-name: /deployment/my_project/my_service
+
+  # example 2: deploy - EventBridge rule target
+  deploy:
+    needs: build
+    uses: GeoNet/Actions/.github/workflows/reusable-aws-deploy.yml@main
+    with:
+      aws-role-arn-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-geonet-deploy-ROLE_NAME
+
+      # update task definition with new container image uri
+      task-name: my_task_name
+      container: my_task_container_name
+      image: ${{ needs.build.output.image }}
+
+      # deploy
+      deployment-type: eventbridge
+      rule-name: my_rule
+
+      # save deployment information
+      deployment-tag-param-name: /deployment/my_project/my_service
+
+  # example 3: only create new task revision
+  deploy:
+    needs: build
+    uses: GeoNet/Actions/.github/workflows/reusable-aws-deploy.yml@main
+    with:
+      aws-role-arn-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-geonet-deploy-ROLE_NAME
+
+      # update task definition with new container image uri
+      task-name: my_task_name
+      container: my_task_container_name
+      image: ${{ needs.build.output.image }}
+      deployment-type: ''
+```
+
+The terraform module `gha_iam_ecs_deploy` can be used to setup appropriate permissions for this workflow.
+The terraform module `ecs_docker_task_ng` can be used to configure services for use with this workflow, via the `use_cicd_deployment` variable.
+
+Some example repos using this workflow: `DevTools` and `gloria`.
+
+
 ## Composite Actions
 
 ### Tagging