You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/files/ini_file.py

260 lines
8.9 KiB
Python

#!/usr/bin/python
# -*- 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
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: ini_file
short_description: Tweak settings in INI files
extends_documentation_fragment: files
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.
9 years ago
- Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file.
version_added: "0.9"
options:
dest:
description:
- Path to the INI-style file; this file is created if required
required: true
default: null
section:
description:
- Section name in INI file. This is added if C(state=present) automatically when
a single value is being set.
required: true
default: null
option:
description:
- if set (required for changing a I(value)), this is the name of the option.
- May be omitted if adding/removing a whole I(section).
required: false
default: null
value:
description:
- the string value to be associated with an I(option). May be omitted when removing an I(option).
required: false
default: null
backup:
description:
- Create a backup file including the timestamp information so you can get
the original file back if you somehow clobbered it incorrectly.
required: false
default: "no"
choices: [ "yes", "no" ]
others:
description:
- all arguments accepted by the M(file) module also work here
required: false
state:
description:
- If set to C(absent) the option or section will be removed if present instead of created.
required: false
default: "present"
choices: [ "present", "absent" ]
no_extra_spaces:
description:
- do not insert spaces before and after '=' symbol
required: false
default: false
version_added: "2.1"
notes:
- While it is possible to add an I(option) without specifying a I(value), this makes
no sense.
- A section named C(default) cannot be added by the module, but if it exists, individual
options within the section can be updated. (This is a limitation of Python's I(ConfigParser).)
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 ]
9 years ago
author:
- "Jan-Piet Mens (@jpmens)"
- "Ales Nosek (@noseka1)"
'''
EXAMPLES = '''
# Ensure "fav=lemonade is in section "[drinks]" in specified file
- ini_file: dest=/etc/conf section=drinks option=fav value=lemonade mode=0600 backup=yes
- ini_file: dest=/etc/anotherconf
section=drinks
option=temperature
value=cold
backup=yes
'''
import os
import re
# ==============================================================
# match_opt
def match_opt(option, line):
option = re.escape(option)
return re.match(' *%s( |\t)*=' % option, line) \
or re.match('# *%s( |\t)*=' % option, line) \
or re.match('; *%s( |\t)*=' % option, line)
# ==============================================================
# match_active_opt
def match_active_opt(option, line):
option = re.escape(option)
return re.match(' *%s( |\t)*=' % option, line)
# ==============================================================
# do_ini
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False, no_extra_spaces=False):
if not os.path.exists(filename):
try:
open(filename,'w').close()
except:
module.fail_json(msg="Destination file %s not writable" % filename)
ini_file = open(filename, 'r')
try:
ini_lines = ini_file.readlines()
# append a fake section line to simplify the logic
ini_lines.append('[')
finally:
ini_file.close()
within_section = not section
section_start = 0
changed = False
if no_extra_spaces:
assignment_format = '%s=%s\n'
else:
assignment_format = '%s = %s\n'
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
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]):
ini_lines.insert(i, assignment_format % (option, value))
changed = True
break
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 match_opt(option, line):
newline = assignment_format % (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 match_active_opt(option, line):
del ini_lines[index]
else:
index = index + 1
break
else:
# comment out the existing option line
if match_active_opt(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(assignment_format % (option, value))
changed = True
backup_file = None
if changed and not module.check_mode:
if backup:
backup_file = module.backup_local(filename)
ini_file = open(filename, 'w')
try:
ini_file.writelines(ini_lines)
finally:
ini_file.close()
return (changed, backup_file)
# ==============================================================
# main
def main():
module = AnsibleModule(
argument_spec = dict(
dest = dict(required=True),
section = dict(required=True),
option = dict(required=False),
value = dict(required=False),
backup = dict(default='no', type='bool'),
state = dict(default='present', choices=['present', 'absent']),
no_extra_spaces = dict(required=False, default=False, type='bool')
),
add_file_common_args = True,
supports_check_mode = True
)
dest = os.path.expanduser(module.params['dest'])
section = module.params['section']
option = module.params['option']
value = module.params['value']
state = module.params['state']
backup = module.params['backup']
no_extra_spaces = module.params['no_extra_spaces']
(changed,backup_file) = do_ini(module, dest, section, option, value, state, backup, no_extra_spaces)
file_args = module.load_file_common_arguments(module.params)
changed = module.set_fs_attributes_if_different(file_args, changed)
results = { 'changed': changed, 'msg': "OK", 'dest': dest }
if backup_file is not None:
results['backup_file'] = backup_file
# Mission complete
module.exit_json(**results)
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()