Skip to content

Commit 9999c1a

Browse files
willthamesfabianvf
authored andcommitted
Provide apply_object method to help check mode (#315)
* Provide apply_object method to help check mode Split existing apply method into one method that finds the changes and one method that does the changes * Add test case * Fix get_deletions when data is of mixed string type Somehow the definition argument passed to apply_object can be mostly str type, whereas the existing resource gets converted to mostly unicode type. I've only seen this happen with ansible's check mode! * Improve code paths through get_deletions Safeguard the case where `last_applied_value` is a `dict` and `desired_value` is not a `dict` This allows us to remove another code path when `desired_value` is `None`
1 parent 1e232e7 commit 9999c1a

File tree

2 files changed

+46
-17
lines changed

2 files changed

+46
-17
lines changed

openshift/dynamic/apply.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
LAST_APPLIED_CONFIG_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration'
77

8-
def apply(resource, definition):
8+
def apply_object(resource, definition):
99
desired_annotation = dict(
1010
metadata=dict(
1111
annotations={
@@ -16,7 +16,7 @@ def apply(resource, definition):
1616
try:
1717
actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
1818
except NotFoundError:
19-
return resource.create(body=dict_merge(definition, desired_annotation), namespace=definition['metadata'].get('namespace'))
19+
return None, dict_merge(definition, desired_annotation)
2020
last_applied = actual.metadata.get('annotations',{}).get(LAST_APPLIED_CONFIG_ANNOTATION)
2121

2222
if last_applied:
@@ -25,18 +25,23 @@ def apply(resource, definition):
2525
del actual_dict['metadata']['annotations'][LAST_APPLIED_CONFIG_ANNOTATION]
2626
patch = merge(last_applied, definition, actual_dict)
2727
if patch:
28-
return resource.patch(body=dict_merge(patch, desired_annotation),
29-
name=definition['metadata']['name'],
30-
namespace=definition['metadata'].get('namespace'),
31-
content_type='application/merge-patch+json')
28+
return actual.to_dict(), dict_merge(patch, desired_annotation)
3229
else:
33-
return actual
30+
return actual.to_dict(), actual.to_dict()
3431
else:
35-
return resource.patch(
36-
body=definition,
37-
name=definition['metadata']['name'],
38-
namespace=definition['metadata'].get('namespace'),
39-
content_type='application/merge-patch+json')
32+
return actual.to_dict(), dict_merge(definition, desired_annotation)
33+
34+
35+
def apply(resource, definition):
36+
existing, desired = apply_object(resource, definition)
37+
if not existing:
38+
return resource.create(body=desired, namespace=definition['metadata'].get('namespace'))
39+
if existing == desired:
40+
return resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace'))
41+
return resource.patch(body=desired,
42+
name=definition['metadata']['name'],
43+
namespace=definition['metadata'].get('namespace'),
44+
content_type='application/merge-patch+json')
4045

4146

4247
# The patch is the difference from actual to desired without deletions, plus deletions
@@ -69,11 +74,7 @@ def get_deletions(last_applied, desired):
6974
patch = {}
7075
for k, last_applied_value in last_applied.items():
7176
desired_value = desired.get(k)
72-
if desired_value is None:
73-
patch[k] = None
74-
elif type(last_applied_value) != type(desired_value):
75-
patch[k] = desired_value
76-
elif isinstance(last_applied_value, dict):
77+
if isinstance(last_applied_value, dict) and isinstance(desired_value, dict):
7778
p = get_deletions(last_applied_value, desired_value)
7879
if p:
7980
patch[k] = p

test/unit/test_apply.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@
7979
),
8080
expected = dict(spec=dict(ports=[dict(port=8081, name="http")]))
8181
),
82+
# This last one is based on a real world case where definition was mostly
83+
# str type and everything else was mostly unicode type (don't ask me how)
84+
dict(
85+
last_applied = {
86+
u'kind': u'ConfigMap',
87+
u'data': {u'one': '1', 'three': '3', 'two': '2'},
88+
u'apiVersion': u'v1',
89+
u'metadata': {u'namespace': u'apply', u'name': u'apply-configmap'}
90+
},
91+
actual = {
92+
u'kind': u'ConfigMap',
93+
u'data': {u'one': '1', 'three': '3', 'two': '2'},
94+
u'apiVersion': u'v1',
95+
u'metadata': {u'namespace': u'apply', u'name': u'apply-configmap',
96+
u'resourceVersion': '1714994',
97+
u'creationTimestamp': u'2019-08-17T05:08:05Z', u'annotations': {},
98+
u'selfLink': u'/api/v1/namespaces/apply/configmaps/apply-configmap',
99+
u'uid': u'fed45fb0-c0ac-11e9-9d95-025000000001'}
100+
},
101+
desired = {
102+
'kind': u'ConfigMap',
103+
'data': {'one': '1', 'three': '3', 'two': '2'},
104+
'apiVersion': 'v1',
105+
'metadata': {'namespace': 'apply', 'name': 'apply-configmap'}
106+
},
107+
expected = dict()
108+
),
109+
82110
]
83111

84112

0 commit comments

Comments
 (0)