Skip to content

Commit 306e7a2

Browse files
committed
Add a Kubernetes specific recursive dict diff
Understands strategic patch keys and can therefore better diff list elements
1 parent 93d66c2 commit 306e7a2

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

openshift/dynamic/apply.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,60 @@ 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+
for key in dict1.keys() - dict2.keys():
193+
result[0].append(dict1[key])
194+
for key in dict2.keys() - dict1.keys():
195+
result[1].append(dict2[key])
196+
for key in dict1.keys() & dict2.keys():
197+
diff = recursive_diff(dict1[key], dict2[key], position)
198+
if diff:
199+
# reinsert patch merge key to relate changes in other keys to
200+
# a specific list element
201+
diff[0].update({patch_merge_key: dict1[key][patch_merge_key]})
202+
diff[1].update({patch_merge_key: dict2[key][patch_merge_key]})
203+
result[0].append(diff[0])
204+
result[1].append(diff[1])
205+
if result[0] or result[1]:
206+
return result
207+
elif list1 != list2:
208+
return (list1, list2)
209+
return None
210+
211+
212+
def recursive_diff(dict1, dict2, position=None):
213+
if not position:
214+
if 'kind' in dict1 and dict1.get('kind') == dict2.get('kind'):
215+
position = dict1['kind']
216+
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
217+
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
218+
for k in (set(dict1.keys()) & set(dict2.keys())):
219+
if position:
220+
this_position = "%s.%s" % (position, k)
221+
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
222+
result = recursive_diff(dict1[k], dict2[k], this_position)
223+
if result:
224+
left[k] = result[0]
225+
right[k] = result[1]
226+
elif isinstance(dict1[k], list) and isinstance(dict2[k], list):
227+
result = recursive_list_diff(dict1[k], dict2[k], this_position)
228+
if result:
229+
left[k] = result[0]
230+
right[k] = result[1]
231+
elif dict1[k] != dict2[k]:
232+
left[k] = dict1[k]
233+
right[k] = dict2[k]
234+
if left or right:
235+
return left, right
236+
else:
237+
return None
238+
239+
186240
def get_deletions(last_applied, desired):
187241
patch = {}
188242
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)