Make issue creation check architecture instead of uname (#146537)

This commit is contained in:
Joost Lekkerkerker
2025-06-11 18:39:46 +02:00
committed by Franck Nijhof
parent 60b8230ecc
commit 02524b8b9b
6 changed files with 202 additions and 103 deletions

View File

@ -9,8 +9,10 @@ from functools import partial
import logging
import os
import re
import struct
from typing import Any, NamedTuple
import aiofiles
from aiohasupervisor import SupervisorError
import voluptuous as vol
@ -56,7 +58,6 @@ from homeassistant.helpers.issue_registry import IssueSeverity
from homeassistant.helpers.service_info.hassio import (
HassioServiceInfo as _HassioServiceInfo,
)
from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.util.async_ import create_eager_task
@ -233,6 +234,17 @@ SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL.extend(
)
def _is_32_bit() -> bool:
size = struct.calcsize("P")
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):
"""Settings for API endpoint."""
@ -554,7 +566,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data[ADDONS_COORDINATOR] = coordinator
system_info = await async_get_system_info(hass)
arch = await _get_arch()
def deprecated_setup_issue() -> None:
os_info = get_os_info(hass)
@ -562,20 +574,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if os_info is None or info is None:
return
is_haos = info.get("hassos") is not None
arch = system_info["arch"]
board = os_info.get("board")
supported_board = board in {"rpi3", "rpi4", "tinker", "odroid-xu4", "rpi2"}
if is_haos and arch == "armv7" and supported_board:
unsupported_board = board in {"tinker", "odroid-xu4", "rpi2"}
unsupported_os_on_board = board in {"rpi3", "rpi4"}
if is_haos and (unsupported_board or unsupported_os_on_board):
issue_id = "deprecated_os_"
if board in {"rpi3", "rpi4"}:
if unsupported_os_on_board:
issue_id += "aarch64"
elif board in {"tinker", "odroid-xu4", "rpi2"}:
elif unsupported_board:
issue_id += "armv7"
ir.async_create_issue(
hass,
"homeassistant",
issue_id,
breaks_in_ha_version="2025.12.0",
learn_more_url=DEPRECATION_URL,
is_fixable=False,
severity=IssueSeverity.WARNING,
@ -584,9 +595,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"installation_guide": "https://www.home-assistant.io/installation/",
},
)
deprecated_architecture = False
if arch in {"i386", "armhf"} or (arch == "armv7" and not supported_board):
deprecated_architecture = True
bit32 = _is_32_bit()
deprecated_architecture = bit32 and not (
unsupported_board or unsupported_os_on_board
)
if not is_haos or deprecated_architecture:
issue_id = "deprecated"
if not is_haos:
@ -597,7 +609,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass,
"homeassistant",
issue_id,
breaks_in_ha_version="2025.12.0",
learn_more_url=DEPRECATION_URL,
is_fixable=False,
severity=IssueSeverity.WARNING,

View File

@ -4,8 +4,10 @@ import asyncio
from collections.abc import Callable, Coroutine
import itertools as it
import logging
import struct
from typing import Any
import aiofiles
import voluptuous as vol
from homeassistant import config as conf_util, core_config
@ -94,6 +96,17 @@ DEPRECATION_URL = (
)
def _is_32_bit() -> bool:
size = struct.calcsize("P")
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
"""Set up general services related to Home Assistant."""
@ -403,23 +416,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
installation_type = info["installation_type"][15:]
if installation_type in {"Core", "Container"}:
deprecated_method = installation_type == "Core"
bit32 = _is_32_bit()
arch = info["arch"]
if arch == "armv7" and installation_type == "Container":
if bit32 and installation_type == "Container":
arch = await _get_arch()
ir.async_create_issue(
hass,
DOMAIN,
"deprecated_container_armv7",
breaks_in_ha_version="2025.12.0",
"deprecated_container",
learn_more_url=DEPRECATION_URL,
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_container_armv7",
translation_key="deprecated_container",
translation_placeholders={"arch": arch},
)
deprecated_architecture = False
if arch in {"i386", "armhf"} or (
arch == "armv7" and installation_type != "Container"
):
deprecated_architecture = True
deprecated_architecture = bit32 and installation_type != "Container"
if deprecated_method or deprecated_architecture:
issue_id = "deprecated"
if deprecated_method:
@ -430,7 +441,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
hass,
DOMAIN,
issue_id,
breaks_in_ha_version="2025.12.0",
learn_more_url=DEPRECATION_URL,
is_fixable=False,
severity=IssueSeverity.WARNING,

View File

@ -107,9 +107,9 @@
"title": "Deprecation notice: 32-bit architecture",
"description": "This system uses 32-bit hardware (`{arch}`), which has been deprecated and will no longer receive updates after the release of Home Assistant 2025.12. As your hardware is no longer capable of running newer versions of Home Assistant, you will need to migrate to new hardware."
},
"deprecated_container_armv7": {
"deprecated_container": {
"title": "[%key:component::homeassistant::issues::deprecated_architecture::title%]",
"description": "This system is running on a 32-bit operating system (`armv7`), which has been deprecated and will no longer receive updates after the release of Home Assistant 2025.12. Check if your system is capable of running a 64-bit operating system. If not, you will need to migrate to new hardware."
"description": "This system is running on a 32-bit operating system (`{arch}`), which has been deprecated and will no longer receive updates after the release of Home Assistant 2025.12. Check if your system is capable of running a 64-bit operating system. If not, you will need to migrate to new hardware."
},
"deprecated_os_aarch64": {
"title": "[%key:component::homeassistant::issues::deprecated_architecture::title%]",

View File

@ -260,3 +260,16 @@ 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,7 +1156,11 @@ def test_deprecated_constants(
("rpi2", "deprecated_os_armv7"),
],
)
async def test_deprecated_installation_issue_aarch64(
@pytest.mark.parametrize(
"arch",
["armv7"],
)
async def test_deprecated_installation_issue_os_armv7(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
freezer: FrozenDateTimeFactory,
@ -1167,18 +1171,15 @@ async def test_deprecated_installation_issue_aarch64(
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.async_get_system_info",
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "armv7",
},
),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "armv7",
},
"homeassistant.components.hassio._is_32_bit",
return_value=True,
),
patch(
"homeassistant.components.hassio.get_os_info", return_value={"board": board}
@ -1228,7 +1229,7 @@ async def test_deprecated_installation_issue_aarch64(
"armv7",
],
)
async def test_deprecated_installation_issue_32bit_method(
async def test_deprecated_installation_issue_32bit_os(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
freezer: FrozenDateTimeFactory,
@ -1238,18 +1239,15 @@ async def test_deprecated_installation_issue_32bit_method(
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.async_get_system_info",
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": arch,
},
),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": arch,
},
"homeassistant.components.hassio._is_32_bit",
return_value=True,
),
patch(
"homeassistant.components.hassio.get_os_info",
@ -1308,18 +1306,15 @@ async def test_deprecated_installation_issue_32bit_supervised(
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.async_get_system_info",
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Supervised",
"arch": arch,
},
),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Supervised",
"arch": arch,
},
"homeassistant.components.hassio._is_32_bit",
return_value=True,
),
patch(
"homeassistant.components.hassio.get_os_info",
@ -1365,6 +1360,75 @@ async def test_deprecated_installation_issue_32bit_supervised(
}
@pytest.mark.parametrize(
"arch",
[
"amd64",
"aarch64",
],
)
async def test_deprecated_installation_issue_64bit_supervised(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
freezer: FrozenDateTimeFactory,
arch: str,
) -> None:
"""Test deprecated architecture issue."""
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Supervised",
"arch": arch,
},
),
patch(
"homeassistant.components.hassio._is_32_bit",
return_value=False,
),
patch(
"homeassistant.components.hassio.get_os_info",
return_value={"board": "generic-x86-64"},
),
patch(
"homeassistant.components.hassio.get_info", return_value={"hassos": None}
),
patch("homeassistant.components.hardware.async_setup", return_value=True),
):
assert await async_setup_component(hass, "homeassistant", {})
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
freezer.tick(REQUEST_REFRESH_DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
freezer.tick(HASSIO_UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert len(issue_registry.issues) == 1
issue = issue_registry.async_get_issue("homeassistant", "deprecated_method")
assert issue.domain == "homeassistant"
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_placeholders == {
"installation_type": "Supervised",
"arch": arch,
}
@pytest.mark.parametrize(
("board", "issue_id"),
[
@ -1382,18 +1446,15 @@ async def test_deprecated_installation_issue_supported_board(
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.async_get_system_info",
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "aarch64",
},
),
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"arch": "aarch64",
},
"homeassistant.components.hassio._is_32_bit",
return_value=False,
),
patch(
"homeassistant.components.hassio.get_os_info", return_value={"board": board}

View File

@ -648,18 +648,24 @@ async def test_reload_all(
"armv7",
],
)
async def test_deprecated_installation_issue_32bit_method(
async def test_deprecated_installation_issue_32bit_core(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
arch: str,
) -> None:
"""Test deprecated installation issue."""
with patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Core",
"arch": arch,
},
with (
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Core",
"arch": arch,
},
),
patch(
"homeassistant.components.homeassistant._is_32_bit",
return_value=True,
),
):
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
await hass.async_block_till_done()
@ -679,48 +685,28 @@ async def test_deprecated_installation_issue_32bit_method(
@pytest.mark.parametrize(
"arch",
[
"i386",
"armhf",
"aarch64",
"generic-x86-64",
],
)
async def test_deprecated_installation_issue_32bit(
async def test_deprecated_installation_issue_64bit_core(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
arch: str,
) -> None:
"""Test deprecated installation issue."""
with patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Container",
"arch": arch,
},
):
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
await hass.async_block_till_done()
assert len(issue_registry.issues) == 1
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_architecture"
)
assert issue.domain == HOMEASSISTANT_DOMAIN
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_placeholders == {
"installation_type": "Container",
"arch": arch,
}
async def test_deprecated_installation_issue_method(
hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
"""Test deprecated installation issue."""
with patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Core",
"arch": "generic-x86-64",
},
with (
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Core",
"arch": arch,
},
),
patch(
"homeassistant.components.homeassistant._is_32_bit",
return_value=False,
),
):
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
await hass.async_block_till_done()
@ -731,28 +717,46 @@ async def test_deprecated_installation_issue_method(
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_placeholders == {
"installation_type": "Core",
"arch": "generic-x86-64",
"arch": arch,
}
async def test_deprecated_installation_issue_armv7_container(
@pytest.mark.parametrize(
"arch",
[
"i386",
"armv7",
"armhf",
],
)
async def test_deprecated_installation_issue_32bit(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
arch: str,
) -> None:
"""Test deprecated installation issue."""
with patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Container",
"arch": "armv7",
},
with (
patch(
"homeassistant.components.homeassistant.async_get_system_info",
return_value={
"installation_type": "Home Assistant Container",
"arch": arch,
},
),
patch(
"homeassistant.components.homeassistant._is_32_bit",
return_value=True,
),
patch(
"homeassistant.components.homeassistant._get_arch",
return_value=arch,
),
):
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
await hass.async_block_till_done()
assert len(issue_registry.issues) == 1
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_container_armv7"
)
issue = issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, "deprecated_container")
assert issue.domain == HOMEASSISTANT_DOMAIN
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_placeholders == {"arch": arch}