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 -*-
|
# -*- 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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue