mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
ini_file: add multiple options with same name to ini file (#3033)
* ini_file - prepare for fixing #273 - restructure tests - fix error message call: fail_json() takes 1 positional argument but 2 were given * ini_file - multiple values for one option (#273) - add module option 'exclusive' (boolean) for the abbility to add single option=value entries without overwriting existing options with the same name but different values - add abbility to define multiple options with the same name but different values * ini_file - add more tests for ini_file * ini_file - fix sanity tests * apply suggested changes: - rename 03-regressions.yml to 03-encoding.yml - fix typos - fix documentation * apply suggested changes: - test errors also for result is failed * apply suggested changes: - make state=absent also work with module option exclusive - add more tests for state=absent and module option exclusive * fix sanity test: - 02-values.yml:251:9: hyphens: too many spaces after hyphen * apply proposed changes * apply proposed changes from review - adjust version_added to 3.6.0 - small syntax change in changelog fragment
This commit is contained in:
parent
f7dba23e50
commit
25267b8094
7 changed files with 1902 additions and 599 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- ini_file - add module option ``exclusive`` (boolean) for the ability to add/remove single ``option=value`` entries without overwriting existing options with the same name but different values (https://github.com/ansible-collections/community.general/pull/3033).
|
||||||
|
- ini_file - add abbility to define multiple options with the same name but different values (https://github.com/ansible-collections/community.general/issues/273, https://github.com/ansible-collections/community.general/issues/1204).
|
|
@ -47,7 +47,18 @@ options:
|
||||||
description:
|
description:
|
||||||
- The string value to be associated with an I(option).
|
- The string value to be associated with an I(option).
|
||||||
- May be omitted when removing an I(option).
|
- May be omitted when removing an I(option).
|
||||||
|
- Mutually exclusive with I(values).
|
||||||
|
- I(value=v) is equivalent to I(values=[v]).
|
||||||
type: str
|
type: str
|
||||||
|
values:
|
||||||
|
description:
|
||||||
|
- The string value to be associated with an I(option).
|
||||||
|
- May be omitted when removing an I(option).
|
||||||
|
- Mutually exclusive with I(value).
|
||||||
|
- I(value=v) is equivalent to I(values=[v]).
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
version_added: 3.6.0
|
||||||
backup:
|
backup:
|
||||||
description:
|
description:
|
||||||
- Create a backup file including the timestamp information so you can get
|
- Create a backup file including the timestamp information so you can get
|
||||||
|
@ -56,10 +67,25 @@ options:
|
||||||
default: no
|
default: no
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- If set to C(absent) the option or section will be removed if present instead of created.
|
- If set to C(absent) and I(exclusive) set to C(yes) all matching I(option) lines are removed.
|
||||||
|
- If set to C(absent) and I(exclusive) set to C(no) the specified C(option=value) lines are removed,
|
||||||
|
but the other I(option)s with the same name are not touched.
|
||||||
|
- If set to C(present) and I(exclusive) set to C(no) the specified C(option=values) lines are added,
|
||||||
|
but the other I(option)s with the same name are not touched.
|
||||||
|
- If set to C(present) and I(exclusive) set to C(yes) all given C(option=values) lines will be
|
||||||
|
added and the other I(option)s with the same name are removed.
|
||||||
type: str
|
type: str
|
||||||
choices: [ absent, present ]
|
choices: [ absent, present ]
|
||||||
default: present
|
default: present
|
||||||
|
exclusive:
|
||||||
|
description:
|
||||||
|
- If set to C(yes) (default), all matching I(option) lines are removed when I(state=absent),
|
||||||
|
or replaced when I(state=present).
|
||||||
|
- If set to C(no), only the specified I(value(s)) are added when I(state=present),
|
||||||
|
or removed when I(state=absent), and existing ones are not modified.
|
||||||
|
type: bool
|
||||||
|
default: yes
|
||||||
|
version_added: 3.6.0
|
||||||
no_extra_spaces:
|
no_extra_spaces:
|
||||||
description:
|
description:
|
||||||
- Do not insert spaces before and after '=' symbol.
|
- Do not insert spaces before and after '=' symbol.
|
||||||
|
@ -103,6 +129,27 @@ EXAMPLES = r'''
|
||||||
option: temperature
|
option: temperature
|
||||||
value: cold
|
value: cold
|
||||||
backup: yes
|
backup: yes
|
||||||
|
|
||||||
|
- name: Add "beverage=lemon juice" is in section "[drinks]" in specified file
|
||||||
|
community.general.ini_file:
|
||||||
|
path: /etc/conf
|
||||||
|
section: drinks
|
||||||
|
option: beverage
|
||||||
|
value: lemon juice
|
||||||
|
mode: '0600'
|
||||||
|
state: present
|
||||||
|
exclusive: no
|
||||||
|
|
||||||
|
- name: Ensure multiple values "beverage=coke" and "beverage=pepsi" are in section "[drinks]" in specified file
|
||||||
|
community.general.ini_file:
|
||||||
|
path: /etc/conf
|
||||||
|
section: drinks
|
||||||
|
option: beverage
|
||||||
|
values:
|
||||||
|
- coke
|
||||||
|
- pepsi
|
||||||
|
mode: '0600'
|
||||||
|
state: present
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
@ -117,24 +164,37 @@ from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||||
|
|
||||||
def match_opt(option, line):
|
def match_opt(option, line):
|
||||||
option = re.escape(option)
|
option = re.escape(option)
|
||||||
return re.match('[#;]?( |\t)*%s( |\t)*(=|$)' % option, line)
|
return re.match('[#;]?( |\t)*(%s)( |\t)*(=|$)( |\t)*(.*)' % option, line)
|
||||||
|
|
||||||
|
|
||||||
def match_active_opt(option, line):
|
def match_active_opt(option, line):
|
||||||
option = re.escape(option)
|
option = re.escape(option)
|
||||||
return re.match('( |\t)*%s( |\t)*(=|$)' % option, line)
|
return re.match('( |\t)*(%s)( |\t)*(=|$)( |\t)*(.*)' % option, line)
|
||||||
|
|
||||||
|
|
||||||
def do_ini(module, filename, section=None, option=None, value=None,
|
def update_section_line(changed, section_lines, index, changed_lines, newline, msg):
|
||||||
state='present', backup=False, no_extra_spaces=False, create=True,
|
option_changed = section_lines[index] != newline
|
||||||
allow_no_value=False):
|
changed = changed or option_changed
|
||||||
|
if option_changed:
|
||||||
|
msg = 'option changed'
|
||||||
|
section_lines[index] = newline
|
||||||
|
changed_lines[index] = 1
|
||||||
|
return (changed, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def do_ini(module, filename, section=None, option=None, values=None,
|
||||||
|
state='present', exclusive=True, backup=False, no_extra_spaces=False,
|
||||||
|
create=True, allow_no_value=False):
|
||||||
|
|
||||||
if section is not None:
|
if section is not None:
|
||||||
section = to_text(section)
|
section = to_text(section)
|
||||||
if option is not None:
|
if option is not None:
|
||||||
option = to_text(option)
|
option = to_text(option)
|
||||||
if value is not None:
|
|
||||||
value = to_text(value)
|
# deduplicate entries in values
|
||||||
|
values_unique = []
|
||||||
|
[values_unique.append(to_text(value)) for value in values if value not in values_unique and value is not None]
|
||||||
|
values = values_unique
|
||||||
|
|
||||||
diff = dict(
|
diff = dict(
|
||||||
before='',
|
before='',
|
||||||
|
@ -145,7 +205,7 @@ def do_ini(module, filename, section=None, option=None, value=None,
|
||||||
|
|
||||||
if not os.path.exists(filename):
|
if not os.path.exists(filename):
|
||||||
if not create:
|
if not create:
|
||||||
module.fail_json(rc=257, msg='Destination %s does not exist !' % filename)
|
module.fail_json(rc=257, msg='Destination %s does not exist!' % filename)
|
||||||
destpath = os.path.dirname(filename)
|
destpath = os.path.dirname(filename)
|
||||||
if not os.path.exists(destpath) and not module.check_mode:
|
if not os.path.exists(destpath) and not module.check_mode:
|
||||||
os.makedirs(destpath)
|
os.makedirs(destpath)
|
||||||
|
@ -185,74 +245,134 @@ def do_ini(module, filename, section=None, option=None, value=None,
|
||||||
section = fake_section_name
|
section = fake_section_name
|
||||||
|
|
||||||
within_section = not section
|
within_section = not section
|
||||||
section_start = 0
|
section_start = section_end = 0
|
||||||
msg = 'OK'
|
msg = 'OK'
|
||||||
if no_extra_spaces:
|
if no_extra_spaces:
|
||||||
assignment_format = u'%s=%s\n'
|
assignment_format = u'%s=%s\n'
|
||||||
else:
|
else:
|
||||||
assignment_format = u'%s = %s\n'
|
assignment_format = u'%s = %s\n'
|
||||||
|
|
||||||
|
option_no_value_present = False
|
||||||
|
|
||||||
non_blank_non_comment_pattern = re.compile(to_text(r'^[ \t]*([#;].*)?$'))
|
non_blank_non_comment_pattern = re.compile(to_text(r'^[ \t]*([#;].*)?$'))
|
||||||
|
|
||||||
|
before = after = []
|
||||||
|
section_lines = []
|
||||||
|
|
||||||
for index, line in enumerate(ini_lines):
|
for index, line in enumerate(ini_lines):
|
||||||
|
# find start and end of section
|
||||||
if line.startswith(u'[%s]' % section):
|
if line.startswith(u'[%s]' % section):
|
||||||
within_section = True
|
within_section = True
|
||||||
section_start = index
|
section_start = index
|
||||||
elif line.startswith(u'['):
|
elif line.startswith(u'['):
|
||||||
if within_section:
|
if within_section:
|
||||||
|
section_end = index
|
||||||
|
break
|
||||||
|
|
||||||
|
before = ini_lines[0:section_start]
|
||||||
|
section_lines = ini_lines[section_start:section_end]
|
||||||
|
after = ini_lines[section_end:len(ini_lines)]
|
||||||
|
|
||||||
|
# Keep track of changed section_lines
|
||||||
|
changed_lines = [0] * len(section_lines)
|
||||||
|
|
||||||
|
# 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[]
|
||||||
|
# 2. edit all the remaing 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_opt(option, line):
|
||||||
|
match = match_opt(option, line)
|
||||||
|
if values and match.group(6) in values:
|
||||||
|
matched_value = match.group(6)
|
||||||
|
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(changed, section_lines, index, changed_lines, 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(changed, section_lines, index, changed_lines, 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_active_opt(option, section_lines[index]):
|
||||||
|
newline = assignment_format % (option, values.pop(0))
|
||||||
|
(changed, msg) = update_section_line(changed, section_lines, index, changed_lines, 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_active_opt(option, section_lines[index]):
|
||||||
|
del section_lines[index]
|
||||||
|
del changed_lines[index]
|
||||||
|
changed = True
|
||||||
|
msg = 'option changed'
|
||||||
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
# insert missing option line at the end of the section
|
# insert missing option line(s) at the end of the section
|
||||||
for i in range(index, 0, -1):
|
for index in range(len(section_lines), 0, -1):
|
||||||
# search backwards for previous non-blank or non-comment line
|
# search backwards for previous non-blank or non-comment line
|
||||||
if not non_blank_non_comment_pattern.match(ini_lines[i - 1]):
|
if not non_blank_non_comment_pattern.match(section_lines[index - 1]):
|
||||||
if option and value is not None:
|
if option and values:
|
||||||
ini_lines.insert(i, assignment_format % (option, value))
|
# 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'
|
msg = 'option added'
|
||||||
changed = True
|
changed = True
|
||||||
elif option and value is None and allow_no_value:
|
elif element is None and allow_no_value:
|
||||||
ini_lines.insert(i, '%s\n' % option)
|
# 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'
|
msg = 'option added'
|
||||||
changed = True
|
changed = True
|
||||||
break
|
break
|
||||||
elif state == 'absent' and not option:
|
|
||||||
# remove the entire section
|
if state == 'absent':
|
||||||
del ini_lines[section_start:index]
|
if option:
|
||||||
|
if exclusive:
|
||||||
|
# delete all option line(s) with given option and ignore value
|
||||||
|
new_section_lines = [line for line in section_lines if not (match_active_opt(option, line))]
|
||||||
|
if section_lines != new_section_lines:
|
||||||
|
changed = True
|
||||||
|
msg = 'option changed'
|
||||||
|
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)]
|
||||||
|
if section_lines != new_section_lines:
|
||||||
|
changed = True
|
||||||
|
msg = 'option changed'
|
||||||
|
section_lines = new_section_lines
|
||||||
|
else:
|
||||||
|
# drop the entire section
|
||||||
|
section_lines = []
|
||||||
msg = 'section removed'
|
msg = 'section removed'
|
||||||
changed = True
|
changed = True
|
||||||
break
|
|
||||||
else:
|
# reassemble the ini_lines after manipulation
|
||||||
if within_section and option:
|
ini_lines = before + section_lines + after
|
||||||
if state == 'present':
|
|
||||||
# change the existing option line
|
|
||||||
if match_opt(option, line):
|
|
||||||
if value is None and allow_no_value:
|
|
||||||
newline = u'%s\n' % option
|
|
||||||
else:
|
|
||||||
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(u'['):
|
|
||||||
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
|
# remove the fake section line
|
||||||
del ini_lines[0]
|
del ini_lines[0]
|
||||||
|
@ -261,9 +381,10 @@ def do_ini(module, filename, section=None, option=None, value=None,
|
||||||
if not within_section and state == 'present':
|
if not within_section and state == 'present':
|
||||||
ini_lines.append(u'[%s]\n' % section)
|
ini_lines.append(u'[%s]\n' % section)
|
||||||
msg = 'section and option added'
|
msg = 'section and option added'
|
||||||
if option and value is not None:
|
if option and values:
|
||||||
|
for value in values:
|
||||||
ini_lines.append(assignment_format % (option, value))
|
ini_lines.append(assignment_format % (option, value))
|
||||||
elif option and value is None and allow_no_value:
|
elif option and not values and allow_no_value:
|
||||||
ini_lines.append(u'%s\n' % option)
|
ini_lines.append(u'%s\n' % option)
|
||||||
else:
|
else:
|
||||||
msg = 'only section added'
|
msg = 'only section added'
|
||||||
|
@ -303,12 +424,17 @@ def main():
|
||||||
section=dict(type='str', required=True),
|
section=dict(type='str', required=True),
|
||||||
option=dict(type='str'),
|
option=dict(type='str'),
|
||||||
value=dict(type='str'),
|
value=dict(type='str'),
|
||||||
|
values=dict(type='list', elements='str'),
|
||||||
backup=dict(type='bool', default=False),
|
backup=dict(type='bool', default=False),
|
||||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||||
|
exclusive=dict(type='bool', default=True),
|
||||||
no_extra_spaces=dict(type='bool', default=False),
|
no_extra_spaces=dict(type='bool', default=False),
|
||||||
allow_no_value=dict(type='bool', default=False),
|
allow_no_value=dict(type='bool', default=False),
|
||||||
create=dict(type='bool', default=True)
|
create=dict(type='bool', default=True)
|
||||||
),
|
),
|
||||||
|
mutually_exclusive=[
|
||||||
|
['value', 'values']
|
||||||
|
],
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
@ -317,16 +443,23 @@ def main():
|
||||||
section = module.params['section']
|
section = module.params['section']
|
||||||
option = module.params['option']
|
option = module.params['option']
|
||||||
value = module.params['value']
|
value = module.params['value']
|
||||||
|
values = module.params['values']
|
||||||
state = module.params['state']
|
state = module.params['state']
|
||||||
|
exclusive = module.params['exclusive']
|
||||||
backup = module.params['backup']
|
backup = module.params['backup']
|
||||||
no_extra_spaces = module.params['no_extra_spaces']
|
no_extra_spaces = module.params['no_extra_spaces']
|
||||||
allow_no_value = module.params['allow_no_value']
|
allow_no_value = module.params['allow_no_value']
|
||||||
create = module.params['create']
|
create = module.params['create']
|
||||||
|
|
||||||
if state == 'present' and not allow_no_value and value is None:
|
if state == 'present' and not allow_no_value and value is None and not values:
|
||||||
module.fail_json("Parameter 'value' must be defined if state=present and allow_no_value=False")
|
module.fail_json(msg="Parameter 'value(s)' must be defined if state=present and allow_no_value=False.")
|
||||||
|
|
||||||
(changed, backup_file, diff, msg) = do_ini(module, path, section, option, value, state, backup, no_extra_spaces, create, allow_no_value)
|
if value is not None:
|
||||||
|
values = [value]
|
||||||
|
elif values is None:
|
||||||
|
values = []
|
||||||
|
|
||||||
|
(changed, backup_file, diff, msg) = do_ini(module, path, section, option, values, state, exclusive, backup, no_extra_spaces, create, allow_no_value)
|
||||||
|
|
||||||
if not module.check_mode and os.path.exists(path):
|
if not module.check_mode and os.path.exists(path):
|
||||||
file_args = module.load_file_common_arguments(module.params)
|
file_args = module.load_file_common_arguments(module.params)
|
||||||
|
|
|
@ -23,545 +23,31 @@
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
- name: record the output directory
|
- name: record the output directory
|
||||||
set_fact: output_file={{ remote_tmp_dir }}/foo.ini
|
|
||||||
|
|
||||||
- name: add "fav=lemonade" is in section "[drinks]" in specified file
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
option: fav
|
|
||||||
value: lemonade
|
|
||||||
register: result1
|
|
||||||
|
|
||||||
- name: verify ini_file 'changed' is true
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result1 is changed
|
|
||||||
- result1.msg == 'section and option added'
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
set_fact:
|
||||||
expected1: |
|
output_file: "{{ remote_tmp_dir }}/foo.ini"
|
||||||
|
non_existing_file: "{{ remote_tmp_dir }}/bar.ini"
|
||||||
|
|
||||||
[drinks]
|
- name: include tasks
|
||||||
fav = lemonade
|
|
||||||
content1: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: Verify content of ini file is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- content1 == expected1
|
|
||||||
|
|
||||||
- name: add "fav=lemonade" is in section "[drinks]" again
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
option: fav
|
|
||||||
value: lemonade
|
|
||||||
register: result2
|
|
||||||
|
|
||||||
- name: Ensure unchanged
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result2 is not changed
|
|
||||||
- result2.msg == 'OK'
|
|
||||||
|
|
||||||
- name: Ensure "beverage=coke" is in section "[drinks]"
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
option: beverage
|
|
||||||
value: coke
|
|
||||||
register: result3
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
expected3: |
|
|
||||||
|
|
||||||
[drinks]
|
|
||||||
fav = lemonade
|
|
||||||
beverage = coke
|
|
||||||
content3: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert 'changed' is true and content is OK
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result3 is changed
|
|
||||||
- result3.msg == 'option added'
|
|
||||||
- content3 == expected3
|
|
||||||
|
|
||||||
- name: Remove option "beverage=coke"
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
option: beverage
|
|
||||||
state: absent
|
|
||||||
register: result4
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: get ini file content
|
|
||||||
set_fact:
|
|
||||||
content4: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert changed and content is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result4 is changed
|
|
||||||
- result4.msg == 'option changed'
|
|
||||||
- content4 == expected1
|
|
||||||
|
|
||||||
- name: remove section 'drinks'
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
state: absent
|
|
||||||
register: result5
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content5: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert changed and content is empty
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result5 is changed
|
|
||||||
- result5.msg == 'section removed'
|
|
||||||
- content5 == "\n"
|
|
||||||
|
|
||||||
# allow_no_value
|
|
||||||
|
|
||||||
- name: test allow_no_value
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: skip-name
|
|
||||||
allow_no_value: yes
|
|
||||||
register: result6
|
|
||||||
|
|
||||||
- name: assert section and option added
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result6 is changed
|
|
||||||
- result6.msg == 'section and option added'
|
|
||||||
|
|
||||||
- name: test allow_no_value idempotency
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: skip-name
|
|
||||||
allow_no_value: yes
|
|
||||||
register: result6
|
|
||||||
|
|
||||||
- name: assert 'changed' false
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result6 is not changed
|
|
||||||
- result6.msg == 'OK'
|
|
||||||
|
|
||||||
- name: test create empty section
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: new_empty_section
|
|
||||||
allow_no_value: yes
|
|
||||||
register: result6a
|
|
||||||
|
|
||||||
- name: assert section added
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result6a is changed
|
|
||||||
- result6a.msg == 'only section added'
|
|
||||||
|
|
||||||
- name: test create empty section idempotency
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: new_empty_section
|
|
||||||
allow_no_value: yes
|
|
||||||
register: result6a
|
|
||||||
|
|
||||||
- name: assert 'changed' false
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result6a is not changed
|
|
||||||
- result6a.msg == 'OK'
|
|
||||||
|
|
||||||
- name: test remove empty section
|
|
||||||
ini_file:
|
|
||||||
state: absent
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: new_empty_section
|
|
||||||
allow_no_value: yes
|
|
||||||
|
|
||||||
- name: test allow_no_value with loop
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: "{{ item.o }}"
|
|
||||||
value: "{{ item.v | d(omit) }}"
|
|
||||||
allow_no_value: yes
|
|
||||||
with_items:
|
|
||||||
- { o: "skip-name-resolve" }
|
|
||||||
- { o: "max_connections", v: "500" }
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content7: "{{ output_content.content | b64decode }}"
|
|
||||||
expected7: |
|
|
||||||
|
|
||||||
[mysqld]
|
|
||||||
skip-name
|
|
||||||
skip-name-resolve
|
|
||||||
max_connections = 500
|
|
||||||
|
|
||||||
- name: Verify content of ini file is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- content7 == expected7
|
|
||||||
|
|
||||||
- name: change option with no value to option with value
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: skip-name
|
|
||||||
value: myvalue
|
|
||||||
register: result8
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content8: "{{ output_content.content | b64decode }}"
|
|
||||||
expected8: |
|
|
||||||
|
|
||||||
[mysqld]
|
|
||||||
skip-name = myvalue
|
|
||||||
skip-name-resolve
|
|
||||||
max_connections = 500
|
|
||||||
|
|
||||||
- name: assert 'changed' and msg 'option changed' and content is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result8 is changed
|
|
||||||
- result8.msg == 'option changed'
|
|
||||||
- content8 == expected8
|
|
||||||
|
|
||||||
- name: change option with value to option with no value
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: skip-name
|
|
||||||
allow_no_value: yes
|
|
||||||
register: result9
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content9: "{{ output_content.content | b64decode }}"
|
|
||||||
expected9: |
|
|
||||||
|
|
||||||
[mysqld]
|
|
||||||
skip-name
|
|
||||||
skip-name-resolve
|
|
||||||
max_connections = 500
|
|
||||||
|
|
||||||
- name: assert 'changed' and msg 'option changed' and content is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result9 is changed
|
|
||||||
- result9.msg == 'option changed'
|
|
||||||
- content9 == expected9
|
|
||||||
|
|
||||||
- name: Remove option with no value
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: mysqld
|
|
||||||
option: skip-name-resolve
|
|
||||||
state: absent
|
|
||||||
register: result10
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content10: "{{ output_content.content | b64decode }}"
|
|
||||||
expected10: |
|
|
||||||
|
|
||||||
[mysqld]
|
|
||||||
skip-name
|
|
||||||
max_connections = 500
|
|
||||||
|
|
||||||
- name: assert 'changed' and msg 'option changed' and content is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result10 is changed
|
|
||||||
- result10.msg == 'option changed'
|
|
||||||
- content10 == expected10
|
|
||||||
|
|
||||||
- name: Clean test file
|
|
||||||
copy:
|
|
||||||
content: ""
|
|
||||||
dest: "{{ output_file }}"
|
|
||||||
force: yes
|
|
||||||
|
|
||||||
- name: Ensure "beverage=coke" is created within no section
|
|
||||||
ini_file:
|
|
||||||
section:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
option: beverage
|
|
||||||
value: coke
|
|
||||||
register: result11
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
expected11: "beverage = coke\n\n"
|
|
||||||
content11: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert 'changed' is true and content is OK (no section)
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result11 is changed
|
|
||||||
- result11.msg == 'option added'
|
|
||||||
- content11 == expected11
|
|
||||||
|
|
||||||
- name: Ensure "beverage=coke" is modified as "beverage=water" within no section
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
option: beverage
|
|
||||||
value: water
|
|
||||||
section:
|
|
||||||
register: result12
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
expected12: "beverage = water\n\n"
|
|
||||||
|
|
||||||
content12: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert 'changed' is true and content is OK (no section)
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result12 is changed
|
|
||||||
- result12.msg == 'option changed'
|
|
||||||
- content12 == expected12
|
|
||||||
|
|
||||||
- name: remove option 'beverage' within no section
|
|
||||||
ini_file:
|
|
||||||
section:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
option: beverage
|
|
||||||
state: absent
|
|
||||||
register: result13
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: get current ini file content
|
|
||||||
set_fact:
|
|
||||||
content13: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: assert changed (no section)
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- result13 is changed
|
|
||||||
- result13.msg == 'option changed'
|
|
||||||
- content13 == "\n"
|
|
||||||
|
|
||||||
- name: Check add option without section before existing section
|
|
||||||
block:
|
block:
|
||||||
- name: Add option with section
|
|
||||||
ini_file:
|
- name: include tasks to perform basic tests
|
||||||
|
include_tasks: tests/00-basic.yml
|
||||||
|
|
||||||
|
- name: reset output file
|
||||||
|
file:
|
||||||
path: "{{ output_file }}"
|
path: "{{ output_file }}"
|
||||||
section: drinks
|
|
||||||
option: beverage
|
|
||||||
value: water
|
|
||||||
- name: Add option without section
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section:
|
|
||||||
option: like
|
|
||||||
value: tea
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
expected14: |
|
|
||||||
like = tea
|
|
||||||
|
|
||||||
[drinks]
|
|
||||||
beverage = water
|
|
||||||
content14: "{{ output_content.content | b64decode }}"
|
|
||||||
|
|
||||||
- name: Verify content of ini file is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- content14 == expected14
|
|
||||||
|
|
||||||
- name: Check add option with empty string value
|
|
||||||
block:
|
|
||||||
- name: Remove drinks
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: drinks
|
|
||||||
state: absent
|
|
||||||
- name: Remove tea
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section:
|
|
||||||
option: like
|
|
||||||
value: tea
|
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
# See https://github.com/ansible-collections/community.general/issues/3031
|
- name: include tasks to perform tests with parameter "value"
|
||||||
- name: Tests with empty strings
|
include_tasks: tests/01-value.yml
|
||||||
ini_file:
|
|
||||||
|
- name: reset output file
|
||||||
|
file:
|
||||||
path: "{{ output_file }}"
|
path: "{{ output_file }}"
|
||||||
section: "{{ item.section | d('extensions') }}"
|
state: absent
|
||||||
option: "{{ item.option }}"
|
|
||||||
value: ""
|
|
||||||
allow_no_value: "{{ item.no_value | d(omit) }}"
|
|
||||||
loop:
|
|
||||||
- option: evolve
|
|
||||||
- option: regress
|
|
||||||
- section: foobar
|
|
||||||
option: foo
|
|
||||||
no_value: true
|
|
||||||
- option: improve
|
|
||||||
no_value: true
|
|
||||||
|
|
||||||
- name: read content from output file
|
- name: include tasks to perform tests with parameter "values"
|
||||||
slurp:
|
include_tasks: tests/02-values.yml
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
- name: include tasks to test regressions
|
||||||
set_fact:
|
include_tasks: tests/03-encoding.yml
|
||||||
expected15: "\n[extensions]\nevolve = \nregress = \nimprove = \n[foobar]\nfoo = \n"
|
|
||||||
content15: "{{ output_content.content | b64decode }}"
|
|
||||||
- debug: var=content15
|
|
||||||
- name: Verify content of ini file is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- content15 == expected15
|
|
||||||
|
|
||||||
- name: Create starting ini file
|
|
||||||
copy:
|
|
||||||
# The content below is the following text file with BOM:
|
|
||||||
# [section1]
|
|
||||||
# var1=aaa
|
|
||||||
# var2=bbb
|
|
||||||
# [section2]
|
|
||||||
# var3=ccc
|
|
||||||
content: !!binary |
|
|
||||||
77u/W3NlY3Rpb24xXQp2YXIxPWFhYQp2YXIyPWJiYgpbc2VjdGlvbjJdCnZhcjM9Y2NjCg==
|
|
||||||
dest: "{{ output_file }}"
|
|
||||||
- name: Test ini breakage
|
|
||||||
ini_file:
|
|
||||||
path: "{{ output_file }}"
|
|
||||||
section: section1
|
|
||||||
option: var4
|
|
||||||
value: 0
|
|
||||||
|
|
||||||
- name: read content from output file
|
|
||||||
slurp:
|
|
||||||
src: "{{ output_file }}"
|
|
||||||
register: output_content
|
|
||||||
|
|
||||||
- name: set expected content and get current ini file content
|
|
||||||
set_fact:
|
|
||||||
expected16: "[section1]\nvar1=aaa\nvar2=bbb\nvar4 = 0\n[section2]\nvar3=ccc\n"
|
|
||||||
content16: "{{ output_content.content | b64decode }}"
|
|
||||||
- debug:
|
|
||||||
var: content16
|
|
||||||
- name: Verify content of ini file is as expected
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- content16 == expected16
|
|
||||||
|
|
||||||
# Regression test for https://github.com/ansible-collections/community.general/pull/2578#issuecomment-868092282
|
|
||||||
- name: Create UTF-8 test file
|
|
||||||
copy:
|
|
||||||
content: !!binary |
|
|
||||||
W2FwcDptYWluXQphdmFpbGFibGVfbGFuZ3VhZ2VzID0gZW4gZnIgZXMgZGUgcHQgamEgbHQgemhf
|
|
||||||
VFcgaWQgZGEgcHRfQlIgcnUgc2wgaXQgbmxfTkwgdWsgdGEgc2kgY3MgbmIgaHUKIyBGdWxsIGxh
|
|
||||||
bmd1YWdlIG5hbWVzIGluIG5hdGl2ZSBsYW5ndWFnZSAoY29tbWEgc2VwYXJhdGVkKQphdmFpbGFi
|
|
||||||
bGVfbGFuZ3VhZ2VzX2Z1bGwgPSBFbmdsaXNoLCBGcmFuw6dhaXMsIEVzcGHDsW9sLCBEZXV0c2No
|
|
||||||
LCBQb3J0dWd1w6pzLCDml6XmnKzoqp4sIExpZXR1dm9zLCDkuK3mlocsIEluZG9uZXNpYSwgRGFu
|
|
||||||
c2ssIFBvcnR1Z3XDqnMgKEJyYXNpbCksINCg0YPRgdGB0LrQuNC5LCBTbG92ZW7FocSNaW5hLCBJ
|
|
||||||
dGFsaWFubywgTmVkZXJsYW5kcywg0KPQutGA0LDRl9C90YHRjNC60LAsIOCupOCuruCuv+CutOCv
|
|
||||||
jSwg4LeD4LeS4LaC4LeE4La9LCDEjGVza3ksIEJva23DpWwsIE1hZ3lhcgo=
|
|
||||||
dest: '{{ output_file }}'
|
|
||||||
- name: Add entries
|
|
||||||
ini_file:
|
|
||||||
section: "{{ item.section }}"
|
|
||||||
option: "{{ item.option }}"
|
|
||||||
value: "{{ item.value }}"
|
|
||||||
path: '{{ output_file }}'
|
|
||||||
create: true
|
|
||||||
loop:
|
|
||||||
- section: app:main
|
|
||||||
option: sqlalchemy.url
|
|
||||||
value: postgresql://app:secret@database/app
|
|
||||||
- section: handler_filelog
|
|
||||||
option: args
|
|
||||||
value: (sys.stderr,)
|
|
||||||
- section: handler_filelog
|
|
||||||
option: class
|
|
||||||
value: StreamHandler
|
|
||||||
- section: handler_exc_handler
|
|
||||||
option: args
|
|
||||||
value: (sys.stderr,)
|
|
||||||
- section: båz
|
|
||||||
option: fföø
|
|
||||||
value: ḃâŗ
|
|
||||||
- section: båz
|
|
||||||
option: fföø
|
|
||||||
value: bar
|
|
||||||
|
|
38
tests/integration/targets/ini_file/tasks/tests/00-basic.yml
Normal file
38
tests/integration/targets/ini_file/tasks/tests/00-basic.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
## basiscs
|
||||||
|
|
||||||
|
- name: test-basic 1 - specify both "value" and "values" and fail
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: fav
|
||||||
|
value: lemonade
|
||||||
|
values:
|
||||||
|
- coke
|
||||||
|
- sprite
|
||||||
|
register: result_basic_1
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: test-basic 1 - verify error message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result_basic_1 is not changed
|
||||||
|
- result_basic_1 is failed
|
||||||
|
- "result_basic_1.msg == 'parameters are mutually exclusive: value|values'"
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-basic 2 - set "create=no" on non-existing file and fail
|
||||||
|
ini_file:
|
||||||
|
path: "{{ non_existing_file }}"
|
||||||
|
section: food
|
||||||
|
create: false
|
||||||
|
value: banana
|
||||||
|
register: result_basic_2
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: test-basic 2 - verify error message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result_basic_2 is not changed
|
||||||
|
- result_basic_2 is failed
|
||||||
|
- result_basic_2.msg == "Destination {{ non_existing_file }} does not exist!"
|
589
tests/integration/targets/ini_file/tasks/tests/01-value.yml
Normal file
589
tests/integration/targets/ini_file/tasks/tests/01-value.yml
Normal file
|
@ -0,0 +1,589 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
## testing value
|
||||||
|
|
||||||
|
- name: test-value 1 - set "state=present" and "value=null" and "allow_no_value=false" and fail
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: cars
|
||||||
|
option: audi
|
||||||
|
value: null
|
||||||
|
allow_no_value: false
|
||||||
|
register: result_value_1
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: test-value 1 - verify error message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result_value_1 is not changed
|
||||||
|
- result_value_1 is failed
|
||||||
|
- result_value_1.msg == "Parameter 'value(s)' must be defined if state=present and allow_no_value=False."
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 2 - set "state=present" and omit "value" and "allow_no_value=false" and fail
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: cars
|
||||||
|
option: audi
|
||||||
|
allow_no_value: false
|
||||||
|
register: result_value_2
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: test-value 2 - verify error message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result_value_2 is not changed
|
||||||
|
- result_value_2 is failed
|
||||||
|
- result_value_2.msg == "Parameter 'value(s)' must be defined if state=present and allow_no_value=False."
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 3 - add "fav=lemonade" in section "[drinks]" in specified file
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: fav
|
||||||
|
value: lemonade
|
||||||
|
register: result3
|
||||||
|
|
||||||
|
- name: test-value 3 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 3 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected3: |
|
||||||
|
|
||||||
|
[drinks]
|
||||||
|
fav = lemonade
|
||||||
|
content3: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 3 - Verify content of ini file is as expected and ini_file 'changed' is true
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result3 is changed
|
||||||
|
- result3.msg == 'section and option added'
|
||||||
|
- content3 == expected3
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 4 - add "fav=lemonade" is in section "[drinks]" again
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: fav
|
||||||
|
value: lemonade
|
||||||
|
register: result4
|
||||||
|
|
||||||
|
- name: test-value 4 - Ensure unchanged
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result4 is not changed
|
||||||
|
- result4.msg == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 5 - Ensure "beverage=coke" is in section "[drinks]"
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: beverage
|
||||||
|
value: coke
|
||||||
|
register: result5
|
||||||
|
|
||||||
|
- name: test-value 5 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 5 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected5: |
|
||||||
|
|
||||||
|
[drinks]
|
||||||
|
fav = lemonade
|
||||||
|
beverage = coke
|
||||||
|
content5: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 5 - assert 'changed' is true and content is OK
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result5 is changed
|
||||||
|
- result5.msg == 'option added'
|
||||||
|
- content5 == expected5
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 6 - Remove option "beverage=coke"
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: beverage
|
||||||
|
state: absent
|
||||||
|
register: result6
|
||||||
|
|
||||||
|
- name: test-value 6 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 6 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected6: |
|
||||||
|
|
||||||
|
[drinks]
|
||||||
|
fav = lemonade
|
||||||
|
content6: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 6 - assert 'changed' is true and content is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result6 is changed
|
||||||
|
- result6.msg == 'option changed'
|
||||||
|
- content6 == expected6
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 7 - remove section 'drinks'
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
state: absent
|
||||||
|
register: result7
|
||||||
|
|
||||||
|
- name: test-value 7 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 7 - get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content7: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 7 - assert 'changed' is true and content is empty
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result7 is changed
|
||||||
|
- result7.msg == 'section removed'
|
||||||
|
- content7 == "\n"
|
||||||
|
|
||||||
|
|
||||||
|
# allow_no_value
|
||||||
|
|
||||||
|
- name: test-value 8 - test allow_no_value
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: skip-name
|
||||||
|
allow_no_value: yes
|
||||||
|
register: result8
|
||||||
|
|
||||||
|
- name: test-value 8 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 8 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content8: "{{ output_content.content | b64decode }}"
|
||||||
|
expected8: |
|
||||||
|
|
||||||
|
[mysqld]
|
||||||
|
skip-name
|
||||||
|
|
||||||
|
- name: test-value 8 - assert 'changed' is true and section and option added
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result8 is changed
|
||||||
|
- result8.msg == 'section and option added'
|
||||||
|
- content8 == expected8
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 9 - test allow_no_value idempotency
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: skip-name
|
||||||
|
allow_no_value: yes
|
||||||
|
register: result9
|
||||||
|
|
||||||
|
- name: test-value 9 - assert 'changed' is false
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result9 is not changed
|
||||||
|
- result9.msg == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 10 - test create empty section
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: new_empty_section
|
||||||
|
allow_no_value: yes
|
||||||
|
register: result10
|
||||||
|
|
||||||
|
- name: test-value 10 - assert 'changed' is true and section added
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result10 is changed
|
||||||
|
- result10.msg == 'only section added'
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 11 - test create empty section idempotency
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: new_empty_section
|
||||||
|
allow_no_value: yes
|
||||||
|
register: result11
|
||||||
|
|
||||||
|
- name: test-value 11 - assert 'changed' is false
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result11 is not changed
|
||||||
|
- result11.msg == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 12 - test remove empty section
|
||||||
|
ini_file:
|
||||||
|
state: absent
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: new_empty_section
|
||||||
|
allow_no_value: yes
|
||||||
|
|
||||||
|
- name: test-value 12 - test allow_no_value with loop
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: "{{ item.o }}"
|
||||||
|
value: "{{ item.v | d(omit) }}"
|
||||||
|
allow_no_value: yes
|
||||||
|
loop:
|
||||||
|
- { o: "skip-name-resolve" }
|
||||||
|
- { o: "max_connections", v: "500" }
|
||||||
|
|
||||||
|
- name: test-value 12 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 12 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content12: "{{ output_content.content | b64decode }}"
|
||||||
|
expected12: |
|
||||||
|
|
||||||
|
[mysqld]
|
||||||
|
skip-name
|
||||||
|
skip-name-resolve
|
||||||
|
max_connections = 500
|
||||||
|
|
||||||
|
- name: test-value 12 - Verify content of ini file is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- content12 == expected12
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 13 - change option with no value to option with value
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: skip-name
|
||||||
|
value: myvalue
|
||||||
|
register: result13
|
||||||
|
|
||||||
|
- name: test-value 13 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 13 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content13: "{{ output_content.content | b64decode }}"
|
||||||
|
expected13: |
|
||||||
|
|
||||||
|
[mysqld]
|
||||||
|
skip-name = myvalue
|
||||||
|
skip-name-resolve
|
||||||
|
max_connections = 500
|
||||||
|
|
||||||
|
- name: test-value 13 - assert 'changed' and msg 'option changed' and content is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result13 is changed
|
||||||
|
- result13.msg == 'option changed'
|
||||||
|
- content13 == expected13
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 14 - change option with value to option with no value
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: skip-name
|
||||||
|
allow_no_value: yes
|
||||||
|
register: result14
|
||||||
|
|
||||||
|
- name: test-value 14 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 14 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content14: "{{ output_content.content | b64decode }}"
|
||||||
|
expected14: |
|
||||||
|
|
||||||
|
[mysqld]
|
||||||
|
skip-name
|
||||||
|
skip-name-resolve
|
||||||
|
max_connections = 500
|
||||||
|
|
||||||
|
- name: test-value 14 - assert 'changed' is true and msg 'option changed' and content is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result14 is changed
|
||||||
|
- result14.msg == 'option changed'
|
||||||
|
- content14 == expected14
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 15 - Remove option with no value
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: mysqld
|
||||||
|
option: skip-name-resolve
|
||||||
|
state: absent
|
||||||
|
register: result15
|
||||||
|
|
||||||
|
- name: test-value 15 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 15 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content15: "{{ output_content.content | b64decode }}"
|
||||||
|
expected15: |
|
||||||
|
|
||||||
|
[mysqld]
|
||||||
|
skip-name
|
||||||
|
max_connections = 500
|
||||||
|
|
||||||
|
- name: test-value 15 - assert 'changed' is true and msg 'option changed' and content is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result15 is changed
|
||||||
|
- result15.msg == 'option changed'
|
||||||
|
- content15 == expected15
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 16 - Clean test file
|
||||||
|
copy:
|
||||||
|
content: ""
|
||||||
|
dest: "{{ output_file }}"
|
||||||
|
force: yes
|
||||||
|
|
||||||
|
- name: test-value 16 - Ensure "beverage=coke" is created within no section
|
||||||
|
ini_file:
|
||||||
|
section:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
option: beverage
|
||||||
|
value: coke
|
||||||
|
register: result16
|
||||||
|
|
||||||
|
- name: test-value 16 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 16 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected16: |+
|
||||||
|
beverage = coke
|
||||||
|
|
||||||
|
content16: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 16 - assert 'changed' is true and content is OK (no section)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result16 is changed
|
||||||
|
- result16.msg == 'option added'
|
||||||
|
- content16 == expected16
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 17 - Ensure "beverage=coke" is modified as "beverage=water" within no section
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
option: beverage
|
||||||
|
value: water
|
||||||
|
section:
|
||||||
|
register: result17
|
||||||
|
|
||||||
|
- name: test-value 17 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 17 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected17: |+
|
||||||
|
beverage = water
|
||||||
|
|
||||||
|
content17: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 17 - assert 'changed' is true and content is OK (no section)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result17 is changed
|
||||||
|
- result17.msg == 'option changed'
|
||||||
|
- content17 == expected17
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 18 - remove option 'beverage' within no section
|
||||||
|
ini_file:
|
||||||
|
section:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
option: beverage
|
||||||
|
state: absent
|
||||||
|
register: result18
|
||||||
|
|
||||||
|
- name: test-value 18 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 18 - get current ini file content
|
||||||
|
set_fact:
|
||||||
|
content18: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 18 - assert 'changed' is true and option is removed (no section)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result18 is changed
|
||||||
|
- result18.msg == 'option changed'
|
||||||
|
- content18 == "\n"
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 19 - Check add option without section before existing section
|
||||||
|
block:
|
||||||
|
- name: test-value 19 - Add option with section
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
option: beverage
|
||||||
|
value: water
|
||||||
|
- name: test-value 19 - Add option without section
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section:
|
||||||
|
option: like
|
||||||
|
value: tea
|
||||||
|
|
||||||
|
- name: test-value 19 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 19 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected19: |
|
||||||
|
like = tea
|
||||||
|
|
||||||
|
[drinks]
|
||||||
|
beverage = water
|
||||||
|
content19: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 19 - Verify content of ini file is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- content19 == expected19
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 20 - Check add option with empty string value
|
||||||
|
block:
|
||||||
|
- name: test-value 20 - Remove drinks
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: drinks
|
||||||
|
state: absent
|
||||||
|
- name: test-value 20 - Remove tea
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section:
|
||||||
|
option: like
|
||||||
|
value: tea
|
||||||
|
state: absent
|
||||||
|
# See https://github.com/ansible-collections/community.general/issues/3031
|
||||||
|
- name: test-value 20 - Tests with empty strings
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: "{{ item.section | d('extensions') }}"
|
||||||
|
option: "{{ item.option }}"
|
||||||
|
value: ""
|
||||||
|
allow_no_value: "{{ item.no_value | d(omit) }}"
|
||||||
|
loop:
|
||||||
|
- option: evolve
|
||||||
|
- option: regress
|
||||||
|
- section: foobar
|
||||||
|
option: foo
|
||||||
|
no_value: true
|
||||||
|
- option: improve
|
||||||
|
no_value: true
|
||||||
|
|
||||||
|
- name: test-value 20 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 20 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected20: |+
|
||||||
|
|
||||||
|
[extensions]
|
||||||
|
evolve =
|
||||||
|
regress =
|
||||||
|
improve =
|
||||||
|
[foobar]
|
||||||
|
foo =
|
||||||
|
content20: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 20 - Verify content of ini file is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- content20 == expected20
|
||||||
|
|
||||||
|
|
||||||
|
- name: test-value 21 - Create starting ini file
|
||||||
|
copy:
|
||||||
|
# The content below is the following text file with BOM:
|
||||||
|
# [section1]
|
||||||
|
# var1=aaa
|
||||||
|
# var2=bbb
|
||||||
|
# [section2]
|
||||||
|
# var3=ccc
|
||||||
|
content: !!binary |
|
||||||
|
77u/W3NlY3Rpb24xXQp2YXIxPWFhYQp2YXIyPWJiYgpbc2VjdGlvbjJdCnZhcjM9Y2NjCg==
|
||||||
|
dest: "{{ output_file }}"
|
||||||
|
|
||||||
|
- name: test-value 21 - Test ini breakage
|
||||||
|
ini_file:
|
||||||
|
path: "{{ output_file }}"
|
||||||
|
section: section1
|
||||||
|
option: var4
|
||||||
|
value: 0
|
||||||
|
register: result21
|
||||||
|
|
||||||
|
- name: test-value 21 - read content from output file
|
||||||
|
slurp:
|
||||||
|
src: "{{ output_file }}"
|
||||||
|
register: output_content
|
||||||
|
|
||||||
|
- name: test-value 21 - set expected content and get current ini file content
|
||||||
|
set_fact:
|
||||||
|
expected21: |
|
||||||
|
[section1]
|
||||||
|
var1=aaa
|
||||||
|
var2=bbb
|
||||||
|
var4 = 0
|
||||||
|
[section2]
|
||||||
|
var3=ccc
|
||||||
|
content21: "{{ output_content.content | b64decode }}"
|
||||||
|
|
||||||
|
- name: test-value 21 - Verify content of ini file is as expected
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result21 is changed
|
||||||
|
- result21.msg == 'option added'
|
||||||
|
- content21 == expected21
|
1013
tests/integration/targets/ini_file/tasks/tests/02-values.yml
Normal file
1013
tests/integration/targets/ini_file/tasks/tests/02-values.yml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
# Regression test for https://github.com/ansible-collections/community.general/pull/2578#issuecomment-868092282
|
||||||
|
- name: Create UTF-8 test file
|
||||||
|
copy:
|
||||||
|
content: !!binary |
|
||||||
|
W2FwcDptYWluXQphdmFpbGFibGVfbGFuZ3VhZ2VzID0gZW4gZnIgZXMgZGUgcHQgamEgbHQgemhf
|
||||||
|
VFcgaWQgZGEgcHRfQlIgcnUgc2wgaXQgbmxfTkwgdWsgdGEgc2kgY3MgbmIgaHUKIyBGdWxsIGxh
|
||||||
|
bmd1YWdlIG5hbWVzIGluIG5hdGl2ZSBsYW5ndWFnZSAoY29tbWEgc2VwYXJhdGVkKQphdmFpbGFi
|
||||||
|
bGVfbGFuZ3VhZ2VzX2Z1bGwgPSBFbmdsaXNoLCBGcmFuw6dhaXMsIEVzcGHDsW9sLCBEZXV0c2No
|
||||||
|
LCBQb3J0dWd1w6pzLCDml6XmnKzoqp4sIExpZXR1dm9zLCDkuK3mlocsIEluZG9uZXNpYSwgRGFu
|
||||||
|
c2ssIFBvcnR1Z3XDqnMgKEJyYXNpbCksINCg0YPRgdGB0LrQuNC5LCBTbG92ZW7FocSNaW5hLCBJ
|
||||||
|
dGFsaWFubywgTmVkZXJsYW5kcywg0KPQutGA0LDRl9C90YHRjNC60LAsIOCupOCuruCuv+CutOCv
|
||||||
|
jSwg4LeD4LeS4LaC4LeE4La9LCDEjGVza3ksIEJva23DpWwsIE1hZ3lhcgo=
|
||||||
|
dest: '{{ output_file }}'
|
||||||
|
- name: Add entries
|
||||||
|
ini_file:
|
||||||
|
section: "{{ item.section }}"
|
||||||
|
option: "{{ item.option }}"
|
||||||
|
value: "{{ item.value }}"
|
||||||
|
path: '{{ output_file }}'
|
||||||
|
create: true
|
||||||
|
loop:
|
||||||
|
- section: app:main
|
||||||
|
option: sqlalchemy.url
|
||||||
|
value: postgresql://app:secret@database/app
|
||||||
|
- section: handler_filelog
|
||||||
|
option: args
|
||||||
|
value: (sys.stderr,)
|
||||||
|
- section: handler_filelog
|
||||||
|
option: class
|
||||||
|
value: StreamHandler
|
||||||
|
- section: handler_exc_handler
|
||||||
|
option: args
|
||||||
|
value: (sys.stderr,)
|
||||||
|
- section: båz
|
||||||
|
option: fföø
|
||||||
|
value: ḃâŗ
|
||||||
|
- section: båz
|
||||||
|
option: fföø
|
||||||
|
value: bar
|
Loading…
Reference in a new issue