Make query with errors='ignore' return a blank list (#57038)
The jinja2 query() function (or lookup with wantslist=True, which is
the same thing) should always return a list.
However, if you combine a query with errors='ignore' and take the
error path, the current code returns a None value. This is important
in a case such as
- name: Conditional include file
import_tasks: '{{ item }}'
vars:
params:
files:
- path/file1.yaml
- path/file2.yaml
loop: "{{ q('first_found', params, errors='ignore') }}"
If neither file1.yaml or file2.yaml exist, this should do nothing by
returning an empty list to the loop. Currently if you run the above
task you'll get a rather unhelpful:
Invalid data passed to 'loop', it requires a list, got this instead: .
This change ensures that when a query ignores an error, it returns a
empty list. The errors='ignore' case is tested in several variants
with first_found. The extant (but deprecated) "skip: True" for
first_found doesn't seem to be explicitly tested; a test is added here
to avoid regressions before removal in 2.12.
This fixes a regression you'll hit if you follow the suggestion in the
deprecation message included with
e17a2b502d
to use errors=ignore over
"skip: True" for first_found. This change adds an example that points
out the query/lookup difference and also fixes the error message to
not mention the now deprecated "skip: True".
Closes #56775
This commit is contained in:
parent
b7f4e2a190
commit
39945b8570
3 changed files with 69 additions and 2 deletions
|
@ -44,6 +44,17 @@ EXAMPLES = """
|
||||||
- "bar.txt" # will be looked in files/ dir relative to role and/or play
|
- "bar.txt" # will be looked in files/ dir relative to role and/or play
|
||||||
- "/path/to/biz.txt"
|
- "/path/to/biz.txt"
|
||||||
|
|
||||||
|
- name: |
|
||||||
|
include tasks only if files exist. Note the use of query() to return
|
||||||
|
a blank list for the loop if no files are found.
|
||||||
|
import_tasks: '{{ item }}'
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files:
|
||||||
|
- path/tasks.yaml
|
||||||
|
- path/other_tasks.yaml
|
||||||
|
loop: "{{ q('first_found', params, errors='ignore') }}"
|
||||||
|
|
||||||
- name: |
|
- name: |
|
||||||
copy first existing file found to /some/file,
|
copy first existing file found to /some/file,
|
||||||
looking in relative directories from where the task is defined and
|
looking in relative directories from where the task is defined and
|
||||||
|
@ -166,5 +177,5 @@ class LookupModule(LookupBase):
|
||||||
return [path]
|
return [path]
|
||||||
if skip:
|
if skip:
|
||||||
return []
|
return []
|
||||||
raise AnsibleLookupError("No file was found when using first_found. Use the 'skip: true' option to allow this task to be skipped if no "
|
raise AnsibleLookupError("No file was found when using first_found. Use errors='ignore' to allow this task to be skipped if no "
|
||||||
"files are found")
|
"files are found")
|
||||||
|
|
|
@ -706,7 +706,7 @@ class Templar:
|
||||||
display.display(msg, log_only=True)
|
display.display(msg, log_only=True)
|
||||||
else:
|
else:
|
||||||
raise AnsibleError(to_native(msg))
|
raise AnsibleError(to_native(msg))
|
||||||
ran = None
|
ran = [] if wantlist else None
|
||||||
|
|
||||||
if ran and not allow_unsafe:
|
if ran and not allow_unsafe:
|
||||||
if wantlist:
|
if wantlist:
|
||||||
|
|
|
@ -270,3 +270,59 @@
|
||||||
- "b__ == 'flattened'"
|
- "b__ == 'flattened'"
|
||||||
- "c__ == 'flattened'"
|
- "c__ == 'flattened'"
|
||||||
- "d__ == 'flattened'"
|
- "d__ == 'flattened'"
|
||||||
|
|
||||||
|
|
||||||
|
# q(FIRST_FOUND)
|
||||||
|
- name: test q(first_found) with no files produces empty list
|
||||||
|
set_fact:
|
||||||
|
first_found_var: "{{ q('first_found', params, errors='ignore') }}"
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files: "not_a_file.yaml"
|
||||||
|
|
||||||
|
- name: verify q(first_found) result
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "first_found_var == []"
|
||||||
|
|
||||||
|
- name: test lookup(first_found) with no files produces empty string
|
||||||
|
set_fact:
|
||||||
|
first_found_var: "{{ lookup('first_found', params, errors='ignore') }}"
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files: "not_a_file.yaml"
|
||||||
|
|
||||||
|
- name: verify lookup(first_found) result
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "first_found_var == ''"
|
||||||
|
|
||||||
|
# NOTE: skip: True deprecated e17a2b502d6601be53c60d7ba1c627df419460c9, remove 2.12
|
||||||
|
- name: test first_found with no matches and skip=True does nothing
|
||||||
|
set_fact: "this_not_set={{ item }}"
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files:
|
||||||
|
- not/a/file.yaml
|
||||||
|
- another/non/file.yaml
|
||||||
|
skip: True
|
||||||
|
loop: "{{ q('first_found', params) }}"
|
||||||
|
|
||||||
|
- name: verify skip
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "this_not_set is not defined"
|
||||||
|
|
||||||
|
- name: test first_found with no matches and errors='ignore' skips in a loop
|
||||||
|
set_fact: "this_not_set={{ item }}"
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files:
|
||||||
|
- not/a/file.yaml
|
||||||
|
- another/non/file.yaml
|
||||||
|
loop: "{{ query('first_found', params, errors='ignore') }}"
|
||||||
|
|
||||||
|
- name: verify errors=ignore
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "this_not_set is not defined"
|
||||||
|
|
Loading…
Reference in a new issue