<p><code>loop</code> requires a valid list. But we got here is,</p>

<pre>
[({'i_am': 'sam', 'eggs_ham': '456'}, <b>AnsibleUndefined</b>),
 ({'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}),
 ({'i_am': 'eggs', 'eggs_ham': '456'}, <b>AnsibleUndefined</b>)]
</pre>

<p>Since no value is assigned to <code>transport_fact_results</code> when <code>common_thing_2</code> is not defined.</p>

<p>if you remove the <code>when</code> condition it will work.</p>

<p>CODE:</p>

<pre><code>- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing:
          transport: "{{ item.transport | default(default_transport) }}"
      when: "item.common_thing_2 is defined and item.common_thing_2 == 'ham'"
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1|default({})) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"
</code></pre>

<p>OUTPUT:</p>

<pre><code>PLAY [localhost] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] *************************************************************************************************************************************************************************************
ok: [localhost] =&gt; (item={u'common_thing_1': u'sam', u'name': u'foo'})
ok: [localhost] =&gt; (item={u'common_thing_1': u'sam', u'common_thing_2': u'ham', u'name': u'bar'})
ok: [localhost] =&gt; (item={u'common_thing_1': u'eggs', u'name': u'biz'})

TASK [Set Optional Things Facts] ********************************************************************************************************************************************************************************
ok: [localhost] =&gt; (item={u'common_thing_1': u'sam', u'name': u'foo'})
ok: [localhost] =&gt; (item={u'common_thing_1': u'sam', u'common_thing_2': u'ham', u'name': u'bar'})
ok: [localhost] =&gt; (item={u'common_thing_1': u'eggs', u'name': u'biz'})

TASK [Debug the Facts] ******************************************************************************************************************************************************************************************
ok: [localhost] =&gt; (item=[{u'eggs_ham': u'456', u'i_am': u'sam'}, {u'transport': u'train'}]) =&gt; {
    "msg": {
        "eggs_ham": "456", 
        "i_am": "sam", 
        "transport": "train"
    }
}
ok: [localhost] =&gt; (item=[{u'eggs_ham': u'ham', u'i_am': u'sam'}, {u'transport': u'train'}]) =&gt; {
    "msg": {
        "eggs_ham": "ham", 
        "i_am": "sam", 
        "transport": "train"
    }
}
ok: [localhost] =&gt; (item=[{u'eggs_ham': u'456', u'i_am': u'eggs'}, {u'transport': u'train'}]) =&gt; {
    "msg": {
        "eggs_ham": "456", 
        "i_am": "eggs", 
        "transport": "train"
    }
}

PLAY RECAP ******************************************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
</code></pre>

CC BY-SA 4.0
2020-04-30T22:05:09.220Z
The point behind the when condition is that the `transport` key being present in the object presents a problem for the task. An example of this would be some of the structure around the Kubernetes objects passed using the k8s module.
David Brown
CC BY-SA 4.0
2020-04-16T07:17:49.387Z
2020-04-16T07:17:49.387Z
Siva
<p>So the way I figured it out was to put the <code>when</code> condition into Jinja.</p>

<pre><code>- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing: |
          {% if item.common_thing_2 is defined and item.common_thing_2 == 'ham' %}
          { "transport": "{{ item.transport | default(default_transport) }}" }
          {% else %}
          {}
          {% endif %}
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"
</code></pre>

<p>This is kinda sloppy but does allow me to specify the else condition to set the result over every iteration to something instead of <code>AnsibleUndefined</code>.</p>

<p>I don't really like this solution but it works.</p>

