Add container arch to system info (#147372)

This commit is contained in:
Stefan Agner 2025-06-24 09:52:21 +02:00 committed by GitHub
parent 121239bcf7
commit e5d19baf3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 99 additions and 118 deletions

View File

@ -12,7 +12,6 @@ import re
import struct import struct
from typing import Any, NamedTuple from typing import Any, NamedTuple
import aiofiles
from aiohasupervisor import SupervisorError from aiohasupervisor import SupervisorError
import voluptuous as vol import voluptuous as vol
@ -239,12 +238,6 @@ def _is_32_bit() -> bool:
return size * 8 == 32 return size * 8 == 32
async def _get_arch() -> str:
async with aiofiles.open("/etc/apk/arch") as arch_file:
raw_arch = await arch_file.read()
return {"x86": "i386"}.get(raw_arch, raw_arch)
class APIEndpointSettings(NamedTuple): class APIEndpointSettings(NamedTuple):
"""Settings for API endpoint.""" """Settings for API endpoint."""
@ -566,8 +559,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[ADDONS_COORDINATOR] = coordinator hass.data[ADDONS_COORDINATOR] = coordinator
arch = await _get_arch()
def deprecated_setup_issue() -> None: def deprecated_setup_issue() -> None:
os_info = get_os_info(hass) os_info = get_os_info(hass)
info = get_info(hass) info = get_info(hass)
@ -575,6 +566,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return return
is_haos = info.get("hassos") is not None is_haos = info.get("hassos") is not None
board = os_info.get("board") board = os_info.get("board")
arch = info.get("arch", "unknown")
unsupported_board = board in {"tinker", "odroid-xu4", "rpi2"} unsupported_board = board in {"tinker", "odroid-xu4", "rpi2"}
unsupported_os_on_board = board in {"rpi3", "rpi4"} unsupported_os_on_board = board in {"rpi3", "rpi4"}
if is_haos and (unsupported_board or unsupported_os_on_board): if is_haos and (unsupported_board or unsupported_os_on_board):

View File

@ -7,7 +7,6 @@ import logging
import struct import struct
from typing import Any from typing import Any
import aiofiles
import voluptuous as vol import voluptuous as vol
from homeassistant import config as conf_util, core_config from homeassistant import config as conf_util, core_config
@ -18,6 +17,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_LATITUDE, ATTR_LATITUDE,
ATTR_LONGITUDE, ATTR_LONGITUDE,
EVENT_HOMEASSISTANT_STARTED,
RESTART_EXIT_CODE, RESTART_EXIT_CODE,
SERVICE_RELOAD, SERVICE_RELOAD,
SERVICE_SAVE_PERSISTENT_STATES, SERVICE_SAVE_PERSISTENT_STATES,
@ -26,6 +26,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
) )
from homeassistant.core import ( from homeassistant.core import (
Event,
HomeAssistant, HomeAssistant,
ServiceCall, ServiceCall,
ServiceResponse, ServiceResponse,
@ -101,12 +102,6 @@ def _is_32_bit() -> bool:
return size * 8 == 32 return size * 8 == 32
async def _get_arch() -> str:
async with aiofiles.open("/etc/apk/arch") as arch_file:
raw_arch = (await arch_file.read()).strip()
return {"x86": "i386", "x86_64": "amd64"}.get(raw_arch, raw_arch)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901
"""Set up general services related to Home Assistant.""" """Set up general services related to Home Assistant."""
@ -411,6 +406,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities
async_set_stop_handler(hass, _async_stop) async_set_stop_handler(hass, _async_stop)
async def _async_check_deprecation(event: Event) -> None:
"""Check and create deprecation issues after startup."""
info = await async_get_system_info(hass) info = await async_get_system_info(hass)
installation_type = info["installation_type"][15:] installation_type = info["installation_type"][15:]
@ -419,7 +416,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
bit32 = _is_32_bit() bit32 = _is_32_bit()
arch = info["arch"] arch = info["arch"]
if bit32 and installation_type == "Container": if bit32 and installation_type == "Container":
arch = await _get_arch() arch = info.get("container_arch", arch)
ir.async_create_issue( ir.async_create_issue(
hass, hass,
DOMAIN, DOMAIN,
@ -451,6 +448,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
}, },
) )
# Delay deprecation check to make sure installation method is determined correctly
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_check_deprecation)
return True return True

View File

