mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
Add min_version to esphome config (#3866)
This commit is contained in:
parent
263b603188
commit
c3a8972550
6 changed files with 77 additions and 18 deletions
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue