Skip to content

Commit a032737

Browse files
committed
Add a Kubernetes specific recursive dict diff
Understands strategic patch keys and can therefore better diff list elements
1 parent 76ebc93 commit a032737

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

openshift/dynamic/apply.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,62 @@ def list_merge(last_applied, actual, desired, position):
183183
return desired
184184

185185

186+
def recursive_list_diff(list1, list2, position=None):
187+
result = (list(), list())
188+
if position in STRATEGIC_MERGE_PATCH_KEYS:
189+
patch_merge_key = STRATEGIC_MERGE_PATCH_KEYS[position]
190+
dict1 = list_to_dict(list1, patch_merge_key, position)
191+
dict2 = list_to_dict(list2, patch_merge_key, position)
192+
dict1_keys = set(dict1.keys())
193+
dict2_keys = set(dict2.keys())
194+
for key in dict1_keys - dict2_keys:
195+
result[0].append(dict1[key])
196+
for key in dict2_keys - dict1_keys:
197+
result[1].append(dict2[key])
198+
for key in dict1_keys & dict2_keys:
199+
diff = recursive_diff(dict1[key], dict2[key], position)
200+
if diff:
201+
# reinsert patch merge key to relate changes in other keys to
202+
# a specific list element
203+
diff[0].update({patch_merge_key: dict1[key][patch_merge_key]})
204+
diff[1].update({patch_merge_key: dict2[key][patch_merge_key]})
205+
result[0].append(diff[0])
206+
result[1].append(diff[1])
207+
if result[0] or result[1]:
208+
return result
209+
elif list1 != list2:
210+
return (list1, list2)
211+
return None
212+
213+
214+
def recursive_diff(dict1, dict2, position=None):
215+
if not position:
216+
if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
217+
position = dict1['kind']
218+
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
219+
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
220+
for k in (set(dict1.keys()) & set(dict2.keys())):
221+
if position:
222+
this_position = "%s.%s" % (position, k)
223+
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
224+
result = recursive_diff(dict1[k], dict2[k], this_position)
225+
if result:
226+
left[k] = result[0]
227+
right[k] = result[1]
228+
elif isinstance(dict1[k], list) and isinstance(dict2[k], list):
229+
result = recursive_list_diff(dict1[k], dict2[k], this_position)
230+
if result:
231+
left[k] = result[0]
232+
right[k] = result[1]
233+
elif dict1[k] != dict2[k]:
234+
left[k] = dict1[k]
235+
right[k] = dict2[k]
236+
if left or right:
237+
return left, right
238+
else:
239+
return None
240+
241+
186242
def get_deletions(last_applied, desired):
187243
patch = {}
188244
for k, last_applied_value in last_applied.items():

test/unit/test_diff.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from openshift.dynamic.apply import recursive_diff
2+
3+
tests = [
4+
dict(
5+
before = dict(
6+
kind="Service",
7+
metadata=dict(name="foo"),
8+
spec=dict(ports=[dict(port=8080, name="http")])
9+
),
10+
after = dict(
11+
kind="Service",
12+
metadata=dict(name="foo"),
13+
spec=dict(ports=[dict(port=8080, name="http")])
14+
),
15+
expected = None
16+
),
17+
dict(
18+
before = dict(
19+
kind="Service",
20+
metadata=dict(name="foo"),
21+
spec=dict(ports=[dict(port=8080, name="http")])
22+
),
23+
after = dict(
24+
kind="Service",
25+
metadata=dict(name="foo"),
26+
spec=dict(ports=[dict(port=8081, name="http")])
27+
),
28+
expected = (
29+
dict(spec=dict(ports=[dict(port=8080, name="http")])),
30+
dict(spec=dict(ports=[dict(port=8081, name="http")]))
31+
)
32+
),
33+
dict(
34+
before = dict(
35+
kind="Service",
36+
metadata=dict(name="foo"),
37+
spec=dict(ports=[dict(port=8080, name="http"), dict(port=8081, name="https")])
38+
),
39+
after = dict(
40+
kind="Service",
41+
metadata=dict(name="foo"),
42+
spec=dict(ports=[dict(port=8081, name="https"), dict(port=8080, name="http")])
43+
),
44+
expected = None
45+
),
46+
47+
dict(
48+
before = dict(
49+
kind="Pod",
50+
metadata=dict(name="foo"),
51+
spec=dict(containers=[dict(name="busybox", image="busybox",
52+
env=[dict(name="hello", value="world"),
53+
dict(name="another", value="next")])])
54+
),
55+
after = dict(
56+
kind="Pod",
57+
metadata=dict(name="foo"),
58+
spec=dict(containers=[dict(name="busybox", image="busybox",
59+
env=[dict(name="hello", value="everyone")])])
60+
),
61+
expected=(dict(spec=dict(containers=[dict(name="busybox", env=[dict(name="another", value="next"), dict(name="hello", value="world")])])),
62+
dict(spec=dict(containers=[dict(name="busybox", env=[dict(name="hello", value="everyone")])])))
63+
),
64+
]
65+
66+
67+
def test_diff():
68+
for test in tests:
69+
assert(recursive_diff(test['before'], test['after']) == test['expected'])

0 commit comments

Comments
 (0)