mirror of https://github.com/ceph/ceph-ansible.git
Sync config_template from upstream
This change pulls in the most recent release of the config_template module into the ceph_ansible action plugins. Signed-off-by: Kevin Carter <kecarter@redhat.com>pull/4038/head
parent
4708b7615f
commit
789cef7621
|
@ -26,6 +26,7 @@ try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
|
@ -215,8 +216,8 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
|
||||||
comments.append('')
|
comments.append('')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line[0] in '#;':
|
if line.lstrip()[0] in '#;':
|
||||||
comments.append(line)
|
comments.append(line.lstrip())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
|
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
|
||||||
|
@ -297,6 +298,98 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
|
||||||
options[name] = _temp_item
|
options[name] = _temp_item
|
||||||
|
|
||||||
|
|
||||||
|
class DictCompare(object):
|
||||||
|
"""
|
||||||
|
Calculate the difference between two dictionaries.
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
>>> base_dict = {'test1': 'val1', 'test2': 'val2', 'test3': 'val3'}
|
||||||
|
>>> new_dict = {'test1': 'val2', 'test3': 'val3', 'test4': 'val3'}
|
||||||
|
>>> dc = DictCompare(base_dict, new_dict)
|
||||||
|
>>> dc.added()
|
||||||
|
... ['test4']
|
||||||
|
>>> dc.removed()
|
||||||
|
... ['test2']
|
||||||
|
>>> dc.changed()
|
||||||
|
... ['test1']
|
||||||
|
>>> dc.get_changes()
|
||||||
|
... {'added':
|
||||||
|
... {'test4': 'val3'},
|
||||||
|
... 'removed':
|
||||||
|
... {'test2': 'val2'},
|
||||||
|
... 'changed':
|
||||||
|
... {'test1': {'current_val': 'vol1', 'new_val': 'val2'}
|
||||||
|
... }
|
||||||
|
"""
|
||||||
|
def __init__(self, base_dict, new_dict):
|
||||||
|
self.new_dict, self.base_dict = new_dict, base_dict
|
||||||
|
self.base_items, self.new_items = set(
|
||||||
|
self.base_dict.keys()), set(self.new_dict.keys())
|
||||||
|
self.intersect = self.new_items.intersection(self.base_items)
|
||||||
|
|
||||||
|
def added(self):
|
||||||
|
return self.new_items - self.intersect
|
||||||
|
|
||||||
|
def removed(self):
|
||||||
|
return self.base_items - self.intersect
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
return set(
|
||||||
|
x for x in self.intersect if self.base_dict[x] != self.new_dict[x])
|
||||||
|
|
||||||
|
def get_changes(self):
|
||||||
|
"""Returns dict of differences between 2 dicts and bool indicating if
|
||||||
|
there are differences
|
||||||
|
|
||||||
|
:param base_dict: ``dict``
|
||||||
|
:param new_dict: ``dict``
|
||||||
|
:returns: ``dict``, ``bool``
|
||||||
|
"""
|
||||||
|
changed = False
|
||||||
|
mods = {'added': {}, 'removed': {}, 'changed': {}}
|
||||||
|
|
||||||
|
for s in self.changed():
|
||||||
|
changed = True
|
||||||
|
if type(self.base_dict[s]) is not dict:
|
||||||
|
mods['changed'] = {
|
||||||
|
s: {'current_val': self.base_dict[s],
|
||||||
|
'new_val': self.new_dict[s]}}
|
||||||
|
continue
|
||||||
|
|
||||||
|
diff = DictCompare(self.base_dict[s], self.new_dict[s])
|
||||||
|
for a in diff.added():
|
||||||
|
if s not in mods['added']:
|
||||||
|
mods['added'][s] = {a: self.new_dict[s][a]}
|
||||||
|
else:
|
||||||
|
mods['added'][s][a] = self.new_dict[s][a]
|
||||||
|
|
||||||
|
for r in diff.removed():
|
||||||
|
if s not in mods['removed']:
|
||||||
|
mods['removed'][s] = {r: self.base_dict[s][r]}
|
||||||
|
else:
|
||||||
|
mods['removed'][s][r] = self.base_dict[s][r]
|
||||||
|
|
||||||
|
for c in diff.changed():
|
||||||
|
if s not in mods['changed']:
|
||||||
|
mods['changed'][s] = {
|
||||||
|
c: {'current_val': self.base_dict[s][c],
|
||||||
|
'new_val': self.new_dict[s][c]}}
|
||||||
|
else:
|
||||||
|
mods['changed'][s][c] = {
|
||||||
|
'current_val': self.base_dict[s][c],
|
||||||
|
'new_val': self.new_dict[s][c]}
|
||||||
|
|
||||||
|
for s in self.added():
|
||||||
|
changed = True
|
||||||
|
mods['added'][s] = self.new_dict[s]
|
||||||
|
|
||||||
|
for s in self.removed():
|
||||||
|
changed = True
|
||||||
|
mods['removed'][s] = self.base_dict[s]
|
||||||
|
|
||||||
|
return mods, changed
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
class ActionModule(ActionBase):
|
||||||
TRANSFERS_FILES = True
|
TRANSFERS_FILES = True
|
||||||
|
|
||||||
|
@ -306,11 +399,12 @@ class ActionModule(ActionBase):
|
||||||
list_extend=True,
|
list_extend=True,
|
||||||
ignore_none_type=True,
|
ignore_none_type=True,
|
||||||
default_section='DEFAULT'):
|
default_section='DEFAULT'):
|
||||||
"""Returns string value from a modified config file.
|
"""Returns string value from a modified config file and dict of
|
||||||
|
merged config
|
||||||
|
|
||||||
:param config_overrides: ``dict``
|
:param config_overrides: ``dict``
|
||||||
:param resultant: ``str`` || ``unicode``
|
:param resultant: ``str`` || ``unicode``
|
||||||
:returns: ``str``
|
:returns: ``str``, ``dict``
|
||||||
"""
|
"""
|
||||||
# If there is an exception loading the RawConfigParser The config obj
|
# If there is an exception loading the RawConfigParser The config obj
|
||||||
# is loaded again without the extra option. This is being done to
|
# is loaded again without the extra option. This is being done to
|
||||||
|
@ -328,6 +422,7 @@ class ActionModule(ActionBase):
|
||||||
|
|
||||||
config_object = StringIO(resultant)
|
config_object = StringIO(resultant)
|
||||||
config.readfp(config_object)
|
config.readfp(config_object)
|
||||||
|
|
||||||
for section, items in config_overrides.items():
|
for section, items in config_overrides.items():
|
||||||
# If the items value is not a dictionary it is assumed that the
|
# If the items value is not a dictionary it is assumed that the
|
||||||
# value is a default item for this config type.
|
# value is a default item for this config type.
|
||||||
|
@ -361,10 +456,23 @@ class ActionModule(ActionBase):
|
||||||
else:
|
else:
|
||||||
config_object.close()
|
config_object.close()
|
||||||
|
|
||||||
|
config_dict_new = {}
|
||||||
|
config_defaults = config.defaults()
|
||||||
|
for s in config.sections():
|
||||||
|
config_dict_new[s] = {}
|
||||||
|
for k, v in config.items(s):
|
||||||
|
if k not in config_defaults or config_defaults[k] != v:
|
||||||
|
config_dict_new[s][k] = v
|
||||||
|
else:
|
||||||
|
if default_section in config_dict_new:
|
||||||
|
config_dict_new[default_section][k] = v
|
||||||
|
else:
|
||||||
|
config_dict_new[default_section] = {k: v}
|
||||||
|
|
||||||
resultant_stringio = StringIO()
|
resultant_stringio = StringIO()
|
||||||
try:
|
try:
|
||||||
config.write(resultant_stringio)
|
config.write(resultant_stringio)
|
||||||
return resultant_stringio.getvalue()
|
return resultant_stringio.getvalue(), config_dict_new
|
||||||
finally:
|
finally:
|
||||||
resultant_stringio.close()
|
resultant_stringio.close()
|
||||||
|
|
||||||
|
@ -391,27 +499,26 @@ class ActionModule(ActionBase):
|
||||||
list_extend=True,
|
list_extend=True,
|
||||||
ignore_none_type=True,
|
ignore_none_type=True,
|
||||||
default_section='DEFAULT'):
|
default_section='DEFAULT'):
|
||||||
"""Returns config json
|
"""Returns config json and dict of merged config
|
||||||
|
|
||||||
Its important to note that file ordering will not be preserved as the
|
Its important to note that file ordering will not be preserved as the
|
||||||
information within the json file will be sorted by keys.
|
information within the json file will be sorted by keys.
|
||||||
|
|
||||||
:param config_overrides: ``dict``
|
:param config_overrides: ``dict``
|
||||||
:param resultant: ``str`` || ``unicode``
|
:param resultant: ``str`` || ``unicode``
|
||||||
:returns: ``str``
|
:returns: ``str``, ``dict``
|
||||||
"""
|
"""
|
||||||
original_resultant = json.loads(resultant)
|
original_resultant = json.loads(resultant)
|
||||||
merged_resultant = self._merge_dict(
|
merged_resultant = self._merge_dict(
|
||||||
base_items=original_resultant,
|
base_items=original_resultant,
|
||||||
new_items=config_overrides,
|
new_items=config_overrides,
|
||||||
list_extend=list_extend,
|
list_extend=list_extend
|
||||||
default_section=default_section
|
|
||||||
)
|
)
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
merged_resultant,
|
merged_resultant,
|
||||||
indent=4,
|
indent=4,
|
||||||
sort_keys=True
|
sort_keys=True
|
||||||
)
|
), merged_resultant
|
||||||
|
|
||||||
def return_config_overrides_yaml(self,
|
def return_config_overrides_yaml(self,
|
||||||
config_overrides,
|
config_overrides,
|
||||||
|
@ -419,11 +526,11 @@ class ActionModule(ActionBase):
|
||||||
list_extend=True,
|
list_extend=True,
|
||||||
ignore_none_type=True,
|
ignore_none_type=True,
|
||||||
default_section='DEFAULT'):
|
default_section='DEFAULT'):
|
||||||
"""Return config yaml.
|
"""Return config yaml and dict of merged config
|
||||||
|
|
||||||
:param config_overrides: ``dict``
|
:param config_overrides: ``dict``
|
||||||
:param resultant: ``str`` || ``unicode``
|
:param resultant: ``str`` || ``unicode``
|
||||||
:returns: ``str``
|
:returns: ``str``, ``dict``
|
||||||
"""
|
"""
|
||||||
original_resultant = yaml.safe_load(resultant)
|
original_resultant = yaml.safe_load(resultant)
|
||||||
merged_resultant = self._merge_dict(
|
merged_resultant = self._merge_dict(
|
||||||
|
@ -436,7 +543,7 @@ class ActionModule(ActionBase):
|
||||||
Dumper=IDumper,
|
Dumper=IDumper,
|
||||||
default_flow_style=False,
|
default_flow_style=False,
|
||||||
width=1000,
|
width=1000,
|
||||||
)
|
), merged_resultant
|
||||||
|
|
||||||
def _merge_dict(self, base_items, new_items, list_extend=True):
|
def _merge_dict(self, base_items, new_items, list_extend=True):
|
||||||
"""Recursively merge new_items into base_items.
|
"""Recursively merge new_items into base_items.
|
||||||
|
@ -631,9 +738,9 @@ class ActionModule(ActionBase):
|
||||||
self._templar._available_variables
|
self._templar._available_variables
|
||||||
)
|
)
|
||||||
|
|
||||||
if _vars['config_overrides']:
|
config_dict_base = {}
|
||||||
type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
|
type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
|
||||||
resultant = type_merger(
|
resultant, config_dict_base = type_merger(
|
||||||
config_overrides=_vars['config_overrides'],
|
config_overrides=_vars['config_overrides'],
|
||||||
resultant=resultant,
|
resultant=resultant,
|
||||||
list_extend=_vars.get('list_extend', True),
|
list_extend=_vars.get('list_extend', True),
|
||||||
|
@ -641,6 +748,39 @@ class ActionModule(ActionBase):
|
||||||
default_section=_vars.get('default_section', 'DEFAULT')
|
default_section=_vars.get('default_section', 'DEFAULT')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
if self._play_context.diff:
|
||||||
|
slurpee = self._execute_module(
|
||||||
|
module_name='slurp',
|
||||||
|
module_args=dict(src=_vars['dest']),
|
||||||
|
task_vars=task_vars
|
||||||
|
)
|
||||||
|
|
||||||
|
config_dict_new = {}
|
||||||
|
if 'content' in slurpee:
|
||||||
|
dest_data = base64.b64decode(
|
||||||
|
slurpee['content']).decode('utf-8')
|
||||||
|
resultant_dest = self._templar.template(
|
||||||
|
dest_data,
|
||||||
|
preserve_trailing_newlines=True,
|
||||||
|
escape_backslashes=False,
|
||||||
|
convert_data=False
|
||||||
|
)
|
||||||
|
type_merger = getattr(self,
|
||||||
|
CONFIG_TYPES.get(_vars['config_type']))
|
||||||
|
resultant_new, config_dict_new = type_merger(
|
||||||
|
config_overrides={},
|
||||||
|
resultant=resultant_dest,
|
||||||
|
list_extend=_vars.get('list_extend', True),
|
||||||
|
ignore_none_type=_vars.get('ignore_none_type', True),
|
||||||
|
default_section=_vars.get('default_section', 'DEFAULT')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compare source+overrides with dest to look for changes and
|
||||||
|
# build diff
|
||||||
|
cmp_dicts = DictCompare(config_dict_new, config_dict_base)
|
||||||
|
mods, changed = cmp_dicts.get_changes()
|
||||||
|
|
||||||
# Re-template the resultant object as it may have new data within it
|
# Re-template the resultant object as it may have new data within it
|
||||||
# as provided by an override variable.
|
# as provided by an override variable.
|
||||||
resultant = self._templar.template(
|
resultant = self._templar.template(
|
||||||
|
@ -691,6 +831,14 @@ class ActionModule(ActionBase):
|
||||||
module_args=new_module_args,
|
module_args=new_module_args,
|
||||||
task_vars=task_vars
|
task_vars=task_vars
|
||||||
)
|
)
|
||||||
|
copy_changed = rc.get('changed')
|
||||||
|
if not copy_changed:
|
||||||
|
rc['changed'] = changed
|
||||||
|
|
||||||
|
if self._play_context.diff:
|
||||||
|
rc['diff'] = []
|
||||||
|
rc['diff'].append(
|
||||||
|
{'prepared': json.dumps(mods, indent=4, sort_keys=True)})
|
||||||
if self._task.args.get('content'):
|
if self._task.args.get('content'):
|
||||||
os.remove(_vars['source'])
|
os.remove(_vars['source'])
|
||||||
return rc
|
return rc
|
||||||
|
|
Loading…
Reference in New Issue