mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 01:36:29 +00:00
Add manual_only
option to addon boot config (#5272)
* Add manual_forced option to addon boot config * Include client library in pull request template * Add boot_config to api output so frontend can use it * `manual_forced` to `manual_only`
This commit is contained in:
parent
91a8fae9b5
commit
0177cd9528
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -38,6 +38,7 @@
|
|||||||
- This PR is related to issue:
|
- This PR is related to issue:
|
||||||
- Link to documentation pull request:
|
- Link to documentation pull request:
|
||||||
- Link to cli pull request:
|
- Link to cli pull request:
|
||||||
|
- Link to client library pull request:
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
@ -55,9 +56,11 @@
|
|||||||
- [ ] The code has been formatted using Ruff (`ruff format supervisor tests`)
|
- [ ] The code has been formatted using Ruff (`ruff format supervisor tests`)
|
||||||
- [ ] Tests have been added to verify that the new code works.
|
- [ ] Tests have been added to verify that the new code works.
|
||||||
|
|
||||||
If API endpoints of add-on configuration are added/changed:
|
If API endpoints or add-on configuration are added/changed:
|
||||||
|
|
||||||
- [ ] Documentation added/updated for [developers.home-assistant.io][docs-repository]
|
- [ ] Documentation added/updated for [developers.home-assistant.io][docs-repository]
|
||||||
|
- [ ] [CLI][cli-repository] updated (if necessary)
|
||||||
|
- [ ] [Client library][client-library-repository] updated (if necessary)
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Thank you for contributing <3
|
Thank you for contributing <3
|
||||||
@ -67,3 +70,5 @@ If API endpoints of add-on configuration are added/changed:
|
|||||||
|
|
||||||
[dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html
|
[dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html
|
||||||
[docs-repository]: https://github.com/home-assistant/developers.home-assistant
|
[docs-repository]: https://github.com/home-assistant/developers.home-assistant
|
||||||
|
[cli-repository]: https://github.com/home-assistant/cli
|
||||||
|
[client-library-repository]: https://github.com/home-assistant-libs/python-supervisor-client/
|
||||||
|
@ -57,6 +57,7 @@ from ..const import (
|
|||||||
ATTR_WATCHDOG,
|
ATTR_WATCHDOG,
|
||||||
DNS_SUFFIX,
|
DNS_SUFFIX,
|
||||||
AddonBoot,
|
AddonBoot,
|
||||||
|
AddonBootConfig,
|
||||||
AddonStartup,
|
AddonStartup,
|
||||||
AddonState,
|
AddonState,
|
||||||
BusEvent,
|
BusEvent,
|
||||||
@ -311,7 +312,9 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def boot(self) -> AddonBoot:
|
def boot(self) -> AddonBoot:
|
||||||
"""Return boot config with prio local settings."""
|
"""Return boot config with prio local settings unless config is forced."""
|
||||||
|
if self.boot_config == AddonBootConfig.MANUAL_ONLY:
|
||||||
|
return super().boot
|
||||||
return self.persist.get(ATTR_BOOT, super().boot)
|
return self.persist.get(ATTR_BOOT, super().boot)
|
||||||
|
|
||||||
@boot.setter
|
@boot.setter
|
||||||
|
@ -83,6 +83,7 @@ from ..const import (
|
|||||||
SECURITY_DISABLE,
|
SECURITY_DISABLE,
|
||||||
SECURITY_PROFILE,
|
SECURITY_PROFILE,
|
||||||
AddonBoot,
|
AddonBoot,
|
||||||
|
AddonBootConfig,
|
||||||
AddonStage,
|
AddonStage,
|
||||||
AddonStartup,
|
AddonStartup,
|
||||||
)
|
)
|
||||||
@ -150,10 +151,15 @@ class AddonModel(JobGroup, ABC):
|
|||||||
return self.data[ATTR_OPTIONS]
|
return self.data[ATTR_OPTIONS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def boot(self) -> AddonBoot:
|
def boot_config(self) -> AddonBootConfig:
|
||||||
"""Return boot config with prio local settings."""
|
"""Return boot config."""
|
||||||
return self.data[ATTR_BOOT]
|
return self.data[ATTR_BOOT]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boot(self) -> AddonBoot:
|
||||||
|
"""Return boot config with prio local settings unless config is forced."""
|
||||||
|
return AddonBoot(self.data[ATTR_BOOT])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_update(self) -> bool | None:
|
def auto_update(self) -> bool | None:
|
||||||
"""Return if auto update is enable."""
|
"""Return if auto update is enable."""
|
||||||
|
@ -98,6 +98,7 @@ from ..const import (
|
|||||||
ROLE_ALL,
|
ROLE_ALL,
|
||||||
ROLE_DEFAULT,
|
ROLE_DEFAULT,
|
||||||
AddonBoot,
|
AddonBoot,
|
||||||
|
AddonBootConfig,
|
||||||
AddonStage,
|
AddonStage,
|
||||||
AddonStartup,
|
AddonStartup,
|
||||||
AddonState,
|
AddonState,
|
||||||
@ -321,7 +322,9 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_STARTUP, default=AddonStartup.APPLICATION): vol.Coerce(
|
vol.Optional(ATTR_STARTUP, default=AddonStartup.APPLICATION): vol.Coerce(
|
||||||
AddonStartup
|
AddonStartup
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_BOOT, default=AddonBoot.AUTO): vol.Coerce(AddonBoot),
|
vol.Optional(ATTR_BOOT, default=AddonBootConfig.AUTO): vol.Coerce(
|
||||||
|
AddonBootConfig
|
||||||
|
),
|
||||||
vol.Optional(ATTR_INIT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_INIT, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_ADVANCED, default=False): vol.Boolean(),
|
vol.Optional(ATTR_ADVANCED, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_STAGE, default=AddonStage.STABLE): vol.Coerce(AddonStage),
|
vol.Optional(ATTR_STAGE, default=AddonStage.STABLE): vol.Coerce(AddonStage),
|
||||||
|
@ -98,6 +98,7 @@ from ..const import (
|
|||||||
ATTR_WEBUI,
|
ATTR_WEBUI,
|
||||||
REQUEST_FROM,
|
REQUEST_FROM,
|
||||||
AddonBoot,
|
AddonBoot,
|
||||||
|
AddonBootConfig,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
@ -109,7 +110,7 @@ from ..exceptions import (
|
|||||||
PwnedSecret,
|
PwnedSecret,
|
||||||
)
|
)
|
||||||
from ..validate import docker_ports
|
from ..validate import docker_ports
|
||||||
from .const import ATTR_REMOVE_CONFIG, ATTR_SIGNED
|
from .const import ATTR_BOOT_CONFIG, ATTR_REMOVE_CONFIG, ATTR_SIGNED
|
||||||
from .utils import api_process, api_validate, json_loads
|
from .utils import api_process, api_validate, json_loads
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -217,6 +218,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_VERSION_LATEST: addon.latest_version,
|
ATTR_VERSION_LATEST: addon.latest_version,
|
||||||
ATTR_PROTECTED: addon.protected,
|
ATTR_PROTECTED: addon.protected,
|
||||||
ATTR_RATING: rating_security(addon),
|
ATTR_RATING: rating_security(addon),
|
||||||
|
ATTR_BOOT_CONFIG: addon.boot_config,
|
||||||
ATTR_BOOT: addon.boot,
|
ATTR_BOOT: addon.boot,
|
||||||
ATTR_OPTIONS: addon.options,
|
ATTR_OPTIONS: addon.options,
|
||||||
ATTR_SCHEMA: addon.schema_ui,
|
ATTR_SCHEMA: addon.schema_ui,
|
||||||
@ -300,6 +302,10 @@ class APIAddons(CoreSysAttributes):
|
|||||||
if ATTR_OPTIONS in body:
|
if ATTR_OPTIONS in body:
|
||||||
addon.options = body[ATTR_OPTIONS]
|
addon.options = body[ATTR_OPTIONS]
|
||||||
if ATTR_BOOT in body:
|
if ATTR_BOOT in body:
|
||||||
|
if addon.boot_config == AddonBootConfig.MANUAL_ONLY:
|
||||||
|
raise APIError(
|
||||||
|
f"Addon {addon.slug} boot option is set to {addon.boot_config} so it cannot be changed"
|
||||||
|
)
|
||||||
addon.boot = body[ATTR_BOOT]
|
addon.boot = body[ATTR_BOOT]
|
||||||
if ATTR_AUTO_UPDATE in body:
|
if ATTR_AUTO_UPDATE in body:
|
||||||
addon.auto_update = body[ATTR_AUTO_UPDATE]
|
addon.auto_update = body[ATTR_AUTO_UPDATE]
|
||||||
|
@ -17,6 +17,7 @@ ATTR_APPARMOR_VERSION = "apparmor_version"
|
|||||||
ATTR_ATTRIBUTES = "attributes"
|
ATTR_ATTRIBUTES = "attributes"
|
||||||
ATTR_AVAILABLE_UPDATES = "available_updates"
|
ATTR_AVAILABLE_UPDATES = "available_updates"
|
||||||
ATTR_BACKGROUND = "background"
|
ATTR_BACKGROUND = "background"
|
||||||
|
ATTR_BOOT_CONFIG = "boot_config"
|
||||||
ATTR_BOOT_SLOT = "boot_slot"
|
ATTR_BOOT_SLOT = "boot_slot"
|
||||||
ATTR_BOOT_SLOTS = "boot_slots"
|
ATTR_BOOT_SLOTS = "boot_slots"
|
||||||
ATTR_BOOT_TIMESTAMP = "boot_timestamp"
|
ATTR_BOOT_TIMESTAMP = "boot_timestamp"
|
||||||
|
@ -382,12 +382,27 @@ ROLE_ADMIN = "admin"
|
|||||||
ROLE_ALL = [ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_BACKUP, ROLE_MANAGER, ROLE_ADMIN]
|
ROLE_ALL = [ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_BACKUP, ROLE_MANAGER, ROLE_ADMIN]
|
||||||
|
|
||||||
|
|
||||||
|
class AddonBootConfig(StrEnum):
|
||||||
|
"""Boot mode config for the add-on."""
|
||||||
|
|
||||||
|
AUTO = "auto"
|
||||||
|
MANUAL = "manual"
|
||||||
|
MANUAL_ONLY = "manual_only"
|
||||||
|
|
||||||
|
|
||||||
class AddonBoot(StrEnum):
|
class AddonBoot(StrEnum):
|
||||||
"""Boot mode for the add-on."""
|
"""Boot mode for the add-on."""
|
||||||
|
|
||||||
AUTO = "auto"
|
AUTO = "auto"
|
||||||
MANUAL = "manual"
|
MANUAL = "manual"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value: str) -> Self | None:
|
||||||
|
"""Convert 'forced' config values to their counterpart."""
|
||||||
|
if value == AddonBootConfig.MANUAL_ONLY:
|
||||||
|
return AddonBoot.MANUAL
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AddonStartup(StrEnum):
|
class AddonStartup(StrEnum):
|
||||||
"""Startup types of Add-on."""
|
"""Startup types of Add-on."""
|
||||||
|
@ -691,6 +691,7 @@ async def test_local_example_install(
|
|||||||
mock_aarch64_arch_supported: None,
|
mock_aarch64_arch_supported: None,
|
||||||
):
|
):
|
||||||
"""Test install of an addon."""
|
"""Test install of an addon."""
|
||||||
|
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||||
assert not (
|
assert not (
|
||||||
data_dir := tmp_supervisor_data / "addons" / "data" / "local_example"
|
data_dir := tmp_supervisor_data / "addons" / "data" / "local_example"
|
||||||
).exists()
|
).exists()
|
||||||
@ -883,3 +884,14 @@ async def test_addon_load_succeeds_with_docker_errors(
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
await install_addon_ssh.load()
|
await install_addon_ssh.load()
|
||||||
assert "Unknown error with test/amd64-addon-ssh:9.2.1" in caplog.text
|
assert "Unknown error with test/amd64-addon-ssh:9.2.1" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_addon_manual_only_boot(coresys: CoreSys, install_addon_example: Addon):
|
||||||
|
"""Test an addon with manual only boot mode."""
|
||||||
|
assert install_addon_example.boot_config == "manual_only"
|
||||||
|
assert install_addon_example.boot == "manual"
|
||||||
|
|
||||||
|
# Users cannot change boot mode of an addon with manual forced so changing boot isn't realistic
|
||||||
|
# However boot mode can change on update and user may have set auto before, ensure it is ignored
|
||||||
|
install_addon_example.boot = "auto"
|
||||||
|
assert install_addon_example.boot == "manual"
|
||||||
|
@ -346,3 +346,23 @@ async def test_api_addon_system_managed(
|
|||||||
body = await resp.json()
|
body = await resp.json()
|
||||||
assert body["data"]["system_managed"] is False
|
assert body["data"]["system_managed"] is False
|
||||||
assert body["data"]["system_managed_config_entry"] is None
|
assert body["data"]["system_managed_config_entry"] is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_addon_options_boot_mode_manual_only_invalid(
|
||||||
|
api_client: TestClient, install_addon_example: Addon
|
||||||
|
):
|
||||||
|
"""Test changing boot mode is invalid if set to manual only."""
|
||||||
|
install_addon_example.data["ingress"] = False
|
||||||
|
resp = await api_client.get("/addons/local_example/info")
|
||||||
|
assert resp.status == 200
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["data"]["boot"] == "manual"
|
||||||
|
assert body["data"]["boot_config"] == "manual_only"
|
||||||
|
|
||||||
|
resp = await api_client.post("/addons/local_example/options", json={"boot": "auto"})
|
||||||
|
assert resp.status == 400
|
||||||
|
body = await resp.json()
|
||||||
|
assert (
|
||||||
|
body["message"]
|
||||||
|
== "Addon local_example boot option is set to manual_only so it cannot be changed"
|
||||||
|
)
|
||||||
|
@ -23,3 +23,4 @@ ingress_port: 0
|
|||||||
breaking_versions:
|
breaking_versions:
|
||||||
- test
|
- test
|
||||||
- 1.0
|
- 1.0
|
||||||
|
boot: manual_only
|
||||||
|
Loading…
x
Reference in New Issue
Block a user