Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6898740

Browse files
committed
Better way to treat interpolation errors
1 parent 4a61aad commit 6898740

File tree

8 files changed

+100
-1
lines changed

8 files changed

+100
-1
lines changed

README-extentions.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,43 @@ The ``${beta:${alpha:two}}`` construct first resolves the ``${alpha:two}`` refer
123123
the reference ``${beta:a}`` to the value 99.
124124

125125

126+
Return missing references
127+
-------------------------
128+
129+
Similarly as new "saltclass" it may return missed references instead of throw InterpolationError.
130+
131+
This is supposed to be feature, as
132+
- you can interpolate them in another ext_pillar engine.
133+
- your pillars will not fail.
134+
135+
Be aware that if this option is enabled, it may resolve in rendering an "reference string" as value in the system critical
136+
files without notice.
137+
138+
Default value is False to keep backward compatible behavior.
139+
140+
.. code-block:: yaml
141+
142+
return_missing_reference: False
143+
144+
145+
Print summary of missed references
146+
----------------------------------
147+
148+
Instead of fail on first interpolation error it will print all missed references at once.
149+
Also will print warnings if reference is used before it is defined in class hierarchy.
150+
151+
.. code-block:: yaml
152+
reclass --nodeinfo mynode
153+
[WARNING] Reference '_param:kkk' undefined. Possibly used too early and defined later in class hierarchy.
154+
[WARNING] Reference '_param:yyy' undefined. Possibly used too early and defined later in class hierarchy.
155+
[WARNING] Reference '_param:kkk' undefined. Possibly used too early and defined later in class hierarchy.
156+
[WARNING] Reference '_param:kkk' undefined. Possibly used too early and defined later in class hierarchy.
157+
None
158+
159+
-> dontpanic
160+
Cannot resolve ${_param:kkk}, in mykey1:path:to:fail
161+
Cannot resolve ${_param:kkk}, in mykey2:another:path:to:fail
162+
126163
127164
Inventory Queries
128165
-----------------

reclass/core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,13 @@ def _nodeinfo(self, nodename, inventory):
187187
node.initialise_interpolation()
188188
if node.parameters.has_inv_query() and inventory is None:
189189
inventory = self._get_inventory(node.parameters.needs_all_envs(), node.environment, node.parameters.get_inv_queries())
190+
191+
return_missing_reference = self._settings.return_missing_reference
192+
self._settings.return_missing_reference = True
190193
node.interpolate(inventory)
194+
self._settings.return_missing_reference = return_missing_reference
195+
if not return_missing_reference:
196+
node.check_failed_interpolation()
191197
return node
192198
except InterpolationError as e:
193199
e.nodename = nodename

reclass/datatypes/entity.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from applications import Applications
1111
from exports import Exports
1212
from parameters import Parameters
13+
from reclass.errors import ResolveError, InterpolationError, ResolveErrorList
14+
from reclass.defaults import REFERENCE_SENTINELS, EXPORT_SENTINELS
1315

1416
class Entity(object):
1517
'''
@@ -99,6 +101,14 @@ def interpolate_exports(self):
99101
def interpolate_single_export(self, references):
100102
self._exports.interpolate_single_from_external(self._parameters, references)
101103

104+
def check_failed_interpolation(self):
105+
p = [ ResolveError(value[len(REFERENCE_SENTINELS[0]):-len(REFERENCE_SENTINELS[1])], uri=path) for path,value in self._parameters.check_failed_interpolation() ]
106+
if len(p) > 0:
107+
raise ResolveErrorList(p)
108+
e = [ ResolveError(value[len(REFERENCE_SENTINELS[0]):-len(REFERENCE_SENTINELS[1])], uri=path) for path,value in self._exports.check_failed_interpolation() ]
109+
if len(e) > 0:
110+
raise ResolveErrorList(e)
111+
102112
def __eq__(self, other):
103113
return isinstance(other, type(self)) \
104114
and self._applications == other._applications \

reclass/datatypes/parameters.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import copy
1111
import sys
1212
import types
13+
import re
1314
from collections import namedtuple
1415
from reclass.utils.dictpath import DictPath
1516
from reclass.values.value import Value
1617
from reclass.values.valuelist import ValueList
1718
from reclass.errors import InfiniteRecursionError, ResolveError, InterpolationError, ParseError, BadReferencesError
19+
from reclass.defaults import REFERENCE_SENTINELS, EXPORT_SENTINELS, REFERENCE_PATTERN
1820

1921
class Parameters(object):
2022
'''
@@ -47,6 +49,8 @@ def __init__(self, mapping, settings, uri):
4749
self._unrendered = None
4850
self._escapes_handled = {}
4951
self._inv_queries = []
52+
self._ref_missed = []
53+
self._ref_pattern = '.*'+ re.escape(REFERENCE_SENTINELS[0]) + REFERENCE_PATTERN + re.escape(REFERENCE_SENTINELS[1])
5054
self._needs_all_envs = False
5155
self._keep_overrides = False
5256
if mapping is not None:
@@ -282,6 +286,8 @@ def _interpolate_render_value(self, path, value, inventory):
282286
self._render_simple_dict(new, path)
283287
elif isinstance(new, list):
284288
self._render_simple_list(new, path)
289+
if re.match(self._ref_pattern, str(new)):
290+
self._ref_missed.append(path)
285291
return new
286292

