# Reusable fact-derived variables

**URL:** <https://forum.ansible.com/t/reusable-fact-derived-variables/11794>\
**Category:** Ansible Project\
**Created:** [August 12, 2012, 9:24am UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794 "2012-08-12T09:24:01Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 12, 2012, 9:24am UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/1 "2012-08-12T09:24:01Z")

</div>

I need to set a variable that is derived from some facts, with a little logic.

For example: default\_interface, as in, the name of the network interface to which the main IP address is assigned.

It can be computed by using the new fact ansible\_ipv4\_address and iterating over all interface facts trying to match it to their IPs:

for iface in ansible\_interfaces:

# somehow access ansible\_${iface}

if ansible\_ipv4\_address == ansible\_$iface.ipv4.address:  
default\_interface = ansible\_$iface.device

This leads to three questions:

1. How do I iterate over all interfaces’ information, or more specifically, how do I construct fact names?  
In the example above, how do I access ansible\_${iface}? Obviously, this is not a valid expression  
If I can’t do that, what’s the use of the ansible\_interfaces fact?

2. Where can I perform such logic, if at all?  
In general, where can any logic be applied to facts to derive new variables?

3. What’s the “ansible approach” towards all this?  
If I need a variable that can be computed from available facts, should I compute it, or should ansible add it as a new fact?  
In this example, new ansible\_ipv4\_interface and ansible\_ipv6\_interface (corresponding to ansible\_ipv4\_address and ansible\_ipv6\_address) facts would be trivial to add to the setup module. But is that the only way to go? How does ansible want to deal with such cases?

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 12, 2012, 1:11pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/2 "2012-08-12T13:11:47Z")

</div>

> In the example above, how do I access ansible\_${iface}? Obviously, this is not a valid expression

The template doesn't currently make Jinja2 "context" available, though you do have access to all variables through {{ hostvars["your hostname here"] }}  
I won't say it's optimal, but it's there.

I really don't expect a lot of folks to be doing complex template iteration, but you can do it if you really want to. Our goal should be, of course, to minimize the levels of complex magic you have to do in templates. Nobody likes a complicated template system.

> If I can't do that, what's the use of the ansible\_interfaces fact?

Well, from the API, setup facts are useful for a lot more than playbooks. For instance, you could easily use an API script to build a basic inventory system, especially when combined with your own modules that also return facts.

logic to make an ansible\_default\_interface fact (in the core setup module) that has the value of the the default interface seems nicest to me, that way everyone could use it. It could have the same data format as ansible\_eth0 (or any other interface) does.

--Michael

---

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 12, 2012, 1:32pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/3 "2012-08-12T13:32:21Z")

</div>

Cool. I will get on it. But first…  
There are a few options here:

1. I was thinking of just adding two simple top-level facts similar to ansible\_ipv4/6\_address: ansible\_ipv4/6\_interface. Though getting any other details besides the address and the interface would be a little tricky. Also, this may bring up the interface vs. alias issue again. I guess this option is out.

2. A single ansible\_eth0-like structure: ansible\_default\_interface. But what if the main IPv4 interface is different from the main IPv6 interface? (not sure if this happens in real life, but can’t think of why it can’t happen.)

3. ansible\_ipv4\_interface and ansible\_ipv6\_interface as two ansible\_eth0-like structures. They could be exact copies of the corresponding interface structures, with each listing both their IPv4 and IPv6 information along with mac address, device, … etc. Easy to implement, consistent with other interface facts, and confusion-resistant!

Options 2 and 3 will obsolete ansible\_ipv4\_address, which would be available as ansible\_ipv4\_interface.ipv4.address, but not ansible\_ipv6\_address, because ansible\_ipv6\_interface may have multiple addresses.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 12, 2012, 2:25pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/4 "2012-08-12T14:25:47Z")

</div>

> 3. ansible\_ipv4\_interface and ansible\_ipv6\_interface as two  
> ansible\_eth0-like structures. They could be exact copies of the  
> corresponding interface structures, with each listing both their IPv4 and  
> IPv6 information along with mac address, device, ... etc. Easy to implement,  
> consistent with other interface facts, and confusion-resistant!

yep, this would good if they were exact copies.

> Options 2 and 3 will obsolete ansible\_ipv4\_address, which would be available  
> as ansible\_ipv4\_interface.ipv4.address, but not ansible\_ipv6\_address,  
> because ansible\_ipv6\_interface may have multiple addresses.

Not sure how it obsoletes a fact that isn't there now, and ipv6 stuff  
is already a list. Seems fine to me.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 12, 2012, 2:26pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/5 "2012-08-12T14:26:53Z")

</div>

Ah, I see my confusion, ipv4\_address and ipv6\_address are not  
documented on the modules page, and probably introduced recently  
without any advertising.

It seems fine to get rid of them with the "exact copy" idea.

