Answers: <p>Use <code>select()</code> with the <code>defined</code> test.</p>
  <pre><code>ssh_listen_to: &quot;{{ [ansible_facts.default_ipv4.address, ansible_facts.default_ipv6.address] | select('defined') | list }}&quot;
  </code></pre>
  <p>As for the linked post about matching dictionary keys in JMESPath, I wouldn't use JMESPath at all for that in Ansible. As I've grown more accustomed to Jinja I've found <code>json_query()</code> less and less necessary.</p>
  <pre><code>ssh_listen_to: &quot;{{ ansible_facts | select('match', '.+_ipv[46]$') | map('extract', ansible_facts, 'address') | select('defined') | list }}&quot;
  </code></pre>
  <p><code>ansible_facts | select('match', '.+_ipv[46]$')</code> filters the keys using a regex. Iterating over a dictionary returns its keys, which are then matched against the provided regex.</p>
  <p>Passing this filtered list of keys to <code>map('extract', ansible_facts, 'address')</code> extracts their values and looks up 'address' in each value. You could do this in two steps as <code>map('extract', ansible_facts) | map(attribute='address')</code>, if that makes things clearer.</p>
  <p><code>select('defined')</code> gets rid of any undefined values, then <code>list</code> converts the generator returned from <code>select()</code> into a list (this generator unrolling happens automatically in ansible-base/ansible-core &gt;= 2.10, but a lot of people are still on Ansible 2.9.)</p>
  
Answers: CC BY-SA 4.0
Answers: 2021-03-18T09:09:50.303Z
Answers: wonderful answer, thank you! Would you mind elaborating on that second command? What is `46`?
Answers: Michael Altfield
Answers: CC BY-SA 4.0
Answers: 2021-03-18T14:41:18.477Z
Answers: `[46]` is a regex character class; it matches a single '4' or '6' in the string.
Answers: flowerysong
Answers: CC BY-SA 4.0
Answers: 2021-03-17T19:56:20.757Z
Answers: 2021-03-18T14:36:27.397Z
Answers: 2021-03-18T14:36:27.397Z
Answers: flowerysong
Answers: <p>Create the list in Jinja2 and convert it, e.g.</p>
  <pre class="lang-yaml prettyprint-override"><code>- hosts: localhost
    gather_facts: true
    tasks:
      - set_fact:
           ssh_listen_to: &quot;{{ _ssh_listen_to|from_yaml }}&quot;
        vars:
          _ssh_listen_to: |
            {% if ansible_default_ipv4.address is defined %}
            - {{ ansible_default_ipv4.address }}
            {% endif %}
            {% if ansible_default_ipv6.address is defined %}
            - {{ ansible_default_ipv6.address }}
            {% endif %}
      - debug:
          var: ssh_listen_to|type_debug
      - debug:
          var: ssh_listen_to
  </code></pre>
  <p>gives</p>
  <pre class="lang-yaml prettyprint-override"><code>TASK [debug] ********************************************************************
  ok: [localhost] =&gt; 
    ssh_listen_to|type_debug: list
  
  TASK [debug] ********************************************************************
  ok: [localhost] =&gt; 
    ssh_listen_to:
    - 10.1.0.27
  </code></pre>
  <hr>
  <p>If you put the name of the variables into the list, e.g.</p>
  <pre class="lang-yaml prettyprint-override"><code>    vars_addr:
        - default_ipv4
        - default_ipv6
  </code></pre>
  <p>the task below gives the same result</p>
  <pre class="lang-yaml prettyprint-override"><code>    - set_fact:
           ssh_listen_to: &quot;{{ vars_addr|
                              map('extract', ansible_facts, 'address')|
                              select('defined')|
                              list  }}&quot;
  </code></pre>
  
Answers: CC BY-SA 4.0
Answers: 2021-03-17T16:20:59.863Z
Answers: 2021-03-17T16:56:12.827Z
Answers: 2021-03-17T16:56:12.827Z
Answers: Vladimir Botka
Answers: <p>This doesn't answer the question directly, but I found that I could query the entire ansible facts dictionary as a json document using the <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#selecting-json-data-json-queries" rel="nofollow noreferrer"><code>json_query</code> filter</a> and a <a href="https://jmespath.org/tutorial.html" rel="nofollow noreferrer">jmespath expression</a>.</p>
  <p>For example, the following produces a list of all the ip addresses.</p>
  <pre><code>ssh_listen_to: &quot;{{ ansible_facts | json_query('*.address') }}&quot;
  </code></pre>
  <p>You can make this safer by just taking the default ipv4 &amp; ipv6 addresses like so:</p>
  <pre><code>ssh_listen_to: &quot;{{ ansible_facts | json_query('[@.default_ipv4, @.default_ipv6][].address') }}&quot;
  </code></pre>
  <p>And you can even further check the sanity of the options by <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters_ipaddr.html#filtering-lists" rel="nofollow noreferrer">removing anything from the list that's not an IP address</a></p>
  <pre><code>ssh_listen_to: &quot;{{ ansible_facts | json_query('[@.default_ipv4, @.default_ipv6][].address') | ansible.netcommon.ipaddr }}&quot;
  </code></pre>
  
