Add min_version to esphome config (#3866)

This commit is contained in:
Jesse Hills 2022-10-05 16:30:56 +13:00 committed by GitHub
parent 263b603188
commit c3a8972550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 18 deletions

View file

@ -98,7 +98,7 @@ async def to_code(config):
def _process_git_config(config: dict, refresh) -> str: def _process_git_config(config: dict, refresh) -> str:
repo_dir = git.clone_or_update( repo_dir, _ = git.clone_or_update(
url=config[CONF_URL], url=config[CONF_URL],
ref=config.get(CONF_REF), ref=config.get(CONF_REF),
refresh=refresh, refresh=refresh,

View file

@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config
from esphome import git, yaml_util from esphome import git, yaml_util
from esphome.const import ( from esphome.const import (
CONF_ESPHOME,
CONF_FILE, CONF_FILE,
CONF_FILES, CONF_FILES,
CONF_MIN_VERSION,
CONF_PACKAGES, CONF_PACKAGES,
CONF_REF, CONF_REF,
CONF_REFRESH, CONF_REFRESH,
CONF_URL, CONF_URL,
CONF_USERNAME, CONF_USERNAME,
CONF_PASSWORD, CONF_PASSWORD,
__version__ as ESPHOME_VERSION,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
def _process_base_package(config: dict) -> dict: def _process_base_package(config: dict) -> dict:
repo_dir = git.clone_or_update( repo_dir, revert = git.clone_or_update(
url=config[CONF_URL], url=config[CONF_URL],
ref=config.get(CONF_REF), ref=config.get(CONF_REF),
refresh=config[CONF_REFRESH], refresh=config[CONF_REFRESH],
@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict:
username=config.get(CONF_USERNAME), username=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD), password=config.get(CONF_PASSWORD),
) )
files: str = config[CONF_FILES] files: list[str] = config[CONF_FILES]
def get_packages(files) -> dict:
packages = {} packages = {}
for file in files: for file in files:
yaml_file: Path = repo_dir / file yaml_file: Path = repo_dir / file
if not yaml_file.is_file(): if not yaml_file.is_file():
raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES]) raise cv.Invalid(
f"{file} does not exist in repository", path=[CONF_FILES]
)
try: try:
packages[file] = yaml_util.load_yaml(yaml_file) new_yaml = yaml_util.load_yaml(yaml_file)
if (
CONF_ESPHOME in new_yaml
and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME]
):
min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION]
if cv.Version.parse(min_version) > cv.Version.parse(
ESPHOME_VERSION
):
raise cv.Invalid(
f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
)
packages[file] = new_yaml
except EsphomeError as e: except EsphomeError as e:
raise cv.Invalid( raise cv.Invalid(
f"{file} is not a valid YAML file. Please check the file contents." f"{file} is not a valid YAML file. Please check the file contents."
) from e ) from e
return packages
packages = {}
try:
packages = get_packages(files)
except cv.Invalid:
if revert is not None:
revert()
packages = get_packages(files)
finally:
if packages is None:
raise cv.Invalid("Failed to load packages")
return {"packages": packages} return {"packages": packages}

View file

@ -1689,7 +1689,7 @@ class Version:
@classmethod @classmethod
def parse(cls, value: str) -> "Version": def parse(cls, value: str) -> "Version":
match = re.match(r"(\d+).(\d+).(\d+)", value) match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
if match is None: if match is None:
raise ValueError(f"Not a valid version number {value}") raise ValueError(f"Not a valid version number {value}")
major = int(match[1]) major = int(match[1])
@ -1703,7 +1703,7 @@ def version_number(value):
try: try:
return str(Version.parse(value)) return str(Version.parse(value))
except ValueError as e: except ValueError as e:
raise Invalid("Not a version number") from e raise Invalid("Not a valid version number") from e
def platformio_version_constraint(value): def platformio_version_constraint(value):

View file

