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
parent 22c2789b72
commit 7f59773460

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com> # (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
# (c) 2015, Ales Nosek <anosek.nosek () gmail.com>
# #
# This file is part of Ansible # This file is part of Ansible
# #
@ -28,7 +29,7 @@ description:
- Manage (add, remove, change) individual settings in an INI-style file without having - Manage (add, remove, change) individual settings in an INI-style file without having
to manage the file as a whole with, say, M(template) or M(assemble). Adds missing to manage the file as a whole with, say, M(template) or M(assemble). Adds missing
sections if they don't exist. sections if they don't exist.
- Comments are discarded when the source file is read, and therefore will not - Comments are discarded when the source file is read, and therefore will not
show up in the destination file. show up in the destination file.
version_added: "0.9" version_added: "0.9"
options: options:
@ -79,7 +80,7 @@ notes:
Either use M(template) to create a base INI file with a C([default]) section, or use Either use M(template) to create a base INI file with a C([default]) section, or use
M(lineinfile) to add the missing line. M(lineinfile) to add the missing line.
requirements: [ ConfigParser ] requirements: [ ConfigParser ]
author: "Jan-Piet Mens (@jpmens)" author: "Jan-Piet Mens (@jpmens), Ales Nosek"
''' '''
EXAMPLES = ''' EXAMPLES = '''
@ -101,79 +102,77 @@ import sys
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False): 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 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: for index, line in enumerate(ini_lines):
f = open(filename) if line.startswith('[%s]' % section):
cp.readfp(f) within_section = True
except IOError: section_start = index
pass elif line.startswith('['):
if within_section:
if state == 'present':
if state == 'absent': # insert missing option line at the end of the section
if option is None: ini_lines.insert(index, '%s = %s\n' % (option, value))
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
if state == 'present':
# DEFAULT section is always there by DEFAULT, so never try to add it.
if not cp.has_section(section) and section.upper() != 'DEFAULT':
cp.add_section(section)
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 changed = True
except ConfigParser.NoSectionError: elif state == 'absent' and not option:
cp.set(section, option, value) # remove the entire section
changed = True del ini_lines[section_start:index]
except ConfigParser.NoOptionError: changed = True
cp.set(section, option, value) break
changed = True else:
except ConfigParser.InterpolationError: if within_section and option:
cp.set(section, option, value) if state == 'present':
changed = True # 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
# remove the fake section line
del ini_lines[-1:]
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 changed and not module.check_mode: if changed and not module.check_mode:
if backup: if backup:
module.backup_local(filename) module.backup_local(filename)
with open(filename, 'w') as ini_file:
try: ini_file.writelines(ini_lines)
f = open(filename, 'w')
cp.write(f)
except:
module.fail_json(msg="Can't create %s" % filename)
return changed 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 # main