Improve is docker env checks (#132404)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Sander Hoentjen <sander@hoentjen.eu>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Craig Andrews 2025-01-02 11:21:49 -05:00 committed by GitHub
parent 5439613bff
commit 9e8df72c0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 85 additions and 22 deletions

View File

@ -89,7 +89,7 @@ from .helpers import (
) )
from .helpers.dispatcher import async_dispatcher_send_internal from .helpers.dispatcher import async_dispatcher_send_internal
from .helpers.storage import get_internal_store_manager from .helpers.storage import get_internal_store_manager
from .helpers.system_info import async_get_system_info, is_official_image from .helpers.system_info import async_get_system_info
from .helpers.typing import ConfigType from .helpers.typing import ConfigType
from .setup import ( from .setup import (
# _setup_started is marked as protected to make it clear # _setup_started is marked as protected to make it clear
@ -106,6 +106,7 @@ from .util.async_ import create_eager_task
from .util.hass_dict import HassKey from .util.hass_dict import HassKey
from .util.logging import async_activate_log_queue_handler from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_docker_env, is_virtual_env from .util.package import async_get_user_site, is_docker_env, is_virtual_env
from .util.system_info import is_official_image
with contextlib.suppress(ImportError): with contextlib.suppress(ImportError):
# Ensure anyio backend is imported to avoid it being imported in the event loop # Ensure anyio backend is imported to avoid it being imported in the event loop

View File

@ -23,10 +23,10 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.system_info import is_official_image
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.signal_type import SignalType from homeassistant.util.signal_type import SignalType
from homeassistant.util.system_info import is_official_image
DOMAIN = "ffmpeg" DOMAIN = "ffmpeg"

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from functools import cache from functools import cache
from getpass import getuser from getpass import getuser
import logging import logging
import os
import platform import platform
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@ -13,6 +12,7 @@ from homeassistant.const import __version__ as current_version
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.package import is_docker_env, is_virtual_env from homeassistant.util.package import is_docker_env, is_virtual_env
from homeassistant.util.system_info import is_official_image
from .hassio import is_hassio from .hassio import is_hassio
from .importlib import async_import_module from .importlib import async_import_module
@ -23,12 +23,6 @@ _LOGGER = logging.getLogger(__name__)
_DATA_MAC_VER = "system_info_mac_ver" _DATA_MAC_VER = "system_info_mac_ver"
@cache
def is_official_image() -> bool:
"""Return True if Home Assistant is running in an official container."""
return os.path.isfile("/OFFICIAL_IMAGE")
@singleton(_DATA_MAC_VER) @singleton(_DATA_MAC_VER)
async def async_get_mac_ver(hass: HomeAssistant) -> str: async def async_get_mac_ver(hass: HomeAssistant) -> str:
"""Return the macOS version.""" """Return the macOS version."""

View File

@ -15,6 +15,8 @@ from urllib.parse import urlparse
from packaging.requirements import InvalidRequirement, Requirement from packaging.requirements import InvalidRequirement, Requirement
from .system_info import is_official_image
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,8 +30,13 @@ def is_virtual_env() -> bool:
@cache @cache
def is_docker_env() -> bool: def is_docker_env() -> bool:
"""Return True if we run in a docker env.""" """Return True if we run in a container env."""
return Path("/.dockerenv").exists() return (
Path("/.dockerenv").exists()
or Path("/run/.containerenv").exists()
or "KUBERNETES_SERVICE_HOST" in os.environ
or is_official_image()
)
def get_installed_versions(specifiers: set[str]) -> set[str]: def get_installed_versions(specifiers: set[str]) -> set[str]:

View File

@ -0,0 +1,12 @@
"""Util to gather system info."""
from __future__ import annotations
from functools import cache
import os
@cache
def is_official_image() -> bool:
"""Return True if Home Assistant is running in an official container."""
return os.path.isfile("/OFFICIAL_IMAGE")

View File

@ -9,17 +9,7 @@ import pytest
from homeassistant.components import hassio from homeassistant.components import hassio
from homeassistant.const import __version__ as current_version from homeassistant.const import __version__ as current_version
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.system_info import async_get_system_info, is_official_image from homeassistant.helpers.system_info import async_get_system_info
async def test_is_official_image() -> None:
"""Test is_official_image."""
is_official_image.cache_clear()
with patch("homeassistant.helpers.system_info.os.path.isfile", return_value=True):
assert is_official_image() is True
is_official_image.cache_clear()
with patch("homeassistant.helpers.system_info.os.path.isfile", return_value=False):
assert is_official_image() is False
async def test_get_system_info(hass: HomeAssistant) -> None: async def test_get_system_info(hass: HomeAssistant) -> None:

View File

@ -410,3 +410,47 @@ def test_check_package_previous_failed_install() -> None:
with patch("homeassistant.util.package.version", return_value=None): with patch("homeassistant.util.package.version", return_value=None):
assert not package.is_installed(installed_package) assert not package.is_installed(installed_package)
assert not package.is_installed(f"{installed_package}=={installed_version}") assert not package.is_installed(f"{installed_package}=={installed_version}")
@pytest.mark.parametrize("dockerenv", [True, False], ids=["dockerenv", "not_dockerenv"])
@pytest.mark.parametrize(
"containerenv", [True, False], ids=["containerenv", "not_containerenv"]
)
@pytest.mark.parametrize(
"kubernetes_service_host", [True, False], ids=["kubernetes", "not_kubernetes"]
)
@pytest.mark.parametrize(
"is_official_image", [True, False], ids=["official_image", "not_official_image"]
)
async def test_is_docker_env(
dockerenv: bool,
containerenv: bool,
kubernetes_service_host: bool,
is_official_image: bool,
) -> None:
"""Test is_docker_env."""
def new_path_mock(path: str):
mock = Mock()
if path == "/.dockerenv":
mock.exists.return_value = dockerenv
elif path == "/run/.containerenv":
mock.exists.return_value = containerenv
return mock
env = {}
if kubernetes_service_host:
env["KUBERNETES_SERVICE_HOST"] = "True"
package.is_docker_env.cache_clear()
with (
patch("homeassistant.util.package.Path", side_effect=new_path_mock),
patch(
"homeassistant.util.package.is_official_image",
return_value=is_official_image,
),
patch.dict(os.environ, env),
):
assert package.is_docker_env() is any(
[dockerenv, containerenv, kubernetes_service_host, is_official_image]
)

View File

@ -0,0 +1,15 @@
"""Tests for the system info helper."""
from unittest.mock import patch
from homeassistant.util.system_info import is_official_image
async def test_is_official_image() -> None:
"""Test is_official_image."""
is_official_image.cache_clear()
with patch("homeassistant.util.system_info.os.path.isfile", return_value=True):
assert is_official_image() is True
is_official_image.cache_clear()
with patch("homeassistant.util.system_info.os.path.isfile", return_value=False):
assert is_official_image() is False