Skip to content

CD — Terraform Apply + Deploy/Destroy (ECS) #97

CD — Terraform Apply + Deploy/Destroy (ECS)

CD — Terraform Apply + Deploy/Destroy (ECS) #97

Workflow file for this run

name: CD — Terraform Apply + Deploy/Destroy (ECS)
on:
workflow_dispatch:
# checkov:skip=CKV_GHA_7 reason: controlled deploy inputs (mode/imageTag) for operator, not general user data
inputs:
mode:
description: "apply: deploy image and scale to 1; destroy: cleanup everything"
required: true
type: choice
options: [apply, destroy]
default: apply
imageTag:
description: "Image tag to deploy (immutable tag in ECR)"
required: true
type: string
env:
AWS_REGION: us-east-1
CLUSTER_NAME: ecs-demo-cluster
SERVICE_NAME: ecs-demo-svc
ECR_REPOSITORY: ecs-demo-app
LOG_GROUP: /ecs/ecs-demo
permissions:
id-token: write
contents: read
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: false
jobs:
cd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Configure AWS (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::097635932419:role/github-actions-ecs-role
aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.5
- name: Terraform init (infra)
working-directory: infra
run: terraform init -input=false
- name: Terraform import ECR if exists (before apply)
if: ${{ inputs.mode == 'apply' }}
working-directory: infra
env:
AWS_PAGER: ""
run: |
if aws ecr describe-repositories --repository-names "${{ env.ECR_REPOSITORY }}" --region "${{ env.AWS_REGION }}" >/dev/null 2>&1; then
terraform state show aws_ecr_repository.this >/dev/null 2>&1 || terraform import aws_ecr_repository.this "${{ env.ECR_REPOSITORY }}"
else
echo "ECR not found — Terraform will create it."
fi
- name: Terraform apply (infra)
if: ${{ inputs.mode == 'apply' }}
working-directory: infra
run: terraform apply -auto-approve -input=false
- name: Compute ECR URL & Tag
id: ecr
run: |
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_URL="${ACCOUNT_ID}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}"
TAG="${{ inputs.imageTag }}"
echo "ECR_URL=$ECR_URL" >> "$GITHUB_OUTPUT"
echo "TAG=$TAG" >> "$GITHUB_OUTPUT"
- name: Assert image tag exists in ECR
if: ${{ inputs.mode == 'apply' }}
env:
AWS_PAGER: ""
run: |
TAG="${{ steps.ecr.outputs.TAG }}"
COUNT=$(aws ecr list-images \
--repository-name "${{ env.ECR_REPOSITORY }}" \
--region "${{ env.AWS_REGION }}" \
--filter tagStatus=TAGGED \
--query "length(imageIds[?imageTag=='${TAG}'])" \
--output text)
if [ "$COUNT" -eq 0 ]; then
echo "❌ Image tag not found in ECR: ${{ steps.ecr.outputs.ECR_URL }}:${TAG}"
exit 1
else
echo "✅ Found image tag: ${{ steps.ecr.outputs.ECR_URL }}:${TAG}"
fi
- name: Scale to 0 and wait (destroy)
if: ${{ inputs.mode == 'destroy' }}
continue-on-error: true
run: |
aws ecs update-service --cluster "${{ env.CLUSTER_NAME }}" --service "${{ env.SERVICE_NAME }}" --desired-count 0 --region "${{ env.AWS_REGION }}" || true
aws ecs wait services-stable --cluster "${{ env.CLUSTER_NAME }}" --services "${{ env.SERVICE_NAME }}" --region "${{ env.AWS_REGION }}" || true
echo "✅ Service scaled to 0."
- name: Delete CloudWatch Log Group (destroy)
if: ${{ inputs.mode == 'destroy' }}
continue-on-error: true
run: |
aws logs delete-log-group --log-group-name "${{ env.LOG_GROUP }}" --region "${{ env.AWS_REGION }}" || true
echo "🧹 CloudWatch log group deleted."
- name: Terraform destroy (full cleanup)
if: ${{ inputs.mode == 'destroy' }}
working-directory: infra
run: terraform destroy -auto-approve -input=false || true
- name: ECR fallback cleanup (destroy)
if: ${{ inputs.mode == 'destroy' }}
continue-on-error: true
env:
AWS_PAGER: ""
run: |
if aws ecr describe-repositories --repository-names "${{ env.ECR_REPOSITORY }}" --region "${{ env.AWS_REGION }}" >/dev/null 2>&1; then
IMAGES=$(aws ecr list-images --repository-name "${{ env.ECR_REPOSITORY }}" --region "${{ env.AWS_REGION }}" --query 'imageIds[*]' --output json || echo "[]")
if [ "$IMAGES" != "[]" ]; then
aws ecr batch-delete-image --repository-name "${{ env.ECR_REPOSITORY }}" --region "${{ env.AWS_REGION }}" --image-ids "$IMAGES" || true
fi
aws ecr delete-repository --repository-name "${{ env.ECR_REPOSITORY }}" --region "${{ env.AWS_REGION }}" --force || true
echo "🗑️ Ensured ECR repo removed."
fi
- name: Verify state is empty (destroy)
if: ${{ inputs.mode == 'destroy' }}
working-directory: infra
run: terraform state list || echo "✅ State is empty"
- name: Get current TaskDefinition ARN
if: ${{ inputs.mode == 'apply' }}
id: svc
run: |
TD=$(aws ecs describe-services --cluster "${{ env.CLUSTER_NAME }}" --services "${{ env.SERVICE_NAME }}" --region "${{ env.AWS_REGION }}" --query "services[0].taskDefinition" --output text)
echo "td=$TD" >> "$GITHUB_OUTPUT"
- name: Download full TaskDefinition JSON
if: ${{ inputs.mode == 'apply' }}
run: |
aws ecs describe-task-definition --task-definition "${{ steps.svc.outputs.td }}" --region "${{ env.AWS_REGION }}" --query "taskDefinition" > taskdef.json
- name: Update image in TaskDefinition
if: ${{ inputs.mode == 'apply' }}
env:
IMG: ${{ steps.ecr.outputs.ECR_URL }}:${{ steps.ecr.outputs.TAG }}
run: |
jq --arg IMG "$IMG" '
del(.revision,.status,.taskDefinitionArn,.requiresAttributes,.compatibilities,.registeredBy,.registeredAt,.deregisteredAt)
| .containerDefinitions = (.containerDefinitions | map(if .name=="app" then .image=$IMG else . end))
' taskdef.json > register.json
echo "Using image: $IMG"
- name: Register new TaskDefinition
if: ${{ inputs.mode == 'apply' }}
id: register
run: |
NEW_TD=$(aws ecs register-task-definition --region "${{ env.AWS_REGION }}" --cli-input-json file://register.json --query "taskDefinition.taskDefinitionArn" --output text)
echo "new=$NEW_TD" >> "$GITHUB_OUTPUT"
- name: Update Service & wait
if: ${{ inputs.mode == 'apply' }}
run: |
aws ecs update-service --cluster "${{ env.CLUSTER_NAME }}" --service "${{ env.SERVICE_NAME }}" --task-definition "${{ steps.register.outputs.new }}" --desired-count 1 --region "${{ env.AWS_REGION }}"
aws ecs wait services-stable --cluster "${{ env.CLUSTER_NAME }}" --services "${{ env.SERVICE_NAME }}" --region "${{ env.AWS_REGION }}"
echo "✅ Deployed and service is stable."