diff --git a/supervisor/jobs/const.py b/supervisor/jobs/const.py index 9a0d4af1d19..27ea8790ce9 100644 --- a/supervisor/jobs/const.py +++ b/supervisor/jobs/const.py @@ -34,6 +34,7 @@ class JobCondition(StrEnum): PLUGINS_UPDATED = "plugins_updated" RUNNING = "running" SUPERVISOR_UPDATED = "supervisor_updated" + ARCHITECTURE_SUPPORTED = "architecture_supported" class JobConcurrency(StrEnum): diff --git a/supervisor/jobs/decorator.py b/supervisor/jobs/decorator.py index df53c7daa13..c1d890797ed 100644 --- a/supervisor/jobs/decorator.py +++ b/supervisor/jobs/decorator.py @@ -441,6 +441,14 @@ async def check_conditions( raise JobConditionException( f"'{method_name}' blocked from execution, supervisor needs to be updated first" ) + if ( + JobCondition.ARCHITECTURE_SUPPORTED in used_conditions + and UnsupportedReason.SYSTEM_ARCHITECTURE + in coresys.sys_resolution.unsupported + ): + raise JobConditionException( + f"'{method_name}' blocked from execution, unsupported system architecture" + ) if JobCondition.PLUGINS_UPDATED in used_conditions and ( out_of_date := [ diff --git a/supervisor/misc/tasks.py b/supervisor/misc/tasks.py index 27c5e0e1348..d559176385c 100644 --- a/supervisor/misc/tasks.py +++ b/supervisor/misc/tasks.py @@ -161,6 +161,7 @@ async def _update_addons(self): JobCondition.INTERNET_HOST, JobCondition.OS_SUPPORTED, JobCondition.RUNNING, + JobCondition.ARCHITECTURE_SUPPORTED, ], concurrency=JobConcurrency.REJECT, ) diff --git a/supervisor/plugins/const.py b/supervisor/plugins/const.py index 94701d03b1c..6338cba6614 100644 --- a/supervisor/plugins/const.py +++ b/supervisor/plugins/const.py @@ -23,4 +23,5 @@ JobCondition.HEALTHY, JobCondition.INTERNET_HOST, JobCondition.SUPERVISOR_UPDATED, + JobCondition.ARCHITECTURE_SUPPORTED, ] diff --git a/supervisor/resolution/const.py b/supervisor/resolution/const.py index 206c5622398..34be58c2d1c 100644 --- a/supervisor/resolution/const.py +++ b/supervisor/resolution/const.py @@ -58,6 +58,7 @@ class UnsupportedReason(StrEnum): SYSTEMD_JOURNAL = "systemd_journal" SYSTEMD_RESOLVED = "systemd_resolved" VIRTUALIZATION_IMAGE = "virtualization_image" + SYSTEM_ARCHITECTURE = "system_architecture" class UnhealthyReason(StrEnum): diff --git a/supervisor/resolution/evaluations/system_architecture.py b/supervisor/resolution/evaluations/system_architecture.py new file mode 100644 index 00000000000..7a034e536a1 --- /dev/null +++ b/supervisor/resolution/evaluations/system_architecture.py @@ -0,0 +1,38 @@ +"""Evaluation class for system architecture support.""" + +from ...const import CoreState +from ...coresys import CoreSys +from ..const import UnsupportedReason +from .base import EvaluateBase + + +def setup(coresys: CoreSys) -> EvaluateBase: + """Initialize evaluation-setup function.""" + return EvaluateSystemArchitecture(coresys) + + +class EvaluateSystemArchitecture(EvaluateBase): + """Evaluate if the current Supervisor architecture is supported.""" + + @property + def reason(self) -> UnsupportedReason: + """Return a UnsupportedReason enum.""" + return UnsupportedReason.SYSTEM_ARCHITECTURE + + @property + def on_failure(self) -> str: + """Return a string that is printed when self.evaluate is True.""" + return "System architecture is no longer supported. Move to a supported system architecture." + + @property + def states(self) -> list[CoreState]: + """Return a list of valid states when this evaluation can run.""" + return [CoreState.INITIALIZE] + + async def evaluate(self): + """Run evaluation.""" + return self.sys_host.info.sys_arch.supervisor in { + "i386", + "armhf", + "armv7", + } diff --git a/supervisor/updater.py b/supervisor/updater.py index 17bffe849c3..9fc5c424762 100644 --- a/supervisor/updater.py +++ b/supervisor/updater.py @@ -242,9 +242,10 @@ async def _check_connectivity(self, connectivity: bool): @Job( name="updater_fetch_data", conditions=[ + JobCondition.ARCHITECTURE_SUPPORTED, JobCondition.INTERNET_SYSTEM, - JobCondition.OS_SUPPORTED, JobCondition.HOME_ASSISTANT_CORE_SUPPORTED, + JobCondition.OS_SUPPORTED, ], on_condition=UpdaterJobError, throttle_period=timedelta(seconds=30), diff --git a/tests/resolution/evaluation/test_system_architecture.py b/tests/resolution/evaluation/test_system_architecture.py new file mode 100644 index 00000000000..8fb07f61b28 --- /dev/null +++ b/tests/resolution/evaluation/test_system_architecture.py @@ -0,0 +1,43 @@ +"""Test evaluation supported system architectures.""" + +from unittest.mock import PropertyMock, patch + +import pytest + +from supervisor.const import CoreState +from supervisor.coresys import CoreSys +from supervisor.resolution.evaluations.system_architecture import ( + EvaluateSystemArchitecture, +) + + +@pytest.mark.parametrize("arch", ["i386", "armhf", "armv7"]) +async def test_evaluation_unsupported_architectures( + coresys: CoreSys, + arch: str, +): + """Test evaluation of unsupported system architectures.""" + system_architecture = EvaluateSystemArchitecture(coresys) + await coresys.core.set_state(CoreState.INITIALIZE) + + with patch.object( + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + ): + await system_architecture() + assert system_architecture.reason in coresys.resolution.unsupported + + +@pytest.mark.parametrize("arch", ["amd64", "aarch64"]) +async def test_evaluation_supported_architectures( + coresys: CoreSys, + arch: str, +): + """Test evaluation of supported system architectures.""" + system_architecture = EvaluateSystemArchitecture(coresys) + await coresys.core.set_state(CoreState.INITIALIZE) + + with patch.object( + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + ): + await system_architecture() + assert system_architecture.reason not in coresys.resolution.unsupported