# Ansible OS Abstraction

**URL:** <https://forum.ansible.com/t/ansible-os-abstraction/15527>\
**Category:** Ansible Project\
**Created:** [June 23, 2014, 5:05pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527 "2014-06-23T17:05:20Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nico\_K](https://avatars.discourse-cdn.com/v4/letter/n/46a35a/32.png) [@Nico\_K](https://forum.ansible.com/u/Nico_K)\
**Post date:** [June 23, 2014, 5:05pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/1 "2014-06-23T17:05:20Z")

</div>

Hi,

So I’ve been scavenging through a lot of posts to figure out how to deal with heterogeneous environments.  
The two approaches that I see most are either using “group\_by” or using “when”.

For example:

site.yml:

- name: whatever  
tasks:

- group\_by: key={{ansible\_os\_family}

- hosts: Debian  
roles:

- role: rolename-debian

- hosts: Darwin  
roles:

- role: rolename-darwin

My problem with this approach is that you are saying that a role is “OS” specific, even though it’s not. Perhaps this has to do with my definition of role, but as I see it a ‘role’ is a task that any given node can perform.  
I should be able to assign the ‘role’ ‘nginx’ to a variety of hosts, how those hosts then implement that role should be defined within the ‘role’ definition.

Now supposedly you can do this using the ‘when’ conditional statement, you would then end up with something like:

roles/myRole/tasks

- include: apt.yml  
when: ansible\_os\_family == “Debian”

- include: brew.yml  
when ansible\_os\_family == “Darwin”

However this is rather chatty, especially when these files include files of there own. And with chatty I mean every task, even when the ‘when’ clause is not matched is being shown, now you can set 'show\_skipped\_hosts" in the ansible configuration, however this still shows the headers of tasks that are (not) being processed.

Should I be dealing with this in a different fashion?  
What I’m trying to accomplish is having a playbook that installs a package on a bunch of machines (running differents OS’s), then configure that package based on the OS and configure the service accordingly.

IMHO the latter approach is the way to go, however the ‘chattyness’ is killing my operators.

Thanks a lot for sharing your insights.

Best regards.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 23, 2014, 6:33pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/2 "2014-06-23T18:33:39Z")

</div>

Most all ansible modules already abstract out OS details.

Package managers we want you to know - not only do package names change, but the way you interact with those packages change - think of how different Apache is between Ubuntu and CentOS for instance.

You can do things like

- include: “{{ ansible\_os\_family }}.yml”

And you can also do things like use “include\_vars” with similar tricks where you want to maintain differences.

In most cases you’ll only differ by variables except having a few tasks with “when” statements on them keying off the OS - which will also minimize task duplication.

---

<div class="post-metadata">

**Author:** ![Nico\_K](https://avatars.discourse-cdn.com/v4/letter/n/46a35a/32.png) [@Nico\_K](https://forum.ansible.com/u/Nico_K)\
**Post date:** [June 23, 2014, 7:25pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/3 "2014-06-23T19:25:04Z")

</div>

Sure, but that’s exactly the thing I would like to deal with within a role, within a role however you can’t perform the ‘include’ you stated in your post as “ansible\_os\_family” doesn’t seem to evaluate.

---

<div class="post-metadata">

**Author:** ![Nico\_K](https://avatars.discourse-cdn.com/v4/letter/n/46a35a/32.png) [@Nico\_K](https://forum.ansible.com/u/Nico_K)\
**Post date:** [June 24, 2014, 10:18am UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/4 "2014-06-24T10:18:43Z")

</div>

To see how far I could come I went with the second approach that I mentioned in my original post.  
With some tinkering I managed to abstract away most of the differences, it’s way too chatty though but it works.  
However, I am stuck with the Handlers. As ‘service’ isn’t supported on OSX I had the idea of defining my own Handler like:

- name: restart someservice  
shell: launchctl {{ item }} {{ service\_plist}}  
with\_items:
- stop
- start  
when: ansible\_os\_family == “Darwin”

This works, except the ‘when’ clause isn’t processed, after all as per the manual, handlers are matched by name. I could then go down the lane of naming the service accordingly:

- name: restart {{ ansible\_os\_family }}

however this doesn’t work either as you can’t seem to dynamically set the name of a handler, so the only approach that I can think of that does work is giving the handlers another name:  
for example “restart nginx darwin / restart nginx ubuntu”.  
However that means that in my generic code where I change a file I will have to make a choice which handler to notify based on the OS I’m running on; same problem as before, you can’t do:  
notify:

- restart nginx {{ ansible\_os\_family }}

Things would be a lot easier if ‘Service’ would be implemented for OS-X and package would be abstracted (like for example Salt does).

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 24, 2014, 1:26pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/5 "2014-06-24T13:26:26Z")

</div>

This is not true.

Task files in roles (main.yml) can include other files.

include\_vars also works as a task as I have mentioned.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 24, 2014, 1:27pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/6 "2014-06-24T13:27:24Z")

</div>

It would be better to enhance the service module to understand launchd, but that’s a different topic.

---

<div class="post-metadata">

**Author:** ![Christian\_Thiemann](https://avatars.discourse-cdn.com/v4/letter/c/f04885/32.png) [@Christian\_Thiemann](https://forum.ansible.com/u/Christian_Thiemann)\
**Post date:** [June 24, 2014, 2:31pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/7 "2014-06-24T14:31:58Z")

</div>

It doesn’t work for me (unless I create a file called roles/foobar/tasks/{{ansible\_os\_family}}.yml… 😉 ).

I abstracted away package manager things using the ansible\_pkg\_mgr fact:

- module: “{{ ansible\_pkg\_mgr }} name=foobar,baz,foo”

To account for OS-specific package names, you can do something like this:

- module: “{{ ansible\_pkg\_mgr }} name={{ pkg\_foobar }},{{ pkg\_baz }},{{ pkg\_foo }}”

The pkg\_XXX variables must be defined in vars/Debian.yml and vars/Darwin.yml, and you do this at the top of the role:

- include\_vars: ‘{{ ansible\_os\_family }}.yml’

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 24, 2014, 4:04pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/8 "2014-06-24T16:04:49Z")

</div>

“It doesn’t work for me (unless I create a file called roles/foobar/tasks/{{ansible\_os\_family}}.yml… 😉 ).”

So in other words, it works 🙂

Also, public service announcement - Don’t use ansible\_pkg\_mgr like that - it won’t join mulitple package installs using with\_items into single transactions. If you want to do a “when: ansible\_pkg\_mgr == ‘apt’” that will still keep it going.

---

<div class="post-metadata">

**Author:** ![Christian\_Thiemann](https://avatars.discourse-cdn.com/v4/letter/c/f04885/32.png) [@Christian\_Thiemann](https://forum.ansible.com/u/Christian_Thiemann)\
**Post date:** [June 24, 2014, 4:36pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/9 "2014-06-24T16:36:41Z")

</div>

> “It doesn’t work for me (unless I create a file called roles/foobar/tasks/{{ansible\_os\_family}}.yml… 😉 ).”
> 
> So in other words, it works 🙂

No, sorry, I meant literally “{{ansible\_os\_family}}.yml” – it doesn’t seem to do templating for the included file name. I think I remember this being a popular feature request some time ago, but you wrote it’s important that the list of tasks in a play is fixed for every host (and it’s determined on parsing time, not run-time). Correct me if I missed something.

> Also, public service announcement - Don’t use ansible\_pkg\_mgr like that - it won’t join mulitple package installs using with\_items into single transactions. If you want to do a “when: ansible\_pkg\_mgr == ‘apt’” that will still keep it going.

Yes, in my roles I am working around that by supplying comma-separated lists myself instead of using with\_items. Using ansible\_pkg\_mgr also has the disadvantage of not being able to use apt/yum/portage-specific parameters, but for virtually all of my package-manager needs it is sufficient – and so, so, so much nicer in the output 😉

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 24, 2014, 7:16pm UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/10 "2014-06-24T19:16:16Z")

</div>

> > "It doesn't work for me (unless I create a file called  
> > roles/foobar/tasks/{{ansible\_os\_family}}.yml... 😉 )."
> > 
> > So in other words, it works 🙂
> 
> No, sorry, I meant literally "{{ansible\_os\_family}}.yml" -- it doesn't  
> seem to do templating for the included file name. I think I remember this  
> being a popular feature request some time ago, but you wrote it's important  
> that the list of tasks in a play is fixed for every host (and it's  
> determined on parsing time, not run-time). Correct me if I missed something.

