ini lookup - catch and handle duplicate key and missing section errors (#74629)

* Remove unused import
* Add integration tests for errors
* Cleanup formatting on existing tests
* Add changelog
This commit is contained in:
Sam Doran 2021-05-12 16:57:02 -04:00 committed by GitHub
parent 829c9c3d46
commit 0affe4d027
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 100 additions and 29 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- ini lookup - handle errors for duplicate keys and missing sections (https://github.com/ansible/ansible/issues/74601)

View file

@ -15,7 +15,7 @@ DOCUMENTATION = """
- "You can also read a property file which - in this case - does not contain section."
options:
_terms:
description: The key(s) to look up
description: The key(s) to look up.
required: True
type:
description: Type of the file. 'properties' refers to the Java properties files.
@ -67,7 +67,7 @@ from collections import defaultdict
from ansible.errors import AnsibleLookupError, AnsibleOptionsError
from ansible.module_utils.six.moves import configparser
from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.common._collections_compat import MutableSequence
from ansible.plugins.lookup import LookupBase
@ -167,8 +167,15 @@ class LookupModule(LookupBase):
config.write(contents)
config.seek(0, os.SEEK_SET)
self.cp.readfp(config)
var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
try:
self.cp.readfp(config)
except configparser.DuplicateOptionError as doe:
raise AnsibleLookupError("Duplicate option in '{file}': {error}".format(file=paramvals['file'], error=to_native(doe)))
try:
var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
except configparser.NoSectionError:
raise AnsibleLookupError("No section '{section}' in {file}".format(section=paramvals['section'], file=paramvals['file']))
if var is not None:
if isinstance(var, MutableSequence):
for v in var:

View file

@ -0,0 +1,3 @@
[reggae]
name = bob
name = marley

View file

@ -0,0 +1,3 @@
[reggae]
name = bob
NAME = marley

View file

@ -0,0 +1,2 @@
[all]
testhost ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}"

View file

@ -2,4 +2,5 @@
set -eux
ansible-playbook test_lookup_properties.yml -i ../../inventory -v "$@"
ansible-playbook test_lookup_properties.yml -i inventory -v "$@"
ansible-playbook test_errors.yml -i inventory -v "$@"

View file

@ -0,0 +1,50 @@
- name: Test INI lookup errors
hosts: testhost
tasks:
- name: Test for failure on Python 3
when: ansible_facts.python.version_info[0] >= 3
block:
- name: Lookup a file with duplicate keys
debug:
msg: "{{ lookup('ini', 'reggae', file='duplicate.ini', section='reggae') }}"
ignore_errors: yes
register: duplicate
- name: Lookup a file with keys that differ only in case
debug:
msg: "{{ lookup('ini', 'reggae', file='duplicate_case_check.ini', section='reggae') }}"
ignore_errors: yes
register: duplicate_case_sensitive
- name: Ensure duplicate key errers were handled properly
assert:
that:
- duplicate is failed
- "'Duplicate option in' in duplicate.msg"
- duplicate_case_sensitive is failed
- "'Duplicate option in' in duplicate_case_sensitive.msg"
- name: Lookup a file with a missing section
debug:
msg: "{{ lookup('ini', 'reggae', file='lookup.ini', section='missing') }}"
ignore_errors: yes
register: missing_section
- name: Ensure error was shown for a missing section
assert:
that:
- missing_section is failed
- "'No section' in missing_section.msg"
- name: Mix options type and push key out of order
debug:
msg: "{{ lookup('ini', 'file=lookup.ini', 'value1', section='value_section') }}"
register: bad_mojo
ignore_errors: yes
- name: Verify bad behavior reported an error
assert:
that:
- bad_mojo is failed
- '"No key to lookup was provided as first term with in string inline option" in bad_mojo.msg'

View file

@ -1,85 +1,88 @@
---
- name: "Lookup test"
hosts: "localhost"
# connection: local
- name: Lookup test
hosts: testhost
tasks:
- name: "read properties value"
set_fact:
test1: "{{lookup('ini', 'value1 type=properties file=lookup.properties')}}"
test2: "{{lookup('ini', 'value2', type='properties', file='lookup.properties')}}"
test_dot: "{{lookup('ini', 'value.dot', type='properties', file='lookup.properties')}}"
test1: "{{lookup('ini', 'value1 type=properties file=lookup.properties')}}"
test2: "{{lookup('ini', 'value2', type='properties', file='lookup.properties')}}"
test_dot: "{{lookup('ini', 'value.dot', type='properties', file='lookup.properties')}}"
field_with_space: "{{lookup('ini', 'field.with.space type=properties file=lookup.properties')}}"
- assert:
that: "{{item}} is defined"
with_items: [ 'test1', 'test2', 'test_dot', 'field_with_space' ]
- name: "read ini value"
set_fact:
value1_global: "{{lookup('ini', 'value1', section='global', file='lookup.ini')}}"
value2_global: "{{lookup('ini', 'value2', section='global', file='lookup.ini')}}"
value1_section1: "{{lookup('ini', 'value1', section='section1', file='lookup.ini')}}"
value1_global: "{{lookup('ini', 'value1', section='global', file='lookup.ini')}}"
value2_global: "{{lookup('ini', 'value2', section='global', file='lookup.ini')}}"
value1_section1: "{{lookup('ini', 'value1', section='section1', file='lookup.ini')}}"
field_with_unicode: "{{lookup('ini', 'unicode', section='global', file='lookup.ini')}}"
- debug: var={{item}}
with_items: [ 'value1_global', 'value2_global', 'value1_section1', 'field_with_unicode' ]
- assert:
that:
- "field_with_unicode == 'été indien où à château français ïîôû'"
- name: "read ini value from iso8859-15 file"
set_fact:
field_with_unicode: "{{lookup('ini', 'field_with_unicode section=global encoding=iso8859-1 file=lookup-8859-15.ini')}}"
- assert:
that:
- "field_with_unicode == 'été indien où à château français ïîôû'"
- name: "read ini value with section and regexp"
set_fact:
value_section: "{{lookup('ini', 'value[1-2] section=value_section file=lookup.ini re=true')}}"
other_section: "{{lookup('ini', 'other[1-2] section=other_section file=lookup.ini re=true')}}"
- debug: var={{item}}
with_items: [ 'value_section', 'other_section' ]
- assert:
that:
- "value_section == '1,2'"
- "other_section == '4,5'"
- name: "Reading unknown value"
set_fact:
unknown: "{{lookup('ini', 'unknown default=unknown section=section1 file=lookup.ini')}}"
- debug: var=unknown
- assert:
that:
- 'unknown == "unknown"'
- name: "Looping over section section1"
debug: msg="{{item}}"
with_ini: value[1-2] section=section1 file=lookup.ini re=true
register: _
- assert:
that:
- '_.results.0.item == "section1/value1"'
- '_.results.1.item == "section1/value2"'
- name: "Looping over section value_section"
debug: msg="{{item}}"
with_ini: value[1-2] section=value_section file=lookup.ini re=true
register: _
- assert:
that:
- '_.results.0.item == "1"'
- '_.results.1.item == "2"'
- debug: msg="{{item}}"
with_ini: value[1-2] section=section1 file=lookup.ini re=true
register: _
- assert:
that:
- '_.results.0.item == "section1/value1"'
- '_.results.1.item == "section1/value2"'
- name: capture bad behaviour
block:
- name: mix options type and push key out of order
debug: msg="{{ lookup('ini', 'file=lookup.ini', 'value1', section='value_section') }}"
register: bad_mojo
ignore_errors: true
- name: verify
assert:
that:
- bad_mojo is failed
- '"No key to lookup was provided as first term with in string inline option" in bad_mojo.msg'