@ -124,6 +124,7 @@
"info": { "info": {
"arch": "CPU architecture", "arch": "CPU architecture",
"config_dir": "Configuration directory", "config_dir": "Configuration directory",
"container_arch": "Container architecture",
"dev": "Development", "dev": "Development",
"docker": "Docker", "docker": "Docker",
"hassio": "Supervisor", "hassio": "Supervisor",

View File

@ -27,6 +27,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"dev": info.get("dev"), "dev": info.get("dev"),
"hassio": info.get("hassio"), "hassio": info.get("hassio"),
"docker": info.get("docker"), "docker": info.get("docker"),
"container_arch": info.get("container_arch"),
"user": info.get("user"), "user": info.get("user"),
"virtualenv": info.get("virtualenv"), "virtualenv": info.get("virtualenv"),
"python_version": info.get("python_version"), "python_version": info.get("python_version"),

View File

@ -21,6 +21,7 @@ from .singleton import singleton
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_DATA_MAC_VER = "system_info_mac_ver" _DATA_MAC_VER = "system_info_mac_ver"
_DATA_CONTAINER_ARCH = "system_info_container_arch"
@singleton(_DATA_MAC_VER) @singleton(_DATA_MAC_VER)
@ -29,6 +30,22 @@ async def async_get_mac_ver(hass: HomeAssistant) -> str:
return (await hass.async_add_executor_job(platform.mac_ver))[0] return (await hass.async_add_executor_job(platform.mac_ver))[0]
@singleton(_DATA_CONTAINER_ARCH)
async def async_get_container_arch(hass: HomeAssistant) -> str:
"""Return the container architecture."""
def _read_arch_file() -> str:
"""Read the architecture from /etc/apk/arch."""
with open("/etc/apk/arch", encoding="utf-8") as arch_file:
return arch_file.read().strip()
try:
raw_arch = await hass.async_add_executor_job(_read_arch_file)
except FileNotFoundError:
return "unknown"
return {"x86": "i386", "x86_64": "amd64"}.get(raw_arch, raw_arch)
# Cache the result of getuser() because it can call getpwuid() which # Cache the result of getuser() because it can call getpwuid() which
# can do blocking I/O to look up the username in /etc/passwd. # can do blocking I/O to look up the username in /etc/passwd.
cached_get_user = cache(getuser) cached_get_user = cache(getuser)
@ -79,6 +96,7 @@ async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]:
if info_object["docker"]: if info_object["docker"]:
if info_object["user"] == "root" and is_official_image(): if info_object["user"] == "root" and is_official_image():
info_object["installation_type"] = "Home Assistant Container" info_object["installation_type"] = "Home Assistant Container"
info_object["container_arch"] = await async_get_container_arch(hass)
else: else:
info_object["installation_type"] = "Unsupported Third Party Container" info_object["installation_type"] = "Unsupported Third Party Container"

View File

@ -3,7 +3,6 @@
aiodhcpwatcher==1.2.0 aiodhcpwatcher==1.2.0
aiodiscover==2.7.0 aiodiscover==2.7.0
aiodns==3.5.0 aiodns==3.5.0
aiofiles==24.1.0
aiohasupervisor==0.3.1 aiohasupervisor==0.3.1
aiohttp-asyncmdnsresolver==0.1.1 aiohttp-asyncmdnsresolver==0.1.1
aiohttp-fast-zlib==0.3.0 aiohttp-fast-zlib==0.3.0
@ -201,6 +200,14 @@ tenacity!=8.4.0
# TypeError: 'Timeout' object does not support the context manager protocol # TypeError: 'Timeout' object does not support the context manager protocol
async-timeout==4.0.3 async-timeout==4.0.3
# aiofiles keeps getting downgraded by custom components
# causing newer methods to not be available and breaking
# some integrations at startup
# https://github.com/home-assistant/core/issues/127529
# https://github.com/home-assistant/core/issues/122508
# https://github.com/home-assistant/core/issues/118004
aiofiles>=24.1.0
# multidict < 6.4.0 has memory leaks # multidict < 6.4.0 has memory leaks
# https://github.com/aio-libs/multidict/issues/1134 # https://github.com/aio-libs/multidict/issues/1134
# https://github.com/aio-libs/multidict/issues/1131 # https://github.com/aio-libs/multidict/issues/1131

View File

@ -24,7 +24,6 @@ classifiers = [
requires-python = ">=3.13.2" requires-python = ">=3.13.2"
dependencies = [ dependencies = [
"aiodns==3.5.0", "aiodns==3.5.0",
"aiofiles==24.1.0",
# Integrations may depend on hassio integration without listing it to # Integrations may depend on hassio integration without listing it to
# change behavior based on presence of supervisor. Deprecated with #127228 # change behavior based on presence of supervisor. Deprecated with #127228
# Lib can be removed with 2025.11 # Lib can be removed with 2025.11

1
requirements.txt generated
View File

@ -4,7 +4,6 @@
# Home Assistant Core # Home Assistant Core
aiodns==3.5.0 aiodns==3.5.0
aiofiles==24.1.0
aiohasupervisor==0.3.1 aiohasupervisor==0.3.1
aiohttp==3.12.13 aiohttp==3.12.13
aiohttp_cors==0.8.1 aiohttp_cors==0.8.1

View File

@ -226,6 +226,14 @@ tenacity!=8.4.0
# TypeError: 'Timeout' object does not support the context manager protocol # TypeError: 'Timeout' object does not support the context manager protocol
async-timeout==4.0.3 async-timeout==4.0.3
# aiofiles keeps getting downgraded by custom components
# causing newer methods to not be available and breaking
# some integrations at startup
# https://github.com/home-assistant/core/issues/127529
# https://github.com/home-assistant/core/issues/122508
# https://github.com/home-assistant/core/issues/118004
aiofiles>=24.1.0
# multidict < 6.4.0 has memory leaks # multidict < 6.4.0 has memory leaks
# https://github.com/aio-libs/multidict/issues/1134 # https://github.com/aio-libs/multidict/issues/1134
# https://github.com/aio-libs/multidict/issues/1131 # https://github.com/aio-libs/multidict/issues/1131

View File

@ -9,6 +9,7 @@
dev | False dev | False
hassio | False hassio | False
docker | False docker | False
container_arch | None
user | hass user | hass
virtualenv | False virtualenv | False
python_version | 3.13.1 python_version | 3.13.1

View File

@ -1931,6 +1931,7 @@ async def test_download_support_package(
"virtualenv": False, "virtualenv": False,
"python_version": "3.13.1", "python_version": "3.13.1",
"docker": False, "docker": False,
"container_arch": None,
"arch": "x86_64", "arch": "x86_64",
"timezone": "US/Pacific", "timezone": "US/Pacific",
"os_name": "Linux", "os_name": "Linux",

View File

@ -260,16 +260,3 @@ def all_setup_requests(
}, },
}, },
) )
@pytest.fixture
def arch() -> str:
"""Arch found in apk file."""
return "amd64"
@pytest.fixture(autouse=True)
def mock_arch_file(arch: str) -> Generator[None]:
"""Mock arch file."""
with patch("homeassistant.components.hassio._get_arch", return_value=arch):
yield

