Skip to content

Commit abd22df

Browse files
committed
fix(runtime): pin docker image builder release
Signed-off-by: 117503445 <t117503445@gmail.com>
1 parent 9ed4d88 commit abd22df

4 files changed

Lines changed: 127 additions & 24 deletions

File tree

docs/en/runtime.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,11 @@ Aliyun UID/AK/SK, and `DOCKER_IMAGE_BUILDER_USERNAME` /
123123
`DOCKER_IMAGE_BUILDER_PASSWORD` for registry auth unless the YAML overrides them
124124
under `cloudBuild.registry`.
125125

126-
When the CLI downloads docker-image-builder automatically, it verifies the
127-
sibling `.sha256` file before caching or running the binary. Set
128-
`DOCKER_IMAGE_BUILDER_BINPATH` to use an explicit local builder binary.
126+
When the CLI downloads docker-image-builder automatically, it uses the pinned
127+
builder release and verifies the embedded SHA256 digest before caching or
128+
running the binary. Set `DOCKER_IMAGE_BUILDER_BINPATH` to use an explicit local
129+
builder binary. Set `DOCKER_IMAGE_BUILDER_BINTAG` to override the release tag;
130+
overridden tags must provide a sibling `.sha256` file.
129131

130132
### Examples
131133

docs/zh/runtime.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@ docker-image-builder;builder 成功退出时输出 `completed`。docker-image-
116116
镜像仓库用户名和密码优先读 YAML 的 `cloudBuild.registry`,否则读取
117117
`DOCKER_IMAGE_BUILDER_USERNAME` / `DOCKER_IMAGE_BUILDER_PASSWORD`
118118

119-
CLI 自动下载 docker-image-builder 时,会先校验同名 `.sha256` 文件,再缓存或执行该
120-
二进制。设置 `DOCKER_IMAGE_BUILDER_BINPATH` 可以使用显式指定的本地 builder。
119+
CLI 自动下载 docker-image-builder 时,会使用固定的 builder 发布版本,并在缓存或执行
120+
二进制前校验内置 SHA256。设置 `DOCKER_IMAGE_BUILDER_BINPATH` 可以使用显式指定的
121+
本地 builder。设置 `DOCKER_IMAGE_BUILDER_BINTAG` 可以覆盖发布 tag;被覆盖的 tag
122+
必须提供同名 `.sha256` 文件。
121123

122124
### Examples
123125

src/agentrun_cli/_utils/cloud_build.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,28 @@
1616

1717
from agentrun_cli._utils.agentruntime_yaml import ParsedAgentRuntime, ParsedCloudBuild
1818

19-
BUILDER_RELEASE_TAG = "latest"
19+
BUILDER_RELEASE_TAG = "v0.0.0-20260518-164317-160dd89efac1"
2020
BUILDER_BASE_URL = "https://images.devsapp.cn/docker-image-builder"
21+
BUILDER_RELEASE_SHA256 = {
22+
"docker-image-builder-darwin-amd64": (
23+
"7311df3d1026a5a66823da951c05b9ec455e2d8514af76aeb550d97e3629cb5e"
24+
),
25+
"docker-image-builder-darwin-arm64": (
26+
"c6112ac61d85815e8103ed21989c40828a49798178b73337cb957d9ff433c338"
27+
),
28+
"docker-image-builder-linux-amd64": (
29+
"ad8af2d620f0509b20cef2967fc296915b85a28ebb40a16116b64b41d67820e2"
30+
),
31+
"docker-image-builder-linux-arm64": (
32+
"ec4cf574ce43051e04f45eeedf2a2622c79d75c62efda9b353578898b0bed714"
33+
),
34+
"docker-image-builder-windows-amd64.exe": (
35+
"cb19f3af6613eba2f42e31d925238b7ef7847fcaa615247fbe4fc179b80a23e7"
36+
),
37+
"docker-image-builder-windows-arm64.exe": (
38+
"7a44d8f36ba30c140700ce5b8dcf8dce78b1e9e71e6583b0b8f07c8fd8e5d6b4"
39+
),
40+
}
2141

2242

