136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
from datetime import UTC, datetime
|
|
from typing import ClassVar
|
|
|
|
import pytest
|
|
|
|
from app import docker_discovery
|
|
from app.models import Service, ServiceKind
|
|
|
|
|
|
class _FakeImage:
|
|
tags: ClassVar[list[str]] = ["example/service:1.0"]
|
|
|
|
|
|
class _FakeContainer:
|
|
id = "abcdef1234567890"
|
|
name = "example-service"
|
|
status = "running"
|
|
image = _FakeImage()
|
|
attrs: ClassVar[dict[str, object]] = {
|
|
"State": {"Status": "running", "Health": {"Status": "healthy"}},
|
|
"Config": {"Image": "example/service:1.0"},
|
|
"NetworkSettings": {
|
|
"Networks": {"default": {}, "monitoring": {}},
|
|
"Ports": {
|
|
"8000/tcp": [{"HostIp": "0.0.0.0", "HostPort": 1253}],
|
|
"5432/tcp": None,
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, **_: object) -> None:
|
|
self.containers = self
|
|
self.closed = False
|
|
|
|
def ping(self) -> bool:
|
|
return True
|
|
|
|
def list(self, all: bool) -> list[_FakeContainer]:
|
|
assert all is True
|
|
return [_FakeContainer()]
|
|
|
|
def close(self) -> None:
|
|
self.closed = True
|
|
|
|
|
|
def test_discovery_normalizes_ports_networks_and_health(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
client = _FakeClient()
|
|
monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client)
|
|
|
|
result = docker_discovery.discover_containers("tcp://docker.local:2375")
|
|
|
|
assert result.docker_host == "tcp://docker.local:2375"
|
|
assert result.candidates[0].name == "example-service"
|
|
assert result.candidates[0].health == "healthy"
|
|
assert result.candidates[0].networks == ["default", "monitoring"]
|
|
assert {(port.container_port, port.host_port) for port in result.candidates[0].ports} == {
|
|
("8000/tcp", 1253),
|
|
("5432/tcp", None),
|
|
}
|
|
assert client.closed is True
|
|
|
|
|
|
def test_discovery_filters_sensitive_label_keys_but_keeps_discovery_metadata(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
token = "do-not-persist-this-token"
|
|
labels = {
|
|
"servicemap.api-token": token,
|
|
"servicemap.password": "do-not-persist-this-password",
|
|
"servicemap.name": "Example API",
|
|
"servicemap.probe": "http",
|
|
"servicemap.port": "8000",
|
|
"com.docker.compose.project": "app-stack",
|
|
"com.docker.compose.service": "web",
|
|
"com.docker.compose.depends_on": "db",
|
|
"com.docker.compose.postgres-password": "do-not-persist-this-compose-password",
|
|
"org.opencontainers.image.title": "Safe image title",
|
|
"other.secret": "not forwarded",
|
|
}
|
|
attrs = {
|
|
**_FakeContainer.attrs,
|
|
"Config": {**_FakeContainer.attrs["Config"], "Labels": labels},
|
|
}
|
|
monkeypatch.setattr(_FakeContainer, "attrs", attrs)
|
|
client = _FakeClient()
|
|
monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client)
|
|
|
|
result = docker_discovery.discover_containers("tcp://docker.local:2375")
|
|
|
|
candidate = result.candidates[0]
|
|
serialized = str(candidate.model_dump(mode="json"))
|
|
assert token not in serialized
|
|
assert "do-not-persist-this-password" not in serialized
|
|
assert "do-not-persist-this-compose-password" not in serialized
|
|
assert "other.secret" not in candidate.labels
|
|
assert candidate.labels == {
|
|
"servicemap.name": "Example API",
|
|
"servicemap.probe": "http",
|
|
"servicemap.port": "8000",
|
|
"com.docker.compose.project": "app-stack",
|
|
"com.docker.compose.service": "web",
|
|
"com.docker.compose.depends_on": "db",
|
|
"org.opencontainers.image.title": "Safe image title",
|
|
}
|
|
assert candidate.stable_source_ref == "docker:compose:app-stack:web"
|
|
assert candidate.suggestions[0].kind == ServiceKind.HTTP
|
|
|
|
|
|
def test_discovery_rejects_untrusted_scheme() -> None:
|
|
with pytest.raises(ValueError, match="must use"):
|
|
docker_discovery.discover_containers("file:///var/run/docker.sock")
|
|
|
|
|
|
def test_discovery_marks_imported_and_carries_review_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
client = _FakeClient()
|
|
monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client)
|
|
existing = Service(
|
|
id="svc-1",
|
|
name="example-service",
|
|
kind=ServiceKind.HTTP,
|
|
url="http://example-service:8000",
|
|
source_type="docker",
|
|
source_ref="docker:abcdef123456@example/service:1.0",
|
|
created_at=datetime.now(UTC),
|
|
updated_at=datetime.now(UTC),
|
|
)
|
|
|
|
result = docker_discovery.discover_containers("tcp://docker.local:2375", [existing])
|
|
|
|
candidate = result.candidates[0]
|
|
assert candidate.import_state == "already imported"
|
|
assert candidate.suggestions[0].source_type == "docker"
|
|
assert candidate.suggestions[0].source_ref == "docker:abcdef123456@example/service:1.0"
|