Add ignore_none_type to config template

It is sometimes useful to tell ConfigTemplateParser to write
out options that are valueless and not suffixed with '=' or ':',
such as when overriding a my.cnf.

This commit adds the 'ignore_none_type' attribute to the config_template
module. If this attribute is set to false, then valueless options will be
written out to the resultant INI file as-is, without the '=' or ':' suffix.

Change-Id: I5c88b2019c01b44193a5d0df9299ecce6de52f01
Partial-Bug: #1693234
This commit is contained in:
Miguel Alex Cantu 2017-06-23 19:32:44 +00:00
parent 30af08208d
commit 76d5f02a32
5 changed files with 90 additions and 10 deletions

View File

@ -155,9 +155,17 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
key = var3 key = var3
key = var2 key = var2
""" """
def _write(self, fp, section, item, entry): def __init__(self, *args, **kwargs):
self.ignore_none_type = bool(kwargs.pop('ignore_none_type', True))
ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)
def _write(self, fp, section, key, item, entry):
if section: if section:
if (item is not None) or (self._optcre == self.OPTCRE): # If we are not ignoring a none type value, then print out
# the option name only if the value type is None.
if not self.ignore_none_type and item is None:
fp.write(key + '\n')
elif (item is not None) or (self._optcre == self.OPTCRE):
fp.write(entry) fp.write(entry)
else: else:
fp.write(entry) fp.write(entry)
@ -167,14 +175,14 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
for item in value: for item in value:
item = str(item).replace('\n', '\n\t') item = str(item).replace('\n', '\n\t')
entry = "%s = %s\n" % (key, item) entry = "%s = %s\n" % (key, item)
self._write(fp, section, item, entry) self._write(fp, section, key, item, entry)
else: else:
if isinstance(value, list): if isinstance(value, list):
_value = [str(i.replace('\n', '\n\t')) for i in value] _value = [str(i.replace('\n', '\n\t')) for i in value]
entry = '%s = %s\n' % (key, ','.join(_value)) entry = '%s = %s\n' % (key, ','.join(_value))
else: else:
entry = '%s = %s\n' % (key, str(value).replace('\n', '\n\t')) entry = '%s = %s\n' % (key, str(value).replace('\n', '\n\t'))
self._write(fp, section, value, entry) self._write(fp, section, key, value, entry)
def write(self, fp): def write(self, fp):
if self._defaults: if self._defaults:
@ -268,7 +276,11 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
class ActionModule(ActionBase): class ActionModule(ActionBase):
TRANSFERS_FILES = True TRANSFERS_FILES = True
def return_config_overrides_ini(self, config_overrides, resultant, list_extend=True): def return_config_overrides_ini(self,
config_overrides,
resultant,
list_extend=True,
ignore_none_type=True):
"""Returns string value from a modified config file. """Returns string value from a modified config file.
:param config_overrides: ``dict`` :param config_overrides: ``dict``
@ -281,7 +293,8 @@ class ActionModule(ActionBase):
try: try:
config = ConfigTemplateParser( config = ConfigTemplateParser(
allow_no_value=True, allow_no_value=True,
dict_type=MultiKeyDict dict_type=MultiKeyDict,
ignore_none_type=ignore_none_type
) )
config.optionxform = str config.optionxform = str
except Exception: except Exception:
@ -344,7 +357,11 @@ class ActionModule(ActionBase):
else: else:
config.set(str(section), str(key), str(value)) config.set(str(section), str(key), str(value))
def return_config_overrides_json(self, config_overrides, resultant, list_extend=True): def return_config_overrides_json(self,
config_overrides,
resultant,
list_extend=True,
ignore_none_type=True):
"""Returns config json """Returns config json
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
@ -366,7 +383,11 @@ class ActionModule(ActionBase):
sort_keys=True sort_keys=True
) )
def return_config_overrides_yaml(self, config_overrides, resultant, list_extend=True): def return_config_overrides_yaml(self,
config_overrides,
resultant,
list_extend=True,
ignore_none_type=True):
"""Return config yaml. """Return config yaml.
:param config_overrides: ``dict`` :param config_overrides: ``dict``
@ -484,13 +505,23 @@ class ActionModule(ActionBase):
if user_dest.endswith(os.sep): if user_dest.endswith(os.sep):
user_dest = os.path.join(user_dest, os.path.basename(source)) user_dest = os.path.join(user_dest, os.path.basename(source))
# Get ignore_none_type
# In some situations(i.e. my.cnf files), INI files can have valueless
# options that don't have a '=' or ':' suffix. In these cases,
# ConfigParser gives these options a "None" value. If ignore_none_type
# is set to true, these key/value options will be ignored, if it's set
# to false, then ConfigTemplateParser will write out only the option
# name with out the '=' or ':' suffix. The default is true.
ignore_none_type = self._task.args.get('ignore_none_type', True)
return True, dict( return True, dict(
source=source, source=source,
dest=user_dest, dest=user_dest,
config_overrides=self._task.args.get('config_overrides', dict()), config_overrides=self._task.args.get('config_overrides', dict()),
config_type=config_type, config_type=config_type,
searchpath=searchpath, searchpath=searchpath,
list_extend=list_extend list_extend=list_extend,
ignore_none_type=ignore_none_type
) )
def run(self, tmp=None, task_vars=None): def run(self, tmp=None, task_vars=None):
@ -563,7 +594,8 @@ class ActionModule(ActionBase):
resultant = type_merger( resultant = 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),
ignore_none_type=_vars.get('ignore_none_type', True)
) )
# 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
@ -595,6 +627,7 @@ class ActionModule(ActionBase):
new_module_args.pop('config_overrides', None) new_module_args.pop('config_overrides', None)
new_module_args.pop('config_type', None) new_module_args.pop('config_type', None)
new_module_args.pop('list_extend', None) new_module_args.pop('list_extend', None)
new_module_args.pop('ignore_none_type', None)
# Content from config_template is converted to src # Content from config_template is converted to src
new_module_args.pop('content', None) new_module_args.pop('content', None)