Sorry, yes, It is true you can't include off inventory scoped variables  
like that.

However

- include: RedHat.yml  
&nbsp;&nbsp;when: ansible\_os\_family == 'RedHat'

That works fine.

include\_vars is fine with such tricks, however

---

<div class="post-metadata">

**Author:** ![Nico\_K](https://avatars.discourse-cdn.com/v4/letter/n/46a35a/32.png) [@Nico\_K](https://forum.ansible.com/u/Nico_K)\
**Post date:** [June 25, 2014, 8:22am UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/11 "2014-06-25T08:22:46Z")

</div>

That’s exactly the workaround I’m using now, however you can’t do something similar for ‘notify’.

e.g.

notify:

- reload {{ ansible\_os\_family }}

doesn’t work, putting a when clause on the entire block just for the sake of using different notifiers means duplication of an entire block and again will also increase the chatty-ness  
What’s the preferred way of dealing with variable notifiers?

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 26, 2014, 12:10am UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/12 "2014-06-26T00:10:06Z")

</div>

No, you cannot notify based on a fact.

---

<div class="post-metadata">

**Author:** ![Michael\_DeHaan1](https://avatars.discourse-cdn.com/v4/letter/m/5e9695/32.png) [@Michael\_DeHaan1](https://forum.ansible.com/u/Michael_DeHaan1)\
**Post date:** [June 26, 2014, 12:11am UTC](https://forum.ansible.com/t/ansible-os-abstraction/15527/13 "2014-06-26T00:11:11Z")

</div>

Here’s the idiom:

tasks:

- shell: foo  
notify:
- ack
- bar

handlers:

- name: ack  
when: some\_condition

- name: bar  
when: not some\_condition
