AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Compare Lists: difference, intersect, union & symmetric

By Luca Berton · Published 2024-01-01 · Category: installation

How to compare two lists in Ansible. Use difference, intersect, union, and symmetric_difference filters to find common, missing, and unique items.

Introduction

When working with Ansible playbooks, comparing two lists is a common task, especially for validating data consistency in automation workflows. However, users often encounter issues with filters like length, particularly in older Ansible versions or due to syntax ambiguities. This guide demonstrates how to effectively compare lists, ensuring they match in both size and content.

---

See also: Enhancing Ansible Role Development with Best Practices with ansible-later

The Scenario: Comparing Two Lists

Consider the following two lists:

list_one:
  - { name: "foo" }
  - { name: "bar" }
  - { name: "baz" }
  - { name: "qux" }
  - { name: "quux" }

list_two: - { name: "foo" } - { name: "bar" } - { name: "baz" } - { name: "quux" }

The goal is to: Verify that both lists are of equal length. Check that all elements in list_one are present in list_two, and vice versa. Fail gracefully if there are mismatches.

---

Playbook: Comparing Two Lists

Here’s the complete playbook to achieve this:

Example Playbook

---
- name: Compare two lists
  hosts: localhost
  vars:
    list_one:
      - { name: "foo" }
      - { name: "bar" }
      - { name: "baz" }
      - { name: "qux" }
      - { name: "quux" }
    list_two:
      - { name: "foo" }
      - { name: "bar" }
      - { name: "baz" }
      - { name: "quux" }
  tasks:
    - name: Verify if lengths of both lists are equal
      debug:
        msg: "The lists are of equal length."
      when: 
        - "{{ list_one | length == list_two | length }}"

- name: Verify if all names in list_one are in list_two debug: msg: "All names in list_one are present in list_two." when: - "{{ list_one | map(attribute='name') | difference(list_two | map(attribute='name')) | length == 0 }}"

- name: Verify if all names in list_two are in list_one debug: msg: "All names in list_two are present in list_one." when: - "{{ list_two | map(attribute='name') | difference(list_one | map(attribute='name')) | length == 0 }}"

- name: Fail if lists are not of equal length or contain different elements fail: msg: "The lists do not match in length or elements." when: - "{{ list_one | length != list_two | length }}" - "{{ list_one | map(attribute='name') | difference(list_two | map(attribute='name')) | length != 0 }}" - "{{ list_two | map(attribute='name') | difference(list_one | map(attribute='name')) | length != 0 }}"

---

See also: Ansible extra-vars: Pass Variables via Command Line (--extra-vars Guide)

Key Features of the Playbook

Length Comparison

• Ensures both lists are of the same size using the length filter.

Element Comparison

• Extracts name attributes with map(attribute='name'). • Uses the difference filter to identify mismatched elements between the lists.

Fail Gracefully

• The fail module provides clear error feedback when the lists differ in length or content.

---

Why It Works

Explicit Template Syntax: • Wrapping conditions in {{ ... }} ensures clear, unambiguous Jinja2 evaluation. Robust Filters: • Filters like length, map, and difference are reliable tools for list manipulation. Error Handling: • By using the fail module, you can terminate playbook execution with descriptive errors when conditions are not met.

---

See also: Ansible Transform JSON Data: Filters for Parsing & Manipulating JSON

Conclusion

Comparing lists in Ansible is straightforward with the right tools and syntax. By addressing common pitfalls, like ambiguous filter usage, you can create reliable playbooks that validate data effectively. Use this approach to ensure data integrity in your automation workflows.

List Comparison Filters

- vars:
    current: [nginx, curl, vim, htop]
    desired: [nginx, curl, git, wget]
  debug:
    msg:
      - "To install: {{ desired | difference(current) }}"
      # [git, wget]
      - "To remove: {{ current | difference(desired) }}"
      # [vim, htop]
      - "Common: {{ current | intersect(desired) }}"
      # [nginx, curl]
      - "All unique: {{ current | union(desired) }}"
      # [nginx, curl, vim, htop, git, wget]
      - "Only in one: {{ current | symmetric_difference(desired) }}"
      # [vim, htop, git, wget]

Practical Examples

Install missing packages

- name: Get installed packages
  ansible.builtin.package_facts:

- name: Install missing packages ansible.builtin.apt: name: "{{ item }}" state: present loop: "{{ required_packages | difference(ansible_facts.packages.keys() | list) }}" become: true

Find new/removed users

- name: Get current users
  command: "awk -F: '$3>=1000 {print $1}' /etc/passwd"
  register: current_users
  changed_when: false

- set_fact: to_create: "{{ desired_users | difference(current_users.stdout_lines) }}" to_remove: "{{ current_users.stdout_lines | difference(desired_users) | difference(['nobody']) }}"

- debug: msg: - "Create: {{ to_create }}" - "Remove: {{ to_remove }}"

