Fix TypeError when Docker returns null labels in container inspect
When a container has no labels, Docker returns {"Labels": null} in the
inspect response. The code was using .get("Labels", {}) which only
returns the default when the key is missing, not when it's null.
This caused _has_webterm_label() to raise TypeError, which was silently
caught by the event watcher's exception handler, causing it to reconnect
and miss the container start event.
Fixed by using .get("Labels") or {} pattern in:
- _handle_event() when processing start events
- _get_container_command() when extracting command label
- _get_container_theme() when extracting theme label
Added test for null labels case.
This commit is contained in:
@@ -151,7 +151,7 @@ class DockerWatcher:
|
|||||||
|
|
||||||
If label is 'auto', empty, or missing, returns default exec command.
|
If label is 'auto', empty, or missing, returns default exec command.
|
||||||
"""
|
"""
|
||||||
labels = container.get("Labels", {})
|
labels = container.get("Labels") or {}
|
||||||
label_value = labels.get(LABEL_NAME)
|
label_value = labels.get(LABEL_NAME)
|
||||||
|
|
||||||
if _is_auto_label(label_value):
|
if _is_auto_label(label_value):
|
||||||
@@ -159,7 +159,7 @@ class DockerWatcher:
|
|||||||
return label_value or AUTO_COMMAND_SENTINEL
|
return label_value or AUTO_COMMAND_SENTINEL
|
||||||
|
|
||||||
def _get_container_theme(self, container: dict) -> str | None:
|
def _get_container_theme(self, container: dict) -> str | None:
|
||||||
labels = container.get("Labels", {})
|
labels = container.get("Labels") or {}
|
||||||
value = labels.get(THEME_LABEL)
|
value = labels.get(THEME_LABEL)
|
||||||
if isinstance(value, str) and value.strip():
|
if isinstance(value, str) and value.strip():
|
||||||
return value.strip()
|
return value.strip()
|
||||||
@@ -287,12 +287,14 @@ class DockerWatcher:
|
|||||||
if status == 200:
|
if status == 200:
|
||||||
container_info = json.loads(body)
|
container_info = json.loads(body)
|
||||||
# Convert to list format expected by _add_container
|
# Convert to list format expected by _add_container
|
||||||
|
# Labels can be None if container has no labels
|
||||||
|
labels = container_info.get("Config", {}).get("Labels") or {}
|
||||||
container = {
|
container = {
|
||||||
"Id": container_id,
|
"Id": container_id,
|
||||||
"Names": ["/" + container_info.get("Name", "").lstrip("/")],
|
"Names": ["/" + container_info.get("Name", "").lstrip("/")],
|
||||||
"Labels": container_info.get("Config", {}).get("Labels", {}),
|
"Labels": labels,
|
||||||
}
|
}
|
||||||
if _has_webterm_label(container.get("Labels", {})):
|
if _has_webterm_label(labels):
|
||||||
await self._add_container(container)
|
await self._add_container(container)
|
||||||
elif action == "die":
|
elif action == "die":
|
||||||
await self._remove_container(container_id)
|
await self._remove_container(container_id)
|
||||||
|
|||||||
@@ -263,6 +263,36 @@ class TestDockerWatcherIntegration:
|
|||||||
|
|
||||||
session_manager.add_app.assert_called_once()
|
session_manager.add_app.assert_called_once()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_handle_start_event_null_labels(self, session_manager):
|
||||||
|
"""Container with null labels in Docker inspect response is handled gracefully."""
|
||||||
|
watcher = DockerWatcher(session_manager)
|
||||||
|
|
||||||
|
async def mock_request(method, path):
|
||||||
|
if "/containers/" in path and "/json" in path:
|
||||||
|
# Docker returns null for Labels when container has none
|
||||||
|
return (
|
||||||
|
200,
|
||||||
|
'{"Name": "/no-labels-container", "Config": {"Labels": null}}',
|
||||||
|
)
|
||||||
|
return 404, ""
|
||||||
|
|
||||||
|
watcher._docker_request = mock_request
|
||||||
|
|
||||||
|
event = {
|
||||||
|
"Action": "start",
|
||||||
|
"Actor": {
|
||||||
|
"ID": "container456",
|
||||||
|
"Attributes": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Should not raise an exception
|
||||||
|
await watcher._handle_event(event)
|
||||||
|
|
||||||
|
# Should not add container (no webterm labels)
|
||||||
|
session_manager.add_app.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("labels", "expected"),
|
("labels", "expected"),
|
||||||
|
|||||||
Reference in New Issue
Block a user