mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Automatically hide secrets in validation (#455)
* Hide secrets in validation * Lint
This commit is contained in:
		| @@ -2,7 +2,6 @@ from __future__ import print_function | |||||||
|  |  | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| import importlib | import importlib | ||||||
| import json |  | ||||||
| import logging | import logging | ||||||
| import re | import re | ||||||
|  |  | ||||||
| @@ -19,6 +18,7 @@ from esphome.util import safe_print | |||||||
| # pylint: disable=unused-import, wrong-import-order | # pylint: disable=unused-import, wrong-import-order | ||||||
| from typing import List, Optional, Tuple, Union  # noqa | from typing import List, Optional, Tuple, Union  # noqa | ||||||
| from esphome.core import ConfigType  # noqa | from esphome.core import ConfigType  # noqa | ||||||
|  | from esphome.yaml_util import is_secret | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -397,10 +397,7 @@ def _nested_getitem(data, path): | |||||||
| def humanize_error(config, validation_error): | def humanize_error(config, validation_error): | ||||||
|     offending_item_summary = _nested_getitem(config, validation_error.path) |     offending_item_summary = _nested_getitem(config, validation_error.path) | ||||||
|     if isinstance(offending_item_summary, dict): |     if isinstance(offending_item_summary, dict): | ||||||
|         try: |         offending_item_summary = None | ||||||
|             offending_item_summary = json.dumps(offending_item_summary) |  | ||||||
|         except (TypeError, ValueError): |  | ||||||
|             pass |  | ||||||
|     validation_error = text_type(validation_error) |     validation_error = text_type(validation_error) | ||||||
|     m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error) |     m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error) | ||||||
|     if m is not None: |     if m is not None: | ||||||
| @@ -408,8 +405,9 @@ def humanize_error(config, validation_error): | |||||||
|     validation_error = validation_error.strip() |     validation_error = validation_error.strip() | ||||||
|     if not validation_error.endswith(u'.'): |     if not validation_error.endswith(u'.'): | ||||||
|         validation_error += u'.' |         validation_error += u'.' | ||||||
|     if offending_item_summary is None: |     if offending_item_summary is None or is_secret(offending_item_summary): | ||||||
|         return validation_error |         return validation_error | ||||||
|  |  | ||||||
|     return u"{} Got '{}'".format(validation_error, offending_item_summary) |     return u"{} Got '{}'".format(validation_error, offending_item_summary) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -438,7 +436,8 @@ def load_config(): | |||||||
|     try: |     try: | ||||||
|         config = yaml_util.load_yaml(CORE.config_path) |         config = yaml_util.load_yaml(CORE.config_path) | ||||||
|     except OSError: |     except OSError: | ||||||
|         raise EsphomeError(u"Invalid YAML at {}".format(CORE.config_path)) |         raise EsphomeError(u"Invalid YAML at {}. Please see YAML syntax reference or use an online " | ||||||
|  |                            u"YAML syntax validator".format(CORE.config_path)) | ||||||
|     CORE.raw_config = config |     CORE.raw_config = config | ||||||
|     config = substitutions.do_substitution_pass(config) |     config = substitutions.do_substitution_pass(config) | ||||||
|     core_config.preload_core_config(config) |     core_config.preload_core_config(config) | ||||||
| @@ -536,6 +535,8 @@ def dump_dict(config, path, at_root=True): | |||||||
|                     msg = msg + u' ' + inf |                     msg = msg + u' ' + inf | ||||||
|             ret += st + msg + u'\n' |             ret += st + msg + u'\n' | ||||||
|     elif isinstance(conf, str): |     elif isinstance(conf, str): | ||||||
|  |         if is_secret(conf): | ||||||
|  |             conf = u'!secret {}'.format(is_secret(conf)) | ||||||
|         if not conf: |         if not conf: | ||||||
|             conf += u"''" |             conf += u"''" | ||||||
|  |  | ||||||
| @@ -545,6 +546,9 @@ def dump_dict(config, path, at_root=True): | |||||||
|         col = 'bold_red' if error else 'white' |         col = 'bold_red' if error else 'white' | ||||||
|         ret += color(col, text_type(conf)) |         ret += color(col, text_type(conf)) | ||||||
|     elif isinstance(conf, core.Lambda): |     elif isinstance(conf, core.Lambda): | ||||||
|  |         if is_secret(conf): | ||||||
|  |             conf = u'!secret {}'.format(is_secret(conf)) | ||||||
|  |  | ||||||
|         conf = u'!lambda |-\n' + indent(text_type(conf.value)) |         conf = u'!lambda |-\n' + indent(text_type(conf.value)) | ||||||
|         error = config.get_error_for_path(path) |         error = config.get_error_for_path(path) | ||||||
|         col = 'bold_red' if error else 'white' |         col = 'bold_red' if error else 'white' | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import yaml.constructor | |||||||
|  |  | ||||||
| from esphome import core | from esphome import core | ||||||
| from esphome.core import EsphomeError, HexInt, IPAddress, Lambda, MACAddress, TimePeriod | from esphome.core import EsphomeError, HexInt, IPAddress, Lambda, MACAddress, TimePeriod | ||||||
| from esphome.py_compat import string_types, text_type | from esphome.py_compat import string_types, text_type, IS_PY2 | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -20,6 +20,8 @@ _LOGGER = logging.getLogger(__name__) | |||||||
| # let's not reinvent the wheel here | # let's not reinvent the wheel here | ||||||
|  |  | ||||||
| SECRET_YAML = u'secrets.yaml' | SECRET_YAML = u'secrets.yaml' | ||||||
|  | _SECRET_CACHE = {} | ||||||
|  | _SECRET_VALUES = {} | ||||||
|  |  | ||||||
|  |  | ||||||
| class NodeListClass(list): | class NodeListClass(list): | ||||||
| @@ -42,6 +44,12 @@ class SafeLineLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | |||||||
|  |  | ||||||
|  |  | ||||||
| def load_yaml(fname): | def load_yaml(fname): | ||||||
|  |     _SECRET_VALUES.clear() | ||||||
|  |     _SECRET_CACHE.clear() | ||||||
|  |     return _load_yaml_internal(fname) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _load_yaml_internal(fname): | ||||||
|     """Load a YAML file.""" |     """Load a YAML file.""" | ||||||
|     try: |     try: | ||||||
|         with codecs.open(fname, encoding='utf-8') as conf_file: |         with codecs.open(fname, encoding='utf-8') as conf_file: | ||||||
| @@ -193,7 +201,7 @@ def _include_yaml(loader, node): | |||||||
|         device_tracker: !include device_tracker.yaml |         device_tracker: !include device_tracker.yaml | ||||||
|     """ |     """ | ||||||
|     fname = os.path.join(os.path.dirname(loader.name), node.value) |     fname = os.path.join(os.path.dirname(loader.name), node.value) | ||||||
|     return _add_reference(load_yaml(fname), loader, node) |     return _add_reference(_load_yaml_internal(fname), loader, node) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _is_file_valid(name): | def _is_file_valid(name): | ||||||
| @@ -217,7 +225,7 @@ def _include_dir_named_yaml(loader, node): | |||||||
|     loc = os.path.join(os.path.dirname(loader.name), node.value) |     loc = os.path.join(os.path.dirname(loader.name), node.value) | ||||||
|     for fname in _find_files(loc, '*.yaml'): |     for fname in _find_files(loc, '*.yaml'): | ||||||
|         filename = os.path.splitext(os.path.basename(fname))[0] |         filename = os.path.splitext(os.path.basename(fname))[0] | ||||||
|         mapping[filename] = load_yaml(fname) |         mapping[filename] = _load_yaml_internal(fname) | ||||||
|     return _add_reference(mapping, loader, node) |     return _add_reference(mapping, loader, node) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -228,7 +236,7 @@ def _include_dir_merge_named_yaml(loader, node): | |||||||
|     for fname in _find_files(loc, '*.yaml'): |     for fname in _find_files(loc, '*.yaml'): | ||||||
|         if os.path.basename(fname) == SECRET_YAML: |         if os.path.basename(fname) == SECRET_YAML: | ||||||
|             continue |             continue | ||||||
|         loaded_yaml = load_yaml(fname) |         loaded_yaml = _load_yaml_internal(fname) | ||||||
|         if isinstance(loaded_yaml, dict): |         if isinstance(loaded_yaml, dict): | ||||||
|             mapping.update(loaded_yaml) |             mapping.update(loaded_yaml) | ||||||
|     return _add_reference(mapping, loader, node) |     return _add_reference(mapping, loader, node) | ||||||
| @@ -237,7 +245,7 @@ def _include_dir_merge_named_yaml(loader, node): | |||||||
| def _include_dir_list_yaml(loader, node): | def _include_dir_list_yaml(loader, node): | ||||||
|     """Load multiple files from directory as a list.""" |     """Load multiple files from directory as a list.""" | ||||||
|     loc = os.path.join(os.path.dirname(loader.name), node.value) |     loc = os.path.join(os.path.dirname(loader.name), node.value) | ||||||
|     return [load_yaml(f) for f in _find_files(loc, '*.yaml') |     return [_load_yaml_internal(f) for f in _find_files(loc, '*.yaml') | ||||||
|             if os.path.basename(f) != SECRET_YAML] |             if os.path.basename(f) != SECRET_YAML] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -248,20 +256,29 @@ def _include_dir_merge_list_yaml(loader, node): | |||||||
|     for fname in _find_files(path, '*.yaml'): |     for fname in _find_files(path, '*.yaml'): | ||||||
|         if os.path.basename(fname) == SECRET_YAML: |         if os.path.basename(fname) == SECRET_YAML: | ||||||
|             continue |             continue | ||||||
|         loaded_yaml = load_yaml(fname) |         loaded_yaml = _load_yaml_internal(fname) | ||||||
|         if isinstance(loaded_yaml, list): |         if isinstance(loaded_yaml, list): | ||||||
|             merged_list.extend(loaded_yaml) |             merged_list.extend(loaded_yaml) | ||||||
|     return _add_reference(merged_list, loader, node) |     return _add_reference(merged_list, loader, node) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def is_secret(value): | ||||||
|  |     try: | ||||||
|  |         return _SECRET_VALUES[text_type(value)] | ||||||
|  |     except (KeyError, ValueError): | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| # pylint: disable=protected-access | # pylint: disable=protected-access | ||||||
| def _secret_yaml(loader, node): | def _secret_yaml(loader, node): | ||||||
|     """Load secrets and embed it into the configuration YAML.""" |     """Load secrets and embed it into the configuration YAML.""" | ||||||
|     secret_path = os.path.join(os.path.dirname(loader.name), SECRET_YAML) |     secret_path = os.path.join(os.path.dirname(loader.name), SECRET_YAML) | ||||||
|     secrets = load_yaml(secret_path) |     secrets = _load_yaml_internal(secret_path) | ||||||
|     if node.value not in secrets: |     if node.value not in secrets: | ||||||
|         raise EsphomeError(u"Secret {} not defined".format(node.value)) |         raise EsphomeError(u"Secret {} not defined".format(node.value)) | ||||||
|     return secrets[node.value] |     val = secrets[node.value] | ||||||
|  |     _SECRET_VALUES[text_type(val)] = node.value | ||||||
|  |     return val | ||||||
|  |  | ||||||
|  |  | ||||||
| def _lambda(loader, node): | def _lambda(loader, node): | ||||||
| @@ -310,17 +327,27 @@ def represent_odict(dump, tag, mapping, flow_style=None): | |||||||
|     return node |     return node | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def represent_secret(value): | ||||||
|  |     return yaml.ScalarNode(tag=u'!secret', value=_SECRET_VALUES[value]) | ||||||
|  |  | ||||||
|  |  | ||||||
| def unicode_representer(_, uni): | def unicode_representer(_, uni): | ||||||
|  |     if is_secret(uni): | ||||||
|  |         return represent_secret(uni) | ||||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) |     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) | ||||||
|     return node |     return node | ||||||
|  |  | ||||||
|  |  | ||||||
| def hex_int_representer(_, data): | def hex_int_representer(_, data): | ||||||
|  |     if is_secret(data): | ||||||
|  |         return represent_secret(data) | ||||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:int', value=str(data)) |     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:int', value=str(data)) | ||||||
|     return node |     return node | ||||||
|  |  | ||||||
|  |  | ||||||
| def stringify_representer(_, data): | def stringify_representer(_, data): | ||||||
|  |     if is_secret(data): | ||||||
|  |         return represent_secret(data) | ||||||
|     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=str(data)) |     node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=str(data)) | ||||||
|     return node |     return node | ||||||
|  |  | ||||||
| @@ -345,18 +372,18 @@ def represent_time_period(dumper, data): | |||||||
|  |  | ||||||
|  |  | ||||||
| def represent_lambda(_, data): | def represent_lambda(_, data): | ||||||
|  |     if is_secret(data.value): | ||||||
|  |         return represent_secret(data.value) | ||||||
|     node = yaml.ScalarNode(tag='!lambda', value=data.value, style='|') |     node = yaml.ScalarNode(tag='!lambda', value=data.value, style='|') | ||||||
|     return node |     return node | ||||||
|  |  | ||||||
|  |  | ||||||
| def represent_id(_, data): | def represent_id(_, data): | ||||||
|  |     if is_secret(data.id): | ||||||
|  |         return represent_secret(data.id) | ||||||
|     return yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=data.id) |     return yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=data.id) | ||||||
|  |  | ||||||
|  |  | ||||||
| def represent_uuid(_, data): |  | ||||||
|     return yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=str(data)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| yaml.SafeDumper.add_representer( | yaml.SafeDumper.add_representer( | ||||||
|     OrderedDict, |     OrderedDict, | ||||||
|     lambda dumper, value: |     lambda dumper, value: | ||||||
| @@ -369,11 +396,13 @@ yaml.SafeDumper.add_representer( | |||||||
|     dumper.represent_sequence('tag:yaml.org,2002:seq', value) |     dumper.represent_sequence('tag:yaml.org,2002:seq', value) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| yaml.SafeDumper.add_representer(text_type, unicode_representer) | yaml.SafeDumper.add_representer(str, unicode_representer) | ||||||
|  | if IS_PY2: | ||||||
|  |     yaml.SafeDumper.add_representer(unicode, unicode_representer) | ||||||
| yaml.SafeDumper.add_representer(HexInt, hex_int_representer) | yaml.SafeDumper.add_representer(HexInt, hex_int_representer) | ||||||
| yaml.SafeDumper.add_representer(IPAddress, stringify_representer) | yaml.SafeDumper.add_representer(IPAddress, stringify_representer) | ||||||
| yaml.SafeDumper.add_representer(MACAddress, stringify_representer) | yaml.SafeDumper.add_representer(MACAddress, stringify_representer) | ||||||
| yaml.SafeDumper.add_multi_representer(TimePeriod, represent_time_period) | yaml.SafeDumper.add_multi_representer(TimePeriod, represent_time_period) | ||||||
| yaml.SafeDumper.add_multi_representer(Lambda, represent_lambda) | yaml.SafeDumper.add_multi_representer(Lambda, represent_lambda) | ||||||
| yaml.SafeDumper.add_multi_representer(core.ID, represent_id) | yaml.SafeDumper.add_multi_representer(core.ID, represent_id) | ||||||
| yaml.SafeDumper.add_multi_representer(uuid.UUID, represent_uuid) | yaml.SafeDumper.add_multi_representer(uuid.UUID, stringify_representer) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user