View File

@ -49,6 +49,16 @@ options:
choices: choices:
- True - True
- False - False
ignore_none_type:
description:
- Can be true or false. If ignore_none_type is set to true, then
valueless INI options will not be written out to the resultant file.
If it's set to false, then config_template will write out only
the option name without the '=' or ':' suffix. The default is true.
choices:
- True
- False
author: Kevin Carter author: Kevin Carter
""" """

View File

@ -0,0 +1,9 @@
---
features:
- |
The config_template template module now supports writing out valueless
INI options without suffixing them with '=' or ':'. This is done via the
'ignore_none_type' attribute. If ignore_none_type is set to true, these
key/value entries will be ignored, if it's set to false, then
ConfigTemplateParser will write out only the option name without the
'=' or ':' suffix. The default is true.

View File

@ -0,0 +1,8 @@
[alfa]
bravo = charlie
delta = echo
[foxtrot]
golf = hotel
india
juliett kilo
lima = mike

View File

@ -136,6 +136,26 @@
that: that:
- "(content_no_overrides_file.content | b64decode | from_json) == (content_no_overrides_file_expected.content | b64decode | from_json)" - "(content_no_overrides_file.content | b64decode | from_json) == (content_no_overrides_file_expected.content | b64decode | from_json)"
# Test the ignore_none_type attribute when set to False
- name: Template test with ignore_none_type set to false
config_template:
src: "{{ playbook_dir }}/templates/test_ignore_none_type.ini"
dest: "/tmp/test_ignore_none_type.ini"
config_overrides: "{{ test_config_ini_overrides }}"
config_type: "ini"
ignore_none_type: False
- name: Read test_ignore_none_type.ini
slurp:
src: /tmp/test_ignore_none_type.ini
register: test_ignore_none_type
- debug:
msg: "test_ignore_none_type.ini - {{ test_ignore_none_type.content | b64decode }}"
- name: Validate output has valueless options printed out
assert:
that:
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^india$') }}"
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^juliett kilo$') }}"
vars: vars:
test_config_ini_overrides: test_config_ini_overrides:
DEFAULT: DEFAULT: