From b08121efe35c52414ba7e4fac907c13bad71f2c3 Mon Sep 17 00:00:00 2001 From: Yngvar Kristiansen <562343+yngvark@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:34:02 +0100 Subject: [PATCH 1/4] Add SSM param --- .../action.yml | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/ecs-update-and-deploy-task-definition/action.yml b/ecs-update-and-deploy-task-definition/action.yml index dd2f12ae..0a8249f7 100644 --- a/ecs-update-and-deploy-task-definition/action.yml +++ b/ecs-update-and-deploy-task-definition/action.yml @@ -1,6 +1,8 @@ name: "Update and deploy ECS task definition" -description: "Downloads an existing ECS task definition, updates multiple container image URIs, and optionally deploys the updated task definition to the specified ECS service." +description: | + Downloads an existing ECS task definition, updates multiple container image URIs, optionally deploys the updated task + definition to the specified ECS service, and writes the images deployed to an SSM parameter. inputs: aws-region: @@ -36,6 +38,10 @@ inputs: required: false default: "true" + images-ssm-parameter-name: + description: 'The name of the SSM parameter to store the images JSON in. Must be provided if deploy is "true"' + required: false + outputs: task-definition-file-name: @@ -47,6 +53,16 @@ runs: using: composite steps: + + + - name: Check if images-ssm-parameter-name is empty ❌ + if: inputs.deploy == 'true' && inputs.images-ssm-parameter-name == '' + shell: bash + run: | + echo "Error: images-ssm-parameter-name is required but not provided." + exit 1 + + - name: Configure AWS credentials using the OpenID Connect (OIDC) provider 🔑 uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0 with: @@ -139,9 +155,40 @@ runs: cluster: "${{ inputs.cluster-name }}" service: "${{ inputs.service-name }}" task-definition: "${{ steps.update-task-definition.outputs.task-definition }}" + propagate-tags: "SERVICE" wait-for-service-stability: "${{ inputs.wait-for-service-stability }}" + - if: inputs.deploy == 'true' + name: Write deployed images to SSM parameter 📝 + shell: bash + env: + DEPLOYED_IMAGES: ${{ inputs.images }} + SSM_PARAMETER_NAME: ${{ inputs.images-ssm-parameter-name }} + run: | + echo "Deployed images:" + echo $DEPLOYED_IMAGES | jq + + CURRENT_IMAGES=$(aws ssm get-parameter \ + --name "$SSM_PARAMETER_NAME" \ + --query "Parameter.Value" \ + --output text) + echo "Current images:" + echo $CURRENT_IMAGES + echo $CURRENT_IMAGES | jq + + MERGED_IMAGES=$(echo "$CURRENT_IMAGES" | jq --argjson new "$DEPLOYED_IMAGES" '. + $new') + echo "Merged images:" + echo $MERGED_IMAGES | jq + + aws ssm put-parameter \ + --name "$SSM_PARAMETER_NAME" \ + --type "String" \ + --value "$MERGED_IMAGES" \ + --overwrite + echo "✅ Wrote deployed images to SSM parameter '$SSM_PARAMETER_NAME'" + + - if: inputs.deploy == 'false' name: Write simple summary for non-deployment 📝 shell: bash @@ -160,6 +207,7 @@ runs: ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }} TASK_DEFINITION_NAME: ${{ inputs.task-definition-name }} TASK_DEFINITION_ARN: ${{ steps.deploy.outputs.task-definition-arn }} + IMAGES_SSM_PARAMETER_NAME: ${{ inputs.images-ssm-parameter-name }} run: | # To test the summary locally, copy the rest of this workflow into a script, uncomment the variables, and run. # @@ -172,6 +220,7 @@ runs: #CLUSTER_NAME='pirates-dev' #TASK_DEFINITION_NAME='too-tikki' #TASK_DEFINITION_ARN="arn:aws:ecs:$AWS_REGION:$AWS_ACCOUNT_ID:task-definition/$TASK_DEFINITION_NAME:143" + #IMAGES_SSM_PARAMETER_NAME="/ecs/pirates-dev/too-tikki/images" IMAGE_COUNT=$(echo "$IMAGES_JSON" | jq 'length') AWS_ACCOUNT_ID=$(echo "$ECR_REGISTRY" | cut -d. -f1) @@ -186,9 +235,9 @@ runs: cat << EOF >> $GITHUB_STEP_SUMMARY - ## Deployment summary 📋 + ## Deployment summary TEMP 📋 - ✅ Updated task definition successfully. + ✅ Successfully updated task definition. | Item | Link | |-------------------|----------------------------------------------------------| @@ -196,8 +245,13 @@ runs: | Task definition | [$TASK_DEFINITION_NAME:$TASK_REVISION]($TASK_DEF_URL) | ### Container(s) updated 📦 - - | Container | Repository | Digest | Tag | - | --- | --- | --- | --- | + | Container | ECR repository | Digest | Tag | + | --------- | -------------- | ------ | --- | $CONTAINER_ROWS + EOF + + if [ -n "$IMAGES_SSM_PARAMETER_NAME" ]; then + SSM_URL="https://$AWS_REGION.console.aws.amazon.com/systems-manager/parameters/$IMAGES_SSM_PARAMETER_NAME/description?region=$AWS_REGION" + echo "✅ Successfully wrote image metadata to SSM parameter [$IMAGES_SSM_PARAMETER_NAME]($SSM_URL)." >> $GITHUB_STEP_SUMMARY + fi From eb38731e8c09077e1b26377dfdddecddb597102c Mon Sep 17 00:00:00 2001 From: Yngvar Kristiansen <562343+yngvark@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:33:12 +0200 Subject: [PATCH 2/4] Deploy with CLI --- .../action.yml | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/ecs-update-and-deploy-task-definition/action.yml b/ecs-update-and-deploy-task-definition/action.yml index 0a8249f7..20bb4b42 100644 --- a/ecs-update-and-deploy-task-definition/action.yml +++ b/ecs-update-and-deploy-task-definition/action.yml @@ -43,12 +43,6 @@ inputs: required: false -outputs: - task-definition-file-name: - description: "The path to the rendered task definition file." - value: "${{ steps.update-task-definition.outputs.task-definition }}" - - runs: using: composite @@ -76,13 +70,28 @@ runs: uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 - - name: Download existing ECS task definition ⚙️ + - name: Download ECS task definition 🏷️ + id: get-task-info shell: bash + env: + TASK_DEFINITION_NAME: "${{ inputs.task-definition-name }}" run: | - aws ecs describe-task-definition \ - --task-definition "${{ inputs.task-definition-name }}" \ - --query taskDefinition \ - | jq 'del(.registeredAt, .registeredBy, .compatibilities, .taskDefinitionArn, .requiresAttributes, .revision, .status)' > "task-definition.json" + TASK_RESPONSE=$(aws ecs describe-task-definition \ + --task-definition "$TASK_DEFINITION_NAME" \ + --include TAGS) + + # Remove unnecessary fields from the task definition + echo "$TASK_RESPONSE" | jq '.taskDefinition | + del(.registeredAt, .registeredBy, .compatibilities, .taskDefinitionArn, + .requiresAttributes, .revision, .status)' > "task-definition.json" + + # Get tags from the API response + TAGS=$(echo "$TASK_RESPONSE" | jq '.tags') + + # Add tags to task definition + jq --argjson tags "$TAGS" '. + {tags: $tags}' "task-definition.json" > "temp.json" && mv "temp.json" "task-definition.json" + + echo "✅ Task definition with tags saved to task-definition.json" - name: Update ECS task definition with new image URIs ⚙️ @@ -95,7 +104,7 @@ runs: const fs = require('fs'); // Set filenames - const taskDefFile = 'task-definition.json'; + const taskDefFile = "task-definition.json"; const outputTaskDefFile = 'updated-task-definition.json'; // Read task definition @@ -144,19 +153,36 @@ runs: console.log('Task definition update complete'); // Set output - core.setOutput('task-definition', outputTaskDefFile); + core.setOutput('task-definition-filename', outputTaskDefFile); - if: inputs.deploy == 'true' name: Deploy task definition 🚀 id: deploy - uses: aws-actions/amazon-ecs-deploy-task-definition@8230edfe842008418c5275908cae75e51d3befb2 # v2.3.0 - with: - cluster: "${{ inputs.cluster-name }}" - service: "${{ inputs.service-name }}" - task-definition: "${{ steps.update-task-definition.outputs.task-definition }}" - propagate-tags: "SERVICE" - wait-for-service-stability: "${{ inputs.wait-for-service-stability }}" + shell: bash + env: + CLUSTER: "${{ inputs.cluster-name }}" + SERVICE: "${{ inputs.service-name }}" + TASK_DEFINITION_FAMILY: "${{ inputs.service-name }}" + TASK_DEFINITION_FILENAME: "${{ steps.update-task-definition.outputs.task-definition-filename }}" + WAIT_FOR_SERVICE_STABILITY: "${{ inputs.wait-for-service-stability }}" + run: | + echo Registering task definition... + OUTPUT=$(aws ecs register-task-definition --cli-input-json "file://$TASK_DEFINITION_FILENAME") + TASK_DEF_REVISION=$(echo "$OUTPUT" | jq -r '.taskDefinition.taskDefinitionArn' | rev | cut -d':' -f1 | rev) + + echo "Done! Task def revision: $TASK_DEF_REVISION" + echo + echo Updating ECS service... + aws ecs update-service \ + --cluster "$CLUSTER" \ + --service "$SERVICE" \ + --task-definition "$TASK_DEFINITION_FAMILY:$TASK_DEF_REVISION" \ + --propagate-tags SERVICE + + echo "✅ Deployment successful." + + echo "::set-output name=task-definition-revision::$TASK_DEF_REVISION" - if: inputs.deploy == 'true' @@ -206,7 +232,7 @@ runs: CLUSTER_NAME: ${{ inputs.cluster-name }} ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }} TASK_DEFINITION_NAME: ${{ inputs.task-definition-name }} - TASK_DEFINITION_ARN: ${{ steps.deploy.outputs.task-definition-arn }} + TASK_DEFINITION_REVISION: ${{ steps.deploy.outputs.task-definition-revision }} IMAGES_SSM_PARAMETER_NAME: ${{ inputs.images-ssm-parameter-name }} run: | # To test the summary locally, copy the rest of this workflow into a script, uncomment the variables, and run. @@ -219,14 +245,13 @@ runs: #AWS_REGION='eu-west-1' #CLUSTER_NAME='pirates-dev' #TASK_DEFINITION_NAME='too-tikki' - #TASK_DEFINITION_ARN="arn:aws:ecs:$AWS_REGION:$AWS_ACCOUNT_ID:task-definition/$TASK_DEFINITION_NAME:143" + #TASK_DEFINITION_REVISION=143" #IMAGES_SSM_PARAMETER_NAME="/ecs/pirates-dev/too-tikki/images" IMAGE_COUNT=$(echo "$IMAGES_JSON" | jq 'length') AWS_ACCOUNT_ID=$(echo "$ECR_REGISTRY" | cut -d. -f1) SERVICE_URL="https://$AWS_REGION.console.aws.amazon.com/ecs/v2/clusters/$CLUSTER_NAME/services/$SERVICE_NAME?region=$AWS_REGION" - TASK_REVISION=$(echo "$TASK_DEFINITION_ARN" | cut -d ':' -f 7) - TASK_DEF_URL="https://$AWS_REGION.console.aws.amazon.com/ecs/v2/task-definitions/$TASK_DEFINITION_NAME/$TASK_REVISION/containers?region=$AWS_REGION" + TASK_DEF_URL="https://$AWS_REGION.console.aws.amazon.com/ecs/v2/task-definitions/$TASK_DEFINITION_NAME/$TASK_DEFINITION_REVISION/containers?region=$AWS_REGION" CONTAINER_ROWS=$(echo "$IMAGES_JSON" | jq -r 'to_entries[] | "| \(.key) | \(.value.imageRepository) | `\(.value.imageDigest)` | `\(.value.imageTag)` |"') ####################################### @@ -235,14 +260,14 @@ runs: cat << EOF >> $GITHUB_STEP_SUMMARY - ## Deployment summary TEMP 📋 + ## Deployment summary 📋 ✅ Successfully updated task definition. | Item | Link | - |-------------------|----------------------------------------------------------| - | ECS service | [$SERVICE_NAME]($SERVICE_URL) | - | Task definition | [$TASK_DEFINITION_NAME:$TASK_REVISION]($TASK_DEF_URL) | + |-------------------|---------------------------------------------------------------------| + | ECS service | [$SERVICE_NAME]($SERVICE_URL) | + | Task definition | [$TASK_DEFINITION_NAME:$TASK_DEFINITION_REVISION]($TASK_DEF_URL) | ### Container(s) updated 📦 | Container | ECR repository | Digest | Tag | From 6c137406ad26bb92f010e60c534e3ad1ee007116 Mon Sep 17 00:00:00 2001 From: Yngvar Kristiansen <562343+yngvark@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:48:49 +0200 Subject: [PATCH 3/4] Deploy with aws-action rather than CLI --- .../action.yml | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/ecs-update-and-deploy-task-definition/action.yml b/ecs-update-and-deploy-task-definition/action.yml index 20bb4b42..f990b137 100644 --- a/ecs-update-and-deploy-task-definition/action.yml +++ b/ecs-update-and-deploy-task-definition/action.yml @@ -2,7 +2,7 @@ name: "Update and deploy ECS task definition" description: | Downloads an existing ECS task definition, updates multiple container image URIs, optionally deploys the updated task - definition to the specified ECS service, and writes the images deployed to an SSM parameter. + definition to the specified ECS service, and optionally writes the images deployed to an SSM parameter. inputs: aws-region: @@ -39,8 +39,9 @@ inputs: default: "true" images-ssm-parameter-name: - description: 'The name of the SSM parameter to store the images JSON in. Must be provided if deploy is "true"' + description: "Set this to store images as an SSM parameter. If empty, the action will not store images in SSM." required: false + default: "" runs: @@ -49,14 +50,6 @@ runs: steps: - - name: Check if images-ssm-parameter-name is empty ❌ - if: inputs.deploy == 'true' && inputs.images-ssm-parameter-name == '' - shell: bash - run: | - echo "Error: images-ssm-parameter-name is required but not provided." - exit 1 - - - name: Configure AWS credentials using the OpenID Connect (OIDC) provider 🔑 uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0 with: @@ -70,7 +63,7 @@ runs: uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 - - name: Download ECS task definition 🏷️ + - name: Download existing ECS task definition ⚙️ id: get-task-info shell: bash env: @@ -153,65 +146,49 @@ runs: console.log('Task definition update complete'); // Set output - core.setOutput('task-definition-filename', outputTaskDefFile); - + core.setOutput('task-definition', outputTaskDefFile); - if: inputs.deploy == 'true' name: Deploy task definition 🚀 id: deploy - shell: bash - env: - CLUSTER: "${{ inputs.cluster-name }}" - SERVICE: "${{ inputs.service-name }}" - TASK_DEFINITION_FAMILY: "${{ inputs.service-name }}" - TASK_DEFINITION_FILENAME: "${{ steps.update-task-definition.outputs.task-definition-filename }}" - WAIT_FOR_SERVICE_STABILITY: "${{ inputs.wait-for-service-stability }}" - run: | - echo Registering task definition... - OUTPUT=$(aws ecs register-task-definition --cli-input-json "file://$TASK_DEFINITION_FILENAME") - TASK_DEF_REVISION=$(echo "$OUTPUT" | jq -r '.taskDefinition.taskDefinitionArn' | rev | cut -d':' -f1 | rev) - - echo "Done! Task def revision: $TASK_DEF_REVISION" - echo - echo Updating ECS service... - aws ecs update-service \ - --cluster "$CLUSTER" \ - --service "$SERVICE" \ - --task-definition "$TASK_DEFINITION_FAMILY:$TASK_DEF_REVISION" \ - --propagate-tags SERVICE - - echo "✅ Deployment successful." - - echo "::set-output name=task-definition-revision::$TASK_DEF_REVISION" + uses: aws-actions/amazon-ecs-deploy-task-definition@8230edfe842008418c5275908cae75e51d3befb2 # v2.3.0 + with: + cluster: "${{ inputs.cluster-name }}" + service: "${{ inputs.service-name }}" + task-definition: "${{ steps.update-task-definition.outputs.task-definition }}" + wait-for-service-stability: "${{ inputs.wait-for-service-stability }}" - - if: inputs.deploy == 'true' + - if: inputs.deploy == 'true' && inputs.images-ssm-parameter-name != '' name: Write deployed images to SSM parameter 📝 shell: bash env: DEPLOYED_IMAGES: ${{ inputs.images }} SSM_PARAMETER_NAME: ${{ inputs.images-ssm-parameter-name }} run: | - echo "Deployed images:" + echo "Deployed images: (This is the input image to this workflow.)" echo $DEPLOYED_IMAGES | jq + echo CURRENT_IMAGES=$(aws ssm get-parameter \ --name "$SSM_PARAMETER_NAME" \ --query "Parameter.Value" \ --output text) - echo "Current images:" - echo $CURRENT_IMAGES + echo "Current images from SSM: (These are all images stored for this task definition, before deploying.)" echo $CURRENT_IMAGES | jq + echo MERGED_IMAGES=$(echo "$CURRENT_IMAGES" | jq --argjson new "$DEPLOYED_IMAGES" '. + $new') - echo "Merged images:" + echo "Current images from SSM merged with deployed images:" echo $MERGED_IMAGES | jq + echo aws ssm put-parameter \ --name "$SSM_PARAMETER_NAME" \ --type "String" \ --value "$MERGED_IMAGES" \ --overwrite + echo "✅ Wrote deployed images to SSM parameter '$SSM_PARAMETER_NAME'" @@ -232,7 +209,7 @@ runs: CLUSTER_NAME: ${{ inputs.cluster-name }} ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }} TASK_DEFINITION_NAME: ${{ inputs.task-definition-name }} - TASK_DEFINITION_REVISION: ${{ steps.deploy.outputs.task-definition-revision }} + TASK_DEFINITION_ARN: ${{ steps.deploy.outputs.task-definition-arn }} IMAGES_SSM_PARAMETER_NAME: ${{ inputs.images-ssm-parameter-name }} run: | # To test the summary locally, copy the rest of this workflow into a script, uncomment the variables, and run. @@ -245,12 +222,13 @@ runs: #AWS_REGION='eu-west-1' #CLUSTER_NAME='pirates-dev' #TASK_DEFINITION_NAME='too-tikki' - #TASK_DEFINITION_REVISION=143" + #TASK_DEFINITION_ARN="arn:aws:ecs:$AWS_REGION:$AWS_ACCOUNT_ID:task-definition/$TASK_DEFINITION_NAME:143" #IMAGES_SSM_PARAMETER_NAME="/ecs/pirates-dev/too-tikki/images" IMAGE_COUNT=$(echo "$IMAGES_JSON" | jq 'length') AWS_ACCOUNT_ID=$(echo "$ECR_REGISTRY" | cut -d. -f1) SERVICE_URL="https://$AWS_REGION.console.aws.amazon.com/ecs/v2/clusters/$CLUSTER_NAME/services/$SERVICE_NAME?region=$AWS_REGION" + TASK_DEFINITION_REVISION=$(echo "$TASK_DEFINITION_ARN" | cut -d ':' -f 7) TASK_DEF_URL="https://$AWS_REGION.console.aws.amazon.com/ecs/v2/task-definitions/$TASK_DEFINITION_NAME/$TASK_DEFINITION_REVISION/containers?region=$AWS_REGION" CONTAINER_ROWS=$(echo "$IMAGES_JSON" | jq -r 'to_entries[] | "| \(.key) | \(.value.imageRepository) | `\(.value.imageDigest)` | `\(.value.imageTag)` |"') From a8e16e65c617095df756065db2f44e06e6930c53 Mon Sep 17 00:00:00 2001 From: Yngvar Kristiansen <562343+yngvark@users.noreply.github.com> Date: Fri, 11 Apr 2025 10:46:03 +0200 Subject: [PATCH 4/4] Add example to README.md --- .../README.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/ecs-update-and-deploy-task-definition/README.md b/ecs-update-and-deploy-task-definition/README.md index ff447e07..17e1689c 100644 --- a/ecs-update-and-deploy-task-definition/README.md +++ b/ecs-update-and-deploy-task-definition/README.md @@ -23,3 +23,72 @@ permissions: } } ``` + +## How to use + +Here is an example deploying two containers in one task definition. + +```yaml + + ecs-deploy: + - runs-on: ubuntu-latest + name: Deploy ECS task definition + environment: pirates-dev-app-too-tikki-ecr + permissions: + id-token: write # For the GitHub's OIDC Token endpoint + + steps: + + - name: Set images to deploy ⚙️ + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + id: set-images + with: + script: | + // Images data structure documentation: + // https://github.com/oslokommune/composite-actions/blob/main/ecs-update-and-deploy-task-definition/README.md + const images = {}; + + const initImageBuildResult = "${{ needs.docker-build-push-init.result }}"; + const appImageBuildResult = "${{ needs.docker-build-push.result }}"; + + console.log("Step result for init container image:", initImageBuildResult); + console.log("Step result for app container image:", appImageBuildResult); + + if (initImageBuildResult === 'success') { + images["init-container"] = { + "imageRepository": "pirates-dev-too-tikki-init", + "imageDigest": "${{ needs.docker-build-push-init.outputs.image_digest }}", + "imageTag": "${{ needs.docker-build-push-init.outputs.image_version }}" + }; + } + + if (appImageBuildResult === 'success') { + images["too-tikki"] = { + "imageRepository": "pirates-dev-too-tikki", + "imageDigest": "${{ needs.docker-build-push.outputs.image_digest }}", + "imageTag": "${{ needs.docker-build-push.outputs.image_version }}" + }; + } + + console.log("Images to deploy:"); + console.log(images); + + return images; + + + - name: "Update and deploy ECS task definition with new image URI" + uses: oslokommune/composite-actions/ecs-update-and-deploy-task-definition@... # set digest + with: + aws-region: "eu-west-1" + aws-role-arn: "${{ secrets.AWS_ROLE_ARN }}" + + cluster-name: "pirates-dev" + service-name: "too-tikki" + task-definition-name: "too-tikki" + + deploy: "true" + wait-for-service-stability: "false" + + images: ${{ steps.set-images.outputs.result }} + images-ssm-parameter-name: "/pirates-dev/ecs/too-tikki/images" +```