fix(update): pin only image tags the registry carries

7e82ba6d71 moved web-app-bigbluebutton's bbb-pads to v1.5.11, the tag
https://raw.githubusercontent.com/bigbluebutton/docker/develop/repos/tags
names for the bbb-pads source. alangecker/bbb-docker-pads has no such
image (Docker Hub: newest v1.5.8), so every BigBlueButton deploy pulls a
missing tag and test-external fails "These pinned Docker images do not
exist in their registry (HTTP 404): web-app-bigbluebutton/bbb-pads".

bbb-pads goes back to v1.5.8. The version-source updater now knows the
image a `version` pin tags (pulled directly or as the base of a custom
build) and withholds a bump when the registry answers 404 for the new
tag; an indeterminate answer does not block it, the same line
test-external draws. Run against main, the updater now reports no
update for bbb-pads instead of v1.5.11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-22 10:30:10 +02:00
1 parent b37d57870e
commit c65c5ce48c
3 files changed
+42 -4

No files matched your search

@@ -152,7 +152,7 @@ etherpad:
# nocheck: dynamic-flag: fixed upstream-compose sub-service, not group-gated
bbb-pads:
image: alangecker/bbb-docker-pads
version: "v1.5.11"
version: "v1.5.8"
update:
type: http_regex
url: https://raw.githubusercontent.com/bigbluebutton/docker/develop/repos/tags
+29 -1
View File
@@ -179,7 +179,10 @@ class TestOutdated(unittest.TestCase):
class TestTemplate(unittest.TestCase):
def _outdated(self, services: str, upstream: str) -> list[str]:
root = _repo(services)
with mock.patch.object(module, "documents", return_value=upstream):
with (
mock.patch.object(module, "documents", return_value=upstream),
mock.patch.object(module, "manifest_exists", return_value=True),
):
updates = module.outdated(module.collect_entries(root), root)
return [update.latest for update in updates]
@@ -206,6 +209,31 @@ class TestTemplate(unittest.TestCase):
)
class TestPulledImage(unittest.TestCase):
def _outdated(self, exists: bool | None) -> tuple[list[str], mock.MagicMock]:
root = _repo(COMPOUND)
with (
mock.patch.object(
module, "documents", return_value="core v3.0.39\npart v1.11.3\n"
),
mock.patch.object(module, "manifest_exists", return_value=exists) as probe,
):
updates = module.outdated(module.collect_entries(root), root)
return [update.latest for update in updates], probe
def test_a_tag_the_image_registry_lacks_is_not_pinned(self) -> None:
latest, probe = self._outdated(False)
self.assertEqual(latest, [])
probe.assert_called_once_with("example/part", "v1.11.3-v3.0.39")
def test_an_indeterminate_registry_answer_keeps_the_bump(self) -> None:
self.assertEqual(self._outdated(None)[0], ["v1.11.3-v3.0.39"])
def test_only_the_version_of_a_pulled_image_carries_the_image(self) -> None:
entries = {e.key: e.image for e in module.collect_entries(_repo())}
self.assertEqual(entries, {"app_version": "", "version": "example"})
class TestAddons(unittest.TestCase):
def test_only_a_declared_or_github_released_addon_pin_is_collected(self) -> None:
entries = module.collect_entries(_addon_repo())
+12 -2
View File
@@ -39,6 +39,7 @@ from urllib.parse import urlencode
from utils.annotations.suppress import is_suppressed_at
from utils.cache.files import read_text
from utils.cache.yaml import load_yaml
from utils.docker.registry import manifest_exists
from utils.roles.mapping import ROLE_FILE_META_SERVICES
from utils.update.addons import GITHUB_RELEASES_CATALOG, iter_addon_files
from utils.update.base import (
@@ -83,6 +84,10 @@ class VersionSourceEntry:
config_path: file that carries the pin.
line: 1-indexed line of the pin.
addon: whether the pin sits in ``meta/addons/<id>.yml``.
image: image the pin tags when it is the entity's ``version``, pulled
directly or as the base of a ``custom`` build; a bump to a tag
its registry answers 404 for is withheld, an indeterminate answer
does not block it.
"""
role: str
@@ -93,6 +98,7 @@ class VersionSourceEntry:
config_path: Path
line: int
addon: bool = False
image: str = ""
@dataclass(frozen=True)
@@ -442,6 +448,7 @@ def collect_entries(repo_root: Path) -> list[VersionSourceEntry]:
source=source,
config_path=config_path,
line=line,
image=config.get("image", "") if key == DEFAULT_KEY else "",
)
)
return entries + _addon_entries(repo_root)
@@ -458,8 +465,11 @@ def outdated(
candidates(entry, repo_root),
assembled=bool(entry.source.get("template")),
)
if newest:
updates.append(VersionSourceUpdate(entry=entry, latest=newest))
if not newest:
continue
if entry.image and manifest_exists(entry.image, newest) is False:
continue
updates.append(VersionSourceUpdate(entry=entry, latest=newest))
return updates