This commit is contained in:
Amos Yuen 2025-07-30 05:11:54 -04:00 committed by GitHub
commit 4a5cb44897
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 246 additions and 66 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- "ini_file - fixes adding or replacing a whole section (https://github.com/ansible-collections/community.general/pull/10288)."

View file

@ -254,6 +254,16 @@ EXAMPLES = r"""
value: xxxxxxxxxxxxxxxxxxxx
mode: '0600'
state: present
- name: Add or replace whole section
community.general.ini_file:
path: /etc/wireguard/wg0.conf
section: Peer
value: |
AllowedIps = 10.4.0.11/32
PublicKey = xxxxxxxxxxxxxxxxxxxx
mode: '0600'
state: present
"""
import io
@ -427,72 +437,84 @@ def do_ini(module, filename, section=None, section_has_values=None, option=None,
# 2. edit all the remaining lines where we have a matching option
# 3. delete remaining lines where we have a matching option
# 4. insert missing option line(s) at the end of the section
if state == 'present' and option:
for index, line in enumerate(section_lines):
if match_function(option, line):
match = match_function(option, line)
if values and match.group(8) in values:
matched_value = match.group(8)
if not matched_value and allow_no_value:
if state == 'present':
if option:
for index, line in enumerate(section_lines):
if match_function(option, line):
match = match_function(option, line)
if values and match.group(8) in values:
matched_value = match.group(8)
if not matched_value and allow_no_value:
# replace existing option with no value line(s)
newline = u'%s\n' % option
option_no_value_present = True
else:
# replace existing option=value line(s)
newline = assignment_format % (option, matched_value)
(changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
values.remove(matched_value)
elif not values and allow_no_value:
# replace existing option with no value line(s)
newline = u'%s\n' % option
(changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
option_no_value_present = True
else:
# replace existing option=value line(s)
newline = assignment_format % (option, matched_value)
(changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
values.remove(matched_value)
elif not values and allow_no_value:
# replace existing option with no value line(s)
newline = u'%s\n' % option
(changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
option_no_value_present = True
break
if state == 'present' and exclusive and not allow_no_value:
# 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_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_function(option, section_lines[index]):
del section_lines[index]
del changed_lines[index]
changed = True
msg = 'option changed'
if state == 'present':
# insert missing option line(s) at the end of the section
for index in range(len(section_lines), 0, -1):
# search backwards for previous non-blank or non-comment line
if not non_blank_non_comment_pattern.match(section_lines[index - 1]):
if option and values:
# insert option line(s)
for element in values[::-1]:
# items are added backwards, so traverse the list backwards to not confuse the user
# otherwise some of their options might appear in reverse order for whatever fancy reason ¯\_(ツ)_/¯
if element is not None:
# insert option=value line
section_lines.insert(index, assignment_format % (option, element))
msg = 'option added'
changed = True
elif element is None and allow_no_value:
# insert option with no value line
section_lines.insert(index, u'%s\n' % option)
msg = 'option added'
changed = True
elif option and not values and allow_no_value and not option_no_value_present:
# insert option with no value line(s)
section_lines.insert(index, u'%s\n' % option)
msg = 'option added'
changed = True
break
if exclusive and not allow_no_value:
# 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_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_function(
option, section_lines[index]
):
del section_lines[index]
del changed_lines[index]
changed = True
msg = 'option changed'
# insert missing option line(s) at the end of the section
for index in range(len(section_lines), 0, -1):
# search backwards for previous non-blank or non-comment line
if not non_blank_non_comment_pattern.match(section_lines[index - 1]):
if values:
# insert option line(s)
for element in values[::-1]:
# items are added backwards, so traverse the list backwards to not confuse the user
# otherwise some of their options might appear in reverse order for whatever fancy reason ¯\_(ツ)_/¯
if element is not None:
# insert option=value line
section_lines.insert(
index, assignment_format % (option, element)
)
msg = 'option added'
changed = True
elif element is None and allow_no_value:
# insert option with no value line
section_lines.insert(index, u'%s\n' % option)
msg = 'option added'
changed = True
elif allow_no_value and not option_no_value_present :
# insert option with no value line(s)
section_lines.insert(index, u'%s\n' % option)
msg = 'option added'
changed = True
break
elif within_section and len(section_lines) > 0 and len(values) > 0:
original = ''.join(section_lines[1:])
replacement = ''.join(values)
if not replacement.endswith('\n'):
replacement += '\n'
if original != replacement:
section_lines = [section_lines[0], replacement]
msg = 'section replaced'
changed = True
if state == 'absent':
if option:
@ -539,11 +561,20 @@ def do_ini(module, filename, section=None, section_has_values=None, option=None,
for value in condition['values']:
if value not in values:
values.append(value)
if option and values:
for value in values:
ini_lines.append(assignment_format % (option, value))
elif option and not values and allow_no_value:
ini_lines.append(u'%s\n' % option)
if option:
if values:
for value in values:
ini_lines.append(assignment_format % (option, value))
elif not values and allow_no_value:
ini_lines.append('%s\n' % option)
else:
msg = 'only section added'
elif len(values) > 0:
replacement = ''.join(values)
if not replacement.endswith('\n'):
replacement += '\n'
ini_lines.append(replacement)
msg = 'section added'
else:
msg = 'only section added'
changed = True

View file

@ -0,0 +1,147 @@
---
# 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 section selection
- name: test-section 1 - Create starting ini file
copy:
content: |
[drinks]
fav = lemonade
beverage = orange juice
dest: "{{ output_file }}"
- name: test-section 1 - Add new block
ini_file:
dest: "{{ output_file }}"
section: food
value: |
fav = hamburger
beverage = None
state: present
register: result1
- name: test-section 1 - Read modified file
slurp:
src: "{{ output_file }}"
register: output_content
- name: test-section 1 - Create expected result
set_fact:
expected1: |
[drinks]
fav = lemonade
beverage = orange juice
car = volvo
[food]
fav = hamburger
beverage = None
output1: "{{ output_content.content | b64decode }}"
- name: test-section 1 - Section was added at end
assert:
that:
- result1 is changed
- result1.msg == 'section added'
- output1 == expected1
# ----------------
- name: test-section 2 - Create starting ini file
copy:
content: |
[drinks]
fav = lemonade
beverage = orange juice
[drinks]
fav = lemonade
beverage = pineapple juice
dest: "{{ output_file }}"
- name: test-section 2 - Modify starting ini file
ini_file:
dest: "{{ output_file }}"
section: drinks
section_has_values:
- option: beverage
value: pineapple juice
value: |
fav = lemonade
car = volvo
state: present
register: result1
- name: test-section 2 - Read modified file
slurp:
src: "{{ output_file }}"
register: output_content
- name: test-section 2 - Create expected result
set_fact:
expected1: |
[drinks]
fav = lemonade
beverage = orange juice
[drinks]
fav = lemonade
car = volvo
output1: "{{ output_content.content | b64decode }}"
- name: test-section 2 - Second section was replaced with value
assert:
that:
- result1 is changed
- result1.msg == 'section replaced'
- output1 == expected1
# ----------------
- name: test-section 3 - Create starting ini file
copy:
content: |
[drinks]
fav = lemonade
beverage = orange juice
[drinks]
fav = lemonade
beverage = pineapple juice
dest: "{{ output_file }}"
- name: test-section 3 - Modify starting ini file
ini_file:
dest: "{{ output_file }}"
section: drinks
section_has_values:
- option: beverage
value: pineapple juice
state: absent
register: result1
- name: test-section 3 - Read modified file
slurp:
src: "{{ output_file }}"
register: output_content
- name: test-section 3 - Create expected result
set_fact:
expected1: |
[drinks]
fav = lemonade
beverage = orange juice
output1: "{{ output_content.content | b64decode }}"
- name: test-section 3 - Section was removed for matching section
assert:
that:
- result1 is changed
- result1.msg == 'section removed'
- output1 == expected1