diff --git a/supervisor/jobs/decorator.py b/supervisor/jobs/decorator.py index bd62e3c71..0b34f48db 100644 --- a/supervisor/jobs/decorator.py +++ b/supervisor/jobs/decorator.py @@ -92,7 +92,7 @@ class Job(CoreSysAttributes): # Handle condition if self.conditions: try: - self._check_conditions() + await self._check_conditions() except JobConditionException as err: error_msg = str(err) if self.on_condition is None: @@ -150,7 +150,7 @@ class Job(CoreSysAttributes): return wrapper - def _check_conditions(self): + async def _check_conditions(self): """Check conditions.""" used_conditions = set(self.conditions) - set(self.sys_jobs.ignore_conditions) ignored_conditions = set(self.conditions) & set(self.sys_jobs.ignore_conditions) @@ -238,13 +238,22 @@ class Job(CoreSysAttributes): f"'{self._method.__qualname__}' blocked from execution, supervisor needs to be updated first" ) - if JobCondition.PLUGINS_UPDATED in self.conditions and 0 < len( - [plugin for plugin in self.sys_plugins.all_plugins if plugin.need_update] + if JobCondition.PLUGINS_UPDATED in self.conditions and ( + out_of_date := [ + plugin for plugin in self.sys_plugins.all_plugins if plugin.need_update + ] ): - raise JobConditionException( - f"'{self._method.__qualname__}' blocked from execution, plugin(s) {', '.join([plugin.slug for plugin in self.sys_plugins.all_plugins if plugin.need_update])} need to be updated first" + errors = await asyncio.gather( + *[plugin.update() for plugin in out_of_date], return_exceptions=True ) + if update_failures := [ + out_of_date[i].slug for i in range(len(errors)) if errors[i] is not None + ]: + raise JobConditionException( + f"'{self._method.__qualname__}' blocked from execution, was unable to update plugin(s) {', '.join(update_failures)} and all plugins must be up to date first" + ) + async def _acquire_exection_limit(self) -> None: """Process exection limits.""" if self.limit not in ( diff --git a/tests/addons/test_addon.py b/tests/addons/test_addon.py index fa1cf2849..e59881bd5 100644 --- a/tests/addons/test_addon.py +++ b/tests/addons/test_addon.py @@ -11,7 +11,7 @@ from supervisor.const import AddonState, BusEvent from supervisor.coresys import CoreSys from supervisor.docker.const import ContainerState from supervisor.docker.monitor import DockerContainerStateEvent -from supervisor.exceptions import AddonsJobError +from supervisor.exceptions import AddonsJobError, AudioUpdateError from ..const import TEST_ADDON_SLUG @@ -287,6 +287,8 @@ async def test_install_update_fails_if_out_of_date( with patch.object( type(coresys.plugins.audio), "need_update", new=PropertyMock(return_value=True) + ), patch.object( + type(coresys.plugins.audio), "update", side_effect=AudioUpdateError ): with pytest.raises(AddonsJobError): await coresys.addons.install(TEST_ADDON_SLUG) diff --git a/tests/homeassistant/test_core.py b/tests/homeassistant/test_core.py index f7f600957..1bf5c99a2 100644 --- a/tests/homeassistant/test_core.py +++ b/tests/homeassistant/test_core.py @@ -4,7 +4,7 @@ from unittest.mock import PropertyMock, patch import pytest from supervisor.coresys import CoreSys -from supervisor.exceptions import HomeAssistantJobError +from supervisor.exceptions import AudioUpdateError, HomeAssistantJobError async def test_update_fails_if_out_of_date(coresys: CoreSys): @@ -18,5 +18,9 @@ async def test_update_fails_if_out_of_date(coresys: CoreSys): with patch.object( type(coresys.plugins.audio), "need_update", new=PropertyMock(return_value=True) - ), pytest.raises(HomeAssistantJobError): + ), patch.object( + type(coresys.plugins.audio), "update", side_effect=AudioUpdateError + ), pytest.raises( + HomeAssistantJobError + ): await coresys.homeassistant.core.update() diff --git a/tests/jobs/test_job_decorator.py b/tests/jobs/test_job_decorator.py index 30e423dc0..a3fdfa697 100644 --- a/tests/jobs/test_job_decorator.py +++ b/tests/jobs/test_job_decorator.py @@ -9,7 +9,12 @@ import time_machine from supervisor.const import CoreState from supervisor.coresys import CoreSys -from supervisor.exceptions import HassioError, JobException, PluginJobError +from supervisor.exceptions import ( + AudioUpdateError, + HassioError, + JobException, + PluginJobError, +) from supervisor.jobs.const import JobExecutionLimit from supervisor.jobs.decorator import Job, JobCondition from supervisor.resolution.const import UnhealthyReason @@ -448,8 +453,11 @@ async def test_plugins_updated(coresys: CoreSys): with patch.object( type(coresys.plugins.audio), "need_update", new=PropertyMock(return_value=True) + ), patch.object( + type(coresys.plugins.audio), "update", side_effect=[AudioUpdateError, None] ): assert not await test.execute() + assert await test.execute() async def test_auto_update(coresys: CoreSys):