1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

ini_file: fix regression reported in #2578 (#2875)

* Add regression test.

* Add more Unicode tests.

* Add fix.

* Add changelog.

* Work completely with Unicode.

* Update plugins/modules/files/ini_file.py

Co-authored-by: quidame <quidame@poivron.org>

Co-authored-by: quidame <quidame@poivron.org>
This commit is contained in:
Felix Fontein 2021-06-27 10:00:01 +02:00 committed by GitHub
parent 1b94d09209
commit 147425ef93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 20 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- "ini_file - fix Unicode processing for Python 2 (https://github.com/ansible-collections/community.general/pull/2875)."

View file

@ -112,6 +112,7 @@ import tempfile
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_bytes, to_text
def match_opt(option, line):
@ -128,6 +129,13 @@ def do_ini(module, filename, section=None, option=None, value=None,
state='present', backup=False, no_extra_spaces=False, create=True,
allow_no_value=False):
if section is not None:
section = to_text(section)
if option is not None:
option = to_text(option)
if value is not None:
value = to_text(value)
diff = dict(
before='',
after='',
@ -144,33 +152,33 @@ def do_ini(module, filename, section=None, option=None, value=None,
ini_lines = []
else:
with io.open(filename, 'r', encoding="utf-8-sig") as ini_file:
ini_lines = ini_file.readlines()
ini_lines = [to_text(line) for line in ini_file.readlines()]
if module._diff:
diff['before'] = ''.join(ini_lines)
diff['before'] = u''.join(ini_lines)
changed = False
# ini file could be empty
if not ini_lines:
ini_lines.append('\n')
ini_lines.append(u'\n')
# last line of file may not contain a trailing newline
if ini_lines[-1] == "" or ini_lines[-1][-1] != '\n':
ini_lines[-1] += '\n'
if ini_lines[-1] == u"" or ini_lines[-1][-1] != u'\n':
ini_lines[-1] += u'\n'
changed = True
# append fake section lines to simplify the logic
# At top:
# Fake random section to do not match any other in the file
# Using commit hash as fake section name
fake_section_name = "ad01e11446efb704fcdbdb21f2c43757423d91c5"
fake_section_name = u"ad01e11446efb704fcdbdb21f2c43757423d91c5"
# Insert it at the beginning
ini_lines.insert(0, '[%s]' % fake_section_name)
ini_lines.insert(0, u'[%s]' % fake_section_name)
# At bottom:
ini_lines.append('[')
ini_lines.append(u'[')
# If no section is defined, fake section is used
if not section:
@ -180,21 +188,23 @@ def do_ini(module, filename, section=None, option=None, value=None,
section_start = 0
msg = 'OK'
if no_extra_spaces:
assignment_format = '%s=%s\n'
assignment_format = u'%s=%s\n'
else:
assignment_format = '%s = %s\n'
assignment_format = u'%s = %s\n'
non_blank_non_comment_pattern = re.compile(to_text(r'^[ \t]*([#;].*)?$'))
for index, line in enumerate(ini_lines):
if line.startswith('[%s]' % section):
if line.startswith(u'[%s]' % section):
within_section = True
section_start = index
elif line.startswith('['):
elif line.startswith(u'['):
if within_section:
if state == 'present':
# insert missing option line at the end of the section
for i in range(index, 0, -1):
# search backwards for previous non-blank or non-comment line
if not re.match(r'^[ \t]*([#;].*)?$', ini_lines[i - 1]):
if not non_blank_non_comment_pattern.match(ini_lines[i - 1]):
if option and value:
ini_lines.insert(i, assignment_format % (option, value))
msg = 'option added'
@ -216,7 +226,7 @@ def do_ini(module, filename, section=None, option=None, value=None,
# change the existing option line
if match_opt(option, line):
if not value and allow_no_value:
newline = '%s\n' % option
newline = u'%s\n' % option
else:
newline = assignment_format % (option, value)
option_changed = ini_lines[index] != newline
@ -229,7 +239,7 @@ def do_ini(module, filename, section=None, option=None, value=None,
index = index + 1
while index < len(ini_lines):
line = ini_lines[index]
if line.startswith('['):
if line.startswith(u'['):
break
if match_active_opt(option, line):
del ini_lines[index]
@ -249,28 +259,29 @@ def do_ini(module, filename, section=None, option=None, value=None,
del ini_lines[-1:]
if not within_section and state == 'present':
ini_lines.append('[%s]\n' % section)
ini_lines.append(u'[%s]\n' % section)
msg = 'section and option added'
if option and value is not None:
ini_lines.append(assignment_format % (option, value))
elif option and value is None and allow_no_value:
ini_lines.append('%s\n' % option)
ini_lines.append(u'%s\n' % option)
else:
msg = 'only section added'
changed = True
if module._diff:
diff['after'] = ''.join(ini_lines)
diff['after'] = u''.join(ini_lines)
backup_file = None
if changed and not module.check_mode:
if backup:
backup_file = module.backup_local(filename)
encoded_ini_lines = [to_bytes(line) for line in ini_lines]
try:
tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
f = os.fdopen(tmpfd, 'w')
f.writelines(ini_lines)
f = os.fdopen(tmpfd, 'wb')
f.writelines(encoded_ini_lines)
f.close()
except IOError:
module.fail_json(msg="Unable to create temporary file %s", traceback=traceback.format_exc())

View file

@ -514,3 +514,43 @@
assert:
that:
- content16 == expected16
# Regression test for https://github.com/ansible-collections/community.general/pull/2578#issuecomment-868092282
- name: Create UTF-8 test file
copy:
content: !!binary |
W2FwcDptYWluXQphdmFpbGFibGVfbGFuZ3VhZ2VzID0gZW4gZnIgZXMgZGUgcHQgamEgbHQgemhf
VFcgaWQgZGEgcHRfQlIgcnUgc2wgaXQgbmxfTkwgdWsgdGEgc2kgY3MgbmIgaHUKIyBGdWxsIGxh
bmd1YWdlIG5hbWVzIGluIG5hdGl2ZSBsYW5ndWFnZSAoY29tbWEgc2VwYXJhdGVkKQphdmFpbGFi
bGVfbGFuZ3VhZ2VzX2Z1bGwgPSBFbmdsaXNoLCBGcmFuw6dhaXMsIEVzcGHDsW9sLCBEZXV0c2No
LCBQb3J0dWd1w6pzLCDml6XmnKzoqp4sIExpZXR1dm9zLCDkuK3mlocsIEluZG9uZXNpYSwgRGFu
c2ssIFBvcnR1Z3XDqnMgKEJyYXNpbCksINCg0YPRgdGB0LrQuNC5LCBTbG92ZW7FocSNaW5hLCBJ
dGFsaWFubywgTmVkZXJsYW5kcywg0KPQutGA0LDRl9C90YHRjNC60LAsIOCupOCuruCuv+CutOCv
jSwg4LeD4LeS4LaC4LeE4La9LCDEjGVza3ksIEJva23DpWwsIE1hZ3lhcgo=
dest: '{{ output_file }}'
- name: Add entries
ini_file:
section: "{{ item.section }}"
option: "{{ item.option }}"
value: "{{ item.value }}"
path: '{{ output_file }}'
create: true
loop:
- section: app:main
option: sqlalchemy.url
value: postgresql://app:secret@database/app
- section: handler_filelog
option: args
value: (sys.stderr,)
- section: handler_filelog
option: class
value: StreamHandler
- section: handler_exc_handler
option: args
value: (sys.stderr,)
- section: båz
option: fföø
value: ḃâŗ
- section: båz
option: fföø
value: bar