Skip to content

Commit 2108f70

Browse files
alex-strukclaude
andcommitted
Retry staged-image verify and tag promotion on transient registry errors
Problem 4 (intermittent Artifactory `Client.Timeout`) previously only wrapped `docker login`. The deploy job's staged-image existence check and the `docker buildx imagetools create` promotion still made un-retried registry calls, so a transient timeout there failed the deploy. Add a shared `with_retries` helper (scripts/lib/retry.sh, 3 attempts / 15s backoff) and use it for: - the verify-images check (accepts only HTTP 200; retries transient 000/5xx, still fails fast-enough on a genuinely-missing 404 after attempts are exhausted), - the CI promote step, and - the local `oc-build-push.sh --promote` path (CI/local parity). Docs updated to describe the broadened retry coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6c4f613 commit 2108f70

5 files changed

Lines changed: 65 additions & 9 deletions

File tree

.github/workflows/deploy-instance.yml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,35 @@ jobs:
227227
SHA_TAG: ${{ needs.metadata.outputs.sha-tag }}
228228
run: |
229229
set -euo pipefail
230+
source scripts/lib/retry.sh
230231
AUTH="${ARTIFACTORY_SA_USERNAME}:${ARTIFACTORY_SA_PASSWORD}"
231232
DOCKER_API="https://${ARTIFACTORY_URL}/artifactory/api/docker/kfd3-fd34fb-local/v2"
232233
SERVICES=(backend-services frontend temporal ches-adapter)
233-
for svc in "${SERVICES[@]}"; do
234-
echo "Checking ${svc}:${SHA_TAG}..."
234+
235+
# Returns 0 only on HTTP 200. A transient failure (curl error -> 000,
236+
# or a 5xx) is retried by with_retries so an intermittent registry
237+
# timeout doesn't fail the deploy; a persistent non-200 (e.g. 404 for a
238+
# genuinely-missing image) still fails after the attempts are exhausted.
239+
check_manifest() {
240+
local svc="$1" code
235241
code=$(curl --connect-timeout 30 --max-time 120 -s -o /dev/null -w "%{http_code}" \
236242
-u "${AUTH}" -I \
237243
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
238244
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
239245
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
240246
-H "Accept: application/vnd.oci.image.index.v1+json" \
241247
"${DOCKER_API}/${svc}/manifests/${SHA_TAG}" || echo "000")
242-
if [[ "${code}" != "200" ]]; then
243-
echo "::error::Missing staged image ${svc}:${SHA_TAG} (HTTP ${code})"
248+
if [[ "${code}" == "200" ]]; then
249+
return 0
250+
fi
251+
echo "[WARN] ${svc}:${SHA_TAG} not confirmed (HTTP ${code})" >&2
252+
return 1
253+
}
254+
255+
for svc in "${SERVICES[@]}"; do
256+
echo "Checking ${svc}:${SHA_TAG}..."
257+
if ! with_retries 3 15 check_manifest "${svc}"; then
258+
echo "::error::Missing staged image ${svc}:${SHA_TAG}"
244259
exit 1
245260
fi
246261
done
@@ -546,13 +561,14 @@ jobs:
546561
run: |
547562
set -euo pipefail
548563
source scripts/lib/artifactory-login.sh
564+
source scripts/lib/retry.sh
549565
artifactory_docker_login "${ARTIFACTORY_URL}" "${ARTIFACTORY_SA_USERNAME}" "${ARTIFACTORY_SA_PASSWORD}"
550566
551567
REGISTRY="${ARTIFACTORY_URL}/kfd3-fd34fb-local"
552568
SERVICES=(backend-services frontend temporal ches-adapter)
553569
for svc in "${SERVICES[@]}"; do
554570
echo "Promoting ${svc}:${SHA_TAG} -> ${svc}:${FLOATING_TAG}"
555-
docker buildx imagetools create \
571+
with_retries 3 15 docker buildx imagetools create \
556572
"${REGISTRY}/${svc}:${FLOATING_TAG}" \
557573
"${REGISTRY}/${svc}:${SHA_TAG}"
558574
done

docs-md/operations/AUTO_DEPLOY.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,14 @@ The workflow uses a per-ref concurrency group with `cancel-in-progress: true`. I
5252
| Prod (`main`) | `bcgov-di-<sha12>` | `bcgov-di` | `oc set image .../<svc>=<registry>/<svc>:bcgov-di-<old-sha12>` | Keep 3 most recent SHA tags per image |
5353
| Manual (`workflow_dispatch`) | `<branch-tag>-<sha12>` | `<branch-tag>` | Rebuild and redeploy | Keep 10 most recent SHA tags |
5454

55-
## Artifactory login retries
55+
## Artifactory retries
5656

57-
The build and promote steps retry `docker login` up to three times with a 15-second backoff (`scripts/lib/artifactory-login.sh`) to handle intermittent `Client.Timeout exceeded` errors against the registry.
57+
To handle intermittent `Client.Timeout exceeded` errors against the registry, registry operations retry up to three times with a 15-second backoff:
58+
59+
- `docker login` in the build and promote steps (`scripts/lib/artifactory-login.sh`).
60+
- The deploy job's staged-image existence check and the `docker buildx imagetools create` promotion, via a shared `with_retries` helper (`scripts/lib/retry.sh`). The existence check only accepts HTTP 200, so a transient timeout (`000`) or `5xx` is retried while a genuinely-missing image still fails after the attempts are exhausted.
61+
62+
All Artifactory REST/registry `curl` calls additionally use `--connect-timeout 30 --max-time 120`.
5863

5964
## Rollout failure handling
6065

scripts/ARTIFACTORY_README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,4 @@ After successful prod deploys, CI keeps the 3 most recent `bcgov-di-????????????
129129
./scripts/artifactory-cleanup.sh --env prod --delete --keep 3 --match 'bcgov-di-????????????'
130130
```
131131

132-
All Artifactory API calls use `--connect-timeout 30 --max-time 120`. Docker login retries up to 3 times via `scripts/lib/artifactory-login.sh`.
132+
All Artifactory API calls use `--connect-timeout 30 --max-time 120`. Docker login retries up to 3 times via `scripts/lib/artifactory-login.sh`, and the CI staged-image existence check and `imagetools create` tag promotion retry via `scripts/lib/retry.sh` (`with_retries`).

scripts/lib/retry.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
#
3+
# retry.sh — run a command with retries and fixed backoff.
4+
#
5+
# Handles intermittent registry/network failures (e.g. Artifactory
6+
# "Client.Timeout exceeded while awaiting headers") without failing the job on
7+
# the first blip.
8+
#
9+
10+
# with_retries <max_attempts> <wait_seconds> <command> [args...]
11+
# Runs the command; on non-zero exit, waits and retries up to max_attempts.
12+
# Returns the command's exit status from the final attempt.
13+
with_retries() {
14+
local max_attempts="$1"
15+
local wait_seconds="$2"
16+
shift 2
17+
local attempt=1
18+
local status=0
19+
20+
while true; do
21+
if "$@"; then
22+
return 0
23+
else
24+
status=$?
25+
fi
26+
if [[ "${attempt}" -ge "${max_attempts}" ]]; then
27+
echo "[ERROR] Command failed after ${max_attempts} attempts: $*" >&2
28+
return "${status}"
29+
fi
30+
echo "[WARN] Attempt ${attempt}/${max_attempts} failed (exit ${status}); retrying in ${wait_seconds}s..." >&2
31+
sleep "${wait_seconds}"
32+
attempt=$((attempt + 1))
33+
done
34+
}

scripts/oc-build-push.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
1818
source "${SCRIPT_DIR}/lib/config-loader.sh"
1919
source "${SCRIPT_DIR}/lib/image-tag.sh"
2020
source "${SCRIPT_DIR}/lib/artifactory-login.sh"
21+
source "${SCRIPT_DIR}/lib/retry.sh"
2122

2223
usage() {
2324
cat <<EOF
@@ -228,7 +229,7 @@ if [[ "${PROMOTE}" == true ]]; then
228229
else
229230
echo "[INFO] Promoting ${PUSH_TAG} -> ${FLOATING_TAG} for all built services..."
230231
for svc in "${SERVICES[@]}"; do
231-
docker buildx imagetools create \
232+
with_retries 3 15 docker buildx imagetools create \
232233
"${IMAGE_BASE}/${svc}:${FLOATING_TAG}" \
233234
"${IMAGE_BASE}/${svc}:${PUSH_TAG}"
234235
done

0 commit comments

Comments
 (0)