<pre><code>$ ansible-playbook playbook.yml 
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] *******************************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] ********************************************************************************************************************************************************
ok: [localhost] =&gt; (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] =&gt; (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] =&gt; (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Set Optional Things Facts] ***************************************************************************************************************************************************
ok: [localhost] =&gt; (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] =&gt; (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] =&gt; (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Debug the Facts] *************************************************************************************************************************************************************
ok: [localhost] =&gt; (item=[{'i_am': 'sam', 'eggs_ham': '456'}, {}]) =&gt; {
    "msg": {
        "eggs_ham": "456",
        "i_am": "sam"
    }
}
ok: [localhost] =&gt; (item=[{'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}]) =&gt; {
    "msg": {
        "eggs_ham": "ham",
        "i_am": "sam",
        "transport": "train"
    }
}
ok: [localhost] =&gt; (item=[{'i_am': 'eggs', 'eggs_ham': '456'}, {}]) =&gt; {
    "msg": {
        "eggs_ham": "456",
        "i_am": "eggs"
    }
}

PLAY RECAP *************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

</code></pre>

<p>Now the <code>transport</code> key is defined in the one object but not the other. So I can pass the item off to a complex object that's part of an API call.</p>

CC BY-SA 4.0
2020-04-30T22:27:34.697Z
2020-04-30T22:27:34.697Z
David Brown
<p>I'm really trying to merge lists of objects along with defaults so I can loop over them and create resources in kubernetes. I've gotten it down to a simple(ish) playbook and will post it here.</p>

<pre><code>- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing:
          transport: "{{ item.transport | default(default_transport) }}"
      when: "item.common_thing_2 is defined and item.common_thing_2 == 'ham'"
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1|default({})) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"
</code></pre>

<p>The output is as follows...</p>

<pre><code>PLAY [localhost] ***********************************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] ************************************************************************************************************************************************************************
ok: [localhost] =&gt; (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] =&gt; (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] =&gt; (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Set Optional Things Facts] *******************************************************************************************************************************************************************
skipping: [localhost] =&gt; (item={'name': 'foo', 'common_thing_1': 'sam'}) 
ok: [localhost] =&gt; (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
skipping: [localhost] =&gt; (item={'name': 'biz', 'common_thing_1': 'eggs'}) 

TASK [Debug the Facts] *****************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! =&gt; {"msg": "Invalid data passed to 'loop', it requires a list, got this instead: [({'i_am': 'sam', 'eggs_ham': '456'}, AnsibleUndefined), ({'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}), ({'i_am': 'eggs', 'eggs_ham': '456'}, AnsibleUndefined)]. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."}

PLAY RECAP *****************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

</code></pre>

<p>I think if I'm reading the loop item that's what I'm looking for. Though I'm not sure why ansible is refusing to perform the loop.</p>

<p>Any help about how to merge lists of objects together with optional attributes that could or could not be set because k8s is pretty picky about what's being set and not when passing in objects.</p>

<p>Thanks.</p>

<ansible>
Combine Lists of Objects in Ansible
David Brown
# Combine Lists of Objects in Ansible

(Asked by: username_0 on 2020-04-16)

I'm really trying to merge lists of objects along with defaults so I can loop over them and create resources in kubernetes. I've gotten it down to a simple(ish) playbook and will post it here.
- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing:
          transport: "{{ item.transport | default(default_transport) }}"
      when: "item.common_thing_2 is defined and item.common_thing_2 == 'ham'"
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1|default({})) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"

The output is as follows...
PLAY [localhost] ***********************************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] ************************************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] => (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] => (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Set Optional Things Facts] *******************************************************************************************************************************************************************
skipping: [localhost] => (item={'name': 'foo', 'common_thing_1': 'sam'}) 
ok: [localhost] => (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
skipping: [localhost] => (item={'name': 'biz', 'common_thing_1': 'eggs'}) 

TASK [Debug the Facts] *****************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "Invalid data passed to 'loop', it requires a list, got this instead: [({'i_am': 'sam', 'eggs_ham': '456'}, AnsibleUndefined), ({'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}), ({'i_am': 'eggs', 'eggs_ham': '456'}, AnsibleUndefined)]. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."}

PLAY RECAP *****************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   


I think if I'm reading the loop item that's what I'm looking for. Though I'm not sure why ansible is refusing to perform the loop.
Any help about how to merge lists of objects together with optional attributes that could or could not be set because k8s is pretty picky about what's being set and not when passing in objects.
Thanks.

**Tags:** ansible


---

## 2 answers

### Answer by username_1 on 2020-04-16. Score: 1

loop requires a valid list. But we got here is,

[({'i_am': 'sam', 'eggs_ham': '456'}, AnsibleUndefined),
 ({'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}),
 ({'i_am': 'eggs', 'eggs_ham': '456'}, AnsibleUndefined)]

Since no value is assigned to transport_fact_results when common_thing_2 is not defined.
if you remove the when condition it will work.
CODE:
- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing:
          transport: "{{ item.transport | default(default_transport) }}"
      when: "item.common_thing_2 is defined and item.common_thing_2 == 'ham'"
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1|default({})) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"

OUTPUT:
PLAY [localhost] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] *************************************************************************************************************************************************************************************
ok: [localhost] => (item={u'common_thing_1': u'sam', u'name': u'foo'})
ok: [localhost] => (item={u'common_thing_1': u'sam', u'common_thing_2': u'ham', u'name': u'bar'})
ok: [localhost] => (item={u'common_thing_1': u'eggs', u'name': u'biz'})

TASK [Set Optional Things Facts] ********************************************************************************************************************************************************************************
ok: [localhost] => (item={u'common_thing_1': u'sam', u'name': u'foo'})
ok: [localhost] => (item={u'common_thing_1': u'sam', u'common_thing_2': u'ham', u'name': u'bar'})
ok: [localhost] => (item={u'common_thing_1': u'eggs', u'name': u'biz'})

TASK [Debug the Facts] ******************************************************************************************************************************************************************************************
ok: [localhost] => (item=[{u'eggs_ham': u'456', u'i_am': u'sam'}, {u'transport': u'train'}]) => {
    "msg": {
        "eggs_ham": "456", 
        "i_am": "sam", 
        "transport": "train"
    }
}
ok: [localhost] => (item=[{u'eggs_ham': u'ham', u'i_am': u'sam'}, {u'transport': u'train'}]) => {
    "msg": {
        "eggs_ham": "ham", 
        "i_am": "sam", 
        "transport": "train"
    }
}
ok: [localhost] => (item=[{u'eggs_ham': u'456', u'i_am': u'eggs'}, {u'transport': u'train'}]) => {
    "msg": {
        "eggs_ham": "456", 
        "i_am": "eggs", 
        "transport": "train"
    }
}

PLAY RECAP ******************************************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

> **Comments:**
> **username_0**: The point behind the when condition is that the `transport` key being present in the object presents a problem for the task. An example of this would be some of the structure around the Kubernetes objects passed using the k8s module.  

---

### Answer by username_0 on 2020-04-30. Score: 0

So the way I figured it out was to put the when condition into Jinja.
- hosts: localhost
  vars:
    default_thing_1: 123
    default_thing_2: 456
    default_transport: train
    list_of_things:
      - name: foo
        common_thing_1: "sam"
      - name: bar
        common_thing_1: "sam"
        common_thing_2: "ham"
      - name: biz
        common_thing_1: "eggs"
  tasks:
    - name: Set the Things Facts
      set_fact:
        thing:
          i_am: "{{ item.common_thing_1 | default(default_thing_1) }}"
          eggs_ham: "{{ item.common_thing_2 | default(default_thing_2) }}"
      loop: "{{ list_of_things }}"
      register: things_fact_results
    - name: Set Optional Things Facts
      set_fact:
        thing: |
          {% if item.common_thing_2 is defined and item.common_thing_2 == 'ham' %}
          { "transport": "{{ item.transport | default(default_transport) }}" }
          {% else %}
          {}
          {% endif %}
      loop: "{{ list_of_things }}"
      register: transport_fact_results
    - name: Debug the Facts
      debug:
        msg: "{{ item.0 | combine(item.1) }}"
      loop: "{{
        things_fact_results.results |
        map(attribute='ansible_facts.thing') | list |
        zip(
          transport_fact_results.results |
          map(attribute='ansible_facts.thing') | list
        ) | list
      }}"

This is kinda sloppy but does allow me to specify the else condition to set the result over every iteration to something instead of AnsibleUndefined.
I don't really like this solution but it works.
$ ansible-playbook playbook.yml 
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] *******************************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************************
ok: [localhost]

TASK [Set the Things Facts] ********************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] => (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] => (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Set Optional Things Facts] ***************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'foo', 'common_thing_1': 'sam'})
ok: [localhost] => (item={'name': 'bar', 'common_thing_1': 'sam', 'common_thing_2': 'ham'})
ok: [localhost] => (item={'name': 'biz', 'common_thing_1': 'eggs'})

TASK [Debug the Facts] *************************************************************************************************************************************************************
ok: [localhost] => (item=[{'i_am': 'sam', 'eggs_ham': '456'}, {}]) => {
    "msg": {
        "eggs_ham": "456",
        "i_am": "sam"
    }
}
ok: [localhost] => (item=[{'i_am': 'sam', 'eggs_ham': 'ham'}, {'transport': 'train'}]) => {
    "msg": {
        "eggs_ham": "ham",
        "i_am": "sam",
        "transport": "train"
    }
}
ok: [localhost] => (item=[{'i_am': 'eggs', 'eggs_ham': '456'}, {}]) => {
    "msg": {
        "eggs_ham": "456",
        "i_am": "eggs"
    }
}

PLAY RECAP *************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


Now the transport key is defined in the one object but not the other. So I can pass the item off to a complex object that's part of an API call.


---

