-
Notifications
You must be signed in to change notification settings - Fork 1
177 lines (154 loc) · 7.24 KB
/
cd.yml
File metadata and controls
177 lines (154 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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."