Skip to content

Commit b1c6ebc

Browse files
committed
Close deploy freshness re-run gaps
1 parent d1eea60 commit b1c6ebc

2 files changed

Lines changed: 86 additions & 23 deletions

File tree

.github/workflows/deploy-app.yml

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ jobs:
9696
# the live app version is already newer/divergent vs the version this run
9797
# would ship — so a stale re-run (or a superseded run's "re-run this job")
9898
# cannot roll the app back. A STEP here (not an upstream gate job) so it
99-
# re-evaluates whenever this job runs. Fail-open on an unreadable /health;
100-
# fail-closed on a live commit unresolvable in git history.
99+
# re-evaluates whenever this job runs. The ECS control plane is a reliable
100+
# read, like the infra SSM marker, so a read error fails CLOSED rather than
101+
# allowing a deploy to proceed blind.
101102
# allow-stale bypasses the guard entirely (step skipped → skip output empty
102103
# → deploy runs), so a deliberate rollback can't be wedged by the guard's own
103104
# machinery (unresolvable target, etc.).
@@ -107,7 +108,8 @@ jobs:
107108
shell: bash
108109
env:
109110
TARGET: ${{ inputs.freshness-target-commit }}
110-
HEALTH_URL: ${{ inputs.stage-url }}
111+
NAME: ${{ steps.build_task_name.outputs.name }}
112+
AWS_REGION: ${{ inputs.aws-region }}
111113
run: |
112114
set -euo pipefail
113115
git fetch --no-tags --prune --unshallow origin 2>/dev/null || git fetch --no-tags origin 2>/dev/null || true
@@ -119,25 +121,37 @@ jobs:
119121
echo "::warning::assert-fresh(app): target '${TARGET}' is not a git commit — cannot evaluate freshness; proceeding UNGUARDED"
120122
echo "skip=false" >> "$GITHUB_OUTPUT"; exit 0
121123
fi
122-
health="$(curl -fsS --max-time 15 --retry 3 --retry-delay 5 "$HEALTH_URL" || true)"
123-
# `|| true`: grep exits 1 when the banner is absent (curl failed / body
124-
# lacks the token — common when /health flaps during an ECS cycle); under
125-
# `set -euo pipefail` that would kill the step BEFORE the fail-open branch,
126-
# turning warn-and-proceed into a red deploy. Keep it fail-open.
127-
short="$(printf '%s' "$health" | grep -oiE 'commit hash: [0-9a-f]{7,40}' | head -n1 | awk '{print $3}' || true)"
128-
running=""; [ -z "$short" ] || running="$(git rev-parse "$short" 2>/dev/null || echo "$short")"
124+
set +e
125+
td="$(aws ecs describe-services --cluster "${NAME}-cluster" --services "${NAME}-service" --query "services[0].deployments[?status=='PRIMARY'].taskDefinition | [0]" --output text --region "$AWS_REGION" 2>/tmp/ecs_err)"
126+
ecs_rc=$?
127+
set -e
128+
if [ "$ecs_rc" -ne 0 ] || [ -z "$td" ] || [ "$td" = "None" ]; then
129+
echo "::error::assert-fresh(app): could not read the PRIMARY ECS task definition ($(tr -d '\n' </tmp/ecs_err)) — refusing to deploy blind (fail-closed). Rerun once ECS is reachable, or set freshness-allow-stale."
130+
exit 1
131+
fi
132+
set +e
133+
image="$(aws ecs describe-task-definition --task-definition "$td" --query "taskDefinition.containerDefinitions[?name=='${NAME}'].image | [0]" --output text --region "$AWS_REGION" 2>/tmp/ecs_err)"
134+
image_rc=$?
135+
set -e
136+
if [ "$image_rc" -ne 0 ] || [ -z "$image" ] || [ "$image" = "None" ]; then
137+
set +e
138+
image="$(aws ecs describe-task-definition --task-definition "$td" --query "taskDefinition.containerDefinitions[0].image" --output text --region "$AWS_REGION" 2>/tmp/ecs_err)"
139+
image_rc=$?
140+
set -e
141+
fi
142+
if [ "$image_rc" -ne 0 ] || [ -z "$image" ] || [ "$image" = "None" ]; then
143+
echo "::error::assert-fresh(app): could not read the PRIMARY ECS task definition ($(tr -d '\n' </tmp/ecs_err)) — refusing to deploy blind (fail-closed). Rerun once ECS is reachable, or set freshness-allow-stale."
144+
exit 1
145+
fi
146+
running="${image##*:}"
129147
skip=false
130-
if [ -z "$running" ] || [ "$running" = "None" ]; then
131-
echo "::warning::assert-fresh(app): live version unreadable at ${HEALTH_URL} — proceeding UNGUARDED (fail-open)"
148+
if ! git cat-file -e "${running}^{commit}" 2>/dev/null; then git fetch --no-tags origin "$running" 2>/dev/null || true; fi
149+
if ! git cat-file -e "${running}^{commit}" 2>/dev/null; then
150+
echo "::warning::assert-fresh(app): live ${running:0:7} unresolvable in history — treating as stale"; skip=true
151+
elif git merge-base --is-ancestor "$running" "$TARGET"; then
152+
echo "assert-fresh(app): live ${running:0:7} is an ancestor of ${TARGET:0:7} — fresh"
132153
else
133-
if ! git cat-file -e "${running}^{commit}" 2>/dev/null; then git fetch --no-tags origin "$running" 2>/dev/null || true; fi
134-
if ! git cat-file -e "${running}^{commit}" 2>/dev/null; then
135-
echo "::warning::assert-fresh(app): live ${running:0:7} unresolvable in history — treating as stale"; skip=true
136-
elif git merge-base --is-ancestor "$running" "$TARGET"; then
137-
echo "assert-fresh(app): live ${running:0:7} is an ancestor of ${TARGET:0:7} — fresh"
138-
else
139-
echo "::warning::assert-fresh(app): live ${running:0:7} is NOT an ancestor of ${TARGET:0:7} — newer/divergent already live; skipping ECS deploy"; skip=true
140-
fi
154+
echo "::warning::assert-fresh(app): live ${running:0:7} is NOT an ancestor of ${TARGET:0:7} — newer/divergent already live; skipping ECS deploy"; skip=true
141155
fi
142156
echo "skip=$skip" >> "$GITHUB_OUTPUT"
143157