View File

@ -1156,10 +1156,6 @@ def test_deprecated_constants(
("rpi2", "deprecated_os_armv7"), ("rpi2", "deprecated_os_armv7"),
], ],
) )
@pytest.mark.parametrize(
"arch",
["armv7"],
)
async def test_deprecated_installation_issue_os_armv7( async def test_deprecated_installation_issue_os_armv7(
hass: HomeAssistant, hass: HomeAssistant,
issue_registry: ir.IssueRegistry, issue_registry: ir.IssueRegistry,
@ -1170,13 +1166,6 @@ async def test_deprecated_installation_issue_os_armv7(
"""Test deprecated installation issue.""" """Test deprecated installation issue."""
with ( with (
patch.dict(os.environ, MOCK_ENVIRON), patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "armv7",
},
),
patch( patch(
"homeassistant.components.hassio._is_32_bit", "homeassistant.components.hassio._is_32_bit",
return_value=True, return_value=True,
@ -1185,7 +1174,8 @@ async def test_deprecated_installation_issue_os_armv7(
"homeassistant.components.hassio.get_os_info", return_value={"board": board} "homeassistant.components.hassio.get_os_info", return_value={"board": board}
), ),
patch( patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": True} "homeassistant.components.hassio.get_info",
return_value={"hassos": True, "arch": "armv7"},
), ),
patch("homeassistant.components.hardware.async_setup", return_value=True), patch("homeassistant.components.hardware.async_setup", return_value=True),
): ):
@ -1238,13 +1228,6 @@ async def test_deprecated_installation_issue_32bit_os(
"""Test deprecated architecture issue.""" """Test deprecated architecture issue."""
with ( with (
patch.dict(os.environ, MOCK_ENVIRON), patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": arch,
},
),
patch( patch(
"homeassistant.components.hassio._is_32_bit", "homeassistant.components.hassio._is_32_bit",
return_value=True, return_value=True,
@ -1254,7 +1237,8 @@ async def test_deprecated_installation_issue_32bit_os(
return_value={"board": "rpi3-64"}, return_value={"board": "rpi3-64"},
), ),
patch( patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": True} "homeassistant.components.hassio.get_info",
return_value={"hassos": True, "arch": arch},
), ),
patch("homeassistant.components.hardware.async_setup", return_value=True), patch("homeassistant.components.hardware.async_setup", return_value=True),
): ):
@ -1305,13 +1289,6 @@ async def test_deprecated_installation_issue_32bit_supervised(
"""Test deprecated architecture issue.""" """Test deprecated architecture issue."""
with ( with (
patch.dict(os.environ, MOCK_ENVIRON), patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Supervised",
"arch": arch,
},
),
patch( patch(
"homeassistant.components.hassio._is_32_bit", "homeassistant.components.hassio._is_32_bit",
return_value=True, return_value=True,
@ -1321,7 +1298,8 @@ async def test_deprecated_installation_issue_32bit_supervised(
return_value={"board": "rpi3-64"}, return_value={"board": "rpi3-64"},
), ),
patch( patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": None} "homeassistant.components.hassio.get_info",
return_value={"hassos": None, "arch": arch},
), ),
patch("homeassistant.components.hardware.async_setup", return_value=True), patch("homeassistant.components.hardware.async_setup", return_value=True),
): ):
@ -1376,13 +1354,6 @@ async def test_deprecated_installation_issue_64bit_supervised(
"""Test deprecated architecture issue.""" """Test deprecated architecture issue."""
with ( with (
patch.dict(os.environ, MOCK_ENVIRON), patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Supervised",
"arch": arch,
},
),
patch( patch(
"homeassistant.components.hassio._is_32_bit", "homeassistant.components.hassio._is_32_bit",
return_value=False, return_value=False,
@ -1392,7 +1363,8 @@ async def test_deprecated_installation_issue_64bit_supervised(
return_value={"board": "generic-x86-64"}, return_value={"board": "generic-x86-64"},
), ),
patch( patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": None} "homeassistant.components.hassio.get_info",
return_value={"hassos": None, "arch": arch},
), ),
patch("homeassistant.components.hardware.async_setup", return_value=True), patch("homeassistant.components.hardware.async_setup", return_value=True),
): ):
@ -1445,13 +1417,6 @@ async def test_deprecated_installation_issue_supported_board(
"""Test no deprecated installation issue for a supported board.""" """Test no deprecated installation issue for a supported board."""
with ( with (
patch.dict(os.environ, MOCK_ENVIRON), patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "aarch64",
},
),
patch( patch(
"homeassistant.components.hassio._is_32_bit", "homeassistant.components.hassio._is_32_bit",
return_value=False, return_value=False,
@ -1460,7 +1425,8 @@ async def test_deprecated_installation_issue_supported_board(
"homeassistant.components.hassio.get_os_info", return_value={"board": board} "homeassistant.components.hassio.get_os_info", return_value={"board": board}
), ),
patch( patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": True} "homeassistant.components.hassio.get_info",
return_value={"hassos": True, "arch": "aarch64"},
), ),
): ):
assert await async_setup_component(hass, "homeassistant", {}) assert await async_setup_component(hass, "homeassistant", {})