2343
class CloudBuildError(RuntimeError):
@@ -213,7 +233,7 @@ def ensure_builder_binary() -> str:
213233
artifact = _artifact_name()
214234
url = f"{BUILDER_BASE_URL}/{tag}/{artifact}"
215235
try:
216-
expected_sha256 = _download_sha256(f"{url}.sha256", artifact)
236+
expected_sha256 = _expected_sha256(tag, url, artifact)
217237
if _is_executable(target) and _sha256_file(target) == expected_sha256:
218238
return str(target)
219239
_download_binary(url, tmp)
@@ -226,6 +246,31 @@ def ensure_builder_binary() -> str:
226246
return str(target)
227247

228248

249+
def _expected_sha256(tag: str, url: str, artifact_name: str) -> str:
250+
"""Return the expected SHA256 digest for a release artifact.
251+
252+
Args:
253+
tag: Release tag being installed.
254+
url: Artifact download URL.
255+
artifact_name: Expected release artifact name.
256+
"""
257+
if tag == BUILDER_RELEASE_TAG:
258+
return _pinned_sha256(artifact_name)
259+
return _download_sha256(f"{url}.sha256", artifact_name)
260+
261+
262+
def _pinned_sha256(artifact_name: str) -> str:
263+
"""Return the pinned release SHA256 digest for an artifact.
264+
265+
Args:
266+
artifact_name: Expected release artifact name.
267+
"""
268+
digest = BUILDER_RELEASE_SHA256.get(artifact_name)
269+
if not digest:
270+
raise CloudBuildError(f"missing pinned sha256 for {artifact_name}")
271+
return digest
272+
273+
229274
def _download_binary(url: str, target: Path) -> None:
230275
"""Download a binary to a temporary local path.
231276

tests/unit/test_cloud_build.py

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ParsedContainer,
1919
)
2020
from agentrun_cli._utils.cloud_build import (
21+
BUILDER_RELEASE_SHA256,
2122
BUILDER_RELEASE_TAG,
2223
CloudBuildError,
2324
build_builder_args,
@@ -159,7 +160,14 @@ def test_ensure_builder_binary_rejects_bad_binpath(monkeypatch, tmp_path):
159160
ensure_builder_binary()
160161

161162

162-
def test_ensure_builder_binary_downloads_latest_with_checksum(monkeypatch, tmp_path):
163+
def test_builder_release_tag_is_pinned():
164+
assert BUILDER_RELEASE_TAG.startswith("v0.0.0-")
165+
assert BUILDER_RELEASE_TAG != "latest"
166+
167+
168+
def test_ensure_builder_binary_downloads_pinned_version_with_checksum(
169+
monkeypatch, tmp_path
170+
):
163171
monkeypatch.delenv("DOCKER_IMAGE_BUILDER_BINPATH", raising=False)
164172
monkeypatch.delenv("DOCKER_IMAGE_BUILDER_BINTAG", raising=False)
165173
monkeypatch.setenv("HOME", str(tmp_path))
@@ -173,18 +181,18 @@ def fake_download(url, target):
173181
assert f"/{BUILDER_RELEASE_TAG}/" in url
174182
target.write_bytes(content)
175183

176-
def fake_download_sha256(url, artifact_name):
177-
assert url.endswith("/docker-image-builder-linux-amd64.sha256")
178-
assert artifact_name == "docker-image-builder-linux-amd64"
179-
return sha256(content).hexdigest()
180-
184+
monkeypatch.setitem(
185+
BUILDER_RELEASE_SHA256,
186+
"docker-image-builder-linux-amd64",
187+
sha256(content).hexdigest(),
188+
)
181189
monkeypatch.setattr(
182190
"agentrun_cli._utils.cloud_build._download_binary",
183191
fake_download,
184192
)
185193
monkeypatch.setattr(
186194
"agentrun_cli._utils.cloud_build._download_sha256",
187-
fake_download_sha256,
195+
lambda *_args: pytest.fail("pinned release should use embedded checksum"),
188196
)
189197
binary = ensure_builder_binary()
190198
expected_suffix = (
@@ -214,7 +222,45 @@ def test_ensure_builder_binary_uses_cached_bintag(monkeypatch, tmp_path):
214222
assert ensure_builder_binary() == str(cached)
215223

216224

217-
def test_ensure_builder_binary_replaces_stale_cached_latest(monkeypatch, tmp_path):
225+
def test_ensure_builder_binary_downloads_custom_bintag_with_remote_checksum(
226+
monkeypatch, tmp_path
227+
):
228+
monkeypatch.delenv("DOCKER_IMAGE_BUILDER_BINPATH", raising=False)
229+
monkeypatch.setenv("DOCKER_IMAGE_BUILDER_BINTAG", "custom-tag")
230+
monkeypatch.setenv("HOME", str(tmp_path))
231+
monkeypatch.setattr(
232+
"agentrun_cli._utils.cloud_build._artifact_name",
233+
lambda: "docker-image-builder-linux-amd64",
234+
)
235+
content = b"custom"
236+
237+
def fake_download(url, target):
238+
assert "/custom-tag/" in url
239+
target.write_bytes(content)
240+
241+
def fake_download_sha256(url, artifact_name):
242+
assert url.endswith("/custom-tag/docker-image-builder-linux-amd64.sha256")
243+
assert artifact_name == "docker-image-builder-linux-amd64"
244+
return sha256(content).hexdigest()
245+
246+
monkeypatch.setattr(
247+
"agentrun_cli._utils.cloud_build._download_binary",
248+
fake_download,
249+
)
250+
monkeypatch.setattr(
251+
"agentrun_cli._utils.cloud_build._download_sha256",
252+
fake_download_sha256,
253+
)
254+
255+
binary = ensure_builder_binary()
256+
257+
assert binary.endswith(".docker-image-builder/custom-tag/docker-image-builder")
258+
assert os.access(binary, os.X_OK)
259+
260+
261+
def test_ensure_builder_binary_replaces_stale_cached_pinned_release(
262+
monkeypatch, tmp_path
263+
):
218264
monkeypatch.delenv("DOCKER_IMAGE_BUILDER_BINPATH", raising=False)
219265
monkeypatch.delenv("DOCKER_IMAGE_BUILDER_BINTAG", raising=False)
220266
monkeypatch.setenv("HOME", str(tmp_path))
@@ -232,9 +278,10 @@ def test_ensure_builder_binary_replaces_stale_cached_latest(monkeypatch, tmp_pat
232278
cached.write_bytes(b"old")
233279
cached.chmod(cached.stat().st_mode | stat.S_IXUSR)
234280
new_content = b"new"
235-
monkeypatch.setattr(
236-
"agentrun_cli._utils.cloud_build._download_sha256",
237-
lambda *_args: sha256(new_content).hexdigest(),
281+
monkeypatch.setitem(
282+
BUILDER_RELEASE_SHA256,
283+
"docker-image-builder-linux-amd64",
284+
sha256(new_content).hexdigest(),
238285
)
239286

240287
def fake_download(_url, target):
@@ -256,9 +303,10 @@ def test_ensure_builder_binary_rejects_checksum_mismatch(monkeypatch, tmp_path):
256303
"agentrun_cli._utils.cloud_build._artifact_name",
257304
lambda: "docker-image-builder-linux-amd64",
258305
)
259-
monkeypatch.setattr(
260-
"agentrun_cli._utils.cloud_build._download_sha256",
261-
lambda *_args: sha256(b"expected").hexdigest(),
306+
monkeypatch.setitem(
307+
BUILDER_RELEASE_SHA256,
308+
"docker-image-builder-linux-amd64",
309+
sha256(b"expected").hexdigest(),
262310
)
263311
monkeypatch.setattr(
264312
"agentrun_cli._utils.cloud_build._download_binary",
@@ -276,9 +324,10 @@ def test_ensure_builder_binary_download_failure(monkeypatch, tmp_path):
276324
"agentrun_cli._utils.cloud_build._artifact_name",
277325
lambda: "docker-image-builder-linux-amd64",
278326
)
279-
monkeypatch.setattr(
280-
"agentrun_cli._utils.cloud_build._download_sha256",
281-
lambda *_args: sha256(b"bin").hexdigest(),
327+
monkeypatch.setitem(
328+
BUILDER_RELEASE_SHA256,
329+
"docker-image-builder-linux-amd64",
330+
sha256(b"bin").hexdigest(),
282331
)
283332
monkeypatch.setattr(
284333
"agentrun_cli._utils.cloud_build._download_binary",
@@ -329,6 +378,11 @@ def read(self):
329378
)
330379

331380

381+
def test_pinned_sha256_rejects_unknown_artifact():
382+
with pytest.raises(CloudBuildError, match="missing pinned sha256"):
383+
cloud_build_mod._pinned_sha256("docker-image-builder-plan9-amd64")
384+
385+
332386
def test_parse_sha256_accepts_raw_digest():
333387
digest = "a" * 64
334388
assert cloud_build_mod._parse_sha256(digest, "artifact") == digest

0 commit comments

Comments
 (0)