# Sets arithmetic, variable precedence

**URL:** <https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949>\
**Category:** Ansible Project\
**Tags:** fedora\
**Created:** [June 12, 2013, 10:45am UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949 "2013-06-12T10:45:35Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 12, 2013, 10:45am UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/1 "2013-06-12T10:45:35Z")

</div>

Hello list,

how would you approach the following problem, which I have  
simplified for the purpose of this list?

Let's assume you want Ansible to configure NTP clients on your  
hosts.

Let's assume furthermore that your infrastructure consists of  
a heterogeneous set of Debian and Fedora nodes, some running as  
VServers, others as KVM instances. VServers cannot set time&date, so  
they cannot be NTP clients.

Finally, imagine that your hosts are situated either in the office,  
or in data centres in Munich and Zurich, and you want to parametrise  
the NTP server to use by location. Since there is no NTP server near  
the office, you just want to use the distribution defaults  
(debian|fedora).pool.ntp.org.

Therefore, the following group vars get defined for the  
distribution groups:

&nbsp;&nbsp;debiannodes::ntp\_server = debian.pool.ntp.org  
&nbsp;&nbsp;fedoranodes::ntp\_server = fedora.pool.ntp.org

I would like to let the location-based variables have precedence  
over the distro-based ones. This means, however, that I have to make  
the location be subgroups of the distro-groups, hence requiring me  
to make four new groups, and to duplicate the information between  
them (I am assuming multiple inheritance is not possible):

&nbsp;&nbsp;debiannodes@zurich::ntp\_server = ch.pool.ntp.org  
&nbsp;&nbsp;debiannodes@munich::ntp\_server = de.pool.ntp.org  
&nbsp;&nbsp;fedoranodes@zurich::ntp\_server = ch.pool.ntp.org  
&nbsp;&nbsp;fedoranodes@munich::ntp\_server = de.pool.ntp.org

Is there any other way to approach this? I would really prefer not  
to have to maintain redundancy in the inventory or the playbooks.

Also, am I right in assuming that the best way to handle VServers is  
to create yet another group for all VServers, and then to use  
'only\_if' in the NTP-client playbook, e.g.

---

<div class="post-metadata">

**Author:** ![GW1](https://avatars.discourse-cdn.com/v4/letter/g/c89c15/32.png) [@GW1](https://forum.ansible.com/u/GW1)\
**Post date:** [June 12, 2013, 11:45am UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/2 "2013-06-12T11:45:32Z")

</div>

Hi,

Why don’t you introduce new groups like eg.:

- hosts (for all hardware nodes)
- vservers (for all VServer containers)
- kvms (for all KVM instances)

?

And than you can have plays that target specific groups and install just things for those groups (like NTP for hosts and kvms).

Greetings,  
gw

---

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 12, 2013, 12:51pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/3 "2013-06-12T12:51:22Z")

</div>

also sprach GW \<gw.2013@tnode.com\> [2013.06.12.1345 +0200]:

> - hosts (for  
> all hardware nodes)  
> - vservers (for all VServer containers)  
> - kvms (for  
> all KVM instances)
> 
> And than you can have plays that target  
> specific groups and install just things for those groups (like NTP for  
> hosts and kvms).

Sure, but if you spin this further, it'll result in a massive number  
of groups and become unmaintainable, I think.

---

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 12, 2013, 12:51pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/4 "2013-06-12T12:51:23Z")

</div>

Following up to my own post, I would like to talk about "reclass",  
which is an "external node classifier" that I wrote many years ago  
for Puppet,¹ have since ported to Salt,² and am now also considering  
for Ansible, because I think it would address some of the issues  
I am facing with Ansible.

The purpose of this message is to find those who think similarly to  
me, and who may have already figured out Ansible-native ways to do  
what I want/need to do. Quite frankly, I'd love to use pure Ansible,  
rather than porting "reclass" over.

Here are a number of paradigms/aspects of Ansible. If any of these  
ring a bell in relation to problems you are having with Ansible,  
then I think you should read on and learn about "reclass":

