diff --git a/roles/web-app-bigbluebutton/meta/services.yml b/roles/web-app-bigbluebutton/meta/services.yml index 1774c3f158..e93411f153 100644 --- a/roles/web-app-bigbluebutton/meta/services.yml +++ b/roles/web-app-bigbluebutton/meta/services.yml @@ -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 diff --git a/tests/unit/python/utils/update/test_source.py b/tests/unit/python/utils/update/test_source.py index e10f86c77f..df905623d2 100644 --- a/tests/unit/python/utils/update/test_source.py +++ b/tests/unit/python/utils/update/test_source.py @@ -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()) diff --git a/utils/update/source.py b/utils/update/source.py index 0cf9166337..3d6405feaa 100644 --- a/utils/update/source.py +++ b/utils/update/source.py @@ -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/.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