Skip to content

Commit 809d785

Browse files
committed
Cleanup strategies from merged pillars
If a merge strategy (__: something) had been present in a pillar that didn't need merging at all, it wasn't removed. This resulted in the `__` key being present in the final pillar. This change deeply scrubs merging strategy keys from pillar data. Due to this scrubbing, which essentially rebuilds all nested dicts and lists on merge, the previous deepcopy isn't needed anymore.
1 parent b5dcd62 commit 809d785

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88
### Added
99
- Add require option to tower.get
1010
- Experimental: Support custom context in template injected `render` function
11+
- Deep scrubbing of merge strategies (`__`) from merged pillar data
1112

1213
## [1.5.2] - 2021-02-10
1314
### Fixed

salt_tower/pillar/tower.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def _compile(
262262
ctx["pillar"] = self
263263
ctx["tower"] = self
264264

265-
def render(path, context=None, renderer='text'):
265+
def render(path, context=None, renderer="text"):
266266
if isinstance(context, dict):
267267
context = {**ctx, **context}
268268
else:
@@ -318,15 +318,27 @@ def get_field(self, key, args, kwargs):
318318
def _merge(tgt, *objects, strategy="merge-last"):
319319
for obj in objects:
320320
if isinstance(tgt, dict):
321-
tgt = _merge_dict(tgt, copy.deepcopy(obj), strategy)
321+
tgt = _merge_dict(tgt, obj, strategy)
322322
elif isinstance(tgt, list):
323-
tgt = _merge_list(tgt, copy.deepcopy(obj), strategy)
323+
tgt = _merge_list(tgt, obj, strategy)
324324
else:
325325
raise TypeError(f"Cannot merge {type(tgt)}")
326326

327327
return tgt
328328

329329

330+
def _merge_clean(obj):
331+
if isinstance(obj, dict):
332+
return {k: _merge_clean(v) for k, v in obj.items() if k != "__"}
333+
334+
if isinstance(obj, list):
335+
if obj and isinstance(obj[0], dict) and len(obj[0]) == 1 and "__" in obj[0]:
336+
obj = obj[1:]
337+
return [_merge_clean(v) for v in obj]
338+
339+
return obj
340+
341+
330342
def _merge_dict(tgt, obj, strategy="merge-last"):
331343
if not isinstance(obj, dict):
332344
raise TypeError(f"Cannot merge non-dict type, but is {type(obj)}")
@@ -346,7 +358,7 @@ def _merge_dict(tgt, obj, strategy="merge-last"):
346358
elif key in tgt and isinstance(tgt[key], list) and isinstance(val, list):
347359
_merge_list(tgt[key], val, strategy)
348360
else:
349-
tgt[key] = val
361+
tgt[key] = _merge_clean(val)
350362

351363
elif strategy == "merge-first":
352364
for key, val in obj.items():
@@ -355,11 +367,11 @@ def _merge_dict(tgt, obj, strategy="merge-last"):
355367
elif key in tgt and isinstance(tgt[key], list) and isinstance(val, list):
356368
_merge_list(tgt[key], val, strategy)
357369
elif key not in tgt:
358-
tgt[key] = val
370+
tgt[key] = _merge_clean(val)
359371

360372
elif strategy == "overwrite":
361373
tgt.clear()
362-
tgt.update(obj)
374+
tgt.update(_merge_clean(obj))
363375

364376
else:
365377
raise ValueError(f"Unknown strategy: {strategy}")
@@ -380,15 +392,15 @@ def _merge_list(tgt, lst, strategy="merge-last"):
380392
tgt.remove(val)
381393

382394
elif strategy == "merge-last":
383-
tgt.extend(lst)
395+
tgt.extend(_merge_clean(lst))
384396

385397
elif strategy == "merge-first":
386-
for val in lst:
398+
for val in _merge_clean(lst):
387399
tgt.insert(0, val)
388400

389401
elif strategy == "overwrite":
390402
del tgt[:]
391-
tgt.extend(lst)
403+
tgt.extend(_merge_clean(lst))
392404

393405
else:
394406
raise ValueError(f"Unknown strategy: {strategy}")

test/pillar/test_tower_tower.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,24 @@ def test_merge_list_strategy_merge_overwrite(tower):
124124
assert tgt == ['c']
125125

126126

127+
def test_merge_list_remove_nested_doubledash(tower):
128+
tgt = {'a': 0}
129+
mod = {'a': [{'__': 'overwrite'}, 'a', 'b']}
130+
131+
tower.merge(tgt, mod)
132+
133+
assert tgt == {'a': ['a', 'b']}
134+
135+
136+
def test_merge_list_remove_nested_doubledash_list(tower):
137+
tgt = [0]
138+
mod = [[{'__': 'overwrite'}, 1]]
139+
140+
tower.merge(tgt, mod)
141+
142+
assert tgt == [0, [1]]
143+
144+
127145
def test_merge_dict_strategy_remove(tower):
128146
tgt = {'a': 0, 'b': 1}
129147
mod = {'__': 'remove', 'a': None}
@@ -160,6 +178,15 @@ def test_merge_dict_strategy_merge_overwrite(tower):
160178
assert tgt == {'c': 2}
161179

162180

181+
def test_merge_dict_remove_nested_doubledash(tower):
182+
tgt = {'a': 0}
183+
mod = {'a': {'__': 'overwrite', 'key': 1}}
184+
185+
tower.merge(tgt, mod)
186+
187+
assert tgt == {'a': {'key': 1}}
188+
189+
163190
def test_format(tower):
164191
tower.update({'app': {'name': 'MyApp'}})
165192

0 commit comments

Comments
 (0)