.github/workflows/deploy-infra.yml

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ jobs:
134134
# Read the marker distinguishing "not created yet" (legit first apply →
135135
# fail OPEN) from a genuine read ERROR — access denied, throttle, timeout
136136
# (anomalous → fail CLOSED). SSM is a reliable read, so an error here is a
137-
# real red flag and we will NOT apply blind and risk a rollback. (The app
138-
# /health guard stays fail-open because it flaps during ECS cycling; this
139-
# marker does not.) `freshness-allow-stale` overrides for a deliberate run.
137+
# real red flag and we will NOT apply blind and risk a rollback. The app
138+
# guard likewise uses the reliable ECS control plane and fails closed.
139+
# `freshness-allow-stale` overrides for a deliberate run.
140140
set +e
141141
running="$(aws ssm get-parameter --name "/${MODULE_ID}/deployed_infra_commit" --query 'Parameter.Value' --output text --region "$AWS_REGION" 2>/tmp/ssm_err)"
142142
ssm_rc=$?
@@ -212,3 +212,52 @@ jobs:
212212
with:
213213
workspace-id: ${{ steps.grafana-get-key.outputs.workspace-id }}
214214
key-name: ${{ steps.grafana-get-key.outputs.key-name }}
215+
216+
- name: Configure AWS Credentials for marker write
217+
if: ${{ inputs.enforce-fresh && !inputs.freshness-allow-stale && steps.freshness.outputs.skip != 'true' }}
218+
uses: aws-actions/configure-aws-credentials@v4
219+
with:
220+
role-to-assume: ${{ inputs.freshness-aws-role-arn }}
221+
aws-region: ${{ inputs.aws-region }}
222+
223+
- name: Advance infra marker
224+
if: ${{ inputs.enforce-fresh && !inputs.freshness-allow-stale && steps.freshness.outputs.skip != 'true' }}
225+
shell: bash
226+
env:
227+
SHA: ${{ inputs.freshness-target-commit }}
228+
MODULE_ID: ${{ inputs.freshness-module-id }}
229+
AWS_REGION: ${{ inputs.aws-region }}
230+
run: |
231+
set -euo pipefail
232+
set +e
233+
current="$(aws ssm get-parameter --name "/${MODULE_ID}/deployed_infra_commit" --query 'Parameter.Value' --output text --region "$AWS_REGION" 2>/tmp/ssm_err)"
234+
ssm_rc=$?
235+
set -e
236+
if [ "$ssm_rc" -ne 0 ]; then
237+
if grep -q "ParameterNotFound" /tmp/ssm_err; then
238+
aws ssm put-parameter --name "/${MODULE_ID}/deployed_infra_commit" --type String --overwrite --value "$SHA" --region "$AWS_REGION" >/dev/null
239+
exit 0
240+
fi
241+
echo "::warning::advance-infra-marker: could not read the deployed_infra_commit marker ($(tr -d '\n' </tmp/ssm_err)) — refusing to risk regressing it"
242+
exit 0
243+
fi
244+
if [ -z "$current" ] || [ "$current" = "None" ] || [ "$current" = "unknown" ]; then
245+
aws ssm put-parameter --name "/${MODULE_ID}/deployed_infra_commit" --type String --overwrite --value "$SHA" --region "$AWS_REGION" >/dev/null
246+
exit 0
247+
fi
248+
if ! git cat-file -e "${SHA}^{commit}" 2>/dev/null; then git fetch --no-tags origin "$SHA" 2>/dev/null || true; fi
249+
if ! git cat-file -e "${current}^{commit}" 2>/dev/null; then git fetch --no-tags origin "$current" 2>/dev/null || true; fi
250+
if ! git cat-file -e "${SHA}^{commit}" 2>/dev/null; then
251+
echo "::warning::advance-infra-marker: target ${SHA:0:7} is unresolvable in history — marker not advanced"
252+
exit 0
253+
fi
254+
if ! git cat-file -e "${current}^{commit}" 2>/dev/null; then
255+
echo "::warning::advance-infra-marker: current marker ${current:0:7} is unresolvable in history — marker not advanced"
256+
exit 0
257+
fi
258+
if git merge-base --is-ancestor "$current" "$SHA"; then
259+
aws ssm put-parameter --name "/${MODULE_ID}/deployed_infra_commit" --type String --overwrite --value "$SHA" --region "$AWS_REGION" >/dev/null
260+
else
261+
echo "::warning::advance-infra-marker: marker ${current:0:7} newer/divergent than ${SHA:0:7} — refusing to regress"
262+
exit 0
263+
fi

0 commit comments

Comments
 (0)