diff --git a/supervisor/resolution/const.py b/supervisor/resolution/const.py index 48ccc2c76..07b7ed052 100644 --- a/supervisor/resolution/const.py +++ b/supervisor/resolution/const.py @@ -28,7 +28,6 @@ class UnsupportedReason(str, Enum): """Reasons for unsupported status.""" APPARMOR = "apparmor" - CONTAINER = "container" CONTENT_TRUST = "content_trust" DBUS = "dbus" DOCKER_CONFIGURATION = "docker_configuration" @@ -39,6 +38,7 @@ class UnsupportedReason(str, Enum): OS = "os" OS_AGENT = "os_agent" PRIVILEGED = "privileged" + SOFTWARE = "software" SOURCE_MODS = "source_mods" SYSTEMD = "systemd" diff --git a/supervisor/resolution/evaluations/container.py b/supervisor/resolution/evaluations/container.py index 7f3f88416..587a722e3 100644 --- a/supervisor/resolution/evaluations/container.py +++ b/supervisor/resolution/evaluations/container.py @@ -1,6 +1,5 @@ """Evaluation class for container.""" import logging -from typing import Any from docker.errors import DockerException from requests import RequestException @@ -18,7 +17,7 @@ from .base import EvaluateBase _LOGGER: logging.Logger = logging.getLogger(__name__) -DOCKER_IMAGE_DENYLIST = [ +UNHEALTHY_IMAGES = [ "watchtower", "ouroboros", ] @@ -41,7 +40,7 @@ class EvaluateContainer(EvaluateBase): @property def reason(self) -> UnsupportedReason: """Return a UnsupportedReason enum.""" - return UnsupportedReason.CONTAINER + return UnsupportedReason.SOFTWARE @property def on_failure(self) -> str: @@ -69,32 +68,32 @@ class EvaluateContainer(EvaluateBase): self._images.clear() for image in await self.sys_run_in_executor(self._get_images): - for tag in image.tags: - self.sys_resolution.evaluate.cached_images.add(tag) + self.sys_resolution.evaluate.cached_images.add(image) - # Evalue system - image_name = tag.partition(":")[0] - if image_name not in self.known_images: - self._images.add(image_name) - if any( - image_name.split("/")[-1].startswith(deny_name) - for deny_name in DOCKER_IMAGE_DENYLIST - ): - _LOGGER.error( - "Found image in deny-list '%s' on the host", image_name - ) - self.sys_resolution.unhealthy = UnhealthyReason.DOCKER + image_name = image.partition(":")[0] + if image_name not in self.known_images: + self._images.add(image_name) + if any( + image_name.split("/")[-1].startswith(unhealthy) + for unhealthy in UNHEALTHY_IMAGES + ): + _LOGGER.error( + "Found image in unhealthy image list '%s' on the host", + image_name, + ) + self.sys_resolution.unhealthy = UnhealthyReason.DOCKER return len(self._images) != 0 - def _get_images(self) -> list[Any]: - """Return a list of images.""" - images = [] - + def _get_images(self) -> set[str]: + """Return a set of images.""" try: - images = [ - container.image for container in self.sys_docker.containers.list() - ] + return { + image + for container in self.sys_docker.containers.list() + if (config := container.attrs.get("Config")) is not None + and (image := config.get("Image")) is not None + } except (DockerException, RequestException) as err: _LOGGER.error("Corrupt docker overlayfs detect: %s", err) self.sys_resolution.create_issue( @@ -103,4 +102,4 @@ class EvaluateContainer(EvaluateBase): suggestions=[SuggestionType.EXECUTE_REPAIR], ) - return images + return {} diff --git a/tests/resolution/evaluation/test_evaluate_container.py b/tests/resolution/evaluation/test_evaluate_container.py index f75de3afd..d67971635 100644 --- a/tests/resolution/evaluation/test_evaluate_container.py +++ b/tests/resolution/evaluation/test_evaluate_container.py @@ -6,6 +6,7 @@ from docker.errors import DockerException from supervisor.const import CoreState from supervisor.coresys import CoreSys +from supervisor.resolution.const import UnhealthyReason from supervisor.resolution.evaluations.container import EvaluateContainer @@ -31,22 +32,20 @@ async def test_evaluation(coresys: CoreSys): coresys.core.state = CoreState.RUNNING assert container.reason not in coresys.resolution.unsupported + assert UnhealthyReason.DOCKER not in coresys.resolution.unhealthy with patch( "supervisor.resolution.evaluations.container.EvaluateContainer._get_images", return_value=[ - MagicMock( - tags=[ - "armhfbuild/watchtower:latest", - "concerco/watchtowerv6:10.0.2", - "containrrr/watchtower:1.1", - "pyouroboros/ouroboros:1.4.3", - ] - ) + "armhfbuild/watchtower:latest", + "concerco/watchtowerv6:10.0.2", + "containrrr/watchtower:1.1", + "pyouroboros/ouroboros:1.4.3", ], ): await container() assert container.reason in coresys.resolution.unsupported + assert UnhealthyReason.DOCKER in coresys.resolution.unhealthy assert coresys.resolution.evaluate.cached_images == { "armhfbuild/watchtower:latest", @@ -57,7 +56,7 @@ async def test_evaluation(coresys: CoreSys): with patch( "supervisor.resolution.evaluations.container.EvaluateContainer._get_images", - return_value=[MagicMock(tags=[])], + return_value=[], ): await container() assert container.reason not in coresys.resolution.unsupported diff --git a/tests/resolution/test_resolution_manager.py b/tests/resolution/test_resolution_manager.py index 1f7234b3d..328b227e7 100644 --- a/tests/resolution/test_resolution_manager.py +++ b/tests/resolution/test_resolution_manager.py @@ -112,10 +112,10 @@ async def test_resolution_create_issue_suggestion(coresys: CoreSys): @pytest.mark.asyncio async def test_resolution_dismiss_unsupported(coresys: CoreSys): """Test resolution manager dismiss unsupported reason.""" - coresys.resolution.unsupported = UnsupportedReason.CONTAINER + coresys.resolution.unsupported = UnsupportedReason.SOFTWARE - coresys.resolution.dismiss_unsupported(UnsupportedReason.CONTAINER) - assert UnsupportedReason.CONTAINER not in coresys.resolution.unsupported + coresys.resolution.dismiss_unsupported(UnsupportedReason.SOFTWARE) + assert UnsupportedReason.SOFTWARE not in coresys.resolution.unsupported with pytest.raises(ResolutionError): - coresys.resolution.dismiss_unsupported(UnsupportedReason.CONTAINER) + coresys.resolution.dismiss_unsupported(UnsupportedReason.SOFTWARE)