---

<div class="post-metadata">

**Author:** ![Stephen\_Fromm](https://avatars.discourse-cdn.com/v4/letter/s/bbce88/32.png) [@Stephen\_Fromm](https://forum.ansible.com/u/Stephen_Fromm)\
**Post date:** [August 12, 2012, 2:59pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/6 "2012-08-12T14:59:20Z")

</div>

This seems like a good option. One word of caution. Right now ipv4\_address and ipv6\_address are set to the first address discovered by parse\_ip\_addr(). In my case, that selected the wrong addresses as the default. One option is to consider the route table when selecting a default.

Also, would it be worth highlighting the globally-scoped ipv6 address in some way?

sf

---

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 12, 2012, 7:50pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/7 "2012-08-12T19:50:12Z")

</div>

> It seems fine to get rid of them with the “exact copy” idea.

Not exactly. We can get rid of the ipv4\_address as it will be available reliably as ipv4\_interface.ipv4.address, but ipv6\_interface may have multiple addresses, in which case it is not clear what ipv6\_address is.  
What I am trying to say is, in the presence of a new ipv4\_interface and ipv6\_interface complete structures, it is not clear what should happen to the recently implemented ipv4\_address and ipv6\_address: ipv4\_address will be redundant, but ipv6\_address will not.

> This seems like a good option. One word of caution. Right now ipv4\_address and ipv6\_address are set to the first address discovered by parse\_ip\_addr(). In my case, that selected the wrong addresses as the default. One option is to consider the route table when selecting a default.

This was a possibility. Now we know it is an issue. Thanks for letting me know. I will change it to use the default route interface and address, which brings the naming issue: shall it be called ansible\_ipv4\_default\_interface?

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 12, 2012, 8:04pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/8 "2012-08-12T20:04:54Z")

</div>

> \> This seems like a good option. One word of caution. Right now ipv4\_address and ipv6\_address are set to the first address discovered by parse\_ip\_addr(). In my case, that selected the wrong addresses as the default. One option is to consider the route table when selecting a default.
> 
> This was a possibility. Now we know it is an issue. Thanks for letting me know. I will change it to use the default route interface and address, which brings the naming issue: shall it be called ansible\_ipv4\_default\_interface?

Yes.

---

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 12, 2012, 8:30pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/9 "2012-08-12T20:30:21Z")

</div>

> > It seems fine to get rid of them with the “exact copy” idea.
> 
> Not exactly. We can get rid of the ipv4\_address as it will be available reliably as ipv4\_interface.ipv4.address, but ipv6\_interface may have multiple addresses, in which case it is not clear what ipv6\_address is.  
> What I am trying to say is, in the presence of a new ipv4\_interface and ipv6\_interface complete structures, it is not clear what should happen to the recently implemented ipv4\_address and ipv6\_address: ipv4\_address will be redundant, but ipv6\_address will not.

And what do we do with the recently implemented ipv4\_address and ipv6\_address? Keep them? Remove them? Keep only ipv6\_address?

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 12, 2012, 8:34pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/10 "2012-08-12T20:34:16Z")

</div>

Remove them both. They are singular in the ipv6 case and don't include the rest of the interface info in either case.

---

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 13, 2012, 3:58am UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/11 "2012-08-13T03:58:25Z")

</div>

How about this structure for network interface facts:

“interfaces”: {  
“eth0”: {  
“macaddress”: xxxx..,  
“mtu”: 1500,  
“ipv4”: [  
{  
“address”: “192.168.1.1”,  
“netmask”: “255.255.255.0”,  
“network”: “192.168.1.0”,  
“alias”: “eth0”  
},  
{  
“address”: “192.168.1.2”,  
“netmask”: “255.255.255.0”,  
“network”: “192.168.1.0”,  
“alias”: “eth0”  
},  
{  
“address”: “192.168.1.3”,  
“netmask”: “255.255.255.0”,  
“network”: “192.168.1.0”,  
“alias”: “eth0:0”  
}  
],  
“ipv6”: [  
{

# No changes

“address”: … ,  
“prefix”: … ,  
“scope”: …  
},  
{  
…  
}  
]  
}, # close interfaces.eth0  
“eth1”: {  
…  
},  
“default\_ipv4”: {  
“interface”: “eth0”,  
“address”: “192.168.1.3”,  
“netmask”: “255.255.255.0”,  
“alias”: “eth0:0”  
},  
“default\_ipv6”: {  
“interface”: “eth1”,  
“address”: … ,  
“prefix”: … ,  
“scope”: …  
}  
} # close interfaces

Advantages:

**1. Backwords compatible**  
You can still iterate over ‘ansible\_interfaces’ as before to get the same result. This does not affect ‘ansible\_eth0’-type facts, which can be deprecated but kept available for a couple more releases.

