39945b8570
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
328 lines
7.7 KiB
YAML
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"
|