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
This commit is contained in:
Flavio Percoco 2017-11-23 14:43:51 +01:00
parent 7145937250
commit 3f5a274374
3 changed files with 64 additions and 1 deletions

View File

@ -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)

View File

@ -0,0 +1,9 @@
# A default section comment
# broken into multiple lines
[DEFAULT]
[foo]
#This is a comment
baz = baz
[bar]

View File

@ -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: