4

I'm trying to transform some fields of the items of a list in an Ansible Playbook. Here is the simplest reproduction path, skipping the transformation. The result should be identical to the users variable.

---
# Run with:
# ansible-playbook -i "localhost," loop3.yml

- hosts: localhost
  connection: local
  gather_facts: false
  vars:
    users:
      - name: paul
        uid: 1
      - name: pete
        uid: 2
  tasks:
    - set_fact:
      args:
        useritem:
          name: '{{ item.name }}'
          uid:  '{{ item.uid }}'
      with_items:
        - users
      register: sf_result

    - debug: var=sf_result

    - set_fact:
        userslist: "{{ sf_result.results | map(attribute='ansible_facts.useritem') | list }}"

    - debug: var=userslist

I get this error:

TASK [set_fact useritem={u'name': u'{{ item.name }}', u'uid': u'{{ item.uid }}'}] ***
fatal: [localhost]: FAILED! => {"failed": true, "msg": "ERROR! 'unicode object' has no attribute 'name'"}

There are several examples very close to what I needbut I could find no working example using set_fact along with with_items and items as a map.

I've tried Ansible 1.9.2, 1.9.4, and 2.0.0-0.6.rc1 with different error messages but no more success. Ansible 2 should allow skipping the second call to set_fact but the error happens before getting there.

techraf
  • 64,883
  • 27
  • 193
  • 198
Laurent Caillette
  • 1,281
  • 2
  • 14
  • 20

2 Answers2

7

I thought I did read somewhere that with_items accepts a bare variable name, but it's not the case.

The program runs as expected using:

with_items: "{{ users }}"
techraf
  • 64,883
  • 27
  • 193
  • 198
Laurent Caillette
  • 1,281
  • 2
  • 14
  • 20
  • The problem was in `with_items:` clause in original code, on the `- users` line - this was creating a single-item list, with the single item itself being the `users` list. Your revised code works and also avoids a deprecation warning. – RichVel Oct 16 '16 at 08:39
0

Referencing simple variables

After you define a variable, use Jinja2 syntax to reference it. Jinja2 variables use double curly braces. For example, the expression users goes to {{ users }} demonstrates the most basic form of variable substitution. You can use Jinja2 syntax in playbooks. For example:

with_items: "{{ users }}"

and also

loop: "{{ users }}"

Now, the following parameters can be used to loop through an array/dictionary/list.

NOTE: When possible, Ansible recommends using the loop parameter, as the loop parameter is meant to supersede the with_items option.

with_items:

Ansible with_items is a lookup type plugin that is used to return list items passed into it. When we pass a list of items to a task, then the task will be performed for all items in that list. If a high-level item has also another list, then that list will be flattened and Ansible will not perform recursion for it. This feature is not available in it. Because that is done by another plugin named list lookup. You can use it to achieve recursion.

Also, you can pass multiple entries in a single item to pass data to parameters when you are running a task that needs more than one parameter like while adding a user, you might need to pass userid, name, groups, etc. This flexibility makes it more suitable in real-world scenarios.

- name: with_items
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_items: "{{ items }}"

- name: with_items -> loop
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ items|flatten(levels=1) }}"

Comparing loop and with_*

  • The with_ keywords rely on Lookup plugins - even items is a lookup.

  • The loop keyword is equivalent to with_list and is the best choice for simple loops.

  • The loop keyword will not accept a string as input, see Ensuring list input for loop: using query rather than lookup.

  • Generally speaking, any use of with_* covered in Migrating from with_X to loop can be updated to use loop.

  • Be careful when changing with_items to loop, as with_items performed implicit single-level flattening. You may need to use flatten(1) with loop to match the exact outcome. For example, to get the same output as:

with_items:
  - 1
  - [2,3]
  - 4

you would need

loop: "{{ [1, [2, 3], 4] | flatten(1) }}"
  • Any with_* statement that requires using lookup within a loop should not be converted to use the loop keyword. For example, instead of doing:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
samnoon
  • 1,340
  • 2
  • 13
  • 23