Config drift detection

- name: Get running services
  command: systemctl list-units --type=service --state=running --plain --no-legend
  register: running
  changed_when: false

- set_fact: running_services: "{{ running.stdout_lines | map('split') | map('first') | list }}" unexpected: "{{ running_services | difference(expected_services) }}"

- debug: msg: "Unexpected services: {{ unexpected }}" when: unexpected | length > 0

Compare hosts across groups

- set_fact:
    in_web_not_db: "{{ groups['webservers'] | difference(groups['dbservers']) }}"
    in_both: "{{ groups['webservers'] | intersect(groups['dbservers']) }}"

Filter Reference

| Filter | Returns | |--------|---------| | A \| difference(B) | Items in A not in B | | A \| intersect(B) | Items in both A and B | | A \| union(B) | All unique items from both | | A \| symmetric_difference(B) | Items in A or B but not both | | A \| unique | Deduplicated A |

Compare Dicts (by attribute)

- vars:
    old_users:
      - { name: alice, role: admin }
      - { name: bob, role: user }
    new_users:
      - { name: alice, role: admin }
      - { name: charlie, role: user }
  set_fact:
    old_names: "{{ old_users | map(attribute='name') | list }}"
    new_names: "{{ new_users | map(attribute='name') | list }}"
    added: "{{ new_names | difference(old_names) }}"     # [charlie]
    removed: "{{ old_names | difference(new_names) }}"   # [bob]

FAQ

Are comparisons case-sensitive?

Yes. Normalize first: list1 | map('lower') | list | difference(list2 | map('lower') | list)

Can I compare nested data structures?

For dicts, extract the key you want to compare with map(attribute='key'). Deep comparison requires custom filters.

How do I check if two lists are equal?

when: (list_a | sort) == (list_b | sort)
# Or
when: (list_a | difference(list_b) | length == 0) and (list_b | difference(list_a) | length == 0)

difference (Items in A Not in B)

- vars:
    required_packages: [nginx, redis, postgresql, git]
    installed_packages: [nginx, git, curl]
  set_fact:
    missing: "{{ required_packages | difference(installed_packages) }}"
# missing: [redis, postgresql]

intersect (Items in Both)

- vars:
    team_a: [alice, bob, charlie, dave]
    team_b: [bob, dave, eve, frank]
  debug:
    msg: "Both teams: {{ team_a | intersect(team_b) }}"
# [bob, dave]

union (All Unique Items)

- vars:
    web_packages: [nginx, openssl]
    app_packages: [python3, openssl, redis]
  debug:
    msg: "All packages: {{ web_packages | union(app_packages) }}"
# [nginx, openssl, python3, redis]

symmetric_difference (Exclusive Items)

- vars:
    expected: [nginx, redis, postgresql]
    actual: [nginx, redis, mysql]
  debug:
    msg: "Mismatched: {{ expected | symmetric_difference(actual) }}"
# [postgresql, mysql]

Practical: Install Missing Packages

- package_facts:

- set_fact: needed: "{{ required_packages | difference(ansible_facts.packages.keys() | list) }}"

- apt: name: "{{ needed }}" state: present when: needed | length > 0 become: true

Practical: Find New/Removed Users

- vars:
    desired_users: [alice, bob, charlie]
  getent:
    database: passwd
  
- set_fact:
    existing_users: "{{ ansible_facts.getent_passwd.keys() | list }}"
    users_to_add: "{{ desired_users | difference(existing_users) }}"
    users_to_remove: "{{ existing_users | intersect(['alice','bob','charlie','olduser']) | difference(desired_users) }}"

Check If List Contains Item

# 'in' test
- debug: msg="nginx is required"
  when: "'nginx' in required_packages"

# 'not in' - debug: msg="redis is missing" when: "'redis' not in installed_packages"

Compare Sorted Lists

# Check if two lists have same items (order-independent)
- vars:
    list_a: [3, 1, 2]
    list_b: [1, 2, 3]
  debug:
    msg: "Lists match: {{ list_a | sort == list_b | sort }}"
# true

Unique / Flatten

# Remove duplicates
- debug:
    msg: "{{ [1, 2, 2, 3, 3, 3] | unique }}"
# [1, 2, 3]

# Flatten nested lists - debug: msg: "{{ [[1,2], [3,4], [5]] | flatten }}" # [1, 2, 3, 4, 5]

FAQ

Are comparisons case-sensitive?

Yes — ['Nginx'] and ['nginx'] won't match. Use | map('lower') | list to normalize.

Can I compare lists of dicts?

Use map(attribute='name') to extract comparable values first, or use json_query for complex comparisons.

What about set operations on large lists?

Jinja2 filters work fine for hundreds of items. For thousands, consider doing the comparison in a custom filter plugin.

Related Articles

the Ansible template module referencefact-based conditionals in Ansiblethe Ansible inventory deep-dive

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home