Skip to content

Commit 61c81f9

Browse files
authored
Recursive diff (#366)
* Fix strategic patch merge of env variables The key is `env`, not `envVars` * Add a Kubernetes specific recursive dict diff Understands strategic patch keys and can therefore better diff list elements * Remove unused dictdiffer dependency
1 parent d5404e7 commit 61c81f9

File tree

5 files changed

+129
-8
lines changed

5 files changed

+129
-8
lines changed

openshift/dynamic/apply.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
'imagePullSecrets': 'name',
1616
'containers.volumeMounts': 'mountPath',
1717
'containers.volumeDevices': 'devicePath',
18-
'containers.envVars': 'name',
18+
'containers.env': 'name',
1919
'containers.ports': 'containerPort',
2020
'initContainers.volumeMounts': 'mountPath',
2121
'initContainers.volumeDevices': 'devicePath',
22-
'initContainers.envVars': 'name',
22+
'initContainers.env': 'name',
2323
'initContainers.ports': 'containerPort',
2424
'ephemeralContainers.volumeMounts': 'mountPath',
2525
'ephemeralContainers.volumeDevices': 'devicePath',
26-
'ephemeralContainers.envVars': 'name',
26+
'ephemeralContainers.env': 'name',
2727
'ephemeralContainers.ports': 'containerPort',
2828
}
2929

@@ -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():

python-openshift.spec

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ BuildRequires: python-setuptools
3535
BuildRequires: git
3636

3737
Requires: python2
38-
Requires: python2-dictdiffer
3938
Requires: python2-kubernetes
4039
Requires: python2-string_utils
4140
Requires: python-requests
@@ -55,7 +54,6 @@ BuildRequires: %{py3}-setuptools
5554
BuildRequires: git
5655

5756
Requires: %{py3}
58-
Requires: %{py3}-dictdiffer
5957
Requires: %{py3}-kubernetes
6058
Requires: %{py3}-string_utils
6159
Requires: %{py3}-requests
@@ -92,7 +90,7 @@ Python client for the OpenShift API
9290
#the requirements are also done in an non-backwards compatible way
9391
%if 0%{?rhel}
9492
sed -i -e "s/find_packages(include='openshift.*')/['openshift', 'openshift.dynamic', 'openshift.helper']/g" setup.py
95-
sed -i -e '30s/^/REQUIRES = [\n "dictdiffer",\n "jinja2",\n "kubernetes",\n "setuptools",\n "six",\n "ruamel.yaml",\n "python-string-utils",\n]\n/g' setup.py
93+
sed -i -e '30s/^/REQUIRES = [\n "jinja2",\n "kubernetes",\n "setuptools",\n "six",\n "ruamel.yaml",\n "python-string-utils",\n]\n/g' setup.py
9694
sed -i -e "s/extract_requirements('requirements.txt')/REQUIRES/g" setup.py
9795
#sed -i -e '14,21d' setup.py
9896
%endif

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
dictdiffer
21
jinja2
32
kubernetes
43
python-string-utils

test-requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ pytest
55
pytest-bdd
66
pytest-cov
77
PyYAML
8-
dictdiffer

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)