ansible/test/integration/targets/iterators/tasks/main.yml
Ian Wienand 39945b8570 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
2019-05-29 13:23:02 -04:00

328 lines
7.7 KiB
YAML

# test code for iterating with lookup plugins
# (c) 2014, James Tanner <tanner.jc@gmail.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WITH_ITEMS
- name: test with_items
set_fact: "{{ item }}=moo"
with_items:
- 'foo'
- 'bar'
- debug: var=foo
- debug: var=bar
- name: verify with_items results
assert:
that:
- "foo == 'moo'"
- "bar == 'moo'"
# WITH_NESTED
- name: test with_nested
set_fact: "{{ item.0 + item.1 }}=x"
with_nested:
- [ 'a', 'b' ]
- [ 'c', 'd' ]
- debug: var=ac
- debug: var=ad
- debug: var=bc
- debug: var=bd
- name: verify with_nested results
assert:
that:
- "ac == 'x'"
- "ad == 'x'"
- "bc == 'x'"
- "bd == 'x'"
# WITH_SEQUENCE
- name: test with_sequence
set_fact: "{{ 'x' + item }}={{ item }}"
with_sequence: start=0 end=3
- name: test with_sequence backwards
set_fact: "{{ 'y' + item }}={{ item }}"
with_sequence: start=3 end=0 stride=-1
- name: verify with_sequence
assert:
that:
- "x0 == '0'"
- "x1 == '1'"
- "x2 == '2'"
- "x3 == '3'"
- "y3 == '3'"
- "y2 == '2'"
- "y1 == '1'"
- "y0 == '0'"
- name: test with_sequence not failing on count == 0
debug: msg='previously failed with backward counting error'
with_sequence: count=0
register: count_of_zero
- name: test with_sequence does 1 when start == end
debug: msg='should run once'
with_sequence: start=1 end=1
register: start_equal_end
- name: test with_sequence count 1
set_fact: "{{ 'x' + item }}={{ item }}"
with_sequence: count=1
register: count_of_one
- assert:
that:
- start_equal_end is not skipped
- count_of_zero is skipped
- count_of_one is not skipped
- name: test with_sequence shortcut syntax (end)
set_fact: "{{ 'ws_z_' + item }}={{ item }}"
with_sequence: '4'
- name: test with_sequence shortcut syntax (start-end/stride)
set_fact: "{{ 'ws_z_' + item }}=stride_{{ item }}"
with_sequence: '2-6/2'
- name: test with_sequence shortcut syntax (start-end:format)
set_fact: "{{ 'ws_z_' + item }}={{ item }}"
with_sequence: '7-8:host%02d'
- name: verify with_sequence shortcut syntax
assert:
that:
- "ws_z_1 == '1'"
- "ws_z_2 == 'stride_2'"
- "ws_z_3 == '3'"
- "ws_z_4 == 'stride_4'"
- "ws_z_6 == 'stride_6'"
- "ws_z_host07 == 'host07'"
- "ws_z_host08 == 'host08'"
# WITH_RANDOM_CHOICE
- name: test with_random_choice
set_fact: "random={{ item }}"
with_random_choice:
- "foo"
- "bar"
- name: verify with_random_choice
assert:
that:
- "random in ['foo', 'bar']"
# WITH_SUBELEMENTS
- name: test with_subelements
set_fact: "{{ '_'+ item.0.id + item.1 }}={{ item.1 }}"
with_subelements:
- "{{element_data}}"
- the_list
- name: verify with_subelements results
assert:
that:
- "_xf == 'f'"
- "_xd == 'd'"
- "_ye == 'e'"
- "_yf == 'f'"
- name: test with_subelements in subkeys
set_fact: "{{ '_'+ item.0.id + item.1 }}={{ item.1 }}"
with_subelements:
- "{{element_data}}"
- the.sub.key.list
- name: verify with_subelements in subkeys results
assert:
that:
- "_xq == 'q'"
- "_xr == 'r'"
- "_yi == 'i'"
- "_yo == 'o'"
- name: test with_subelements with missing key or subkey
set_fact: "{{ '_'+ item.0.id + item.1 }}={{ item.1 }}"
with_subelements:
- "{{element_data_missing}}"
- the.sub.key.list
- skip_missing: yes
register: _subelements_missing_subkeys
- debug: var=_subelements_missing_subkeys
- debug: var=_subelements_missing_subkeys.results|length
- name: verify with_subelements in subkeys results
assert:
that:
- _subelements_missing_subkeys.skipped is not defined
- _subelements_missing_subkeys.results|length == 2
- "_xk == 'k'"
- "_xl == 'l'"
# WITH_TOGETHER
- name: test with_together
#shell: echo {{ item }}
set_fact: "{{ item.0 }}={{ item.1 }}"
with_together:
- [ 'a', 'b', 'c', 'd' ]
- [ '1', '2', '3', '4' ]
- name: verify with_together results
assert:
that:
- "a == '1'"
- "b == '2'"
- "c == '3'"
- "d == '4'"
# WITH_FIRST_FOUND
- name: test with_first_found
#shell: echo {{ item }}
set_fact: "first_found={{ item }}"
with_first_found:
- "{{ role_path + '/files/does_not_exist' }}"
- "{{ role_path + '/files/foo1' }}"
- "{{ role_path + '/files/bar1' }}"
- name: set expected
set_fact: first_expected="{{ role_path + '/files/foo1' }}"
- name: set unexpected
set_fact: first_unexpected="{{ role_path + '/files/bar1' }}"
- name: verify with_first_found results
assert:
that:
- "first_found == first_expected"
- "first_found != first_unexpected"
# WITH_LINES
- name: test with_lines
#shell: echo "{{ item }}"
set_fact: "{{ item }}=set"
with_lines: for i in $(seq 1 5); do echo "l$i" ; done;
- name: verify with_lines results
assert:
that:
- "l1 == 'set'"
- "l2 == 'set'"
- "l3 == 'set'"
- "l4 == 'set'"
- "l5 == 'set'"
# WITH_INDEX
- name: create unindexed list
shell: for i in $(seq 1 5); do echo "x" ; done;
register: list_data
- name: create indexed list
set_fact: "{{ item[1] + item[0]|string }}=set"
with_indexed_items: "{{list_data.stdout_lines}}"
- name: verify with_indexed_items result
assert:
that:
- "x0 == 'set'"
- "x1 == 'set'"
- "x2 == 'set'"
- "x3 == 'set'"
- "x4 == 'set'"
# WITH_FLATTENED
- name: test with_flattened
set_fact: "{{ item }}=flattened"
with_flattened:
- [ 'a__' ]
- [ 'b__', ['c__', 'd__'] ]
- name: verify with_flattened results
assert:
that:
- "a__ == 'flattened'"
- "b__ == 'flattened'"
- "c__ == '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"