Answers: CC BY-SA 4.0
Answers: 2021-03-17T19:38:54.937Z
Answers: Be careful `shell> export address="This is not what I want."` will render the code unusable.
Answers: Vladimir Botka
Answers: CC BY-SA 4.0
Answers: 2021-03-18T09:22:55.100Z
Answers: @VladimirBotka I updated the answer with some sanity checks :)
Answers: Michael Altfield
Answers: CC BY-SA 4.0
Answers: 2021-03-17T16:52:28.117Z
Answers: 2021-03-18T09:21:48.097Z
Answers: 2021-03-18T09:21:48.097Z
Answers: Michael Altfield
Body: <p>How do I write a yaml file such that it defines an array with a list of values, and it skips those values from being added to the yaml array if the given value is undefined?</p>
  <p>Specifically, I'm trying to define an array of IP addresses to which I want a service (sshd) to bind to, including the ipv4 and ipv6 addresses. If there is both an ipv4 &amp; ipv6 address, then the array should contain both values. If there is only an ipv4 address or only an ipv6 address, then it should contain one of those values.</p>
  <p>This doesn't work, as it will produce an error <code>&quot;msg&quot;: &quot;AnsibleUndefinedVariable: 'dict object' has no attribute 'address'&quot;}</code></p>
  <pre><code>    ssh_listen_to:
        - &quot;{{ ansible_default_ipv4.address }}&quot;
        - &quot;{{ ansible_default_ipv6.address }}&quot;
  </code></pre>
  <p>This also doesn't work, as it will end-up adding a line <code>ListenAddress </code> with no argument, that breaks the sshd server (this may be a shortsighted design of my ansible role, but that's out-of-scope in this question).</p>
  <pre><code>    ssh_listen_to:
        - &quot;{{ ansible_default_ipv4.address | default('') }}&quot;
        - &quot;{{ ansible_default_ipv6.address | default('') }}&quot;
  </code></pre>
  <p>How can I update the above yaml block such that it will skip adding the values to the array if the value isn't defined? Note that this is being done in my playbook's <code>vars:</code> section, so no tasks-based solutions please.</p>
  
