Ansible [WARNING]: The loop variable ‘item’ is already in use.

I made a simple change to an existing Ansible playbook. I used the include_role command to invoke another role. Since I was calling the role on a list of hosts that I had dynamically discovered at runtime, I used with_items to make the call iterate over the list.

Not good. I saw the following warning and error:

[WARNING]: The loop variable 'item' is already in use. You should set
the `loop_var` value in the `loop_control` option for the task to 
something else to avoid variable collisions and unexpected behavior.

fatal: [localhost]: FAILED! => {
 "failed": true,
 "msg": "The conditional check ''u16' not in item|json_query('Name')
failed. The error was: error while evaluating conditional ('u16' not 
in item|json_query('Name')): 'item' is undefined... }

After a bit of searching and reading docs, I figured out how to fix. But the docs and examples were not straightforward. I hope you will find a better explanation herein.

First, Ansible (I’m using 2.2.1) doesn’t handle nested with_items loops properly. There’s something special about the way item is handled such that using item in nested loops causes one of the expected values to be overwritten.

My outer loop:

- name: Use ci-destroy to clean unused VMs
  include_role:
    name: ci-destroy
  with_items:
    - "{{ my_vm_list }}"

ci-destroy is a role that we use to garbage collect VMs from test failures in our CI environment. Before this task, the code gathers a list of the orphan VMs in the environment. The ci-destroy role is called on each one.

The ci-destroy role is my inner loop. It contains, among other things:

- name: Remove several entries from /etc/hosts file
  lineinfile:
    dest: /etc/hosts
    line: "{{ item }}"
    state: absent
  with_items: "{{ line_list }}"

With the outer loop and the inner loop using {{ item }}, Ansible had a problem. WARNING and the ERROR, as shown above.

The fix? Use loop_control to specify a non-default variable name for the inner item variable name. In my case:

- name: Remove several entries from /etc/hosts file
  lineinfile:
    dest: /etc/hosts
    line: "{{ line_item }}"
    state: absent
  with_items: "{{ line_list }}"
  loop_control:
    loop_var: line_item

The changed lines are shown in red. Basically, I changed the inner loop such that it used line_item instead of item. Worked like a charm.

Advertisements