Skip to content

Commit 46db388

Browse files
genednaclaude
andcommitted
v0.41.16: BB-19 OCI image index manifest case
Register `OCI image index manifest` (plan-20261001 BB-19): push linux/amd64 and linux/arm64 single-platform manifests with oras --artifact-platform (verified through their configs), PUT an OCI index referencing both (201), and read it back with the index media type, exactly the two pushed digests and their platforms. oci_oras_push gains an optional platform. Marks BB-18 done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018bJDjze83ToyPysYzmxxWS Signed-off-by: Eli Ma <eli@patch.sh>
1 parent 9eec3e9 commit 46db388

5 files changed

Lines changed: 51 additions & 9 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mega2"
3-
version = "0.41.15"
3+
version = "0.41.16"
44
edition = "2024"
55
license = "MIT OR Apache-2.0"
66
description = "Monorepo engine and Git hosting server for AI agents: trunk-based storage, Git Smart HTTP, Git LFS, OCI registry, and Agent Session Capture"

‎docs/deploy-trunk.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ CASE='OCI ping' # 换成要跑的 case 名
395395
docker compose -p mega2-trunk -f docker/docker-compose-storage-only.yml --profile interop exec -T -e MEGA2_SMOKE_CASE="$CASE" interop-smoke bash /repo/scripts/oci_client_smoke_storage_only.sh 2>&1 | tee target/tmp/bb-oci-case.log
396396
```
397397

398-
已登记 case:`OCI ping`、`OCI oras push and pull`、`OCI reject unauthenticated push`、`OCI chunked blob upload`、`OCI cross-repo blob mount`、`OCI manifest HEAD and conditional GET`、`OCI blob range`、`OCI tags list pagination`。
398+
已登记 case:`OCI ping`、`OCI oras push and pull`、`OCI reject unauthenticated push`、`OCI chunked blob upload`、`OCI cross-repo blob mount`、`OCI manifest HEAD and conditional GET`、`OCI blob range`、`OCI tags list pagination`、`OCI image index manifest`。
399399

400400
不设 `MEGA2_SMOKE_CASE` 时整跑全部默认栈 case;opt-in case(auth-none)只经 `scripts/bb_optin_run.sh none oci_client_smoke_storage_only.sh '<case>' <log>` 运行。
401401

‎docs/plan/plan-20261001.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,7 @@
13701370
### Task BB-18: OCI tags list pagination
13711371

13721372
**Task type:** `implementation`
1373-
**Lifecycle / Acceptance:** `in-progress` / `remote-pending`(v0.41.15;Codex R2 PASS(P2 已修);C 组 2023 测试通过;待 D 组 `docker` job)
1373+
**Lifecycle / Acceptance:** `done` / `complete`(v0.41.15;C 组 2023 测试通过;D 组 docker run 36955654543 success)
13741374
**Description:** 在 `scripts/oci_client_smoke_storage_only.sh` 注册 case `OCI tags list pagination`。同一仓库推送 3 个 tag 后分页读取。 唯一行为轴:tags/list 分页。
13751375
**Out of scope:** 其它 case(各自成卡);改 `src/**`(产品缺陷按 ADR-BB-08 立 `FIX-BB-*`);改既有 smoke 脚本(GC-BB-06);以 cargo IT 作 VER(GC-BB-04)。
13761376
**Current evidence:**
@@ -1420,7 +1420,7 @@
14201420
### Task BB-19: OCI image index manifest
14211421

14221422
**Task type:** `implementation`
1423-
**Lifecycle / Acceptance:** `pending` / (空)
1423+
**Lifecycle / Acceptance:** `in-progress` / `remote-pending`(v0.41.16;Codex R2 PASS;C 组 2023 测试通过;待 D 组 `docker` job)
14241424
**Description:** 在 `scripts/oci_client_smoke_storage_only.sh` 注册 case `OCI image index manifest`。推送两个单平台 manifest 后 PUT 引用二者的 OCI index。 唯一行为轴:OCI index 推送。
14251425
**Out of scope:** 其它 case(各自成卡);改 `src/**`(产品缺陷按 ADR-BB-08 立 `FIX-BB-*`);改既有 smoke 脚本(GC-BB-06);以 cargo IT 作 VER(GC-BB-04)。
14261426
**Current evidence:**

‎scripts/oci_client_smoke_storage_only.sh‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,16 @@ case_oci_cross_repo_mount() {
194194
[ -n "$(oci_header "$d/h" docker-upload-uuid)" ] || { echo "fallback session without Docker-Upload-UUID" >&2; return 1; }
195195
}
196196

197-
# oci_oras_push <repo:tag> <dir>: push one small file with oras; prints the
198-
# manifest digest reported by the push (its single `Digest:` line).
197+
# oci_oras_push <repo:tag> <dir> [<os/arch>]: push one small file with oras
198+
# (as a single-platform manifest when <os/arch> is given); prints the manifest
199+
# digest reported by the push (its single `Digest:` line).
199200
oci_oras_push() {
200-
local ref="$REGISTRY/$1" dir="$2"
201+
local ref="$REGISTRY/$1" dir="$2" platform="${3:-}"
201202
mkdir -p "$dir"
202203
export DOCKER_CONFIG="$dir/docker"
203204
printf 'bb %s %s\n' "$1" "$RUN_ID" > "$dir/payload.txt"
204205
printf '%s' "$TOKEN" | oci_run "$dir/login.out" oras login --plain-http -u bb --password-stdin "$REGISTRY" || return 1
205-
(cd "$dir" && oci_run "$dir/push.out" oras push --plain-http "$ref" payload.txt) || return 1
206+
(cd "$dir" && oci_run "$dir/push.out" oras push --plain-http ${platform:+--artifact-platform "$platform"} "$ref" payload.txt) || return 1
206207
local pushed
207208
pushed=$(rg -o '^Digest: (sha256:[0-9a-f]{64})$' -r '$1' "$dir/push.out")
208209
[ "$(printf '%s\n' "$pushed" | grep -c '^sha256:')" = 1 ] \
@@ -270,6 +271,46 @@ case_oci_tags_pagination() {
270271
[ "$(jq -r '.errors[0].code' "$d/bad.json")" = PAGINATION_NUMBER_INVALID ] || { echo "n=abc error code mismatch" >&2; return 1; }
271272
}
272273

274+
# --- BB-19 OCI image index manifest -------------------------------------
275+
case_oci_image_index() {
276+
local d="$WORK/index" repo="bb-$RUN_ID/index" d1 d2 s1 s2 code ct arch
277+
oci_case_begin
278+
d1=$(oci_oras_push "$repo:amd64" "$d/amd64" linux/amd64)
279+
d2=$(oci_oras_push "$repo:arm64" "$d/arm64" linux/arm64)
280+
for arch in amd64 arm64; do
281+
oci_run "$d/config-$arch.json" oras manifest fetch-config --plain-http "$REGISTRY/$repo:$arch"
282+
[ "$(jq -c '{os, architecture}' "$d/config-$arch.json")" = "{\"os\":\"linux\",\"architecture\":\"$arch\"}" ] \
283+
|| { echo "the $arch manifest is not a linux/$arch single-platform manifest" >&2; return 1; }
284+
done
285+
oci_run "$d/desc1.json" oras manifest fetch --plain-http --descriptor "$REGISTRY/$repo:amd64"
286+
oci_run "$d/desc2.json" oras manifest fetch --plain-http --descriptor "$REGISTRY/$repo:arm64"
287+
[ "$(jq -r .digest "$d/desc1.json")" = "$d1" ] && [ "$(jq -r .digest "$d/desc2.json")" = "$d2" ] \
288+
|| { echo "fetched descriptors differ from the pushed digests" >&2; return 1; }
289+
s1=$(jq -r .size "$d/desc1.json")
290+
s2=$(jq -r .size "$d/desc2.json")
291+
jq -n --arg d1 "$d1" --argjson s1 "$s1" --arg d2 "$d2" --argjson s2 "$s2" '{
292+
schemaVersion: 2,
293+
mediaType: "application/vnd.oci.image.index.v1+json",
294+
manifests: [
295+
{mediaType: "application/vnd.oci.image.manifest.v1+json", digest: $d1, size: $s1, platform: {architecture: "amd64", os: "linux"}},
296+
{mediaType: "application/vnd.oci.image.manifest.v1+json", digest: $d2, size: $s2, platform: {architecture: "arm64", os: "linux"}}
297+
]}' > "$d/index.json"
298+
code=$(oci_curl -u "bb:$TOKEN" -X PUT -o /dev/null -w '%{http_code}' \
299+
-H 'Content-Type: application/vnd.oci.image.index.v1+json' --data-binary @"$d/index.json" \
300+
"$MEGA2_BASE_URL/v2/$repo/manifests/index")
301+
[ "$code" = 201 ] || { echo "index PUT returned $code" >&2; return 1; }
302+
code=$(oci_curl -H 'Accept: application/vnd.oci.image.index.v1+json' -D "$d/h" -o "$d/got.json" -w '%{http_code}' \
303+
"$MEGA2_BASE_URL/v2/$repo/manifests/index")
304+
[ "$code" = 200 ] || { echo "index GET returned $code" >&2; return 1; }
305+
ct=$(oci_header "$d/h" content-type)
306+
[ "$ct" = "application/vnd.oci.image.index.v1+json" ] || { echo "index Content-Type is '$ct'" >&2; return 1; }
307+
[ "$(jq '.manifests | length' "$d/got.json")" = 2 ] || { echo "index does not list 2 manifests" >&2; return 1; }
308+
[ "$(jq -c '[.manifests[].digest]' "$d/got.json")" = "$(jq -cn --arg a "$d1" --arg b "$d2" '[$a, $b]')" ] \
309+
|| { echo "index manifests are not the two pushed manifests" >&2; return 1; }
310+
[ "$(jq -c '[.manifests[].platform | "\(.os)/\(.architecture)"]' "$d/got.json")" = '["linux/amd64","linux/arm64"]' ] \
311+
|| { echo "index platforms are not linux/amd64 and linux/arm64" >&2; return 1; }
312+
}
313+
273314
run_case "OCI ping" case_oci_ping
274315
run_case "OCI oras push and pull" case_oci_oras_push_pull
275316
run_case "OCI reject unauthenticated push" case_oci_reject_unauth_push
@@ -278,5 +319,6 @@ run_case "OCI cross-repo blob mount" case_oci_cross_repo_mount
278319
run_case "OCI manifest HEAD and conditional GET" case_oci_manifest_head_304
279320
run_case "OCI blob range" case_oci_blob_range
280321
run_case "OCI tags list pagination" case_oci_tags_pagination
322+
run_case "OCI image index manifest" case_oci_image_index
281323

282324
finish

0 commit comments

Comments
 (0)