Ansible can get tricky when it comes to accessing variables from another group. There are ways to access a particular host's variables.

{{ hostvars['']['ansible_distribution'] }}

Or even to loop on each host a group to access variables.

{% for host in groups['app_servers'] %}
   {{ hostvars[host]['ansible_fqdn'] }}:{{ hostvars[host]['port'] }}
{% endfor %}


Use the standard map extract filter as shown at the end of this article.

Jinja2 custom filter

Jinja2 Templating cannot produce a list of a variables files. It can only produce a string output.

Consider this list defined in group_vars.

  - http://server1:port1
  - http://server2:port2

What if server3 is added? You must remember to add another line to this list. This can be a source of error as the servers in this list are redundant to the servers in the inventory. As well, each inventory must have its own list, which could be of different size.

Of course, servers are not added on a regular basis and this list could remain static. Problem is, by the time another server should be added, chances are this list will not be updated. And this gets even worse for people using dynamic inventories.

The solution would be to have a dynamic list, based on the inventory. It would be great if Ansible could have the following notation.

{{ groupvars['app_servers']['url'] }}

Since this doesn't exist, let's try defining some custom filters. The filters would be used this way.

{{ groups['app_servers'] | each_hostvars(hostvars) | each_format('http://{ansible_fqdn}{port}/') }}

The first filter each_hostvars returns a list of hostvars, one for each host in groups['app_servers'].

The second filter returns the formatted string http://{ansible_fqdn}{port}/ using each hostvars.

Doing so, loops have been masked behind functional programming style and usage is cleaner.

We could go one step further by having the formatted string be a variable in app_servers.yml.

url: "http://{{ ansible_fqdn }}{{ port }}/"

By doing so, we could use another filter to extract only a single attribute from the list of hostvars.

"{{ groups['app_servers'] | each_hostvars(hostvars) | keep_attribute('url') }}"

Notice that url is defined in app_servers.yml as it references the URL to access each app_server. Previously, this URL would have been in the caller's group. It makes more sense to have it in app_servers.yml as it concerns app_servers.

The filters used here are described on GitHub. The main playbook is an example similar this article.

Using standard map extract filter

But custom filters aren't really necessary to do this. There is a more standard way to achieve the same result using jinja2 function map.

"{{ groups['app_servers'] | map('extract',hostvars,'url') | list }}"

the map extract is the same thing as the custom filters each_hostvars and keep_attribute combined, although it returns a generator and not a list. This is why the result has to be converted to a list afterward. But still, this definitely allows reusability and readability are great as it is concise.

Sometimes a value if the same for a group of hosts. It can, therefore, be useless to have a list containing the same value repeated for each host. In such cases, a derivative from the previous example can be applied.

"{{ groups['app_servers'] | map('extract',hostvars,'port') | first }}"

Syntax remains very similar, which is good for comprehension. The syntax is flexible enough to allow selection of a list result or a single value.