Skip to content

Commit 2a2a2f3

Browse files
genednaclaude
andcommitted
v0.41.11: BB-14 OCI chunked blob upload case
Register `OCI chunked blob upload` (plan-20261001 BB-14): POST uploads/ 202, two contiguous PATCH chunks, status GET 204 with Range 0-2999, and the completing PUT 201 with the expected Docker-Content-Digest. oci_abs only follows Location values under this registry /v2/ so the token never leaves the origin. Marks BB-13 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 c7eff10 commit 2a2a2f3

5 files changed

Lines changed: 49 additions & 6 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.10"
3+
version = "0.41.11"
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`。
398+
已登记 case:`OCI ping`、`OCI oras push and pull`、`OCI reject unauthenticated push`、`OCI chunked blob upload`。
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
@@ -1122,7 +1122,7 @@
11221122
### Task BB-13: OCI reject unauthenticated push
11231123

11241124
**Task type:** `implementation`
1125-
**Lifecycle / Acceptance:** `in-progress` / `remote-pending`(v0.41.10;Codex R1 PASS;C 组 2023 测试通过;待 D 组 `docker` job)
1125+
**Lifecycle / Acceptance:** `done` / `complete`(v0.41.10;C 组 2023 测试通过;D 组 docker run 36936390340 success)
11261126
**Description:** 在 `scripts/oci_client_smoke_storage_only.sh` 注册 case `OCI reject unauthenticated push`。不登录执行 `oras push`,随后匿名查询 tags/list。 唯一行为轴:无凭据写拒绝。
11271127
**Out of scope:** 其它 case(各自成卡);改 `src/**`(产品缺陷按 ADR-BB-08 立 `FIX-BB-*`);改既有 smoke 脚本(GC-BB-06);以 cargo IT 作 VER(GC-BB-04)。
11281128
**Current evidence:**
@@ -1172,7 +1172,7 @@
11721172
### Task BB-14: OCI chunked blob upload
11731173

11741174
**Task type:** `implementation`
1175-
**Lifecycle / Acceptance:** `pending` / (空)
1175+
**Lifecycle / Acceptance:** `in-progress` / `remote-pending`(v0.41.11;Codex R2 PASS;C 组 2023 测试通过;待 D 组 `docker` job)
11761176
**Description:** 在 `scripts/oci_client_smoke_storage_only.sh` 注册 case `OCI chunked blob upload`。`curl` 走 POST → 两次 PATCH → 状态 GET → PUT `?digest=`。 唯一行为轴:分块上传状态机。
11771177
**Out of scope:** 其它 case(各自成卡);改 `src/**`(产品缺陷按 ADR-BB-08 立 `FIX-BB-*`);改既有 smoke 脚本(GC-BB-06);以 cargo IT 作 VER(GC-BB-04)。
11781178
**Current evidence:**

‎scripts/oci_client_smoke_storage_only.sh‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ SCRIPT_DIR="${BASH_SOURCE[0]%/*}"
1919
source "$SCRIPT_DIR/lib/smoke-runner.sh"
2020

2121
smoke_init "oci client"
22-
require_tools curl jq oras sha256sum mktemp date rm perl awk tr timeout rg grep
22+
require_tools curl jq oras sha256sum mktemp date rm perl awk tr timeout rg grep head tail cut
2323

2424
MEGA2_BASE_URL="${MEGA2_BASE_URL:-http://127.0.0.1:9000}"
2525
MEGA2_BASE_URL="${MEGA2_BASE_URL%/}"
@@ -125,8 +125,51 @@ case_oci_reject_unauth_push() {
125125
[ "$(jq -r '.errors[0].code' "$dir/tags.json")" = NAME_UNKNOWN ] || { echo "tags/list error code is not NAME_UNKNOWN" >&2; return 1; }
126126
}
127127

128+
# --- OCI helpers (introduced with BB-14) --------------------------------
129+
# oci_abs <location>: absolute URL for a Location header value. Only paths
130+
# under this registry's /v2/ are accepted, so the push token is never sent
131+
# to another origin.
132+
oci_abs() {
133+
case "$1" in
134+
/v2/*) printf '%s%s' "$MEGA2_BASE_URL" "$1" ;;
135+
"$MEGA2_BASE_URL"/v2/*) printf '%s' "$1" ;;
136+
*) echo "refusing a Location outside $MEGA2_BASE_URL/v2/ (value not shown)" >&2; return 1 ;;
137+
esac
138+
}
139+
oci_digest() { printf 'sha256:%s' "$(sha256sum < "$1" | cut -d' ' -f1)"; }
140+
141+
# --- BB-14 OCI chunked blob upload --------------------------------------
142+
case_oci_chunked_upload() {
143+
local d="$WORK/chunked" repo="bb-$RUN_ID/chunked" code loc dg
144+
oci_case_begin
145+
mkdir -p "$d"
146+
head -c 3000 /dev/urandom > "$d/blob"
147+
head -c 1500 "$d/blob" > "$d/part1"
148+
tail -c 1500 "$d/blob" > "$d/part2"
149+
dg=$(oci_digest "$d/blob")
150+
code=$(oci_curl -u "bb:$TOKEN" -X POST -D "$d/h0" -o /dev/null -w '%{http_code}' "$MEGA2_BASE_URL/v2/$repo/blobs/uploads/")
151+
[ "$code" = 202 ] || { echo "upload init returned $code" >&2; return 1; }
152+
loc=$(oci_abs "$(oci_header "$d/h0" location)") || return 1
153+
code=$(oci_curl -u "bb:$TOKEN" -X PATCH -D "$d/h1" -o /dev/null -w '%{http_code}' \
154+
-H 'Content-Type: application/octet-stream' -H 'Content-Range: 0-1499' --data-binary @"$d/part1" "$loc")
155+
[ "$code" = 202 ] || { echo "first PATCH returned $code" >&2; return 1; }
156+
loc=$(oci_abs "$(oci_header "$d/h1" location)") || return 1
157+
code=$(oci_curl -u "bb:$TOKEN" -X PATCH -D "$d/h2" -o /dev/null -w '%{http_code}' \
158+
-H 'Content-Type: application/octet-stream' -H 'Content-Range: 1500-2999' --data-binary @"$d/part2" "$loc")
159+
[ "$code" = 202 ] || { echo "second PATCH returned $code" >&2; return 1; }
160+
loc=$(oci_abs "$(oci_header "$d/h2" location)") || return 1
161+
code=$(oci_curl -u "bb:$TOKEN" -D "$d/h3" -o /dev/null -w '%{http_code}' "$loc")
162+
[ "$code" = 204 ] || { echo "upload status GET returned $code" >&2; return 1; }
163+
[ "$(oci_header "$d/h3" range)" = "0-2999" ] || { echo "status Range is '$(oci_header "$d/h3" range)'" >&2; return 1; }
164+
case "$loc" in *\?*) loc="$loc&digest=$dg" ;; *) loc="$loc?digest=$dg" ;; esac
165+
code=$(oci_curl -u "bb:$TOKEN" -X PUT -D "$d/h4" -o /dev/null -w '%{http_code}' "$loc")
166+
[ "$code" = 201 ] || { echo "completing PUT returned $code" >&2; return 1; }
167+
[ "$(oci_header "$d/h4" docker-content-digest)" = "$dg" ] || { echo "Docker-Content-Digest mismatch" >&2; return 1; }
168+
}
169+
128170
run_case "OCI ping" case_oci_ping
129171
run_case "OCI oras push and pull" case_oci_oras_push_pull
130172
run_case "OCI reject unauthenticated push" case_oci_reject_unauth_push
173+
run_case "OCI chunked blob upload" case_oci_chunked_upload
131174

132175
finish

0 commit comments

Comments
 (0)