View File

@ -24,6 +24,7 @@ from homeassistant.const import (
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE, ENTITY_MATCH_NONE,
EVENT_CORE_CONFIG_UPDATE, EVENT_CORE_CONFIG_UPDATE,
EVENT_HOMEASSISTANT_STARTED,
SERVICE_SAVE_PERSISTENT_STATES, SERVICE_SAVE_PERSISTENT_STATES,
SERVICE_TOGGLE, SERVICE_TOGGLE,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
@ -668,6 +669,7 @@ async def test_deprecated_installation_issue_32bit_core(
), ),
): ):
assert await async_setup_component(hass, DOMAIN, {}) assert await async_setup_component(hass, DOMAIN, {})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(issue_registry.issues) == 1 assert len(issue_registry.issues) == 1
@ -707,6 +709,7 @@ async def test_deprecated_installation_issue_64bit_core(
), ),
): ):
assert await async_setup_component(hass, DOMAIN, {}) assert await async_setup_component(hass, DOMAIN, {})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(issue_registry.issues) == 1 assert len(issue_registry.issues) == 1
@ -738,6 +741,7 @@ async def test_deprecated_installation_issue_32bit(
"homeassistant.components.homeassistant.async_get_system_info", "homeassistant.components.homeassistant.async_get_system_info",
return_value={ return_value={
"installation_type": "Home Assistant Container", "installation_type": "Home Assistant Container",
"container_arch": arch,
"arch": arch, "arch": arch,
}, },
), ),
@ -745,12 +749,9 @@ async def test_deprecated_installation_issue_32bit(
"homeassistant.components.homeassistant._is_32_bit", "homeassistant.components.homeassistant._is_32_bit",
return_value=True, return_value=True,
), ),
patch(
"homeassistant.components.homeassistant._get_arch",
return_value=arch,
),
): ):
assert await async_setup_component(hass, DOMAIN, {}) assert await async_setup_component(hass, DOMAIN, {})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(issue_registry.issues) == 1 assert len(issue_registry.issues) == 1