Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modify_inactive_option option to ini_file module to ignore matching commented key:value pairs #7401

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- ini_file - add `modify_inactive_option` option (https://github.com/ansible-collections/community.general/pull/7401).
31 changes: 23 additions & 8 deletions plugins/modules/ini_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@
- Allow option without value and without '=' symbol.
type: bool
default: false
modify_inactive_option:
description:
- Do not replace a commented line that matches the given option. This is useful when you want to keep example
key:value pairs for documentation purposes.
type: bool
default: true
version_added: 7.6.0
follow:
description:
- This flag indicates that filesystem links, if they exist, should be followed.
Expand Down Expand Up @@ -190,7 +197,7 @@ def match_opt(option, line):

def match_active_opt(option, line):
option = re.escape(option)
return re.match('( |\t)*(%s)( |\t)*(=|$)( |\t)*(.*)' % option, line)
return re.match('()( |\t)*(%s)( |\t)*(=|$)( |\t)*(.*)' % option, line)


def update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg):
Expand All @@ -213,7 +220,7 @@ def update_section_line(option, changed, section_lines, index, changed_lines, ig

def do_ini(module, filename, section=None, option=None, values=None,
state='present', exclusive=True, backup=False, no_extra_spaces=False,
ignore_spaces=False, create=True, allow_no_value=False, follow=False):
ignore_spaces=False, create=True, allow_no_value=False, modify_inactive_option=True, follow=False):

if section is not None:
section = to_text(section)
Expand Down Expand Up @@ -310,6 +317,12 @@ def do_ini(module, filename, section=None, option=None, values=None,
# Keep track of changed section_lines
changed_lines = [0] * len(section_lines)

# Determine whether to consider using commented out/inactive options or only active ones
if modify_inactive_option:
match_function = match_opt
else:
match_function = match_active_opt

# handling multiple instances of option=value when state is 'present' with/without exclusive is a bit complex
#
# 1. edit all lines where we have a option=value pair with a matching value in values[]
Expand All @@ -319,8 +332,8 @@ def do_ini(module, filename, section=None, option=None, values=None,

if state == 'present' and option:
for index, line in enumerate(section_lines):
if match_opt(option, line):
match = match_opt(option, line)
if match_function(option, line):
match = match_function(option, line)
if values and match.group(7) in values:
matched_value = match.group(7)
if not matched_value and allow_no_value:
Expand All @@ -343,14 +356,14 @@ def do_ini(module, filename, section=None, option=None, values=None,
# override option with no value to option with value if not allow_no_value
if len(values) > 0:
for index, line in enumerate(section_lines):
if not changed_lines[index] and match_opt(option, line):
if not changed_lines[index] and match_function(option, line):
newline = assignment_format % (option, values.pop(0))
(changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
if len(values) == 0:
break
# remove all remaining option occurrences from the rest of the section
for index in range(len(section_lines) - 1, 0, -1):
if not changed_lines[index] and match_opt(option, section_lines[index]):
if not changed_lines[index] and match_function(option, section_lines[index]):
del section_lines[index]
del changed_lines[index]
changed = True
Expand Down Expand Up @@ -394,7 +407,7 @@ def do_ini(module, filename, section=None, option=None, values=None,
section_lines = new_section_lines
elif not exclusive and len(values) > 0:
# delete specified option=value line(s)
new_section_lines = [i for i in section_lines if not (match_active_opt(option, i) and match_active_opt(option, i).group(6) in values)]
new_section_lines = [i for i in section_lines if not (match_active_opt(option, i) and match_active_opt(option, i).group(7) in values)]
if section_lines != new_section_lines:
changed = True
msg = 'option changed'
Expand Down Expand Up @@ -466,6 +479,7 @@ def main():
no_extra_spaces=dict(type='bool', default=False),
ignore_spaces=dict(type='bool', default=False),
allow_no_value=dict(type='bool', default=False),
modify_inactive_option=dict(type='bool', default=True),
create=dict(type='bool', default=True),
follow=dict(type='bool', default=False)
),
Expand All @@ -487,6 +501,7 @@ def main():
no_extra_spaces = module.params['no_extra_spaces']
ignore_spaces = module.params['ignore_spaces']
allow_no_value = module.params['allow_no_value']
modify_inactive_option = module.params['modify_inactive_option']
create = module.params['create']
follow = module.params['follow']

Expand All @@ -500,7 +515,7 @@ def main():

(changed, backup_file, diff, msg) = do_ini(
module, path, section, option, values, state, exclusive, backup,
no_extra_spaces, ignore_spaces, create, allow_no_value, follow)
no_extra_spaces, ignore_spaces, create, allow_no_value, modify_inactive_option, follow)

if not module.check_mode and os.path.exists(path):
file_args = module.load_file_common_arguments(module.params)
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/targets/ini_file/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@

- name: include tasks to test ignore_spaces
include_tasks: tests/05-ignore_spaces.yml

- name: include tasks to test modify_inactive_option
include_tasks: tests/06-modify_inactive_option.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

## testing modify_inactive_option option

- name: test-modify_inactive_option 1 - create test file
copy:
content: |

[section1]
# Uncomment the line below to enable foo
# foo = bar

dest: "{{ output_file }}"

- name: test-modify_inactive_option 1 - set value for foo with modify_inactive_option set to true
ini_file:
path: "{{ output_file }}"
section: section1
option: foo
value: bar
modify_inactive_option: true
register: result1

- name: test-modify_inactive_option 1 - read content from output file
slurp:
src: "{{ output_file }}"
register: output_content

- name: test-modify_inactive_option 1 - set expected content and get current ini file content
set_fact:
expected1: |

[section1]
# Uncomment the line below to enable foo
foo = bar

content1: "{{ output_content.content | b64decode }}"

- name: test-modify_inactive_option 1 - assert 'changed' is true, content is OK and option changed
assert:
that:
- result1 is changed
- result1.msg == 'option changed'
- content1 == expected1


- name: test-modify_inactive_option 2 - create test file
copy:
content: |

[section1]
# Uncomment the line below to enable foo
# foo = bar

dest: "{{ output_file }}"

- name: test-modify_inactive_option 2 - set value for foo with modify_inactive_option set to false
ini_file:
path: "{{ output_file }}"
section: section1
option: foo
value: bar
modify_inactive_option: false
register: result2

- name: test-modify_inactive_option 2 - read content from output file
slurp:
src: "{{ output_file }}"
register: output_content

- name: test-modify_inactive_option 2 - set expected content and get current ini file content
set_fact:
expected2: |

[section1]
foo = bar
# Uncomment the line below to enable foo
# foo = bar

content2: "{{ output_content.content | b64decode }}"

- name: test-modify_inactive_option 2 - assert 'changed' is true and content is OK and option added
assert:
that:
- result2 is changed
- result2.msg == 'option added'
- content2 == expected2


- name: test-modify_inactive_option 3 - remove foo=bar with modify_inactive_option set to true to ensure it doesn't have effect for removal
ini_file:
path: "{{ output_file }}"
section: section1
option: foo
value: bar
modify_inactive_option: true
state: absent
register: result3

- name: test-modify_inactive_option 3 - read content from output file
slurp:
src: "{{ output_file }}"
register: output_content

- name: test-modify_inactive_option 3 - set expected content and get current ini file content
set_fact:
expected3: |

[section1]
# Uncomment the line below to enable foo
# foo = bar

content3: "{{ output_content.content | b64decode }}"

- name: test-modify_inactive_option 3 - assert 'changed' is true and content is OK and active option removed
assert:
that:
- result3 is changed
- result3.msg == 'option changed'
- content3 == expected3