The Problem

You're running Ansible 2.9+ and seeing deprecation warnings:

bash
[DEPRECATION WARNING]: Using 'with_items' is deprecated. Use 'loop' with a list value instead. 
This feature will be removed in version 2.14. Deprecation warnings can be disabled by setting 
deprecation_warnings=False in ansible.cfg.

Your playbook still works, but you want to fix it before the deprecated syntax is removed entirely.

Why This Happens

Ansible 2.5 introduced the loop keyword as a unified replacement for: - with_items - with_list - with_dict - with_subelements - with_nested - with_sequence - with_together

The old syntax still works but is deprecated and will be removed.

Understanding the Difference

Old with_items syntax:

yaml
- name: Old style
  debug:
    msg: "{{ item }}"
  with_items:
    - item1
    - item2

New loop syntax:

yaml
- name: New style
  debug:
    msg: "{{ item }}"
  loop:
    - item1
    - item2

Migrating from with_items

Basic with_items Migration

Simple list migration:

```yaml # BEFORE - name: Install packages yum: name: "{{ item }}" state: present with_items: - nginx - mysql - redis

# AFTER - name: Install packages yum: name: "{{ item }}" state: present loop: - nginx - mysql redis ```

Variable-based list:

```yaml # BEFORE - name: Process items debug: msg: "{{ item }}" with_items: "{{ my_list }}"

# AFTER - name: Process items debug: msg: "{{ item }}" loop: "{{ my_list }}" ```

with_items with Flattening

with_items automatically flattens nested lists. loop does not:

```yaml # BEFORE - with_items flattens - name: Process debug: msg: "{{ item }}" with_items: - [a, b] - [c, d] # Results: a, b, c, d

# AFTER - must use flatten filter - name: Process debug: msg: "{{ item }}" loop: "{{ [[a, b], [c, d]] | flatten }}" ```

Or use explicit flattened list:

yaml
- name: Process
  debug:
    msg: "{{ item }}"
  loop:
    - a
    - b
    - c
    - d

with_dict Migration

Dictionary iteration requires filter:

```yaml # BEFORE - name: Process dict debug: msg: "Key: {{ item.key }} Value: {{ item.value }}" with_dict: user1: alice user2: bob

# AFTER - use dict2items - name: Process dict debug: msg: "Key: {{ item.key }} Value: {{ item.value }}" loop: "{{ {'user1': 'alice', 'user2': 'bob'} | dict2items }}" ```

with_subelements Migration

Nested structure iteration:

```yaml # BEFORE - name: Process subelements debug: msg: "User: {{ item.0.name }} Group: {{ item.1 }}" with_subelements: - users - groups

# AFTER - use subelements filter - name: Process subelements debug: msg: "User: {{ item.0.name }} Group: {{ item.1 }}" loop: "{{ users | subelements('groups') }}" ```

with_nested Migration

Cross-product iteration:

```yaml # BEFORE - name: Process combinations debug: msg: "{{ item[0] }} - {{ item[1] }}" with_nested: - [a, b] - [1, 2]

# AFTER - use product filter - name: Process combinations debug: msg: "{{ item[0] }} - {{ item[1] }}" loop: "{{ ['a', 'b'] | product([1, 2]) | list }}" ```

with_sequence Migration

Number sequences:

```yaml # BEFORE - name: Create numbered items debug: msg: "Item {{ item }}" with_sequence: start=1 end=5

# AFTER - use range - name: Create numbered items debug: msg: "Item {{ item }}" loop: "{{ range(1, 6) | list }}" ```

with_together Migration

Parallel iteration:

```yaml # BEFORE - name: Process together debug: msg: "{{ item[0] }} - {{ item[1] }}" with_together: - [a, b, c] - [1, 2, 3]

# AFTER - use zip filter - name: Process together debug: msg: "{{ item[0] }} - {{ item[1] }}" loop: "{{ ['a', 'b', 'c'] | zip([1, 2, 3]) | list }}" ```

Common Migration Pitfalls

Pitfall 1: Flattening Differences

with_items flattens, loop does not:

```yaml # This behaves differently vars: nested_list: - [a, b] - [c]

# BEFORE - outputs a, b, c with_items: "{{ nested_list }}"

# AFTER - outputs [a,b], [c] (wrong!) loop: "{{ nested_list }}"

# CORRECT loop: "{{ nested_list | flatten }}" ```

Pitfall 2: Empty Lists

Both handle empty lists gracefully, but verify:

yaml
- name: Handle empty
  debug:
    msg: "{{ item }}"
  loop: "{{ my_list | default([]) }}"
  when: my_list | default([]) | length > 0

Pitfall 3: Dictionary Key Access

Old dict syntax vs new:

```yaml # BEFORE with_dict: "{{ my_dict }}" # item.key and item.value available

# AFTER loop: "{{ my_dict | dict2items }}" # item.key and item.value available (same) ```

Verifying the Migration

Test converted playbooks:

```bash # Check syntax ansible-playbook playbook.yml --syntax-check

# Run in check mode ansible-playbook playbook.yml --check -v

# Run with deprecation warnings enabled ansible-playbook playbook.yml | grep -i deprecation ```

Suppressing Warnings Temporarily

While migrating, suppress warnings:

ini
# ansible.cfg - temporary during migration
[defaults]
deprecation_warnings = False

But migrate properly before Ansible 2.14+.

Prevention

Write new playbooks using loop:

yaml
# New playbook template
- hosts: all
  tasks:
    - name: Modern loop
      debug:
        msg: "{{ item }}"
      loop: "{{ items }}"
      loop_control:
        label: "{{ item.name | default(item) }}"

Add ansible-lint to catch deprecated syntax:

bash
ansible-lint playbook.yml
# Warns about with_items usage