blob: 5c3ea928428fdcc202adfbffdefb68e31917c25e [file] [log] [blame]
Dennis Dmitriev566db4b2017-07-18 18:13:07 +03001# Copyright 2013 - 2017 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +030015import json
Dennis Dmitriev566db4b2017-07-18 18:13:07 +030016import os
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +030017import yaml
18
Dennis Dmitrievde847d92017-06-26 18:58:05 +030019
20def get_nested_key(data, path=None):
21 if type(path) is not list:
22 raise("Use 'list' object with key names for 'path'")
23 for key in path:
24 value = data.get(key, None)
Dennis Dmitriev686c2602018-03-08 17:17:35 +020025 if value is not None:
Dennis Dmitrievde847d92017-06-26 18:58:05 +030026 data = value
27 else:
28 return None
29 return data
30
31
Dennis Dmitriev30dfb892017-06-29 20:58:11 +030032def create_nested_key(data, path=None, value=None):
33 if type(data) is not dict:
34 raise("Use 'dict' object for 'data'")
35 if type(path) is not list:
36 raise("Use 'list' object with key names for 'path'")
37 for key in path[:-1]:
38 if key not in data:
39 data[key] = {}
40 data = data[key]
41 data[path[-1]] = value
42
43
Dennis Dmitrievde847d92017-06-26 18:58:05 +030044def remove_nested_key(data, path=None):
45 if type(path) is not list:
46 raise("Use 'list' object with key names for 'path'")
47
48 # Remove the value from the specified key
49 val = get_nested_key(data, path[:-1])
50 val[path[-1]] = None
51
52 # Clear parent keys if empty
53 while path:
54 val = get_nested_key(data, path)
Dennis Dmitriev686c2602018-03-08 17:17:35 +020055 if val is not None:
Dennis Dmitrievde847d92017-06-26 18:58:05 +030056 # Non-empty value, nothing to do
57 return
58 else:
59 get_nested_key(data, path[:-1]).pop(path[-1])
60 path = path[:-1]
61
62
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +030063def yaml_read(yaml_file):
64 if os.path.isfile(yaml_file):
65 with open(yaml_file, 'r') as f:
66 return yaml.load(f)
67 else:
68 print("\'{}\' is not a file!".format(yaml_file))
69
70
71def json_read(yaml_file):
72 if os.path.isfile(yaml_file):
73 with open(yaml_file, 'r') as f:
74 return json.load(f)
75 else:
76 print("\'{}\' is not a file!".format(yaml_file))
77
78
79def merge_nested_objects(obj_1, obj_2):
80 """Merge two objects with optional key overwrites
81
82 Original : https://stackoverflow.com/a/17860173
83 - Merges dicts and lists
84 - If a dict key has the suffix '__overwrite__' and boolean value,
85 then the key is assumed as a special keyword for merging:
Dennis Dmitriev566db4b2017-07-18 18:13:07 +030086 <key>__overwrite__: True # Overwrite the existing <key> content
87 # with <key> from obj_2
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +030088 <key>__overwrite__: False # Keep the existing <key> content from obj_1
89
90
91 Case #1: Merge dicts and lists, overwrite other types with latest value
92
93 dict_a = {
94 'host': '1.1.1.1',
95 'ssh': {
96 'login': 'user'
97 }
98 }
99
100 dict_b = {
101 'host': '2.2.2.2',
102 'ssh': {
103 'password': 'pass'
104 }
105 }
106
107 print(merge_nested_objects(dict_a, dict_b))
108 {
109 'host': '2.2.2.2',
110 'ssh': {
111 'login': 'user',
112 'password': 'pass',
113 }
114 }
115
116 Case #2: Use <key>__overwrite__: True to remove previous key content
117
118 dict_a = {
119 'host': '1.1.1.1'
120 'ssh': {
121 'login': 'user'
122 }
123 }
124
125 dict_b = {
126 'ssh__overwrite__': True
127 'ssh': {
128 'password': 'pass'
129 }
130 }
131
132 print(merge_nested_objects(dict_a, dict_b))
133 {
134 'host': '1.1.1.1',
135 'ssh': {
136 'password': 'pass',
137 }
138 }
139
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300140 Case #3: Use <key>__overwrite__: False to skip merging key
141 if already exists
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +0300142
143 dict_a = {
144 'host': '1.1.1.1'
145 'ssh': {
146 'login': 'user'
147 }
148 }
149
150 dict_b = {
151 'host__overwrite__': False
152 'host': '2.2.2.2'
153 'ssh': {
154 'login__overwrite__': False
155 'login': 'new_user'
156 'password': 'pass'
157 }
158 }
159
160 print(merge_nested_objects(dict_a, dict_b))
161 {
162 'host': '1.1.1.1',
163 'ssh': {
164 'login': 'user',
165 'password': 'pass'
166 }
167 }
168
169
170 """
171 # Merge two dicts
172 if isinstance(obj_1, dict) and isinstance(obj_2, dict):
173 result = {}
174 for key, value in obj_1.iteritems():
175 if key not in obj_2:
176 result[key] = value
177 else:
178 overwrite_key = key + '__overwrite__'
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300179 if overwrite_key in obj_2 and obj_2[overwrite_key] is True:
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +0300180 result[key] = obj_2[key]
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300181 elif overwrite_key in obj_2 and obj_2[overwrite_key] is False:
Dennis Dmitriev65a80ee2017-06-30 17:30:37 +0300182 result[key] = value
183 else:
184 result[key] = merge_nested_objects(value, obj_2[key])
185 for key, value in obj_2.iteritems():
186 if key not in obj_1:
187 result[key] = value
188 return result
189
190 # Add two lists
191 if isinstance(obj_1, list) and isinstance(obj_2, list):
192 return obj_1 + obj_2
193
194 # Overwrite a value with new one
195 return obj_2