&nbsp;&nbsp;- The inventory is a list of groups of hosts, and hosts may appear  
&nbsp;&nbsp;&nbsp;&nbsp;in more than one group. The inventory is not a group of hosts  
&nbsp;&nbsp;&nbsp;&nbsp;with associated groups.

&nbsp;&nbsp;- Groups can be nested, but a group can only ever have one parent  
&nbsp;&nbsp;&nbsp;&nbsp;(no multiple inheritance);

&nbsp;&nbsp;- Variable precedence depends on group inheritance: host variables  
&nbsp;&nbsp;&nbsp;&nbsp;override those variables set in subgroups, which override those  
&nbsp;&nbsp;&nbsp;&nbsp;variables set in parent groups;

&nbsp;&nbsp;- If a host belongs to two groups that are not in the same  
&nbsp;&nbsp;&nbsp;&nbsp;inheritance graph, and both groups define different values for  
&nbsp;&nbsp;&nbsp;&nbsp;the same variable, it is undefined which value the variable will  
&nbsp;&nbsp;&nbsp;&nbsp;have for the given host;

&nbsp;&nbsp;- A playbook hard-codes the group of hosts to which it will be  
&nbsp;&nbsp;&nbsp;&nbsp;applied. While the set may be reduced using --limit, it is not  
&nbsp;&nbsp;&nbsp;&nbsp;possible to apply a playbook to a host not in the target set  
&nbsp;&nbsp;&nbsp;&nbsp;(without changing the set);

Let's illustrate the way reclass would work (if I ported it to  
Ansible), using the NTP client example.

Let's assume:

