Search code examples
ansible

Ansible: Usage of with_items in a more compact way?


Considering the following test playbook:

---
- name: Playbook
  hosts: localhost
  become: false
  gather_facts: false
  vars:
    plugins:
      - name: diff
        enabled: true
        dependencies:
          - python3-jsonpatch
          - python3-kubernetes
        repository:
          name: helm-diff
          org: databus23
        version: v3.9.6
      - name: git
        enabled: false
        repository:
          name: helm-git
          org: aslafy-z
        version: v0.16.0
  tasks:
    - name: Loop
      block:
        - name: List plugin names
          ansible.builtin.debug:
            msg: '{{ item.name }}'
          when: item.enabled
          with_items: '{{ plugins }}'

        - name: List dependencies
          ansible.builtin.debug:
            msg: '{{ item }}'
          with_items: "{{ plugins | selectattr('enabled', 'eq', true) | selectattr('dependencies', 'defined') | map(attribute='dependencies') }}"

Which produces the expected results:

TASK [List plugin names] ***
ok: [localhost] => (item={'name': 'diff', 'enabled': True, 'dependencies': ['python3-jsonpatch', 'python3-kubernetes'], 'repository': {'name': 'helm-diff', 'org': 'databus23'}, 'version': 'v3.9.6'}) =>
  msg: diff

TASK [List dependencies] ***
ok: [localhost] => (item=python3-jsonpatch) =>
  msg: python3-jsonpatch
ok: [localhost] => (item=python3-kubernetes) =>
  msg: python3-kubernetes

Is there a better way to render the use of with_items more compact into second task, ideally with a combined with condition, as used into first task?

I will have multiple tasks using the same with_items loops listed above, I don't know if there is a way to group multiple tasks under the same with_items loop.

Edit: While @larks's suggestion is a great approach, I ended using the following conditions which IMO, produces a clean readable code:

  tasks:
    - name: Loop
      block:
        - name: List plugin names
          ansible.builtin.debug:
            msg: '{{ item.name }}'
          loop: "{{ plugins }}"
          when: item.enabled

        - name: List dependencies
          ansible.builtin.debug:
            msg: '{{ item.dependencies }}'
          loop: '{{ plugins }}'
          when:
            - item.enabled
            - item.dependencies is defined

Solution

  • If you use the json_query filter, you could write your task like this:

    - name: List dependencies
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ plugins | json_query('[].dependencies[]') }}"
    

    That produces as output:

    TASK [List dependencies] **********************************************************************************************************************
    ok: [localhost] => (item=python3-jsonpatch) => {
        "msg": "python3-jsonpatch"
    }
    ok: [localhost] => (item=python3-kubernetes) => {
        "msg": "python3-kubernetes"
    }
    

    Depending on your goal, you could drop the loop and instead write:

    - name: List dependencies
      ansible.builtin.debug:
        msg: "{{ plugins | json_query('[].dependencies[]') }}"
    

    Which produces a single merged list of dependencies:

    TASK [List dependencies] **********************************************************************************************************************
    ok: [localhost] => {
        "msg": [
            "python3-jsonpatch",
            "python3-kubernetes"
        ]
    }