From 3f5a2743744c5d9a870481cb4bb56df28cfd52e1 Mon Sep 17 00:00:00 2001 From: Flavio Percoco Date: Thu, 23 Nov 2017 14:43:51 +0100 Subject: [PATCH] Preserve comments in INI files config_template currently strips comments out from the INI files. This commit adds support for comments. The default ConfigParser supports reading comments but it doesn't writes them back. There are other libraries that have better support for comments, however, adopting another library would require a bigger refactor of this code. Change-Id: I376be69cd2cf34dc147f9ade716791125a424e2c --- action/config_template.py | 30 +++++++++++++++++++++++++- tests/templates/test_with_comments.ini | 9 ++++++++ tests/test-config_template.yml | 26 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/templates/test_with_comments.ini diff --git a/action/config_template.py b/action/config_template.py index 9e4a474..c7fcfc1 100644 --- a/action/config_template.py +++ b/action/config_template.py @@ -134,6 +134,7 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): key = var2 """ def __init__(self, *args, **kwargs): + self._comments = {} self.ignore_none_type = bool(kwargs.pop('ignore_none_type', True)) ConfigParser.RawConfigParser.__init__(self, *args, **kwargs) @@ -163,21 +164,31 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): self._write(fp, section, key, value, entry) def write(self, fp): + def _write_comments(section, optname=None): + comsect = self._comments.get(section, {}) + if optname in comsect: + fp.write(''.join(comsect[optname])) + if self._defaults: + _write_comments('DEFAULT') fp.write("[%s]\n" % 'DEFAULT') for key, value in self._defaults.items(): + _write_comments('DEFAULT', optname=key) self._write_check(fp, key=key, value=value) else: fp.write("\n") for section in self._sections: + _write_comments(section) fp.write("[%s]\n" % section) for key, value in self._sections[section].items(): + _write_comments(section, optname=key) self._write_check(fp, key=key, value=value, section=True) else: fp.write("\n") def _read(self, fp, fpname): + comments = [] cursect = None optname = None lineno = 0 @@ -187,8 +198,15 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): if not line: break lineno += 1 - if line.strip() == '' or line[0] in '#;': + if line.strip() == '': + if comments: + comments.append('') continue + + if line[0] in '#;': + comments.append(line) + continue + if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR": continue if line[0].isspace() and cursect is not None and optname: @@ -215,6 +233,13 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): cursect = self._dict() self._sections[sectname] = cursect optname = None + + comsect = self._comments.setdefault(sectname, {}) + if comments: + # NOTE(flaper87): Using none as the key for + # section level comments + comsect[None] = comments + comments = [] elif cursect is None: raise ConfigParser.MissingSectionHeaderError( fpname, @@ -235,6 +260,9 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser): if optval == '""': optval = '' cursect[optname] = optval + if comments: + comsect[optname] = comments + comments = [] else: if not e: e = ConfigParser.ParsingError(fpname) diff --git a/tests/templates/test_with_comments.ini b/tests/templates/test_with_comments.ini new file mode 100644 index 0000000..aa292fb --- /dev/null +++ b/tests/templates/test_with_comments.ini @@ -0,0 +1,9 @@ +# A default section comment +# broken into multiple lines +[DEFAULT] + +[foo] +#This is a comment +baz = baz + +[bar] \ No newline at end of file diff --git a/tests/test-config_template.yml b/tests/test-config_template.yml index 9575765..ffcb5c5 100644 --- a/tests/test-config_template.yml +++ b/tests/test-config_template.yml @@ -177,6 +177,32 @@ - "{{ test_ignore_none_type.content | b64decode | search('(?m)^india$') }}" - "{{ test_ignore_none_type.content | b64decode | search('(?m)^juliett kilo$') }}" + # Test basic function of config_template + - name: Template test INI comments + config_template: + src: "{{ playbook_dir }}/templates/test_with_comments.ini" + dest: "/tmp/test_with_comments.ini" + config_overrides: "{{ test_config_ini_overrides }}" + config_type: "ini" + tags: test + + - name: Read test.ini + slurp: + src: /tmp/test_with_comments.ini + register: ini_file + tags: test + + - debug: + msg: "ini - {{ ini_file.content | b64decode }}" + - name: Validate output + tags: test + assert: + that: + - "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test_with_comments.ini')) == 'new_value'" + - "(lookup('ini', 'baz section=foo file=/tmp/test_with_comments.ini')) == 'bar'" + - "{{ ini_file.content | b64decode | search('#This is a comment')}}" + - "{{ ini_file.content | b64decode | search('# A default section comment\n# broken into multiple lines\n\\[DEFAULT\\]')}}" + vars: test_config_ini_overrides: DEFAULT: