1""" 2Utilities for comparing and updating configurations while keeping track of 3changes in a way that can be easily reported in a state. 4""" 5 6 7def compare_and_update_config(config, update_config, changes, namespace=""): 8 """ 9 Recursively compare two configs, writing any needed changes to the 10 update_config and capturing changes in the changes dict. 11 """ 12 if isinstance(config, dict): 13 if not update_config: 14 if config: 15 # the updated config is more valid--report that we are using it 16 changes[namespace] = { 17 "new": config, 18 "old": update_config, 19 } 20 return config 21 elif not isinstance(update_config, dict): 22 # new config is a dict, other isn't--new one wins 23 changes[namespace] = { 24 "new": config, 25 "old": update_config, 26 } 27 return config 28 else: 29 # compare each key in the base config with the values in the 30 # update_config, overwriting the values that are different but 31 # keeping any that are not defined in config 32 for key, value in config.items(): 33 _namespace = key 34 if namespace: 35 _namespace = "{}.{}".format(namespace, _namespace) 36 update_config[key] = compare_and_update_config( 37 value, 38 update_config.get(key, None), 39 changes, 40 namespace=_namespace, 41 ) 42 return update_config 43 44 elif isinstance(config, list): 45 if not update_config: 46 if config: 47 # the updated config is more valid--report that we are using it 48 changes[namespace] = { 49 "new": config, 50 "old": update_config, 51 } 52 return config 53 elif not isinstance(update_config, list): 54 # new config is a list, other isn't--new one wins 55 changes[namespace] = { 56 "new": config, 57 "old": update_config, 58 } 59 return config 60 else: 61 # iterate through config list, ensuring that each index in the 62 # update_config list is the same 63 for idx, item in enumerate(config): 64 _namespace = "[{}]".format(idx) 65 if namespace: 66 _namespace = "{}{}".format(namespace, _namespace) 67 _update = None 68 if len(update_config) > idx: 69 _update = update_config[idx] 70 if _update: 71 update_config[idx] = compare_and_update_config( 72 config[idx], 73 _update, 74 changes, 75 namespace=_namespace, 76 ) 77 else: 78 changes[_namespace] = { 79 "new": config[idx], 80 "old": _update, 81 } 82 update_config.append(config[idx]) 83 84 if len(update_config) > len(config): 85 # trim any items in update_config that are not in config 86 for idx, old_item in enumerate(update_config): 87 if idx < len(config): 88 continue 89 _namespace = "[{}]".format(idx) 90 if namespace: 91 _namespace = "{}{}".format(namespace, _namespace) 92 changes[_namespace] = { 93 "new": None, 94 "old": old_item, 95 } 96 del update_config[len(config) :] 97 return update_config 98 99 else: 100 if config != update_config: 101 changes[namespace] = { 102 "new": config, 103 "old": update_config, 104 } 105 return config 106