diff --git a/supervisor/api/audio.py b/supervisor/api/audio.py index 6020913e0..535719764 100644 --- a/supervisor/api/audio.py +++ b/supervisor/api/audio.py @@ -33,11 +33,12 @@ from ..const import ( from ..coresys import CoreSysAttributes from ..exceptions import APIError from ..host.sound import StreamType +from ..validate import simple_version from .utils import api_process, api_process_raw, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): simple_version}) SCHEMA_VOLUME = vol.Schema( { diff --git a/supervisor/api/cli.py b/supervisor/api/cli.py index 170167248..6bcb855ce 100644 --- a/supervisor/api/cli.py +++ b/supervisor/api/cli.py @@ -19,11 +19,12 @@ from ..const import ( ATTR_VERSION_LATEST, ) from ..coresys import CoreSysAttributes +from ..validate import simple_version from .utils import api_process, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): simple_version}) class APICli(CoreSysAttributes): diff --git a/supervisor/api/dns.py b/supervisor/api/dns.py index d41fe45e4..21a43787e 100644 --- a/supervisor/api/dns.py +++ b/supervisor/api/dns.py @@ -24,7 +24,7 @@ from ..const import ( ) from ..coresys import CoreSysAttributes from ..exceptions import APIError -from ..validate import dns_server_list +from ..validate import dns_server_list, simple_version from .utils import api_process, api_process_raw, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) # pylint: disable=no-value-for-parameter SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_SERVERS): dns_server_list}) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): simple_version}) class APICoreDNS(CoreSysAttributes): diff --git a/supervisor/api/homeassistant.py b/supervisor/api/homeassistant.py index 373c3886b..b6a1332a7 100644 --- a/supervisor/api/homeassistant.py +++ b/supervisor/api/homeassistant.py @@ -33,7 +33,7 @@ from ..const import ( ) from ..coresys import CoreSysAttributes from ..exceptions import APIError -from ..validate import docker_image, network_port +from ..validate import docker_image, network_port, complex_version from .utils import api_process, api_process_raw, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -53,7 +53,7 @@ SCHEMA_OPTIONS = vol.Schema( } ) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): complex_version}) class APIHomeAssistant(CoreSysAttributes): diff --git a/supervisor/api/multicast.py b/supervisor/api/multicast.py index bdd470ae2..59f1607fd 100644 --- a/supervisor/api/multicast.py +++ b/supervisor/api/multicast.py @@ -21,11 +21,12 @@ from ..const import ( ) from ..coresys import CoreSysAttributes from ..exceptions import APIError +from ..validate import simple_version from .utils import api_process, api_process_raw, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): simple_version}) class APIMulticast(CoreSysAttributes): diff --git a/supervisor/api/os.py b/supervisor/api/os.py index 340b7bf9a..dd90d4d72 100644 --- a/supervisor/api/os.py +++ b/supervisor/api/os.py @@ -8,11 +8,12 @@ import voluptuous as vol from ..const import ATTR_BOARD, ATTR_BOOT, ATTR_VERSION, ATTR_VERSION_LATEST from ..coresys import CoreSysAttributes +from ..validate import complex_version from .utils import api_process, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): complex_version}) class APIOS(CoreSysAttributes): diff --git a/supervisor/api/supervisor.py b/supervisor/api/supervisor.py index 857aff463..66d7deeed 100644 --- a/supervisor/api/supervisor.py +++ b/supervisor/api/supervisor.py @@ -43,7 +43,7 @@ from ..const import ( from ..coresys import CoreSysAttributes from ..exceptions import APIError from ..utils.validate import validate_timezone -from ..validate import repositories, wait_boot +from ..validate import repositories, wait_boot, simple_version from .utils import api_process, api_process_raw, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -61,7 +61,7 @@ SCHEMA_OPTIONS = vol.Schema( } ) -SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) +SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): simple_version}) class APISupervisor(CoreSysAttributes): diff --git a/supervisor/plugins/validate.py b/supervisor/plugins/validate.py index fc82e8902..4f0360b7e 100644 --- a/supervisor/plugins/validate.py +++ b/supervisor/plugins/validate.py @@ -3,11 +3,11 @@ import voluptuous as vol from ..const import ATTR_ACCESS_TOKEN, ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION -from ..validate import dns_server_list, docker_image, token +from ..validate import dns_server_list, docker_image, token, simple_version SCHEMA_DNS_CONFIG = vol.Schema( { - vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)), + vol.Optional(ATTR_VERSION): simple_version, vol.Optional(ATTR_IMAGE): docker_image, vol.Optional(ATTR_SERVERS, default=list): dns_server_list, }, @@ -17,7 +17,7 @@ SCHEMA_DNS_CONFIG = vol.Schema( SCHEMA_AUDIO_CONFIG = vol.Schema( { - vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)), + vol.Optional(ATTR_VERSION): simple_version, vol.Optional(ATTR_IMAGE): docker_image, }, extra=vol.REMOVE_EXTRA, @@ -26,7 +26,7 @@ SCHEMA_AUDIO_CONFIG = vol.Schema( SCHEMA_CLI_CONFIG = vol.Schema( { - vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)), + vol.Optional(ATTR_VERSION): simple_version, vol.Optional(ATTR_IMAGE): docker_image, vol.Optional(ATTR_ACCESS_TOKEN): token, }, @@ -36,7 +36,7 @@ SCHEMA_CLI_CONFIG = vol.Schema( SCHEMA_MULTICAST_CONFIG = vol.Schema( { - vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)), + vol.Optional(ATTR_VERSION): simple_version, vol.Optional(ATTR_IMAGE): docker_image, }, extra=vol.REMOVE_EXTRA, diff --git a/supervisor/snapshots/validate.py b/supervisor/snapshots/validate.py index 1c08e0ca8..6eb6097a3 100644 --- a/supervisor/snapshots/validate.py +++ b/supervisor/snapshots/validate.py @@ -31,7 +31,7 @@ from ..const import ( SNAPSHOT_FULL, SNAPSHOT_PARTIAL, ) -from ..validate import docker_image, network_port, repositories +from ..validate import docker_image, network_port, repositories, complex_version ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL] @@ -58,7 +58,7 @@ SCHEMA_SNAPSHOT = vol.Schema( vol.Inclusive(ATTR_CRYPTO, "encrypted"): CRYPTO_AES128, vol.Optional(ATTR_HOMEASSISTANT, default=dict): vol.Schema( { - vol.Optional(ATTR_VERSION): vol.Coerce(str), + vol.Optional(ATTR_VERSION): complex_version, vol.Optional(ATTR_IMAGE): docker_image, vol.Optional(ATTR_BOOT, default=True): vol.Boolean(), vol.Optional(ATTR_SSL, default=False): vol.Boolean(), diff --git a/supervisor/validate.py b/supervisor/validate.py index 535c04599..6ccb2a75d 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -2,8 +2,10 @@ import ipaddress import re import uuid +from typing import Optional, Union import voluptuous as vol +from packaging import version as pkg_version from .const import ( ATTR_ACCESS_TOKEN, @@ -51,6 +53,29 @@ sha256 = vol.Match(r"^[0-9a-f]{64}$") token = vol.Match(r"^[0-9a-f]{32,256}$") +def simple_version(value: Union[str, int, None]) -> Optional[str]: + """Validate main version handling.""" + if not isinstance(value, (str, int)): + return None + elif isinstance(value, int): + return str(value) + elif value.isnumeric() or value == "dev": + return value + return None + + +def complex_version(value: Union[str, None]) -> Optional[str]: + """Validate main version handling.""" + if not isinstance(value, str): + return None + + try: + pkg_version.parse(value) + except pkg_version.InvalidVersion: + raise vol.Invalid(f"Invalid version format {value}") + return value + + def dns_url(url: str) -> str: """Take a DNS url (str) and validates that it matches the scheme dns://.""" if not url.lower().startswith("dns://"): @@ -100,7 +125,7 @@ DOCKER_PORTS_DESCRIPTION = vol.Schema( SCHEMA_HASS_CONFIG = vol.Schema( { vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): uuid_match, - vol.Optional(ATTR_VERSION): vol.Coerce(str), + vol.Optional(ATTR_VERSION): complex_version, vol.Optional(ATTR_IMAGE): docker_image, vol.Optional(ATTR_ACCESS_TOKEN): token, vol.Optional(ATTR_BOOT, default=True): vol.Boolean(), @@ -123,13 +148,13 @@ SCHEMA_UPDATER_CONFIG = vol.Schema( vol.Optional(ATTR_CHANNEL, default=UpdateChannels.STABLE): vol.Coerce( UpdateChannels ), - vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str), - vol.Optional(ATTR_SUPERVISOR): vol.Coerce(str), - vol.Optional(ATTR_HASSOS): vol.Coerce(str), - vol.Optional(ATTR_CLI): vol.Coerce(str), - vol.Optional(ATTR_DNS): vol.Coerce(str), - vol.Optional(ATTR_AUDIO): vol.Coerce(str), - vol.Optional(ATTR_MULTICAST): vol.Coerce(str), + vol.Optional(ATTR_HOMEASSISTANT): complex_version, + vol.Optional(ATTR_SUPERVISOR): simple_version, + vol.Optional(ATTR_HASSOS): complex_version, + vol.Optional(ATTR_CLI): simple_version, + vol.Optional(ATTR_DNS): simple_version, + vol.Optional(ATTR_AUDIO): simple_version, + vol.Optional(ATTR_MULTICAST): simple_version, vol.Optional(ATTR_IMAGE, default=dict): vol.Schema( { vol.Optional(ATTR_HOMEASSISTANT): docker_image, @@ -151,7 +176,7 @@ SCHEMA_SUPERVISOR_CONFIG = vol.Schema( { vol.Optional(ATTR_TIMEZONE, default="UTC"): validate_timezone, vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str), - vol.Optional(ATTR_VERSION): vol.Coerce(str), + vol.Optional(ATTR_VERSION): simple_version, vol.Optional( ATTR_ADDONS_CUSTOM_LIST, default=["https://github.com/hassio-addons/repository"],