287293
def _interpolate_references(self, path, value, inventory):
@@ -318,3 +324,12 @@ def _interpolate_references(self, path, value, inventory):
318324
value.assembleRefs(self._base)
319325
if old == len(value.get_references()):
320326
raise BadReferencesError(value.get_references(), str(path), value.uri())
327+
328+
def check_failed_interpolation(self):
329+
missing = []
330+
for p in self._ref_missed:
331+
v = str(p.get_value(self._base))
332+
if re.match(self._ref_pattern, v):
333+
missing.append((str(p), v))
334+
return missing
335+

reclass/defaults.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
OPT_IGNORE_CLASS_NOTFOUND_REGEXP = ['.*']
2222
OPT_IGNORE_CLASS_NOTFOUND_WARNING = True
2323

24+
OPT_RETURN_MISSING_REFERENCE = False
25+
2426
OPT_ALLOW_SCALAR_OVER_DICT = False
2527
OPT_ALLOW_SCALAR_OVER_LIST = False
2628
OPT_ALLOW_LIST_OVER_SCALAR = False
@@ -41,6 +43,7 @@
4143
PARAMETER_INTERPOLATION_DELIMITER = ':'
4244
PARAMETER_DICT_KEY_OVERRIDE_PREFIX = '~'
4345
ESCAPE_CHARACTER = '\\'
46+
REFERENCE_PATTERN = '[-_{}.\w]+'.format(PARAMETER_INTERPOLATION_DELIMITER)
4447

4548
AUTOMATIC_RECLASS_PARAMETERS = True
4649
DEFAULT_ENVIRONMENT = 'base'

reclass/errors.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,28 @@ def __init__(self, reference, uri=None, context=None):
178178
super(ResolveError, self).__init__(msg=None)
179179
self.reference = reference
180180

181+
if uri:
182+
self.uri = uri
183+
184+
if context:
185+
self.context = context
186+
181187
def _get_error_message(self):
182188
msg = 'Cannot resolve {0}'.format(self.reference.join(REFERENCE_SENTINELS)) + self._add_context_and_uri()
183189
return [ msg ]
184190

191+
class ResolveErrorList(InterpolationError):
192+
193+
def __init__(self, resolve_errors=[]):
194+
super(ResolveErrorList, self).__init__(msg=None)
195+
self.resolve_errors = resolve_errors
196+
197+
def _get_error_message(self):
198+
msgs = []
199+
for e in self.resolve_errors:
200+
msgs.append('Cannot resolve {0}'.format(e.reference.join(REFERENCE_SENTINELS)) + e._add_context_and_uri())
201+
return msgs
202+
185203

186204
class InvQueryError(InterpolationError):
187205

reclass/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ def __init__(self, options={}):
2626

2727
self.ignore_class_notfound_warning = options.get('ignore_class_notfound_warning', OPT_IGNORE_CLASS_NOTFOUND_WARNING)
2828

29+
# passing missing reference is a feature (for example in saltclass),
30+
# allow additional processing on next ext_pillar.
31+
self.return_missing_reference = options.get('return_missing_reference', OPT_RETURN_MISSING_REFERENCE)
32+
2933

3034
self.ref_parser = reclass.values.parser_funcs.get_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
3135
self.simple_ref_parser = reclass.values.parser_funcs.get_simple_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)

reclass/values/refitem.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from reclass.settings import Settings
99
from reclass.utils.dictpath import DictPath
1010
from reclass.errors import ResolveError
11+
from reclass.defaults import REFERENCE_SENTINELS, EXPORT_SENTINELS
12+
import sys
1113

1214
class RefItem(Item):
1315

@@ -52,7 +54,11 @@ def _resolve(self, ref, context):
5254
try:
5355
return path.get_value(context)
5456
except (KeyError, TypeError) as e:
55-
raise ResolveError(ref)
57+
print >>sys.stderr, "[WARNING] Reference '%s' undefined. Possibly used too early and defined later in class hierarchy." % (ref)
58+
if self._settings.return_missing_reference:
59+
return "%s" % ref.join(REFERENCE_SENTINELS)
60+
else:
61+
raise ResolveError(ref)
5662

5763
def render(self, context, inventory):
5864
if len(self._items) == 1:

0 commit comments

Comments
 (0)