1616
1717permissions :
1818 id-token : write
19- contents : read
19+ contents : write
2020
2121concurrency :
2222 group : ${{ github.head_ref || github.run_id }}
@@ -92,27 +92,67 @@ jobs:
9292 - name : Tear down the Stack
9393 run : docker compose -f docker-compose.github.yml down
9494
95- deploy :
96- name : Deploy
95+ release-main :
96+ # Bumps the version with commitizen, then builds, pushes images, and
97+ # deploys — all in one workflow run so every main release is deployed
98+ # at exactly the version it claims. The bump commit is only pushed
99+ # back to main after the ECR push succeeds, so a failed build never
100+ # leaves a stranded bump on the branch. Skipped on bump: commits
101+ # (already deployed by the run that created them) to avoid recursion.
102+ name : Release (main)
97103 runs-on : ubuntu-24.04-arm
98104 needs : [linter, test]
99- if : github.event_name == 'push'
100- environment : ${{ github.ref_name == 'main' && ' production' || 'dev' }}
105+ if : github.event_name == 'push' && github.ref_name == 'main' && !startsWith(github.event.head_commit.message, 'bump:')
106+ environment : production
101107
102108 env :
103109 AWS_REGION : us-east-1
104- # Branch-based environment config
105- ECR_REPOSITORY : ${{ github.ref_name == 'main' && 'kcprofiles' || 'kcprofiles-dev' }}
106- ECR_REPOSITORY_TRAEFIK : ${{ github.ref_name == 'main' && 'kcprofiles-traefik' || 'kcprofiles-traefik-dev' }}
110+ ECR_REPOSITORY : kcprofiles
111+ ECR_REPOSITORY_TRAEFIK : kcprofiles-traefik
107112 ECR_REPOSITORY_MONITOR : kcprofiles-monitor
108- ECS_CLUSTER : ${{ github.ref_name == 'main' && ' kcprofiles' || 'kcprofiles-dev-2' }}
109- ECS_SERVICE : ${{ github.ref_name == 'main' && ' kcprofiles-blue' || 'kcprofiles-dev' }}
110- AUTOSCALE_GROUP : ${{ github.ref_name == 'main' && ' Infra-ECS-Cluster-kcprofiles-ee645084-ECSAutoScalingGroup-v3MrRnH7laIx' || 'Infra-ECS-Cluster-kcprofiles-dev-2-f89c2350-ECSAutoScalingGroup-zUYaGlEf1GWi' }}
111- COMPOSE_FILE : ${{ github.ref_name == 'main' && ' docker-compose.production.yml' || 'docker-compose.dev.yml' }}
113+ ECS_CLUSTER : kcprofiles
114+ ECS_SERVICE : kcprofiles-blue
115+ AUTOSCALE_GROUP : Infra-ECS-Cluster-kcprofiles-ee645084-ECSAutoScalingGroup-v3MrRnH7laIx
116+ COMPOSE_FILE : docker-compose.production.yml
112117
113118 steps :
114119 - name : Checkout
115120 uses : actions/checkout@v6
121+ with :
122+ fetch-depth : 0
123+ ssh-key : ${{ secrets.COMMIT_KEY }}
124+
125+ - name : Bump version with commitizen
126+ id : bump
127+ env :
128+ COMMITIZEN_VERSION : " 4.16.2"
129+ run : |
130+ python3 -m pip install --user "commitizen==${COMMITIZEN_VERSION}"
131+ git config user.name "github-actions[bot]"
132+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
133+ PRE_SHA=$(git rev-parse HEAD)
134+ set +e
135+ "$HOME/.local/bin/cz" bump --yes
136+ EXIT_CODE=$?
137+ set -e
138+ POST_SHA=$(git rev-parse HEAD)
139+ if [ "$EXIT_CODE" = "0" ] && [ "$PRE_SHA" != "$POST_SHA" ]; then
140+ echo "bumped=true" >> $GITHUB_OUTPUT
141+ elif [ "$EXIT_CODE" = "0" ] || [ "$EXIT_CODE" = "21" ]; then
142+ # cz returned 21 (NoCommitsFoundError) or succeeded without
143+ # changing HEAD: nothing semantic to bump, deploy at the
144+ # existing version.
145+ echo "bumped=false" >> $GITHUB_OUTPUT
146+ else
147+ echo "cz bump failed unexpectedly with exit code $EXIT_CODE" >&2
148+ exit "$EXIT_CODE"
149+ fi
150+
151+ - name : Identify bump commit
152+ id : bump_commit
153+ run : |
154+ BUMP_SHA=$(git rev-parse HEAD)
155+ echo "sha=$BUMP_SHA" >> $GITHUB_OUTPUT
116156
117157 - name : Configure AWS credentials
118158 uses : aws-actions/configure-aws-credentials@v6
@@ -128,60 +168,51 @@ jobs:
128168 id : project_version
129169 run : |
130170 VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
131- SHORT_SHA="${GITHUB_SHA::7}"
171+ SHORT_SHA=$(git rev-parse --short=7 HEAD)
132172 echo "version=v${VERSION}" >> $GITHUB_OUTPUT
133173 echo "build=v${VERSION}-${SHORT_SHA}" >> $GITHUB_OUTPUT
134174
135175 - name : Build, tag, and push images to Amazon ECR
136176 id : build-image
137177 env :
138178 ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
139- IMAGE_TAG : ${{ github .sha }}
179+ IMAGE_TAG : ${{ steps.bump_commit.outputs .sha }}
140180 VERSION_TAG : ${{ steps.project_version.outputs.version }}
141181 BUILD_TAG : ${{ steps.project_version.outputs.build }}
142182 run : |
143183 docker compose -f $COMPOSE_FILE build
144184
145185 # Re-tag the freshly built images with the meaningful version-anchored
146186 # names used for emergency rollback. BUILD_TAG (vX.Y.Z-<sha7>) is
147- # always unique; VERSION_TAG (vX.Y.Z) is published only on main, where
148- # commitizen guarantees a unique version per release .
187+ # always unique; VERSION_TAG (vX.Y.Z) tracks the latest build of a
188+ # given release on main .
149189 docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
150190 $ECR_REGISTRY/$ECR_REPOSITORY:$BUILD_TAG
191+ docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
192+ $ECR_REGISTRY/$ECR_REPOSITORY:$VERSION_TAG
151193 docker tag $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG \
152194 $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$BUILD_TAG
195+ docker tag $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG \
196+ $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$VERSION_TAG
153197
154198 # Push Django image
155199 docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
156200 docker push $ECR_REGISTRY/$ECR_REPOSITORY:$BUILD_TAG
201+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$VERSION_TAG
157202 docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
158203
159204 # Push Traefik image
160205 docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG
161206 docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$BUILD_TAG
207+ docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$VERSION_TAG
162208 docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:latest
163209
164210 echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
165211
166- - name : Tag and push canonical version tag (main only)
167- if : github.ref_name == 'main'
168- env :
169- ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
170- IMAGE_TAG : ${{ github.sha }}
171- VERSION_TAG : ${{ steps.project_version.outputs.version }}
172- run : |
173- docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
174- $ECR_REGISTRY/$ECR_REPOSITORY:$VERSION_TAG
175- docker tag $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG \
176- $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$VERSION_TAG
177- docker push $ECR_REGISTRY/$ECR_REPOSITORY:$VERSION_TAG
178- docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$VERSION_TAG
179-
180212 - name : Build, tag, and push monitor image
181- if : github.ref_name == 'main'
182213 env :
183214 ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
184- IMAGE_TAG : ${{ github .sha }}
215+ IMAGE_TAG : ${{ steps.bump_commit.outputs .sha }}
185216 VERSION_TAG : ${{ steps.project_version.outputs.version }}
186217 BUILD_TAG : ${{ steps.project_version.outputs.build }}
187218 run : |
@@ -195,6 +226,91 @@ jobs:
195226 docker push $ECR_REGISTRY/$ECR_REPOSITORY_MONITOR:$VERSION_TAG
196227 docker push $ECR_REGISTRY/$ECR_REPOSITORY_MONITOR:latest
197228
229+ - name : Push bump commit and tag to main
230+ # Only happens after every ECR push above has succeeded; a failure
231+ # in the build leaves no stranded bump on the branch. Skipped when
232+ # commitizen had nothing semantic to bump.
233+ if : steps.bump.outputs.bumped == 'true'
234+ run : |
235+ git push origin main --tags
236+
237+ - name : Force deployment
238+ continue-on-error : true
239+ run : |
240+ aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --force-new-deployment
241+ aws autoscaling start-instance-refresh --auto-scaling-group-name $AUTOSCALE_GROUP --region $AWS_REGION --preferences '{"SkipMatching": false}'
242+
243+ - name : Set a Sentry release
244+ uses : getsentry/action-release@v3
245+ env :
246+ SENTRY_AUTH_TOKEN : ${{ secrets.SENTRY_AUTH_TOKEN }}
247+ SENTRY_ORG : ${{ secrets.SENTRY_ORG }}
248+ SENTRY_PROJECT : ${{ secrets.SENTRY_PROJECT }}
249+ with :
250+ environment : production
251+ ignore_missing : true
252+
253+ deploy-dev :
254+ name : Deploy (dev)
255+ runs-on : ubuntu-24.04-arm
256+ needs : [linter, test]
257+ if : github.event_name == 'push' && github.ref_name == 'dev'
258+ environment : dev
259+
260+ env :
261+ AWS_REGION : us-east-1
262+ ECR_REPOSITORY : kcprofiles-dev
263+ ECR_REPOSITORY_TRAEFIK : kcprofiles-traefik-dev
264+ ECS_CLUSTER : kcprofiles-dev-2
265+ ECS_SERVICE : kcprofiles-dev
266+ AUTOSCALE_GROUP : Infra-ECS-Cluster-kcprofiles-dev-2-f89c2350-ECSAutoScalingGroup-zUYaGlEf1GWi
267+ COMPOSE_FILE : docker-compose.dev.yml
268+
269+ steps :
270+ - name : Checkout
271+ uses : actions/checkout@v6
272+
273+ - name : Configure AWS credentials
274+ uses : aws-actions/configure-aws-credentials@v6
275+ with :
276+ role-to-assume : arn:aws:iam::755997884632:role/github-actions-kcprofiles
277+ aws-region : ${{ env.AWS_REGION }}
278+
279+ - name : Login to Amazon ECR
280+ id : login-ecr
281+ uses : aws-actions/amazon-ecr-login@v2
282+
283+ - name : Extract project version
284+ id : project_version
285+ run : |
286+ VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
287+ SHORT_SHA="${GITHUB_SHA::7}"
288+ echo "build=v${VERSION}-${SHORT_SHA}" >> $GITHUB_OUTPUT
289+
290+ - name : Build, tag, and push images to Amazon ECR
291+ id : build-image
292+ env :
293+ ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
294+ IMAGE_TAG : ${{ github.sha }}
295+ BUILD_TAG : ${{ steps.project_version.outputs.build }}
296+ run : |
297+ docker compose -f $COMPOSE_FILE build
298+
299+ docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
300+ $ECR_REGISTRY/$ECR_REPOSITORY:$BUILD_TAG
301+ docker tag $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG \
302+ $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$BUILD_TAG
303+
304+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
305+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$BUILD_TAG
306+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
307+
308+ docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$IMAGE_TAG
309+ docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:$BUILD_TAG
310+ docker push $ECR_REGISTRY/$ECR_REPOSITORY_TRAEFIK:latest
311+
312+ echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
313+
198314 - name : Force deployment
199315 continue-on-error : true
200316 run : |
@@ -208,5 +324,5 @@ jobs:
208324 SENTRY_ORG : ${{ secrets.SENTRY_ORG }}
209325 SENTRY_PROJECT : ${{ secrets.SENTRY_PROJECT }}
210326 with :
211- environment : ${{ github.ref_name == 'main' && 'production' || ' dev' }}
327+ environment : dev
212328 ignore_missing : true
0 commit comments