&nbsp;&nbsp;1. we have Debian and Fedora hosts, some of which run as VServers  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(which don't allow setting date/time, so NTP makes no sense).  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;The distribution defaults for NTP servers are  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(debian|fedora).pool.ntp.org and we like that;

&nbsp;&nbsp;2. our nodes are either in the office basement, or in datacentres  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;in Munich or Zurich. At the office, we are fine with the  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;distribution defaults, except the host "red" should use  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"example.org". In the datacentres, we would like to use  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(de|ch).pool.ntp.org instead. Yes, this is maybe a bit  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;contrived, but it'll do as an example.

Reclass works based on the concepts of nodes and roles, but since  
"role" is already taken within Ansible, let's use "class" instead.  
Conceptually, classes are similar to Ansible host groups, but  
I think it's better to keep them separate for now.

Fundamentally, nodes and classes use identical data structures with  
three fields — classes, playbooks, and variables. Their difference  
is only in the way they are processed by "reclass".

A node usually only defines a set of classes, and optionally  
variables (host\_vars), but may also define playbooks directly, e.g.  
for quick tests or single-host exceptions. A node is identified by  
its FQDN (inventory\_hostname).

A class defines other classes from which it inherits, playbooks that  
apply to this class, and variables (group\_vars). A class may also  
just define variables to override other classes, as we shall see  
shortly.

Therefore, reclass deals with tree graphs rooted at a node (a host),  
while all other vertices in that graph are classes. The further away  
a class vertex is from the root node, the more generic the class is  
said to be, e.g.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/ | \ `--- backuppc.client  
&nbsp;&nbsp;&nbsp;debian@wheezy | `--- postfix.satellite  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/ |  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debian munich  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/ \  
&nbsp;&nbsp;&nbsp;&nbsp;unix molly-guard  
&nbsp;&nbsp;&nbsp;&nbsp;/  
ntpclient

For each node, reclass performs a depth-first walk of all the  
classes (in order of their definition), and their parent classes.  
When it reaches a leaf (a class), it reads the list of playbooks and  
stores the variables defined. As it ascends back up the tree, it  
merges the set of playbooks and the set of variables.

Put differently: the last class to set a variable takes precedence  
over earlier classes, and the node has the final say. In the above  
graph, if all classes defined $foo, then the final value of $foo  
would be whatever backuppc.client set, unless overridden by the node  
itself. If both the 'debian' and the 'munich' class defined an NTP  
server, the server from the 'munich' class would win.

Let's introduce six hosts for our example.

&nbsp;&nbsp;black Debian Munich  
&nbsp;&nbsp;yellow Debian Munich VServer  
&nbsp;&nbsp;blue Debian Zurich  
&nbsp;&nbsp;white Fedora Zurich  
&nbsp;&nbsp;red Debian Office ntp\_server: example.org  
&nbsp;&nbsp;green Fedora Office

Here's what the reclass data structures would look like to achieve  
our NTP client goal (not as complex as the graph above). First, the  
classes:

&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: unixnode name: vservers  
&nbsp;&nbsp;playbooks: playbooks:  
&nbsp;&nbsp;- ntp\_client - ~ntp\_client  
&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: debiannode name: fedoranode  
&nbsp;&nbsp;classes: classes:  
&nbsp;&nbsp;- unixnode - unixnode  
&nbsp;&nbsp;playbooks: playbooks:  
&nbsp;&nbsp;- apt - yum  
&nbsp;&nbsp;variables: variables  
&nbsp;&nbsp;- ntp\_server: debian.pool.ntp.org - ntp\_server: fedora.pool.ntp.org  
&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: hosted@munich name: hosted@zurich  
&nbsp;&nbsp;playbooks: playbooks:  
&nbsp;&nbsp;- motd@servus - motd@gruezi # whatever …  
&nbsp;&nbsp;variables: variables:  
&nbsp;&nbsp;- ntp\_server: de.pool.ntp.org - ntp\_server: ch.pool.ntp.org

And now the nodes:

&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: black name: yellow  
&nbsp;&nbsp;classes: classes:  
&nbsp;&nbsp;- debiannode - debiannode  
&nbsp;&nbsp;- hosted@munich - hosted@munich  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- vservers  
&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: blue name: white  
&nbsp;&nbsp;classes: classes:  
&nbsp;&nbsp;- debiannode - fedoranode  
&nbsp;&nbsp;- hosted@zurich - hosted@zurich  
&nbsp;&nbsp;--- ---  
&nbsp;&nbsp;name: red name: green  
&nbsp;&nbsp;classes: classes:  
&nbsp;&nbsp;- debiannode - fedoranode  
&nbsp;&nbsp;variables:  
&nbsp;&nbsp;- ntp\_server: example.org

Since the 'hosted@' classes appear later in the classes list for  
each node, their variables take precedence, and so the NTP servers  
are set according to location, unless there are no 'hosted@'  
classes, when the values from the distro-classes are used. In the  
case of "red", the host-specific variable definition trumps  
everything else.

And since the 'vservers' class negates the ntp\_client playbook, it  
would cause that playbook to be removed from the list of playbooks  
for the host "yellow", since the class is listed after 'unixnodes'

\* \* \*

At this point, I would be really curious to hear from people who  
think alike, and how they do it.

Or if you think that I am completely down the wrong track, tell me  
why.

As I said before, I have better things to do that to port reclass,  
but I will port it to Ansible if I cannot find another way to  
achieve the paradigm I am striving for.

If I were to port this to Ansible, then it would be an external  
inventory script. It would put hosts into groups that correspond to  
playbooks, e.g.

&nbsp;&nbsp;"ntp\_servers\_hosts" : ['blue','red','green','white','black']  
&nbsp;&nbsp;"apt\_hosts" : ['blue','red','green','black','yellow']  
&nbsp;&nbsp;"yum\_hosts" : ['green','white']

Finally, I would create a playbook for each of these groups,  
targetting that group, and also a site playbook that combines them  
all.

Thoughts?

-martin

Footnotes:  
(¹) [http://projects.puppetlabs.com/projects/hiera/](http://projects.puppetlabs.com/projects/hiera/) seems like it  
&nbsp;&nbsp;&nbsp;&nbsp;grew out of the ideas behind reclass, but I had left Puppet  
&nbsp;&nbsp;&nbsp;&nbsp;before Hiera was started, so I don't know for sure.  
(²) [https://github.com/madduck/salt-reclass/blob/master/README](https://github.com/madduck/salt-reclass/blob/master/README)

---

<div class="post-metadata">

**Author:** ![Brian\_Coca1](https://avatars.discourse-cdn.com/v4/letter/b/278dde/32.png) [@Brian\_Coca1](https://forum.ansible.com/u/Brian_Coca1)\
**Post date:** [June 12, 2013, 3:49pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/5 "2013-06-12T15:49:02Z")

</div>

I use a play ‘dynamic\_groups.yml’ which creates vm based, pkg manager based and locatoin based (from standard hostnames) groups, yes you get tons of groups but you don’t need to maintain the lists. I have empty [newyork] group that this populates via group\_by, then set the ntp in group\_vars/newyork.

this is a bit messy and i want to change to a dynamic\_groups\_inventory.py that should make it much cleaner (dynamic\_groups always shows changed)

---

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 13, 2013, 6:36am UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/6 "2013-06-13T06:36:23Z")

</div>

also sprach Brian Coca \<briancoca@gmail.com\> [2013.06.12.1749 +0200]:

> I use a play 'dynamic\_groups.yml' which creates vm based, pkg manager based  
> and locatoin based (from standard hostnames) groups, yes you get tons of  
> groups but you don't need to maintain the lists. I have empty [newyork]  
> group that this populates via group\_by, then set the ntp in  
> group\_vars/newyork.

Would you share that with us? It sounds interesting and I'd love to  
look at an example.

---

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 14, 2013, 5:58pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/7 "2013-06-14T17:58:32Z")

</div>

Dear list,

I found some time today to port reclass to Ansible. Actually,  
I rewrote it, but you shouldn't care.

There's an elaborate README, so head on over to  
[https://github.com/madduck/reclass](https://github.com/madduck/reclass) if you are interested.  
Integration with Ansible is a breeze. And problems with scope and  
variable precedence a thing of the past. 😉

Comments, ideas, patches, flames, and cookies welcome,

---

<div class="post-metadata">

**Author:** ![Romeo\_Theriault](https://avatars.discourse-cdn.com/v4/letter/r/839c29/32.png) [@Romeo\_Theriault](https://forum.ansible.com/u/Romeo_Theriault)\
**Post date:** [June 14, 2013, 8:40pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/8 "2013-06-14T20:40:30Z")

</div>

Cool. Thanks for sharing.

---

<div class="post-metadata">

**Author:** ![Brian\_Coca1](https://avatars.discourse-cdn.com/v4/letter/b/278dde/32.png) [@Brian\_Coca1](https://forum.ansible.com/u/Brian_Coca1)\
**Post date:** [June 16, 2013, 3:50pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/9 "2013-06-16T15:50:51Z")

</div>

[http://pastebin.com/4QTMn5ey](http://pastebin.com/4QTMn5ey)

working on moving this to an inventory script still.

---

<div class="post-metadata">

**Author:** ![martin\_f\_krafft](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@martin\_f\_krafft](https://forum.ansible.com/u/martin_f_krafft)\
**Post date:** [June 16, 2013, 4:30pm UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/10 "2013-06-16T16:30:09Z")

</div>

also sprach Brian Coca \<briancoca@gmail.com\> [2013.06.16.1750 +0200]:

> [http://pastebin.com/4QTMn5ey](http://pastebin.com/4QTMn5ey)
> 
> working on moving this to an inventory script still.

Try reclass! [https://github.com/madduck/reclass](https://github.com/madduck/reclass)

---

<div class="post-metadata">

**Author:** ![Brian\_Coca1](https://avatars.discourse-cdn.com/v4/letter/b/278dde/32.png) [@Brian\_Coca1](https://forum.ansible.com/u/Brian_Coca1)\
**Post date:** [June 17, 2013, 4:01am UTC](https://forum.ansible.com/t/sets-arithmetic-variable-precedence/12949/11 "2013-06-17T04:01:03Z")

</div>

Thanks, I saw your previous announcement, it was already on my queue to test.

This weekend was the first time i had some free time, long list to catch up on.
