|
1 | 1 | from collections import OrderedDict
|
2 | 2 | from copy import deepcopy
|
3 | 3 | import json
|
| 4 | +import sys |
4 | 5 |
|
5 | 6 | from openshift.dynamic.exceptions import NotFoundError, ApplyException
|
6 | 7 |
|
|
49 | 50 | )
|
50 | 51 |
|
51 | 52 |
|
52 |
| -def apply_object(resource, definition): |
53 |
| - desired_annotation = dict( |
| 53 | +if sys.version_info.major >= 3: |
| 54 | + json_loads_byteified = json.loads |
| 55 | +else: |
| 56 | + # https://stackoverflow.com/a/33571117 |
| 57 | + def json_loads_byteified(json_text): |
| 58 | + return _byteify( |
| 59 | + json.loads(json_text, object_hook=_byteify), |
| 60 | + ignore_dicts=True |
| 61 | + ) |
| 62 | + |
| 63 | + def _byteify(data, ignore_dicts = False): |
| 64 | + # if this is a unicode string, return its string representation |
| 65 | + if isinstance(data, unicode): # noqa: F821 |
| 66 | + return data.encode('utf-8') |
| 67 | + # if this is a list of values, return list of byteified values |
| 68 | + if isinstance(data, list): |
| 69 | + return [ _byteify(item, ignore_dicts=True) for item in data ] |
| 70 | + # if this is a dictionary, return dictionary of byteified keys and values |
| 71 | + # but only if we haven't already byteified it |
| 72 | + if isinstance(data, dict) and not ignore_dicts: |
| 73 | + return { |
| 74 | + _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) |
| 75 | + for key, value in data.items() |
| 76 | + } |
| 77 | + # if it's anything else, return it in its original form |
| 78 | + return data |
| 79 | + |
| 80 | + |
| 81 | +def annotate(desired): |
| 82 | + return dict( |
54 | 83 | metadata=dict(
|
55 | 84 | annotations={
|
56 |
| - LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(definition, separators=(',', ':'), indent=None) |
| 85 | + LAST_APPLIED_CONFIG_ANNOTATION: json.dumps(desired, separators=(',', ':'), indent=None, sort_keys=True) |
57 | 86 | }
|
58 | 87 | )
|
59 | 88 | )
|
60 |
| - try: |
61 |
| - actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace')) |
62 |
| - except NotFoundError: |
63 |
| - return None, dict_merge(definition, desired_annotation) |
64 |
| - last_applied = actual.metadata.get('annotations',{}).get(LAST_APPLIED_CONFIG_ANNOTATION) |
| 89 | + |
| 90 | + |
| 91 | +def apply_patch(actual, desired): |
| 92 | + last_applied = actual['metadata'].get('annotations',{}).get(LAST_APPLIED_CONFIG_ANNOTATION) |
65 | 93 |
|
66 | 94 | if last_applied:
|
67 |
| - last_applied = json.loads(last_applied) |
68 |
| - actual_dict = actual.to_dict() |
69 |
| - del actual_dict['metadata']['annotations'][LAST_APPLIED_CONFIG_ANNOTATION] |
70 |
| - patch = merge(last_applied, definition, actual_dict) |
| 95 | + # ensure that last_applied doesn't come back as a dict of unicode key/value pairs |
| 96 | + # json.loads can be used if we stop supporting python 2 |
| 97 | + last_applied = json_loads_byteified(last_applied) |
| 98 | + patch = merge(dict_merge(last_applied, annotate(last_applied)), |
| 99 | + dict_merge(desired, annotate(desired)), actual) |
71 | 100 | if patch:
|
72 |
| - return actual.to_dict(), dict_merge(patch, desired_annotation) |
| 101 | + return actual, patch |
73 | 102 | else:
|
74 |
| - return actual.to_dict(), actual.to_dict() |
| 103 | + return actual, actual |
75 | 104 | else:
|
76 |
| - return actual.to_dict(), dict_merge(definition, desired_annotation) |
| 105 | + return actual, dict_merge(desired, annotate(desired)) |
| 106 | + |
| 107 | + |
| 108 | +def apply_object(resource, definition): |
| 109 | + try: |
| 110 | + actual = resource.get(name=definition['metadata']['name'], namespace=definition['metadata'].get('namespace')) |
| 111 | + except NotFoundError: |
| 112 | + return None, dict_merge(definition, annotate(definition)) |
| 113 | + return apply_patch(actual.to_dict(), definition) |
77 | 114 |
|
78 | 115 |
|
79 | 116 | def apply(resource, definition):
|
|
0 commit comments