[store_yaml] Force-load deferred includes; cover secrets.yml

- Promote `_force_load_include_files` from bundle.py to yaml_util.py as
  `force_load_include_files`. bundle.py and the new caller now share it.
- Call it inside the `track_yaml_loads` block in config.py so deferred
  `!include` values, packages, and similar are loaded while the listener
  is active. Without this, `CORE.data["yaml_sources"]` only contained the
  entry YAML, and store_yaml shipped an incomplete recovery blob.
- Treat both `secrets.yaml` and `secrets.yml` as secrets (matches
  `esphome.const.SECRETS_FILES`); also check the original (non-resolved)
  filename to catch a `secrets.yaml` symlinked to an unrelated target.
- Update bundle tests for the new import path and message wording.
This commit is contained in:
J. Nick Koston
2026-05-15 01:31:21 -07:00
parent 783afaeaf2
commit 150a65de8c
2 changed files with 10 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ import struct
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_RAW_DATA_ID
from esphome.const import CONF_ID, CONF_RAW_DATA_ID, SECRETS_FILES
from esphome.core import CORE, EsphomeError, HexInt
_LOGGER = logging.getLogger(__name__)
@@ -69,7 +69,10 @@ def _gather_files(include_secrets: bool) -> list[tuple[str, bytes]]:
continue
seen.add(resolved)
is_secret = resolved.name == "secrets.yaml"
# Either secrets.yaml or secrets.yml. Symlinks are followed by resolve()
# above, so we re-check the original path's name too in case someone
# symlinks `secrets.yaml` to a differently-named target.
is_secret = resolved.name in SECRETS_FILES or Path(path).name in SECRETS_FILES
if is_secret and not include_secrets:
content = REDACTED_PLACEHOLDER
else:

View File

@@ -1194,8 +1194,11 @@ def _load_config(
try:
with yaml_util.track_yaml_loads() as loaded_files:
config = yaml_util.load_yaml(CORE.config_path)
# Stash for components that want the on-disk YAML at codegen time
# (e.g. store_yaml embeds them into firmware for recovery).
# Resolve deferred !include / package references so the listener
# captures every reachable file. Components that want the on-disk
# YAML at codegen time (e.g. store_yaml for firmware recovery)
# read the list out of CORE.data["yaml_sources"] below.
yaml_util.force_load_include_files(config)
CORE.data["yaml_sources"] = loaded_files
except EsphomeError as e:
raise InvalidYAMLError(e) from e