Skip to content

Commit fccd625

Browse files
committed
Merge branch 'dev'
2 parents ea2d92d + 10d8b3e commit fccd625

3 files changed

Lines changed: 172 additions & 77 deletions

File tree

.github/workflows/ci.yml

Lines changed: 149 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ on:
1616

1717
permissions:
1818
id-token: write
19-
contents: read
19+
contents: write
2020

2121
concurrency:
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

.github/workflows/version-release.yml

Lines changed: 0 additions & 25 deletions
This file was deleted.

docs/TAGGING.md

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,23 @@ build time. `<sha7>` is the first 7 characters of the commit SHA.
2020

2121
## Why the `-<sha7>` suffix?
2222

23-
Disambiguation. On `dev`, commitizen never bumps the version (the
24-
`Bump version` workflow only runs on pushes to `main`), so every dev
25-
deploy would otherwise compete for the same `v<version>` tag and the
26-
previous build would be untaggable by name. Appending the short SHA
27-
gives every build its own immutable, version-anchored handle.
23+
Disambiguation. On `dev`, commitizen never bumps the version, so every
24+
dev deploy would otherwise compete for the same `v<version>` tag and
25+
the previous build would be untaggable by name. Appending the short
26+
SHA gives every build its own immutable, version-anchored handle.
2827

29-
On `main` the same suffix is still pushed for two reasons:
30-
31-
1. Between a feature merge and the commitizen bump that follows, two
32-
commits can deploy with the same `v<version>` value, so the suffix
33-
keeps each build addressable.
34-
2. Symmetry: rollback procedures are the same on both environments.
28+
On `main` the same suffix is still pushed for symmetry — the rollback
29+
procedure is identical on both environments, and the suffix gives a
30+
guaranteed-unique handle even if the same release is rebuilt (for
31+
example, a CI retry on the same commit).
3532

3633
## Why `v<version>` is `main`-only
3734

38-
`main` runs commitizen on every push and guarantees a fresh version per
39-
release, so `v<version>` is a stable name for "the build that
40-
corresponds to release X.Y.Z." On `dev` the same tag would change
41-
meaning every push, which is misleading — so we don't publish it there.
35+
`main` runs commitizen as part of every release run, which bumps the
36+
version before the build, so `v<version>` is a stable name for "the
37+
build that corresponds to release X.Y.Z." On `dev` the same tag would
38+
change meaning every push, which is misleading — so we don't publish
39+
it there.
4240

4341
## Example
4442

@@ -79,7 +77,13 @@ build is published it moves forward.
7977

8078
## Where the tagging is configured
8179

82-
`.github/workflows/ci.yml`, in the `deploy` job. The `Extract project
83-
version` step reads `pyproject.toml` via `tomllib`; subsequent build
84-
and push steps consume `steps.project_version.outputs.version`
85-
(`v<version>`) and `.build` (`v<version>-<sha7>`).
80+
`.github/workflows/ci.yml`. The `release-main` job (on `main`) runs
81+
commitizen first, then extracts the version, builds, and pushes; the
82+
`deploy-dev` job (on `dev`) skips the bump. Both jobs read
83+
`pyproject.toml` via `tomllib` in the `Extract project version` step,
84+
and subsequent build/push steps consume
85+
`steps.project_version.outputs.version` (`v<version>`) and `.build`
86+
(`v<version>-<sha7>`). On `main`, the SHA used in `<sha7>` and as the
87+
exact-commit tag is the **bump commit** (the one created by
88+
commitizen inside the same workflow run), not the merge commit that
89+
triggered the run.

0 commit comments

Comments
 (0)