From df8201ca33119c12719d2f511be6da73e9a342ce Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Tue, 27 Jan 2026 21:00:33 +0100 Subject: [PATCH] Update `get_docker_args()` to return `mounts` not `volumes` (#6499) * Update `get_docker_args()` to return `mounts` not `volumes` * fix more mocks to return PurePaths --- supervisor/addons/build.py | 34 ++++++++++++++++++++++++---------- tests/addons/test_addon.py | 10 ++++++---- tests/addons/test_build.py | 33 +++++++++++++++++---------------- tests/api/test_addons.py | 5 +++-- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/supervisor/addons/build.py b/supervisor/addons/build.py index 55c562906..359b36fca 100644 --- a/supervisor/addons/build.py +++ b/supervisor/addons/build.py @@ -24,7 +24,7 @@ from ..const import ( CpuArch, ) from ..coresys import CoreSys, CoreSysAttributes -from ..docker.const import DOCKER_HUB, DOCKER_HUB_LEGACY +from ..docker.const import DOCKER_HUB, DOCKER_HUB_LEGACY, DockerMount, MountType from ..docker.interface import MAP_ARCH from ..exceptions import ( AddonBuildArchitectureNotSupportedError, @@ -232,24 +232,38 @@ class AddonBuild(FileConfiguration, CoreSysAttributes): self.addon.path_location ) - volumes = { - SOCKET_DOCKER: {"bind": "/var/run/docker.sock", "mode": "rw"}, - addon_extern_path: {"bind": "/addon", "mode": "ro"}, - } + mounts = [ + DockerMount( + type=MountType.BIND, + source=SOCKET_DOCKER.as_posix(), + target="/var/run/docker.sock", + read_only=False, + ), + DockerMount( + type=MountType.BIND, + source=addon_extern_path.as_posix(), + target="/addon", + read_only=True, + ), + ] # Mount Docker config with registry credentials if available if docker_config_path: docker_config_extern_path = self.sys_config.local_to_extern_path( docker_config_path ) - volumes[docker_config_extern_path] = { - "bind": "/root/.docker/config.json", - "mode": "ro", - } + mounts.append( + DockerMount( + type=MountType.BIND, + source=docker_config_extern_path.as_posix(), + target="/root/.docker/config.json", + read_only=True, + ) + ) return { "command": build_cmd, - "volumes": volumes, + "mounts": mounts, "working_dir": PurePath("/addon"), } diff --git a/tests/addons/test_addon.py b/tests/addons/test_addon.py index 347be66e9..83d2d50ff 100644 --- a/tests/addons/test_addon.py +++ b/tests/addons/test_addon.py @@ -4,7 +4,7 @@ import asyncio from datetime import timedelta import errno from http import HTTPStatus -from pathlib import Path +from pathlib import Path, PurePath from typing import Any from unittest.mock import MagicMock, PropertyMock, call, patch @@ -851,7 +851,7 @@ async def test_addon_loads_wrong_image( patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): await install_addon_ssh.load() @@ -899,7 +899,7 @@ async def test_addon_loads_missing_image(coresys: CoreSys, install_addon_ssh: Ad patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): await install_addon_ssh.load() @@ -937,7 +937,9 @@ async def test_addon_load_succeeds_with_docker_errors( with ( patch("pathlib.Path.is_file", return_value=True), patch.object( - CoreConfig, "local_to_extern_path", return_value="/addon/path/on/host" + CoreConfig, + "local_to_extern_path", + return_value=PurePath("/addon/path/on/host"), ), patch.object( DockerAPI, "run_command", return_value=CommandReturn(1, ["error"]) diff --git a/tests/addons/test_build.py b/tests/addons/test_build.py index 80dad1d42..d585bd463 100644 --- a/tests/addons/test_build.py +++ b/tests/addons/test_build.py @@ -2,7 +2,7 @@ import base64 import json -from pathlib import Path +from pathlib import Path, PurePath from unittest.mock import PropertyMock, patch from awesomeversion import AwesomeVersion @@ -11,7 +11,7 @@ import pytest from supervisor.addons.addon import Addon from supervisor.addons.build import AddonBuild from supervisor.coresys import CoreSys -from supervisor.docker.const import DOCKER_HUB +from supervisor.docker.const import DOCKER_HUB, MountType from supervisor.exceptions import AddonBuildDockerfileMissingError from tests.common import is_in_list @@ -31,7 +31,7 @@ async def test_platform_set(coresys: CoreSys, install_addon_ssh: Addon): patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): args = await coresys.run_in_executor( @@ -55,7 +55,7 @@ async def test_dockerfile_evaluation(coresys: CoreSys, install_addon_ssh: Addon) patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): args = await coresys.run_in_executor( @@ -83,7 +83,7 @@ async def test_dockerfile_evaluation_arch(coresys: CoreSys, install_addon_ssh: A patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): args = await coresys.run_in_executor( @@ -231,7 +231,7 @@ async def test_docker_args_with_config_path(coresys: CoreSys, install_addon_ssh: patch.object( type(coresys.config), "local_to_extern_path", - side_effect=lambda p: f"/extern{p}", + side_effect=lambda p: PurePath(f"/extern{p}"), ), ): config_path = Path("/data/supervisor/tmp/config.json") @@ -242,13 +242,14 @@ async def test_docker_args_with_config_path(coresys: CoreSys, install_addon_ssh: config_path, ) - # Check that config is mounted - assert "/extern/data/supervisor/tmp/config.json" in args["volumes"] - assert ( - args["volumes"]["/extern/data/supervisor/tmp/config.json"]["bind"] - == "/root/.docker/config.json" + # Check that config is mounted (3 mounts: docker socket, addon path, config) + assert len(args["mounts"]) == 3 + config_mount = next( + m for m in args["mounts"] if m.target == "/root/.docker/config.json" ) - assert args["volumes"]["/extern/data/supervisor/tmp/config.json"]["mode"] == "ro" + assert config_mount.source == "/extern/data/supervisor/tmp/config.json" + assert config_mount.read_only is True + assert config_mount.type == MountType.BIND async def test_docker_args_without_config_path( @@ -267,7 +268,7 @@ async def test_docker_args_without_config_path( patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): args = await coresys.run_in_executor( @@ -275,7 +276,7 @@ async def test_docker_args_without_config_path( ) # Only docker socket and addon path should be mounted - assert len(args["volumes"]) == 2 + assert len(args["mounts"]) == 2 # Verify no docker config mount - for bind in args["volumes"].values(): - assert bind["bind"] != "/root/.docker/config.json" + for mount in args["mounts"]: + assert mount.target != "/root/.docker/config.json" diff --git a/tests/api/test_addons.py b/tests/api/test_addons.py index 38c9075a1..4bd1ad1e1 100644 --- a/tests/api/test_addons.py +++ b/tests/api/test_addons.py @@ -2,6 +2,7 @@ import asyncio from collections.abc import Awaitable, Callable +from pathlib import PurePath from unittest.mock import MagicMock, PropertyMock, patch import aiodocker @@ -240,7 +241,7 @@ async def test_api_addon_rebuild_healthcheck( patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): resp = await api_client.post("/addons/local_ssh/rebuild") @@ -334,7 +335,7 @@ async def test_api_addon_rebuild_force( patch.object( type(coresys.config), "local_to_extern_path", - return_value="/addon/path/on/host", + return_value=PurePath("/addon/path/on/host"), ), ): resp = await api_client.post("/addons/local_ssh/rebuild", json={"force": True})