mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 09:46:29 +00:00
Add hard-coded image fallback for plugins for offline start (#5204)
This commit is contained in:
parent
4ab4350c58
commit
5ee7d16687
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Code: https://github.com/home-assistant/plugin-audio
|
Code: https://github.com/home-assistant/plugin-audio
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
@ -73,7 +74,9 @@ class PluginAudio(PluginBase):
|
|||||||
@property
|
@property
|
||||||
def default_image(self) -> str:
|
def default_image(self) -> str:
|
||||||
"""Return default image for audio plugin."""
|
"""Return default image for audio plugin."""
|
||||||
return self.sys_updater.image_audio
|
if self.sys_updater.image_audio:
|
||||||
|
return self.sys_updater.image_audio
|
||||||
|
return super().default_image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion | None:
|
def latest_version(self) -> AwesomeVersion | None:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Code: https://github.com/home-assistant/plugin-cli
|
Code: https://github.com/home-assistant/plugin-cli
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
@ -42,7 +43,9 @@ class PluginCli(PluginBase):
|
|||||||
@property
|
@property
|
||||||
def default_image(self) -> str:
|
def default_image(self) -> str:
|
||||||
"""Return default image for cli plugin."""
|
"""Return default image for cli plugin."""
|
||||||
return self.sys_updater.image_cli
|
if self.sys_updater.image_cli:
|
||||||
|
return self.sys_updater.image_cli
|
||||||
|
return super().default_image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion | None:
|
def latest_version(self) -> AwesomeVersion | None:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Code: https://github.com/home-assistant/plugin-dns
|
Code: https://github.com/home-assistant/plugin-dns
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import errno
|
import errno
|
||||||
@ -111,7 +112,9 @@ class PluginDns(PluginBase):
|
|||||||
@property
|
@property
|
||||||
def default_image(self) -> str:
|
def default_image(self) -> str:
|
||||||
"""Return default image for dns plugin."""
|
"""Return default image for dns plugin."""
|
||||||
return self.sys_updater.image_dns
|
if self.sys_updater.image_dns:
|
||||||
|
return self.sys_updater.image_dns
|
||||||
|
return super().default_image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion | None:
|
def latest_version(self) -> AwesomeVersion | None:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Code: https://github.com/home-assistant/plugin-multicast
|
Code: https://github.com/home-assistant/plugin-multicast
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
@ -44,7 +45,9 @@ class PluginMulticast(PluginBase):
|
|||||||
@property
|
@property
|
||||||
def default_image(self) -> str:
|
def default_image(self) -> str:
|
||||||
"""Return default image for multicast plugin."""
|
"""Return default image for multicast plugin."""
|
||||||
return self.sys_updater.image_multicast
|
if self.sys_updater.image_multicast:
|
||||||
|
return self.sys_updater.image_multicast
|
||||||
|
return super().default_image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion | None:
|
def latest_version(self) -> AwesomeVersion | None:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Code: https://github.com/home-assistant/plugin-observer
|
Code: https://github.com/home-assistant/plugin-observer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
@ -47,7 +48,9 @@ class PluginObserver(PluginBase):
|
|||||||
@property
|
@property
|
||||||
def default_image(self) -> str:
|
def default_image(self) -> str:
|
||||||
"""Return default image for observer plugin."""
|
"""Return default image for observer plugin."""
|
||||||
return self.sys_updater.image_observer
|
if self.sys_updater.image_observer:
|
||||||
|
return self.sys_updater.image_observer
|
||||||
|
return super().default_image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> AwesomeVersion | None:
|
def latest_version(self) -> AwesomeVersion | None:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test base plugin functionality."""
|
"""Test base plugin functionality."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||||
|
|
||||||
@ -60,14 +61,17 @@ async def fixture_plugin(
|
|||||||
)
|
)
|
||||||
async def test_plugin_watchdog(coresys: CoreSys, plugin: PluginBase) -> None:
|
async def test_plugin_watchdog(coresys: CoreSys, plugin: PluginBase) -> None:
|
||||||
"""Test plugin watchdog works correctly."""
|
"""Test plugin watchdog works correctly."""
|
||||||
with patch.object(type(plugin.instance), "attach"), patch.object(
|
with (
|
||||||
type(plugin.instance), "is_running", return_value=True
|
patch.object(type(plugin.instance), "attach"),
|
||||||
|
patch.object(type(plugin.instance), "is_running", return_value=True),
|
||||||
):
|
):
|
||||||
await plugin.load()
|
await plugin.load()
|
||||||
|
|
||||||
with patch.object(type(plugin), "rebuild") as rebuild, patch.object(
|
with (
|
||||||
type(plugin), "start"
|
patch.object(type(plugin), "rebuild") as rebuild,
|
||||||
) as start, patch.object(type(plugin.instance), "current_state") as current_state:
|
patch.object(type(plugin), "start") as start,
|
||||||
|
patch.object(type(plugin.instance), "current_state") as current_state,
|
||||||
|
):
|
||||||
current_state.return_value = ContainerState.UNHEALTHY
|
current_state.return_value = ContainerState.UNHEALTHY
|
||||||
coresys.bus.fire_event(
|
coresys.bus.fire_event(
|
||||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
||||||
@ -168,9 +172,10 @@ async def test_plugin_watchdog_max_failed_attempts(
|
|||||||
|
|
||||||
container.status = "stopped"
|
container.status = "stopped"
|
||||||
container.attrs = {"State": {"ExitCode": 1}}
|
container.attrs = {"State": {"ExitCode": 1}}
|
||||||
with patch("supervisor.plugins.base.WATCHDOG_RETRY_SECONDS", 0), patch.object(
|
with (
|
||||||
type(plugin), "start", side_effect=error
|
patch("supervisor.plugins.base.WATCHDOG_RETRY_SECONDS", 0),
|
||||||
) as start:
|
patch.object(type(plugin), "start", side_effect=error) as start,
|
||||||
|
):
|
||||||
await plugin.watchdog_container(
|
await plugin.watchdog_container(
|
||||||
DockerContainerStateEvent(
|
DockerContainerStateEvent(
|
||||||
name=plugin.instance.name,
|
name=plugin.instance.name,
|
||||||
@ -198,17 +203,18 @@ async def test_plugin_load_running_container(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test plugins load and attach to a running container."""
|
"""Test plugins load and attach to a running container."""
|
||||||
test_version = AwesomeVersion("2022.7.3")
|
test_version = AwesomeVersion("2022.7.3")
|
||||||
with patch.object(
|
with (
|
||||||
type(coresys.bus), "register_event"
|
patch.object(type(coresys.bus), "register_event") as register_event,
|
||||||
) as register_event, patch.object(
|
patch.object(type(plugin.instance), "attach") as attach,
|
||||||
type(plugin.instance), "attach"
|
patch.object(type(plugin), "install") as install,
|
||||||
) as attach, patch.object(type(plugin), "install") as install, patch.object(
|
patch.object(type(plugin), "start") as start,
|
||||||
type(plugin), "start"
|
patch.object(
|
||||||
) as start, patch.object(
|
type(plugin.instance),
|
||||||
type(plugin.instance),
|
"get_latest_version",
|
||||||
"get_latest_version",
|
return_value=test_version,
|
||||||
return_value=test_version,
|
),
|
||||||
), patch.object(type(plugin.instance), "is_running", return_value=True):
|
patch.object(type(plugin.instance), "is_running", return_value=True),
|
||||||
|
):
|
||||||
await plugin.load()
|
await plugin.load()
|
||||||
register_event.assert_any_call(
|
register_event.assert_any_call(
|
||||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
||||||
@ -230,17 +236,18 @@ async def test_plugin_load_stopped_container(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test plugins load and start existing container."""
|
"""Test plugins load and start existing container."""
|
||||||
test_version = AwesomeVersion("2022.7.3")
|
test_version = AwesomeVersion("2022.7.3")
|
||||||
with patch.object(
|
with (
|
||||||
type(coresys.bus), "register_event"
|
patch.object(type(coresys.bus), "register_event") as register_event,
|
||||||
) as register_event, patch.object(
|
patch.object(type(plugin.instance), "attach") as attach,
|
||||||
type(plugin.instance), "attach"
|
patch.object(type(plugin), "install") as install,
|
||||||
) as attach, patch.object(type(plugin), "install") as install, patch.object(
|
patch.object(type(plugin), "start") as start,
|
||||||
type(plugin), "start"
|
patch.object(
|
||||||
) as start, patch.object(
|
type(plugin.instance),
|
||||||
type(plugin.instance),
|
"get_latest_version",
|
||||||
"get_latest_version",
|
return_value=test_version,
|
||||||
return_value=test_version,
|
),
|
||||||
), patch.object(type(plugin.instance), "is_running", return_value=False):
|
patch.object(type(plugin.instance), "is_running", return_value=False),
|
||||||
|
):
|
||||||
await plugin.load()
|
await plugin.load()
|
||||||
register_event.assert_any_call(
|
register_event.assert_any_call(
|
||||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
||||||
@ -262,17 +269,20 @@ async def test_plugin_load_missing_container(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test plugins load and create and start container."""
|
"""Test plugins load and create and start container."""
|
||||||
test_version = AwesomeVersion("2022.7.3")
|
test_version = AwesomeVersion("2022.7.3")
|
||||||
with patch.object(
|
with (
|
||||||
type(coresys.bus), "register_event"
|
patch.object(type(coresys.bus), "register_event") as register_event,
|
||||||
) as register_event, patch.object(
|
patch.object(
|
||||||
type(plugin.instance), "attach", side_effect=DockerError()
|
type(plugin.instance), "attach", side_effect=DockerError()
|
||||||
) as attach, patch.object(type(plugin), "install") as install, patch.object(
|
) as attach,
|
||||||
type(plugin), "start"
|
patch.object(type(plugin), "install") as install,
|
||||||
) as start, patch.object(
|
patch.object(type(plugin), "start") as start,
|
||||||
type(plugin.instance),
|
patch.object(
|
||||||
"get_latest_version",
|
type(plugin.instance),
|
||||||
return_value=test_version,
|
"get_latest_version",
|
||||||
), patch.object(type(plugin.instance), "is_running", return_value=False):
|
return_value=test_version,
|
||||||
|
),
|
||||||
|
patch.object(type(plugin.instance), "is_running", return_value=False),
|
||||||
|
):
|
||||||
await plugin.load()
|
await plugin.load()
|
||||||
register_event.assert_any_call(
|
register_event.assert_any_call(
|
||||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, plugin.watchdog_container
|
||||||
@ -301,9 +311,12 @@ async def test_update_fails_if_out_of_date(
|
|||||||
"""Test update of plugins fail when supervisor is out of date."""
|
"""Test update of plugins fail when supervisor is out of date."""
|
||||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||||
|
|
||||||
with patch.object(
|
with (
|
||||||
type(coresys.supervisor), "need_update", new=PropertyMock(return_value=True)
|
patch.object(
|
||||||
), pytest.raises(error):
|
type(coresys.supervisor), "need_update", new=PropertyMock(return_value=True)
|
||||||
|
),
|
||||||
|
pytest.raises(error),
|
||||||
|
):
|
||||||
await plugin.update()
|
await plugin.update()
|
||||||
|
|
||||||
|
|
||||||
@ -316,10 +329,14 @@ async def test_repair_failed(
|
|||||||
coresys: CoreSys, capture_exception: Mock, plugin: PluginBase
|
coresys: CoreSys, capture_exception: Mock, plugin: PluginBase
|
||||||
):
|
):
|
||||||
"""Test repair failed."""
|
"""Test repair failed."""
|
||||||
with patch.object(DockerInterface, "exists", return_value=False), patch.object(
|
with (
|
||||||
DockerInterface, "arch", new=PropertyMock(return_value=CpuArch.AMD64)
|
patch.object(DockerInterface, "exists", return_value=False),
|
||||||
), patch(
|
patch.object(
|
||||||
"supervisor.security.module.cas_validate", side_effect=CodeNotaryUntrusted
|
DockerInterface, "arch", new=PropertyMock(return_value=CpuArch.AMD64)
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"supervisor.security.module.cas_validate", side_effect=CodeNotaryUntrusted
|
||||||
|
),
|
||||||
):
|
):
|
||||||
await plugin.repair()
|
await plugin.repair()
|
||||||
|
|
||||||
@ -360,3 +377,16 @@ async def test_load_with_incorrect_image(
|
|||||||
platform="linux/amd64",
|
platform="linux/amd64",
|
||||||
)
|
)
|
||||||
assert plugin.image == correct_image
|
assert plugin.image == correct_image
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"plugin",
|
||||||
|
[PluginAudio, PluginCli, PluginDns, PluginMulticast, PluginObserver],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
async def test_default_image_fallback(
|
||||||
|
coresys: CoreSys, container: MagicMock, plugin: PluginBase
|
||||||
|
):
|
||||||
|
"""Test default image falls back to hard-coded constant if we fail to fetch version file."""
|
||||||
|
assert getattr(coresys.updater, f"image_{plugin.slug}") is None
|
||||||
|
assert plugin.default_image == f"ghcr.io/home-assistant/amd64-hassio-{plugin.slug}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user