ini_file should only change what was specified and nothing more #5860

See also:
http://alesnosek.com/blog/2015/08/03/improving-ansibles-ini-file-module/
This commit is contained in:
Ales Nosek 2015-10-28 22:04:32 -07:00 committed by Matt Clay
parent 35240830f6
commit bd03dd67ff

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
# (c) 2015, Ales Nosek <anosek.nosek () gmail.com>
#
# This file is part of Ansible
#
@ -79,7 +80,7 @@ notes:
Either use M(template) to create a base INI file with a C([default]) section, or use
M(lineinfile) to add the missing line.
requirements: [ ConfigParser ]
author: "Jan-Piet Mens (@jpmens)"
author: "Jan-Piet Mens (@jpmens), Ales Nosek"
'''
EXAMPLES = '''
@ -101,79 +102,77 @@ import sys
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):
with open(filename, 'r') as ini_file:
ini_lines = ini_file.readlines()
# append a fake section line to simplify the logic
ini_lines.append('[')
within_section = not section
section_start = 0
changed = False
if (sys.version_info[0] == 2 and sys.version_info[1] >= 7) or sys.version_info[0] >= 3:
cp = ConfigParser.ConfigParser(allow_no_value=True)
else:
cp = ConfigParser.ConfigParser()
cp.optionxform = identity
try:
f = open(filename)
cp.readfp(f)
except IOError:
pass
if state == 'absent':
if option is None:
changed = cp.remove_section(section)
else:
try:
changed = cp.remove_option(section, option)
except ConfigParser.NoSectionError:
# Option isn't present if the section isn't either
pass
for index, line in enumerate(ini_lines):
if line.startswith('[%s]' % section):
within_section = True
section_start = index
elif line.startswith('['):
if within_section:
if state == 'present':
# insert missing option line at the end of the section
ini_lines.insert(index, '%s = %s\n' % (option, value))
changed = True
elif state == 'absent' and not option:
# remove the entire section
del ini_lines[section_start:index]
changed = True
break
else:
if within_section and option:
if state == 'present':
# change the existing option line
if re.match('%s *=' % option, line) \
or re.match('# *%s *=' % option, line) \
or re.match('; *%s *=' % option, line):
newline = '%s = %s\n' % (option, value)
changed = ini_lines[index] != newline
ini_lines[index] = newline
if changed:
# remove all possible option occurences from the rest of the section
index = index + 1
while index < len(ini_lines):
line = ini_lines[index]
if line.startswith('['):
break
if re.match('%s *=' % option, line):
del ini_lines[index]
else:
index = index + 1
break
else:
# comment out the existing option line
if re.match('%s *=' % option, line):
ini_lines[index] = '#%s' % ini_lines[index]
changed = True
break
# DEFAULT section is always there by DEFAULT, so never try to add it.
if not cp.has_section(section) and section.upper() != 'DEFAULT':
# remove the fake section line
del ini_lines[-1:]
cp.add_section(section)
if not within_section and option and state == 'present':
ini_lines.append('[%s]\n' % section)
ini_lines.append('%s = %s\n' % (option, value))
changed = True
if option is not None and value is not None:
try:
oldvalue = cp.get(section, option)
if str(value) != str(oldvalue):
cp.set(section, option, value)
changed = True
except ConfigParser.NoSectionError:
cp.set(section, option, value)
changed = True
except ConfigParser.NoOptionError:
cp.set(section, option, value)
changed = True
except ConfigParser.InterpolationError:
cp.set(section, option, value)
changed = True
if changed and not module.check_mode:
if backup:
module.backup_local(filename)
try:
f = open(filename, 'w')
cp.write(f)
except:
module.fail_json(msg="Can't create %s" % filename)
with open(filename, 'w') as ini_file:
ini_file.writelines(ini_lines)
return changed
# ==============================================================
# identity
def identity(arg):
"""
This function simply returns its argument. It serves as a
replacement for ConfigParser.optionxform, which by default
changes arguments to lower case. The identity function is a
better choice than str() or unicode(), because it is
encoding-agnostic.
"""
return arg
# ==============================================================
# main