Skip to content

Commit 46ee4a3

Browse files
committed
fix(ci): prefetch buildx attestation manifests
Buildx can embed attestation manifests directly in the OCI index (vnd.docker.reference.type=attestation-manifest). In our ECR -> Workers -> R2 pull-through flow, these digests can be referenced by the index but missing from R2 if they are not requested while ECR still retains them, leading to pull failures. - Add a small helper script to extract these digests from the index - Force-fetch each digest via the live registry and warm its blobs
1 parent 05c6e75 commit 46ee4a3

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

.github/workflows/docker-riverproui.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,15 @@ jobs:
468468
echo "amd64_digest=$AMD64_DIGEST" >> $GITHUB_OUTPUT
469469
echo "arm64_digest=$ARM64_DIGEST" >> $GITHUB_OUTPUT
470470
471+
- name: Prefetch buildx attestation manifests referenced by index (legacy non-referrers)
472+
env:
473+
AUTH_USER: river
474+
AUTH_PASSWORD: ${{ secrets.RIVERPRO_GO_MOD_CREDENTIAL }}
475+
FORCE_FETCH_SECRET: ${{ secrets.FORCE_FETCH_SECRET }}
476+
REGISTRY_MANIFEST_URL: https://riverqueue.com/v2/riverproui/manifests
477+
run: |
478+
bash scripts/prefetch-buildx-attestation-manifests.sh /tmp/index-manifest-docker.json
479+
471480
- name: Fetch amd64 manifest with crane (Docker media type)
472481
run: crane manifest "$IMAGE_NAME@${{ steps.platform-digests.outputs.amd64_digest }}" > /tmp/amd64-manifest-docker.json
473482

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Prefetch buildx "attestation-manifest" descriptors referenced directly in an OCI image index.
5+
#
6+
# Why: buildx can embed attestation manifests in the index's `manifests[]` with
7+
# `annotations["vnd.docker.reference.type"]="attestation-manifest"`. Some clients try to pull them.
8+
# In a pull-through-cache setup (ECR -> Worker -> R2), you must request these while the upstream
9+
# still has them or they can later 404 from the R2-backed registry.
10+
#
11+
# Inputs:
12+
# - Arg1: path to an index JSON file (e.g. from `crane manifest ... > /tmp/index.json`)
13+
#
14+
# Environment variables:
15+
# - IMAGE_NAME (required): e.g. "riverqueue.com/riverproui"
16+
# - REGISTRY_MANIFEST_URL (required): full manifest base URL, e.g. "https://riverqueue.com/v2/riverproui/manifests"
17+
# - AUTH_USER (required): basic auth username
18+
# - AUTH_PASSWORD (required): basic auth password
19+
# - FORCE_FETCH_SECRET (optional): if set, sent as X-Force-Fetch-From-Upstream to force pull-through from upstream
20+
21+
INDEX_JSON_PATH="${1:-}"
22+
if [[ -z "$INDEX_JSON_PATH" ]]; then
23+
echo "usage: $0 <index-json-path>" >&2
24+
exit 2
25+
fi
26+
27+
: "${IMAGE_NAME:?IMAGE_NAME is required (e.g. riverqueue.com/riverproui)}"
28+
: "${REGISTRY_MANIFEST_URL:?REGISTRY_MANIFEST_URL is required (e.g. https://riverqueue.com/v2/riverproui/manifests)}"
29+
: "${AUTH_USER:?AUTH_USER is required}"
30+
: "${AUTH_PASSWORD:?AUTH_PASSWORD is required}"
31+
32+
tmp_digests="$(mktemp)"
33+
jq -r '
34+
.manifests[]?
35+
| select((.annotations["vnd.docker.reference.type"] // "") == "attestation-manifest")
36+
| .digest
37+
' "$INDEX_JSON_PATH" | sort -u > "$tmp_digests"
38+
39+
if [[ ! -s "$tmp_digests" ]]; then
40+
echo "No buildx attestation-manifest descriptors found in index."
41+
exit 0
42+
fi
43+
44+
echo "Prefetching buildx attestation manifests referenced by index:"
45+
cat "$tmp_digests"
46+
47+
while read -r digest; do
48+
[[ -z "$digest" ]] && continue
49+
50+
echo "Prefetching attestation manifest: $digest"
51+
52+
# Ensure the pull-through cache fetches the manifest bytes from upstream and stores them.
53+
# Note: ORAS cannot send our custom force-refresh header, so we use curl for that part.
54+
curl_args=(
55+
-f
56+
-u "${AUTH_USER}:${AUTH_PASSWORD}"
57+
-H "Accept: application/vnd.oci.image.manifest.v1+json"
58+
"${REGISTRY_MANIFEST_URL}/${digest}"
59+
-o /dev/null
60+
)
61+
if [[ -n "${FORCE_FETCH_SECRET:-}" ]]; then
62+
curl_args=(
63+
-f
64+
-u "${AUTH_USER}:${AUTH_PASSWORD}"
65+
-H "X-Force-Fetch-From-Upstream: ${FORCE_FETCH_SECRET}"
66+
-H "Accept: application/vnd.oci.image.manifest.v1+json"
67+
"${REGISTRY_MANIFEST_URL}/${digest}"
68+
-o /dev/null
69+
)
70+
fi
71+
curl "${curl_args[@]}"
72+
73+
# Warm referenced blobs so subsequent clients don't have to traverse to upstream.
74+
mf="$(mktemp)"
75+
oras manifest fetch --output "$mf" "${IMAGE_NAME}@${digest}"
76+
77+
cfg="$(jq -r '.config.digest // empty' "$mf")"
78+
if [[ -n "$cfg" ]]; then
79+
oras blob fetch --output /dev/null "${IMAGE_NAME}@${cfg}"
80+
fi
81+
82+
jq -r '.layers[]?.digest // empty' "$mf" | while read -r layer_digest; do
83+
[[ -n "$layer_digest" ]] && oras blob fetch --output /dev/null "${IMAGE_NAME}@${layer_digest}"
84+
done
85+
done < "$tmp_digests"
86+
87+

0 commit comments

Comments
 (0)