diff --git a/action/_v2_config_template.py b/action/_v2_config_template.py index dde4b54..0cc6198 100644 --- a/action/_v2_config_template.py +++ b/action/_v2_config_template.py @@ -155,9 +155,17 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): key = var3 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 (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) else: fp.write(entry) @@ -167,14 +175,14 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): for item in value: item = str(item).replace('\n', '\n\t') entry = "%s = %s\n" % (key, item) - self._write(fp, section, item, entry) + self._write(fp, section, key, item, entry) else: if isinstance(value, list): _value = [str(i.replace('\n', '\n\t')) for i in value] entry = '%s = %s\n' % (key, ','.join(_value)) else: 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): if self._defaults: @@ -268,7 +276,11 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): class ActionModule(ActionBase): 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. :param config_overrides: ``dict`` @@ -281,7 +293,8 @@ class ActionModule(ActionBase): try: config = ConfigTemplateParser( allow_no_value=True, - dict_type=MultiKeyDict + dict_type=MultiKeyDict, + ignore_none_type=ignore_none_type ) config.optionxform = str except Exception: @@ -344,7 +357,11 @@ class ActionModule(ActionBase): else: 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 Its important to note that file ordering will not be preserved as the @@ -366,7 +383,11 @@ class ActionModule(ActionBase): 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. :param config_overrides: ``dict`` @@ -484,13 +505,23 @@ class ActionModule(ActionBase): if user_dest.endswith(os.sep): 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( source=source, dest=user_dest, config_overrides=self._task.args.get('config_overrides', dict()), config_type=config_type, searchpath=searchpath, - list_extend=list_extend + list_extend=list_extend, + ignore_none_type=ignore_none_type ) def run(self, tmp=None, task_vars=None): @@ -563,7 +594,8 @@ class ActionModule(ActionBase): resultant = type_merger( config_overrides=_vars['config_overrides'], 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 @@ -595,6 +627,7 @@ class ActionModule(ActionBase): new_module_args.pop('config_overrides', None) new_module_args.pop('config_type', None) new_module_args.pop('list_extend', None) + new_module_args.pop('ignore_none_type', None) # Content from config_template is converted to src new_module_args.pop('content', None) diff --git a/library/config_template b/library/config_template index f789b6c..f879d90 100644 --- a/library/config_template +++ b/library/config_template @@ -49,6 +49,16 @@ options: choices: - True - 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 """ diff --git a/releasenotes/notes/config-template-ignore-none-type-9387c236853f061b.yaml b/releasenotes/notes/config-template-ignore-none-type-9387c236853f061b.yaml new file mode 100644 index 0000000..a553974 --- /dev/null +++ b/releasenotes/notes/config-template-ignore-none-type-9387c236853f061b.yaml @@ -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. diff --git a/tests/templates/test_ignore_none_type.ini b/tests/templates/test_ignore_none_type.ini new file mode 100644 index 0000000..99caa20 --- /dev/null +++ b/tests/templates/test_ignore_none_type.ini @@ -0,0 +1,8 @@ +[alfa] +bravo = charlie +delta = echo +[foxtrot] +golf = hotel +india +juliett kilo +lima = mike diff --git a/tests/test-config_template.yml b/tests/test-config_template.yml index 4961243..450b3d6 100644 --- a/tests/test-config_template.yml +++ b/tests/test-config_template.yml @@ -136,6 +136,26 @@ that: - "(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: test_config_ini_overrides: DEFAULT: