Addon devs can block auto update for breaking versions (#4832)

This commit is contained in:
Mike Degatano 2024-01-26 02:08:03 -05:00 committed by GitHub
parent d24543e103
commit ddadbec7e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 70 additions and 0 deletions

View File

@ -15,6 +15,7 @@ from tempfile import TemporaryDirectory
from typing import Any, Final from typing import Any, Final
import aiohttp import aiohttp
from awesomeversion import AwesomeVersionCompareException
from deepmerge import Merger from deepmerge import Merger
from securetar import atomic_contents_add, secure_path from securetar import atomic_contents_add, secure_path
import voluptuous as vol import voluptuous as vol
@ -279,6 +280,28 @@ class Addon(AddonModel):
"""Set auto update.""" """Set auto update."""
self.persist[ATTR_AUTO_UPDATE] = value self.persist[ATTR_AUTO_UPDATE] = value
@property
def auto_update_available(self) -> bool:
"""Return if it is safe to auto update addon."""
if not self.need_update or not self.auto_update:
return False
for version in self.breaking_versions:
try:
# Must update to latest so if true update crosses a breaking version
if self.version < version:
return False
except AwesomeVersionCompareException:
# If version scheme changed, we may get compare exception
# If latest version >= breaking version then assume update will
# cross it as the version scheme changes
# If both versions have compare exception, ignore as its in the past
with suppress(AwesomeVersionCompareException):
if self.latest_version >= version:
return False
return True
@property @property
def watchdog(self) -> bool: def watchdog(self) -> bool:
"""Return True if watchdog is enable.""" """Return True if watchdog is enable."""

View File

@ -28,6 +28,7 @@ class MappingType(StrEnum):
ATTR_BACKUP = "backup" ATTR_BACKUP = "backup"
ATTR_BREAKING_VERSIONS = "breaking_versions"
ATTR_CODENOTARY = "codenotary" ATTR_CODENOTARY = "codenotary"
ATTR_READ_ONLY = "read_only" ATTR_READ_ONLY = "read_only"
ATTR_PATH = "path" ATTR_PATH = "path"

View File

@ -90,6 +90,7 @@ from ..utils import version_is_new_enough
from .configuration import FolderMapping from .configuration import FolderMapping
from .const import ( from .const import (
ATTR_BACKUP, ATTR_BACKUP,
ATTR_BREAKING_VERSIONS,
ATTR_CODENOTARY, ATTR_CODENOTARY,
ATTR_PATH, ATTR_PATH,
ATTR_READ_ONLY, ATTR_READ_ONLY,
@ -620,6 +621,11 @@ class AddonModel(JobGroup, ABC):
"""Return Signer email address for CAS.""" """Return Signer email address for CAS."""
return self.data.get(ATTR_CODENOTARY) return self.data.get(ATTR_CODENOTARY)
@property
def breaking_versions(self) -> list[AwesomeVersion]:
"""Return breaking versions of addon."""
return self.data[ATTR_BREAKING_VERSIONS]
def validate_availability(self) -> None: def validate_availability(self) -> None:
"""Validate if addon is available for current system.""" """Validate if addon is available for current system."""
return self._validate_availability(self.data, logger=_LOGGER.error) return self._validate_availability(self.data, logger=_LOGGER.error)

View File

@ -112,6 +112,7 @@ from ..validate import (
) )
from .const import ( from .const import (
ATTR_BACKUP, ATTR_BACKUP,
ATTR_BREAKING_VERSIONS,
ATTR_CODENOTARY, ATTR_CODENOTARY,
ATTR_PATH, ATTR_PATH,
ATTR_READ_ONLY, ATTR_READ_ONLY,
@ -422,6 +423,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
vol.Coerce(int), vol.Range(min=10, max=300) vol.Coerce(int), vol.Range(min=10, max=300)
), ),
vol.Optional(ATTR_JOURNALD, default=False): vol.Boolean(), vol.Optional(ATTR_JOURNALD, default=False): vol.Boolean(),
vol.Optional(ATTR_BREAKING_VERSIONS, default=list): [version_tag],
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
) )

View File

@ -95,6 +95,14 @@ class Tasks(CoreSysAttributes):
# Evaluate available updates # Evaluate available updates
if not addon.need_update: if not addon.need_update:
continue continue
if not addon.auto_update_available:
_LOGGER.debug(
"Not updating add-on %s from %s to %s as that would cross a known breaking version",
addon.slug,
addon.version,
addon.latest_version,
)
continue
if not addon.test_update_schema(): if not addon.test_update_schema():
_LOGGER.warning( _LOGGER.warning(
"Add-on %s will be ignored, schema tests failed", addon.slug "Add-on %s will be ignored, schema tests failed", addon.slug

View File

@ -6,6 +6,7 @@ import errno
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, PropertyMock, patch from unittest.mock import MagicMock, PropertyMock, patch
from awesomeversion import AwesomeVersion
from docker.errors import DockerException, NotFound from docker.errors import DockerException, NotFound
import pytest import pytest
from securetar import SecureTarFile from securetar import SecureTarFile
@ -721,3 +722,29 @@ def test_addon_pulse_error(
assert "can't write pulse/client.config" in caplog.text assert "can't write pulse/client.config" in caplog.text
assert coresys.core.healthy is False assert coresys.core.healthy is False
def test_auto_update_available(coresys: CoreSys, install_addon_example: Addon):
"""Test auto update availability based on versions."""
assert install_addon_example.auto_update is False
assert install_addon_example.need_update is False
assert install_addon_example.auto_update_available is False
with patch.object(
Addon, "version", new=PropertyMock(return_value=AwesomeVersion("1.0"))
):
assert install_addon_example.need_update is True
assert install_addon_example.auto_update_available is False
install_addon_example.auto_update = True
assert install_addon_example.auto_update_available is True
with patch.object(
Addon, "version", new=PropertyMock(return_value=AwesomeVersion("0.9"))
):
assert install_addon_example.auto_update_available is False
with patch.object(
Addon, "version", new=PropertyMock(return_value=AwesomeVersion("test"))
):
assert install_addon_example.auto_update_available is False

View File

@ -20,3 +20,6 @@ schema:
message: "str?" message: "str?"
ingress: true ingress: true
ingress_port: 0 ingress_port: 0
breaking_versions:
- test
- 1.0