Comments: CC BY-SA 4.0
Comments: 2021-03-17T16:46:57.670Z
Comments: See also https://stackoverflow.com/questions/66677527/how-to-match-a-regex-on-an-array-name-itself-in-jmespath-json-element-string-re
Comments: Michael Altfield
Tags: <ansible><yaml>
Title: Add item(s) to yaml array only if defined (ansible)
UserDisplayName: Michael Altfield
ThreadText: # Add item(s) to yaml array only if defined (ansible)
  
  (Asked by: username_0 on 2021-03-17)
  
  How do I write a yaml file such that it defines an array with a list of values, and it skips those values from being added to the yaml array if the given value is undefined?
  
  Specifically, I'm trying to define an array of IP addresses to which I want a service (sshd) to bind to, including the ipv4 and ipv6 addresses. If there is both an ipv4 & ipv6 address, then the array should contain both values. If there is only an ipv4 address or only an ipv6 address, then it should contain one of those values.
  
  This doesn't work, as it will produce an error `"msg": "AnsibleUndefinedVariable: 'dict object' has no attribute 'address'"}`
  
  ```
      ssh_listen_to:
        - "{{ ansible_default_ipv4.address }}"
        - "{{ ansible_default_ipv6.address }}"
  
  ```
  
  This also doesn't work, as it will end-up adding a line `ListenAddress`  with no argument, that breaks the sshd server (this may be a shortsighted design of my ansible role, but that's out-of-scope in this question).
  
  ```
      ssh_listen_to:
        - "{{ ansible_default_ipv4.address | default('') }}"
        - "{{ ansible_default_ipv6.address | default('') }}"
  
  ```
  
  How can I update the above yaml block such that it will skip adding the values to the array if the value isn't defined? Note that this is being done in my playbook's `vars:` section, so no tasks-based solutions please.
  
  **Tags:** ansible, yaml
  
  > **Comments:**
  > **username_0**: See also https://stackoverflow.com/questions/66677527/how-to-match-a-regex-on-an-array-name-itself-in-jmespath-json-element-string-re  
  
  ---
  
  ## 3 answers
  
  ### Answer by username_1 on 2021-03-17. Score: 1 | Accepted answer
  
  Use `select()` with the `defined` test.
  
  ```
  ssh_listen_to: "{{ [ansible_facts.default_ipv4.address, ansible_facts.default_ipv6.address] | select('defined') | list }}"
  
  ```
  
  As for the linked post about matching dictionary keys in JMESPath, I wouldn't use JMESPath at all for that in Ansible. As I've grown more accustomed to Jinja I've found `json_query()` less and less necessary.
  
  ```
  ssh_listen_to: "{{ ansible_facts | select('match', '.+_ipv[46]$') | map('extract', ansible_facts, 'address') | select('defined') | list }}"
  
  ```
  
  `ansible_facts | select('match', '.+_ipv[46]$')` filters the keys using a regex. Iterating over a dictionary returns its keys, which are then matched against the provided regex.
  
  Passing this filtered list of keys to `map('extract', ansible_facts, 'address')` extracts their values and looks up 'address' in each value. You could do this in two steps as `map('extract', ansible_facts) | map(attribute='address')`, if that makes things clearer.
  
  `select('defined')` gets rid of any undefined values, then `list` converts the generator returned from `select()` into a list (this generator unrolling happens automatically in ansible-base/ansible-core >= 2.10, but a lot of people are still on Ansible 2.9.)
  
  > **Comments:**
  > **username_0**: wonderful answer, thank you! Would you mind elaborating on that second command? What is `46`?  
  > **username_1**: `[46]` is a regex character class; it matches a single '4' or '6' in the string.  
  
  ---
  
  ### Answer by username_2 on 2021-03-17. Score: 1
  
  Create the list in Jinja2 and convert it, e.g.
  
  ```yaml
  - hosts: localhost
    gather_facts: true
    tasks:
      - set_fact:
           ssh_listen_to: "{{ _ssh_listen_to|from_yaml }}"
        vars:
          _ssh_listen_to: |
            {% if ansible_default_ipv4.address is defined %}
            - {{ ansible_default_ipv4.address }}
            {% endif %}
            {% if ansible_default_ipv6.address is defined %}
            - {{ ansible_default_ipv6.address }}
            {% endif %}
      - debug:
          var: ssh_listen_to|type_debug
      - debug:
          var: ssh_listen_to
  
  ```
  
  gives
  
  ```yaml
  TASK [debug] ********************************************************************
  ok: [localhost] => 
    ssh_listen_to|type_debug: list
  
  TASK [debug] ********************************************************************
  ok: [localhost] => 
    ssh_listen_to:
    - 10.1.0.27
  
  ```
  
  ---
  
  If you put the name of the variables into the list, e.g.
  
  ```yaml
      vars_addr:
        - default_ipv4
        - default_ipv6
  
  ```
  
  the task below gives the same result
  
  ```yaml
      - set_fact:
           ssh_listen_to: "{{ vars_addr|
                              map('extract', ansible_facts, 'address')|
                              select('defined')|
                              list  }}"
  
  ```
  
  
  ---
  
  ### Answer by username_0 on 2021-03-17. Score: 0
  
  This doesn't answer the question directly, but I found that I could query the entire ansible facts dictionary as a json document using the [`json_query` filter](https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#selecting-json-data-json-queries) and a [jmespath expression](https://jmespath.org/tutorial.html).
  
  For example, the following produces a list of all the ip addresses.
  
  ```
  ssh_listen_to: "{{ ansible_facts | json_query('*.address') }}"
  
  ```
  
  You can make this safer by just taking the default ipv4 & ipv6 addresses like so:
  
  ```
  ssh_listen_to: "{{ ansible_facts | json_query('[@.default_ipv4, @.default_ipv6][].address') }}"
  
  ```
  
  And you can even further check the sanity of the options by [removing anything from the list that's not an IP address](https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters_ipaddr.html#filtering-lists)
  
  ```
  ssh_listen_to: "{{ ansible_facts | json_query('[@.default_ipv4, @.default_ipv6][].address') | ansible.netcommon.ipaddr }}"
  
  ```
  
  > **Comments:**
  > **username_2**: Be careful `shell> export address="This is not what I want."` will render the code unusable.  
  > **username_0**: @username_2 I updated the answer with some sanity checks :)  
  
  ---
  
  