mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-08-17 10:23:22 +01:00
Canonicalizing index.docker.io to docker.io made _get_credentials() qualify Docker Hub images from the raw reference, so a reference already carrying a Docker Hub domain gained a second prefix: index.docker.io/org/app was pulled as docker.io/index.docker.io/org/app. Before the canonicalization the legacy domain matched no configured registry and the image was pulled anonymously under its original name, so this only surfaced now, although the same doubling already applied to an explicit docker.io/org/app reference. Qualify from the remainder instead, which covers references without a domain and with either Docker Hub domain. Three more sites split an image reference on its first colon and hit the same problem a registry with a port causes in the unsupported container evaluation: - The image property of DockerInterface reported myreg:5000/supervisor:1.0 as myreg, and a digest reference as name@sha256. - get_latest_version() read the tag of a RepoTags entry, which for myreg:5000/homeassistant:2026.8.0 yielded 5000/homeassistant:2026.8.0. That is not a known version strategy, so every tag was skipped and the lookup failed with "No version found". This is reachable with a user-overridden Core image or a plugin image on a registry with a port. - The Supervisor start tag repair took the image name from a RepoTags entry the same way, leaving myreg as the name to tag. Also align two Docker Hub details with normalize.go: the library/ prefix for official images now applies whenever the resolved registry is Docker Hub, not only when the reference carried no domain, and credential lookup falls back to the legacy hub.docker.com key for an explicit Docker Hub domain as it already did for references without one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""Test evaluation base."""
|
|
|
|
# pylint: disable=import-error,protected-access
|
|
from unittest.mock import MagicMock, PropertyMock, patch
|
|
|
|
import aiodocker
|
|
from aiodocker.containers import DockerContainer
|
|
|
|
from supervisor.const import CoreState
|
|
from supervisor.coresys import CoreSys
|
|
from supervisor.resolution.const import ContextType, IssueType, UnhealthyReason
|
|
from supervisor.resolution.data import Issue
|
|
from supervisor.resolution.evaluations.container import EvaluateContainer
|
|
|
|
|
|
def _make_image_attr(image: str) -> DockerContainer:
|
|
out = MagicMock(spec=DockerContainer)
|
|
out.show.return_value = {
|
|
"Config": {
|
|
"Image": image,
|
|
},
|
|
}
|
|
return out
|
|
|
|
|
|
async def test_evaluation(coresys: CoreSys):
|
|
"""Test evaluation."""
|
|
container = EvaluateContainer(coresys)
|
|
await coresys.core.set_state(CoreState.RUNNING)
|
|
|
|
assert container.reason not in coresys.resolution.unsupported
|
|
assert UnhealthyReason.DOCKER not in coresys.resolution.unhealthy
|
|
|
|
coresys.docker.containers.list.return_value = [
|
|
_make_image_attr("armhfbuild/watchtower:latest"),
|
|
_make_image_attr("concerco/watchtowerv6:10.0.2"),
|
|
_make_image_attr("containrrr/watchtower:1.1"),
|
|
_make_image_attr("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",
|
|
"concerco/watchtowerv6:10.0.2",
|
|
"containrrr/watchtower:1.1",
|
|
"pyouroboros/ouroboros:1.4.3",
|
|
}
|
|
|
|
coresys.docker.containers.list.return_value = []
|
|
await container()
|
|
assert container.reason not in coresys.resolution.unsupported
|
|
|
|
assert coresys.resolution.evaluate.cached_images == set()
|
|
|
|
|
|
async def test_evaluation_registry_with_port(coresys: CoreSys):
|
|
"""Test an app image from a registry with a port is not flagged unsupported."""
|
|
container = EvaluateContainer(coresys)
|
|
await coresys.core.set_state(CoreState.RUNNING)
|
|
|
|
image = "gitlab.example.com:5005/home-assistant/addon-connector/aarch64"
|
|
with patch.object(
|
|
EvaluateContainer, "known_images", new=PropertyMock(return_value={image})
|
|
):
|
|
coresys.docker.containers.list.return_value = [
|
|
_make_image_attr(f"{image}:0.3.3-dev1")
|
|
]
|
|
await container()
|
|
|
|
assert container.reason not in coresys.resolution.unsupported
|
|
assert UnhealthyReason.DOCKER not in coresys.resolution.unhealthy
|
|
|
|
|
|
async def test_corrupt_docker(coresys: CoreSys):
|
|
"""Test corrupt docker issue."""
|
|
container = EvaluateContainer(coresys)
|
|
await coresys.core.set_state(CoreState.RUNNING)
|
|
|
|
corrupt_docker = Issue(IssueType.CORRUPT_DOCKER, ContextType.SYSTEM)
|
|
assert corrupt_docker not in coresys.resolution.issues
|
|
|
|
coresys.docker.containers.list.side_effect = aiodocker.DockerError(
|
|
500, {"message": "fail"}
|
|
)
|
|
await container()
|
|
assert corrupt_docker in coresys.resolution.issues
|
|
|
|
|
|
async def test_did_run(coresys: CoreSys):
|
|
"""Test that the evaluation ran as expected."""
|
|
container = EvaluateContainer(coresys)
|
|
should_run = container.states
|
|
should_not_run = [state for state in CoreState if state not in should_run]
|
|
assert len(should_run) != 0
|
|
assert len(should_not_run) != 0
|
|
|
|
with patch(
|
|
"supervisor.resolution.evaluations.container.EvaluateContainer.evaluate",
|
|
return_value=None,
|
|
) as evaluate:
|
|
for state in should_run:
|
|
await coresys.core.set_state(state)
|
|
await container()
|
|
evaluate.assert_called_once()
|
|
evaluate.reset_mock()
|
|
|
|
for state in should_not_run:
|
|
await coresys.core.set_state(state)
|
|
await container()
|
|
evaluate.assert_not_called()
|
|
evaluate.reset_mock()
|