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 value: xxxxxxxxxxxxxxxxxxxx
mode: '0600' mode: '0600'
state: present 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 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 # 2. edit all the remaining lines where we have a matching option
# 3. delete 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 # 4. insert missing option line(s) at the end of the section
if state == 'present':
if state == 'present' and option: if option:
for index, line in enumerate(section_lines): for index, line in enumerate(section_lines):
if match_function(option, line): if match_function(option, line):
match = match_function(option, line) match = match_function(option, line)
if values and match.group(8) in values: if values and match.group(8) in values:
matched_value = match.group(8) matched_value = match.group(8)
if not matched_value and allow_no_value: 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) # replace existing option with no value line(s)
newline = u'%s\n' % option 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 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 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': if exclusive and not allow_no_value:
# insert missing option line(s) at the end of the section # override option with no value to option with value if not allow_no_value
for index in range(len(section_lines), 0, -1): if len(values) > 0:
# search backwards for previous non-blank or non-comment line for index, line in enumerate(section_lines):
if not non_blank_non_comment_pattern.match(section_lines[index - 1]): if not changed_lines[index] and match_function(option, line):
if option and values: newline = assignment_format % (option, values.pop(0))
# insert option line(s) (changed, msg) = update_section_line(option, changed, section_lines, index, changed_lines, ignore_spaces, newline, msg)
for element in values[::-1]: if len(values) == 0:
# items are added backwards, so traverse the list backwards to not confuse the user break
# otherwise some of their options might appear in reverse order for whatever fancy reason ¯\_(ツ)_/¯ # remove all remaining option occurrences from the rest of the section
if element is not None: for index in range(len(section_lines) - 1, 0, -1):
# insert option=value line if not changed_lines[index] and match_function(
section_lines.insert(index, assignment_format % (option, element)) option, section_lines[index]
msg = 'option added' ):
changed = True del section_lines[index]
elif element is None and allow_no_value: del changed_lines[index]
# insert option with no value line changed = True
section_lines.insert(index, u'%s\n' % option) msg = 'option changed'
msg = 'option added'
changed = True # insert missing option line(s) at the end of the section
elif option and not values and allow_no_value and not option_no_value_present: for index in range(len(section_lines), 0, -1):
# insert option with no value line(s) # search backwards for previous non-blank or non-comment line
section_lines.insert(index, u'%s\n' % option) if not non_blank_non_comment_pattern.match(section_lines[index - 1]):
msg = 'option added' if values:
changed = True # insert option line(s)
break 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 state == 'absent':
if option: if option:
@ -539,11 +561,20 @@ def do_ini(module, filename, section=None, section_has_values=None, option=None,
for value in condition['values']: for value in condition['values']:
if value not in values: if value not in values:
values.append(value) values.append(value)
if option and values: if option:
for value in values: if values:
ini_lines.append(assignment_format % (option, value)) for value in values:
elif option and not values and allow_no_value: ini_lines.append(assignment_format % (option, value))
ini_lines.append(u'%s\n' % option) 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: else:
msg = 'only section added' msg = 'only section added'
changed = True 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