**2. Easy to iterate over all interfaces’ information**  
Currently it is not straightforward to iterate over all addresses of all interfaces, because each interface has its own ansible\_$iface fact. With this new structure, each interface can be explored using ansible\_interfaces[$iface].

**3. More natural representation of aliased interfaces**  
With this new structure, all IPv4 addresses assigned to an interface, whether as additional addresses for the same interface via ‘ip addr add’, or as interface aliases (eth0:0), are listed together in one list, with an alias key to be able to tell aliased interfaces apart. This structures resembles the output of the ‘ip addr’ command:

2: venet0: \<BROADCAST,POINTOPOINT,NOARP,UP,LOWER\_UP\> mtu 1500 qdisc noqueue state UNKNOWN  
link/void  
inet [127.0.0.2/32](http://127.0.0.2/32) scope host venet0  
inet \<…\>/32 scope global venet0:0

**4. Differentiates multiple IPv4 addresses assigned to one interface from aliased interfaces**  
The current structure represents additional IPv4 addresses that are assigned to one interface as if they were assigned to an alias: it creates eth0:0 for the second IPv4 assigned to eth0, although there may not be a et0:0 in the underlying system. Worse, if there were an actual eth0:0, it would be named eth0:1, because eth0:0 had already been used.  
The new structure lists all those addresses under the main interface, and distinguishes those assigned to an aliased interface using the ‘alias’ key.

**5. Easier queries**  
Get all IPv4 addresses assigned to eth0:  
[i[‘address’] for i in ansible\_interfaces[‘eth0’][‘ipv4’]]

Get all IPv4 addresses assigned only to eth0, and not to any of its aliases:  
[i[‘address’] for i in ansible\_interfaces[‘eth0’][‘ipv4’] if i[‘alias’] == ‘eth0’]

**6. Straightforward default interface information**  
With the current structure, using an exact copy of an interface tree for ‘default\_ipv6’ is not enough, because that tree may have multiple addresses. Also, getting the actual IPv4 default interface is a little awkward:  
ansible\_default\_ipv4\_interface[‘ipv4’][‘device’]

Compared to:  
ansible\_interfaces[‘default\_ipv4’][‘interface’]

**7. No “device”**  
Avoid using the ‘device’ key which was used to map aliased interfaces to their real interfaces. ‘device’ vs. ‘interface’ can get confusing.

**8. Consistent: ipv4 vs. ipv6, multi-ip vs. alias**  
More overall consistency. IPv4 data has the same structure as IPv6. Multiple IPv4 addresses assigned to the same interface are listed together, whether they are assigned to the interface or one of its aliases.

**9. Portability to BSD**  
[https://github.com/ansible/ansible/issues/842#issuecomment-7656871](https://github.com/ansible/ansible/issues/842#issuecomment-7656871)

Let me know if this structure is missing anything, or if it breaks any currently working use cases.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan](https://avatars.discourse-cdn.com/v4/letter/m/c4cdca/32.png) [@Michael\_DeHaan](https://forum.ansible.com/u/Michael_DeHaan)\
**Post date:** [August 13, 2012, 12:00pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/12 "2012-08-13T12:00:41Z")

</div>

This discussion has gone on a bit long. We have a large list of 250+  
plus people and few people actually seem interested in this, plus we  
can't really be rearranging facts at all, so I'd like be finished with  
it.

We have a documented structure already, and while it may not be  
conceptually super-ideal to all people (nothing is), iteration over  
interfaces in templates is not something that comes up a lot.

Most folks just need the ip address for a given interface, and I agree  
it would be nice if they didn't have to care what the name was, which  
is where ansible\_ipv4\_interface comes in -- if they just need to know  
from dbserver A what the IP address of all the webservers are... you  
can do that.

We should just add the ansible\_ipv4\_interface fact -- we do not need  
an 'ipv4' subkey, and then this is done.

I am not willing to have a parallel structure of facts -- takes up  
unneccessary space and feels redundant -- and I'm not willing to break  
backwards compatibility either.

--Michael

---

<div class="post-metadata">

**Author:** ![Ahmad\_Khayyat](https://avatars.discourse-cdn.com/v4/letter/a/858c86/32.png) [@Ahmad\_Khayyat](https://forum.ansible.com/u/Ahmad_Khayyat)\
**Post date:** [August 13, 2012, 12:42pm UTC](https://forum.ansible.com/t/reusable-fact-derived-variables/11794/13 "2012-08-13T12:42:28Z")

</div>

Oh well… I felt good about it, being backwords compatible and all. I had already implemented most of it when you sent this message.

Besides, it does fix a broken scenario: when eth0 has two IPs, the current implementation will create eth0 and eth0:0 interfaces. When it then finds a real eth0:0, it will name it eth0:1.

Anyway, it’s done and ready to try if you are interested. I’ll send a pull request anyway. The ball is in your court.
