From 0878ee7cda3dcc7915c5d9184b495350a63a67d4 Mon Sep 17 00:00:00 2001 From: chase Date: Tue, 3 Nov 2026 09:37:01 +0800 Subject: [PATCH] fix(deploy): fall back to unfiltered export for legacy single-platform images #4277 made offline export pin docker save --platform, but legacy single-platform images such as supabase/postgres:15.8.1.060 carry a platform annotation that never matches linux/amd64 or linux/arm64 exactly: docker pull --platform silently lands the only available variant, then docker save --platform aborts the whole offline package with 'no suitable export target found'. Both the amd64 and arm64 package builds failed at exactly this image. Retry the export without the platform filter when the filtered save fails and the daemon supports platform export: the pull step already pinned the variant, so the unfiltered save ships exactly what this package intends. Docker without platform export support keeps its single-attempt behaviour. Verified locally with a fake docker that reproduces the CI failure: filtered save fails, fallback save succeeds, tar is written. --- deploy/offline/build_offline_package.sh | 20 ++++++++-- .../deploy/test_offline_image_export.py | 37 ++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/deploy/offline/build_offline_package.sh b/deploy/offline/build_offline_package.sh index e135455b36..12405fd01b 100755 --- a/deploy/offline/build_offline_package.sh +++ b/deploy/offline/build_offline_package.sh @@ -502,10 +502,24 @@ save_image_to_tar() { if docker save "${save_args[@]}" "$image"; then echo "✅ Saved: $output_file" return 0 - else - echo "❌ Failed to save image: $image" - return 1 fi + + # Legacy single-platform images (e.g. supabase/postgres:15.8.1.060) carry a + # platform annotation that does not match linux/amd64 or linux/arm64 exactly, + # so `docker pull --platform` silently lands the only available variant while + # `docker save --platform` rejects it with "no suitable export target". + # Retry without the platform filter: the pull step already pinned the + # variant, so the local image is the one this package intends to ship. + if docker_supports_save_platform; then + echo "⚠️ Platform-filtered export failed, retrying without --platform: $image" + if docker save -o "$output_file" "$image"; then + echo "✅ Saved: $output_file" + return 0 + fi + fi + + echo "❌ Failed to save image: $image" + return 1 } save_all_images() { diff --git a/test/backend/deploy/test_offline_image_export.py b/test/backend/deploy/test_offline_image_export.py index 1abe6b4f4f..a409a0a921 100644 --- a/test/backend/deploy/test_offline_image_export.py +++ b/test/backend/deploy/test_offline_image_export.py @@ -41,6 +41,9 @@ def export_image(tmp_path): if os.environ["FAKE_REQUIRE_PLATFORM"] == "true" and "--platform" not in arguments: print("unable to create manifests file: amd64 content digest not found", file=sys.stderr) sys.exit(1) + if os.environ["FAKE_PLATFORM_SAVE_FAILS"] == "true" and "--platform" in arguments: + print("no suitable export target found: image was found but does not provide the specified platform", file=sys.stderr) + sys.exit(1) if int(os.environ["FAKE_SAVE_STATUS"]): print("synthetic image export failure", file=sys.stderr) sys.exit(int(os.environ["FAKE_SAVE_STATUS"])) @@ -51,7 +54,8 @@ def export_image(tmp_path): docker.chmod(0o755) def run(platform="arm64", client_api="1.54", server_api="1.54", *, - version_status=0, save_status=0, require_platform=False): + version_status=0, save_status=0, require_platform=False, + platform_save_fails=False): log_path = tmp_path / "docker.jsonl" output = tmp_path / "offline images.tar" environment = os.environ.copy() @@ -63,6 +67,7 @@ def run(platform="arm64", client_api="1.54", server_api="1.54", *, FAKE_VERSION_STATUS=str(version_status), FAKE_SAVE_STATUS=str(save_status), FAKE_REQUIRE_PLATFORM=str(require_platform).lower(), + FAKE_PLATFORM_SAVE_FAILS=str(platform_save_fails).lower(), ) result = subprocess.run( ["bash", "-c", functions + '\nPLATFORM="$1"\nsave_image_to_tar "$2" "$3"', @@ -141,3 +146,33 @@ def test_export_failure_is_reported_without_an_unrestricted_retry(export_image, saves = [call for call in calls if call[0] == "save"] assert len(saves) == 1 assert ("--platform" in saves[0]) is has_platform + + +@pytest.mark.parametrize("platform", ["arm64", "amd64"]) +def test_platform_export_falls_back_to_unrestricted_save_for_legacy_images(export_image, platform): + # Legacy single-platform images (e.g. supabase/postgres:15.8.1.060) fail a + # platform-filtered export even though the pinned pull already landed the + # only available variant, so the save must fall back to an unfiltered export. + result, calls, output = export_image(platform, platform_save_fails=True) + + assert result.returncode == 0, result.stdout + result.stderr + saves = [call for call in calls if call[0] == "save"] + assert len(saves) == 2 + assert "--platform" in saves[0] + assert "--platform" not in saves[1] + assert saves[1][-1] == "nexent/nexent:v2.8.3" + assert "retrying without --platform" in result.stdout + assert output.read_bytes() == b"synthetic image tar" + + +def test_unrestricted_retry_does_not_run_on_unsupported_docker(export_image): + # On API versions without platform-filtered export there is nothing to + # fall back from: a failing save must stay a single, failed attempt. + result, calls, output = export_image( + client_api="1.39", server_api="1.39", save_status=1, + ) + + assert result.returncode != 0 + saves = [call for call in calls if call[0] == "save"] + assert len(saves) == 1 + assert "--platform" not in saves[0]