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:
parent
22c2789b72
commit
7f59773460
1 changed files with 64 additions and 65 deletions
|
@ -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
|
||||
#
|
||||
|
@ -28,7 +29,7 @@ description:
|
|||
- 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
|
||||
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.
|
||||
version_added: "0.9"
|
||||
options:
|
||||
|
@ -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
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
# 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 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
|
||||
|
||||
|
|
Loading…
Reference in a new issue