For example:
ini_file.py php.ini --section PHP --option memory_limit --value 256M
| #!/usr/bin/python | |
| # -*- coding: utf-8 -*- | |
| # Copyright: (c) 2012, Jan-Piet Mens <jpmens () gmail.com> | |
| # Copyright: (c) 2015, Ales Nosek <anosek.nosek () gmail.com> | |
| # Copyright: (c) 2017, Ansible Project | |
| # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
| # from __future__ import absolute_import, division, print_function | |
| __metaclass__ = type | |
| # | |
| 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. | |
| - Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file. | |
| - Since version 2.3, this module adds missing ending newlines to files to keep in line with the POSIX standard, even when | |
| no other modifications need to be applied. | |
| version_added: "0.9" | |
| options: | |
| path: | |
| description: | |
| - Path to the INI-style file; this file is created if required. | |
| - Before 2.3 this option was only usable as I(dest). | |
| required: true | |
| default: null | |
| aliases: ['dest'] | |
| section: | |
| description: | |
| - Section name in INI file. This is added if C(state=present) automatically when | |
| a single value is being set. | |
| - If left empty or set to `null`, the I(option) will be placed before the first I(section). | |
| Using `null` is also required if the config format does not support sections. | |
| 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" | |
| create: | |
| required: false | |
| choices: [ "yes", "no" ] | |
| default: "yes" | |
| description: | |
| - If set to 'no', the module will fail if the file does not already exist. | |
| By default it will create the file if it is missing. | |
| version_added: "2.2" | |
| notes: | |
| - While it is possible to add an I(option) without specifying a I(value), this makes | |
| no sense. | |
| - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but | |
| I(dest) still works as well. | |
| author: | |
| - "Jan-Piet Mens (@jpmens)" | |
| - "Ales Nosek (@noseka1)" | |
| ''' | |
| import os | |
| import re | |
| import argparse | |
| # ============================================================== | |
| # match_opt | |
| def match_opt(option, line): | |
| option = re.escape(option) | |
| return re.match('( |\t)*%s( |\t)*=' % option, line) \ | |
| or re.match('#( |\t)*%s( |\t)*=' % option, line) \ | |
| or re.match(';( |\t)*%s( |\t)*=' % option, line) | |
| # ============================================================== | |
| # match_active_opt | |
| def match_active_opt(option, line): | |
| option = re.escape(option) | |
| return re.match('( |\t)*%s( |\t)*=' % option, line) | |
| # ============================================================== | |
| # do_ini | |
| def do_ini(filename, section=None, option=None, value=None, | |
| state='present', backup=False, no_extra_spaces=False, create=True): | |
| diff = {'before': '', | |
| 'after': '', | |
| 'before_header': '%s (content)' % filename, | |
| 'after_header': '%s (content)' % filename} | |
| if not os.path.exists(filename): | |
| # if not create: | |
| # module.fail_json(rc=257, msg='Destination %s does not exist !' % filename) | |
| destpath = os.path.dirname(filename) | |
| if not os.path.exists(destpath): | |
| os.makedirs(destpath) | |
| ini_lines = [] | |
| else: | |
| ini_file = open(filename, 'r') | |
| try: | |
| ini_lines = ini_file.readlines() | |
| finally: | |
| ini_file.close() | |
| # if module._diff: | |
| diff['before'] = ''.join(ini_lines) | |
| changed = False | |
| # ini file could be empty | |
| if not ini_lines: | |
| ini_lines.append('\n') | |
| # last line of file may not contain a trailing newline | |
| if ini_lines[-1] == "" or ini_lines[-1][-1] != '\n': | |
| ini_lines[-1] += '\n' | |
| changed = True | |
| # append a fake section line to simplify the logic | |
| ini_lines.append('[') | |
| within_section = not section | |
| section_start = 0 | |
| msg = 'OK' | |
| 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)) | |
| msg = 'option added' | |
| changed = True | |
| break | |
| elif state == 'absent' and not option: | |
| # remove the entire section | |
| del ini_lines[section_start:index] | |
| msg = 'section removed' | |
| 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) | |
| option_changed = ini_lines[index] != newline | |
| changed = changed or option_changed | |
| if option_changed: | |
| msg = 'option changed' | |
| ini_lines[index] = newline | |
| if option_changed: | |
| # remove all possible option occurrences 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 | |
| elif state == 'absent': | |
| # delete the existing line | |
| if match_active_opt(option, line): | |
| del ini_lines[index] | |
| changed = True | |
| msg = 'option changed' | |
| 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 | |
| msg = 'section and option added' | |
| # if module._diff: | |
| diff['after'] = ''.join(ini_lines) | |
| backup_file = None | |
| if changed: | |
| # 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, diff, msg) | |
| # ============================================================== | |
| # main | |
| def main(): | |
| parser = argparse.ArgumentParser(description='Tweak settings in INI files', | |
| epilog='While it is possible to add an "option" without specifying a I(value), this makes no sense.') | |
| parser.add_argument('path', help='Path to the INI-style file; this file is created if required') | |
| parser.add_argument('--section', | |
| help='Section name in INI file. This is added if state is present automatically when a single value is being set.', | |
| default=None) | |
| parser.add_argument('--option', | |
| help='If set (required for changing a value), this is the name of the option. May be omitted if adding/removing a whole section.') | |
| parser.add_argument('--value', | |
| help='The string value to be associated with an option. May be omitted when removing an option.') | |
| parser.add_argument('--state', | |
| help='If set to "absent" the option or section will be removed if present instead of created.', | |
| default='present') | |
| parser.add_argument('--backup', | |
| help='Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly.', | |
| default=False) | |
| parser.add_argument('--no_extra_spaces', help='Do not insert spaces before and after "=" symbol', default=False, | |
| action='store_true') | |
| parser.add_argument('--create', | |
| help='If set to "no", the module will fail if the file does not already exist. By default it will create the file if it is missing.', | |
| default=True) | |
| args = parser.parse_args() | |
| path = args.path | |
| section = args.section | |
| option = args.option | |
| value = args.value | |
| state = args.state | |
| backup = args.backup | |
| no_extra_spaces = args.no_extra_spaces | |
| create = args.create | |
| (changed, backup_file, diff, msg) = do_ini(path, section, option, value, state, backup, no_extra_spaces, create) | |
| # if not module.check_mode and os.path.exists(path): | |
| # file_args = module.load_file_common_arguments(args) | |
| # changed = module.set_fs_attributes_if_different(file_args, changed) | |
| results = {'changed': changed, 'msg': msg, 'path': path, 'diff': diff} | |
| if backup_file is not None: | |
| results['backup_file'] = backup_file | |
| # Mission complete | |
| # module.exit_json(**results) | |
| if __name__ == '__main__': | |
| main() |