mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-02-15 07:27:13 +00:00
* Update `get_docker_args()` to return `mounts` not `volumes` * fix more mocks to return PurePaths
283 lines
9.9 KiB
Python
283 lines
9.9 KiB
Python
"""Test addon build."""
|
|
|
|
import base64
|
|
import json
|
|
from pathlib import Path, PurePath
|
|
from unittest.mock import PropertyMock, patch
|
|
|
|
from awesomeversion import AwesomeVersion
|
|
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, MountType
|
|
from supervisor.exceptions import AddonBuildDockerfileMissingError
|
|
|
|
from tests.common import is_in_list
|
|
|
|
|
|
async def test_platform_set(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test platform set in container build args."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
patch.object(
|
|
type(coresys.config),
|
|
"local_to_extern_path",
|
|
return_value=PurePath("/addon/path/on/host"),
|
|
),
|
|
):
|
|
args = await coresys.run_in_executor(
|
|
build.get_docker_args, AwesomeVersion("latest"), "test-image:latest", None
|
|
)
|
|
|
|
assert is_in_list(["--platform", "linux/amd64"], args["command"])
|
|
|
|
|
|
async def test_dockerfile_evaluation(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test dockerfile path in container build args."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
patch.object(
|
|
type(coresys.config),
|
|
"local_to_extern_path",
|
|
return_value=PurePath("/addon/path/on/host"),
|
|
),
|
|
):
|
|
args = await coresys.run_in_executor(
|
|
build.get_docker_args, AwesomeVersion("latest"), "test-image:latest", None
|
|
)
|
|
|
|
assert is_in_list(["--file", "Dockerfile"], args["command"])
|
|
assert str(await coresys.run_in_executor(build.get_dockerfile)).endswith(
|
|
"fixtures/addons/local/ssh/Dockerfile"
|
|
)
|
|
assert build.arch == "amd64"
|
|
|
|
|
|
async def test_dockerfile_evaluation_arch(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test dockerfile arch evaluation in container build args."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["aarch64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="aarch64")
|
|
),
|
|
patch.object(
|
|
type(coresys.config),
|
|
"local_to_extern_path",
|
|
return_value=PurePath("/addon/path/on/host"),
|
|
),
|
|
):
|
|
args = await coresys.run_in_executor(
|
|
build.get_docker_args, AwesomeVersion("latest"), "test-image:latest", None
|
|
)
|
|
|
|
assert is_in_list(["--file", "Dockerfile.aarch64"], args["command"])
|
|
assert str(await coresys.run_in_executor(build.get_dockerfile)).endswith(
|
|
"fixtures/addons/local/ssh/Dockerfile.aarch64"
|
|
)
|
|
assert build.arch == "aarch64"
|
|
|
|
|
|
async def test_build_valid(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test platform set in docker args."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["aarch64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="aarch64")
|
|
),
|
|
):
|
|
assert (await build.is_valid()) is None
|
|
|
|
|
|
async def test_build_invalid(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test build not supported because Dockerfile missing for specified architecture."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
pytest.raises(AddonBuildDockerfileMissingError),
|
|
):
|
|
await build.is_valid()
|
|
|
|
|
|
async def test_docker_config_no_registries(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test docker config generation when no registries configured."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
# No registries configured by default
|
|
assert build.get_docker_config_json() is None
|
|
|
|
|
|
async def test_docker_config_no_matching_registry(
|
|
coresys: CoreSys, install_addon_ssh: Addon
|
|
):
|
|
"""Test docker config generation when registry doesn't match base image."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
# Configure a registry that doesn't match the base image
|
|
# pylint: disable-next=protected-access
|
|
coresys.docker.config._data["registries"] = {
|
|
"some.other.registry": {"username": "user", "password": "pass"}
|
|
}
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
):
|
|
# Base image is ghcr.io/home-assistant/... which doesn't match
|
|
assert build.get_docker_config_json() is None
|
|
|
|
|
|
async def test_docker_config_matching_registry(
|
|
coresys: CoreSys, install_addon_ssh: Addon
|
|
):
|
|
"""Test docker config generation when registry matches base image."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
# Configure ghcr.io registry which matches the default base image
|
|
# pylint: disable-next=protected-access
|
|
coresys.docker.config._data["registries"] = {
|
|
"ghcr.io": {"username": "testuser", "password": "testpass"}
|
|
}
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
):
|
|
config_json = build.get_docker_config_json()
|
|
assert config_json is not None
|
|
|
|
config = json.loads(config_json)
|
|
assert "auths" in config
|
|
assert "ghcr.io" in config["auths"]
|
|
|
|
# Verify base64-encoded credentials
|
|
expected_auth = base64.b64encode(b"testuser:testpass").decode()
|
|
assert config["auths"]["ghcr.io"]["auth"] == expected_auth
|
|
|
|
|
|
async def test_docker_config_docker_hub(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test docker config generation for Docker Hub registry."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
# Configure Docker Hub registry
|
|
# pylint: disable-next=protected-access
|
|
coresys.docker.config._data["registries"] = {
|
|
DOCKER_HUB: {"username": "hubuser", "password": "hubpass"}
|
|
}
|
|
|
|
# Mock base_image to return a Docker Hub image (no registry prefix)
|
|
with patch.object(
|
|
type(build),
|
|
"base_image",
|
|
new=PropertyMock(return_value="library/alpine:latest"),
|
|
):
|
|
config_json = build.get_docker_config_json()
|
|
assert config_json is not None
|
|
|
|
config = json.loads(config_json)
|
|
# Docker Hub uses special URL as key
|
|
assert "https://index.docker.io/v1/" in config["auths"]
|
|
|
|
expected_auth = base64.b64encode(b"hubuser:hubpass").decode()
|
|
assert config["auths"]["https://index.docker.io/v1/"]["auth"] == expected_auth
|
|
|
|
|
|
async def test_docker_args_with_config_path(coresys: CoreSys, install_addon_ssh: Addon):
|
|
"""Test docker args include config volume when path provided."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
patch.object(
|
|
type(coresys.config),
|
|
"local_to_extern_path",
|
|
side_effect=lambda p: PurePath(f"/extern{p}"),
|
|
),
|
|
):
|
|
config_path = Path("/data/supervisor/tmp/config.json")
|
|
args = await coresys.run_in_executor(
|
|
build.get_docker_args,
|
|
AwesomeVersion("latest"),
|
|
"test-image:latest",
|
|
config_path,
|
|
)
|
|
|
|
# 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 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(
|
|
coresys: CoreSys, install_addon_ssh: Addon
|
|
):
|
|
"""Test docker args don't include config volume when no path provided."""
|
|
build = await AddonBuild(coresys, install_addon_ssh).load_config()
|
|
|
|
with (
|
|
patch.object(
|
|
type(coresys.arch), "supported", new=PropertyMock(return_value=["amd64"])
|
|
),
|
|
patch.object(
|
|
type(coresys.arch), "default", new=PropertyMock(return_value="amd64")
|
|
),
|
|
patch.object(
|
|
type(coresys.config),
|
|
"local_to_extern_path",
|
|
return_value=PurePath("/addon/path/on/host"),
|
|
),
|
|
):
|
|
args = await coresys.run_in_executor(
|
|
build.get_docker_args, AwesomeVersion("latest"), "test-image:latest", None
|
|
)
|
|
|
|
# Only docker socket and addon path should be mounted
|
|
assert len(args["mounts"]) == 2
|
|
# Verify no docker config mount
|
|
for mount in args["mounts"]:
|
|
assert mount.target != "/root/.docker/config.json"
|