fix: re-register orphaned images and harden catalog branches

build_module() now re-registers when a Docker image exists but the
registry entry is missing (e.g., after manual registry edits or
schema migrations). This fixes auto-build in combo workflows where
a module's image exists but isn't tracked.

Also fixes gr-rds catalog branch (main → maint-3.10) and adds
tests for per-entry registry validation and orphan re-registration.
This commit is contained in:
Ryan Malloy 2026-02-02 00:02:12 -07:00
parent 66f1b260f2
commit 15c17aa0a0
3 changed files with 56 additions and 1 deletions

View File

@ -128,6 +128,23 @@ class OOTInstallerMiddleware:
# Idempotent: skip if image already exists # Idempotent: skip if image already exists
if not force and self._image_exists(image_tag): if not force and self._image_exists(image_tag):
existing = self._registry.get(module_name) existing = self._registry.get(module_name)
if existing is None:
# Image exists but registry entry missing — re-register
existing = OOTImageInfo(
module_name=module_name,
image_tag=image_tag,
git_url=git_url,
branch=branch,
git_commit=commit,
base_image=effective_base,
built_at=datetime.now(timezone.utc).isoformat(),
)
self._registry[module_name] = existing
self._save_registry()
logger.info(
"Re-registered existing image '%s' for module '%s'",
image_tag, module_name,
)
return OOTInstallResult( return OOTInstallResult(
success=True, success=True,
image=existing, image=existing,

View File

@ -139,7 +139,7 @@ CATALOG: dict[str, OOTModuleEntry] = {
description="FM RDS/RBDS (Radio Data System) decoder", description="FM RDS/RBDS (Radio Data System) decoder",
category="Broadcast", category="Broadcast",
git_url="https://github.com/bastibl/gr-rds", git_url="https://github.com/bastibl/gr-rds",
branch="main", branch="maint-3.10",
homepage="https://github.com/bastibl/gr-rds", homepage="https://github.com/bastibl/gr-rds",
preinstalled=True, preinstalled=True,
), ),

View File

@ -187,6 +187,44 @@ class TestRegistry:
mw._save_registry() mw._save_registry()
assert mw._registry_path.exists() assert mw._registry_path.exists()
def test_load_skips_corrupt_entries(self, oot):
"""Per-entry validation: one corrupt entry doesn't nuke valid ones."""
data = {
"good_module": {
"module_name": "good_module",
"image_tag": "gr-oot-good:main-abc1234",
"git_url": "https://example.com/gr-good",
"branch": "main",
"git_commit": "abc1234",
"base_image": "gnuradio-runtime:latest",
"built_at": "2025-01-01T00:00:00+00:00",
},
"bad_module": {
"image_id": "sha256:deadbeef",
"build_deps": ["libfoo-dev"],
},
}
oot._registry_path.write_text(json.dumps(data))
loaded = oot._load_registry()
assert "good_module" in loaded
assert "bad_module" not in loaded
def test_build_module_reregisters_orphaned_image(self, oot):
"""build_module re-registers when image exists but registry empty."""
# Mock: _get_remote_commit returns a commit, _image_exists says yes
oot._get_remote_commit = MagicMock(return_value="abc1234")
oot._image_exists = MagicMock(return_value=True)
result = oot.build_module(
git_url="https://github.com/example/gr-test",
branch="main",
)
assert result.success is True
assert result.skipped is True
assert "test" in oot._registry
assert oot._registry["test"].image_tag == "gr-oot-test:main-abc1234"
# ────────────────────────────────────────── # ──────────────────────────────────────────
# Image Naming # Image Naming