mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-12-26 05:16:32 +00:00
Fix CID file handling to prevent directory creation (#6225)
* Fix CID file handling to prevent directory creation It seems that under certain conditions Docker creates a directory instead of a file for the CID file. This change ensures that the CID file is always created as a file, and any existing directory is removed before creating the file. * Fix tests * Fix pytest
This commit is contained in:
@@ -326,11 +326,19 @@ class DockerAPI(CoreSysAttributes):
|
||||
if name:
|
||||
cidfile_path = self.coresys.config.path_cid_files / f"{name}.cid"
|
||||
|
||||
# Remove the file if it exists e.g. as a leftover from unclean shutdown
|
||||
if cidfile_path.is_file():
|
||||
with suppress(OSError):
|
||||
# Remove the file/directory if it exists e.g. as a leftover from unclean shutdown
|
||||
# Note: Can be a directory if Docker auto-started container with restart policy
|
||||
# before Supervisor could write the CID file
|
||||
with suppress(OSError):
|
||||
if cidfile_path.is_dir():
|
||||
cidfile_path.rmdir()
|
||||
elif cidfile_path.is_file():
|
||||
cidfile_path.unlink(missing_ok=True)
|
||||
|
||||
# Create empty CID file before adding it to volumes to prevent Docker
|
||||
# from creating it as a directory if container auto-starts
|
||||
cidfile_path.touch()
|
||||
|
||||
extern_cidfile_path = (
|
||||
self.coresys.config.path_extern_cid_files / f"{name}.cid"
|
||||
)
|
||||
|
||||
@@ -318,7 +318,10 @@ def test_not_journald_addon(
|
||||
|
||||
|
||||
async def test_addon_run_docker_error(
|
||||
coresys: CoreSys, addonsdata_system: dict[str, Data], path_extern
|
||||
coresys: CoreSys,
|
||||
addonsdata_system: dict[str, Data],
|
||||
path_extern,
|
||||
tmp_supervisor_data: Path,
|
||||
):
|
||||
"""Test docker error when addon is run."""
|
||||
await coresys.dbus.timedate.connect(coresys.dbus.bus)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Test Docker interface."""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, PropertyMock, call, patch
|
||||
|
||||
@@ -281,6 +282,7 @@ async def test_run_missing_image(
|
||||
container: MagicMock,
|
||||
capture_exception: Mock,
|
||||
path_extern,
|
||||
tmp_supervisor_data: Path,
|
||||
):
|
||||
"""Test run captures the exception when image is missing."""
|
||||
coresys.docker.containers.create.side_effect = [NotFound("missing"), MagicMock()]
|
||||
|
||||
@@ -293,6 +293,8 @@ async def test_cidfile_cleanup_handles_oserror(
|
||||
# Mock the containers.get method and cidfile cleanup to raise OSError
|
||||
with (
|
||||
patch.object(docker.containers, "get", return_value=mock_container),
|
||||
patch("pathlib.Path.is_dir", return_value=False),
|
||||
patch("pathlib.Path.is_file", return_value=True),
|
||||
patch(
|
||||
"pathlib.Path.unlink", side_effect=OSError("File not found")
|
||||
) as mock_unlink,
|
||||
@@ -306,3 +308,46 @@ async def test_cidfile_cleanup_handles_oserror(
|
||||
|
||||
# Verify cidfile cleanup was attempted
|
||||
mock_unlink.assert_called_once_with(missing_ok=True)
|
||||
|
||||
|
||||
async def test_run_container_with_leftover_cidfile_directory(
|
||||
coresys: CoreSys, docker: DockerAPI, path_extern, tmp_supervisor_data
|
||||
):
|
||||
"""Test container creation removes leftover cidfile directory before creating new one.
|
||||
|
||||
This can happen when Docker auto-starts a container with restart policy
|
||||
before Supervisor could write the CID file, causing Docker to create
|
||||
the bind mount source as a directory.
|
||||
"""
|
||||
# Mock container
|
||||
mock_container = MagicMock()
|
||||
mock_container.id = "test_container_id_new"
|
||||
|
||||
container_name = "test_container"
|
||||
cidfile_path = coresys.config.path_cid_files / f"{container_name}.cid"
|
||||
|
||||
# Create a leftover directory (simulating Docker's behavior)
|
||||
cidfile_path.mkdir()
|
||||
assert cidfile_path.is_dir()
|
||||
|
||||
# Mock container creation
|
||||
with patch.object(
|
||||
docker.containers, "create", return_value=mock_container
|
||||
) as create_mock:
|
||||
# Execute run with a container name
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(
|
||||
None,
|
||||
lambda kwrgs: docker.run(**kwrgs),
|
||||
{"image": "test_image", "tag": "latest", "name": container_name},
|
||||
)
|
||||
|
||||
# Verify container was created
|
||||
create_mock.assert_called_once()
|
||||
|
||||
# Verify new cidfile was written as a file (not directory)
|
||||
assert cidfile_path.exists()
|
||||
assert cidfile_path.is_file()
|
||||
assert cidfile_path.read_text() == mock_container.id
|
||||
|
||||
assert result == mock_container
|
||||
|
||||
Reference in New Issue
Block a user