@ -396,6 +396,7 @@ CONF_MIN_POWER = "min_power"
CONF_MIN_RANGE = "min_range" CONF_MIN_RANGE = "min_range"
CONF_MIN_TEMPERATURE = "min_temperature" CONF_MIN_TEMPERATURE = "min_temperature"
CONF_MIN_VALUE = "min_value" CONF_MIN_VALUE = "min_value"
CONF_MIN_VERSION = "min_version"
CONF_MINUTE = "minute" CONF_MINUTE = "minute"
CONF_MINUTES = "minutes" CONF_MINUTES = "minutes"
CONF_MISO_PIN = "miso_pin" CONF_MISO_PIN = "miso_pin"

View file

@ -15,6 +15,7 @@ from esphome.const import (
CONF_FRAMEWORK, CONF_FRAMEWORK,
CONF_INCLUDES, CONF_INCLUDES,
CONF_LIBRARIES, CONF_LIBRARIES,
CONF_MIN_VERSION,
CONF_NAME, CONF_NAME,
CONF_ON_BOOT, CONF_ON_BOOT,
CONF_ON_LOOP, CONF_ON_LOOP,
@ -30,6 +31,7 @@ from esphome.const import (
KEY_CORE, KEY_CORE,
TARGET_PLATFORMS, TARGET_PLATFORMS,
PLATFORM_ESP8266, PLATFORM_ESP8266,
__version__ as ESPHOME_VERSION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.helpers import copy_file_if_changed, walk_files from esphome.helpers import copy_file_if_changed, walk_files
@ -96,6 +98,16 @@ def valid_project_name(value: str):
return value return value
def validate_version(value: str):
min_version = cv.Version.parse(value)
current_version = cv.Version.parse(ESPHOME_VERSION)
if current_version < min_version:
raise cv.Invalid(
f"Your ESPHome version is too old. Please update to at least {min_version}"
)
return value
CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash"
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
@ -136,6 +148,9 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_VERSION): cv.string_strict, cv.Required(CONF_VERSION): cv.string_strict,
} }
), ),
cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All(
cv.version_number, validate_version
),
} }
), ),
validate_hostname, validate_hostname,

View file

@ -2,6 +2,7 @@ from pathlib import Path
import subprocess import subprocess
import hashlib import hashlib
import logging import logging
from typing import Callable, Optional
import urllib.parse import urllib.parse
from datetime import datetime from datetime import datetime
@ -12,7 +13,7 @@ import esphome.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def run_git_command(cmd, cwd=None): def run_git_command(cmd, cwd=None) -> str:
try: try:
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
except FileNotFoundError as err: except FileNotFoundError as err:
@ -28,6 +29,8 @@ def run_git_command(cmd, cwd=None):
raise cv.Invalid(lines[-1][len("fatal: ") :]) raise cv.Invalid(lines[-1][len("fatal: ") :])
raise cv.Invalid(err_str) raise cv.Invalid(err_str)
return ret.stdout.decode("utf-8").strip()
def _compute_destination_path(key: str, domain: str) -> Path: def _compute_destination_path(key: str, domain: str) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / domain base_dir = Path(CORE.config_dir) / ".esphome" / domain
@ -44,7 +47,7 @@ def clone_or_update(
domain: str, domain: str,
username: str = None, username: str = None,
password: str = None, password: str = None,
) -> Path: ) -> tuple[Path, Optional[Callable[[], None]]]:
key = f"{url}@{ref}" key = f"{url}@{ref}"
if username is not None and password is not None: if username is not None and password is not None:
@ -78,6 +81,7 @@ def clone_or_update(
file_timestamp = Path(repo_dir / ".git" / "HEAD") file_timestamp = Path(repo_dir / ".git" / "HEAD")
age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime) age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime)
if age.total_seconds() > refresh.total_seconds: if age.total_seconds() > refresh.total_seconds:
old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
_LOGGER.info("Updating %s", key) _LOGGER.info("Updating %s", key)
_LOGGER.debug("Location: %s", repo_dir) _LOGGER.debug("Location: %s", repo_dir)
# Stash local changes (if any) # Stash local changes (if any)
@ -92,4 +96,10 @@ def clone_or_update(
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
return repo_dir def revert():
_LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))
return repo_dir, revert
return repo_dir, None