Role to encapsulate several OS specific "sub-roles"

Hello,

I’ve written a role to install and configure Apache, the role is at the moment specific to Debian.
This means my role/apache/tasks/main.yml contains corresponding tasks.

I need also to support CentOS.
I could add corresponding tasks to the same file and add a when clause for each task, based on OS version.
But it makes more sense to create a specific role for each os: apache-debian and apache-centos

My main playbook, site.yml include the apache role:
role:

  • apache

I’d like my playbook to describe I want Apache installed and the apache role to install the right role based on the host OS.
As a task file can’t conditionally include (yet!) a role, the only option (not tested) I’ve come so far to implement it, is to use role dependency and when clause by putting in role/apache/meta/main.yml:
dependencies:

  • { role: apache-debian, when: “ansible_os_family == ‘Debian’” }

  • { role: apache-centos, when: “ansible_os_family == ‘CentOS’” }

and role/apache/tasks/main.yml would be empty and just acts as a dispatcher.

This way my apache role encapsulates the apache-debian and apache-centos roles, so far so good.
But I’d like these sub-roles to be packaged with the apache role to be shared easily.
This means they could be defined in a “roles” sub-directory of the apache role.

I don’t think it’s currently possible so the sub-roles should be located at the same level than my shared apache role, which is far from perfect :slight_smile:

Do you see other options to achieve clean role encapsulation and packaging?

Thanks

@sebbrochet

Replying to myself…

It’s possible to reference a role using a path, i.e apache/roles/debian
Then I can create my sub-roles directly under my apache role:

apache
_ meta
_ main.yml
_ roles
_ centos
_ tasks
_ main.yml
_ debian

_ tasks
_ main.yml

And put in apache/meta/main.yml:

dependencies:

  • { role: apache/roles/debian, when: “ansible_os_family == ‘Debian’” }
  • { role: apache/roles/centos, when: “ansible_os_family == ‘RedHat’” }

Only minor drawback is that with the when clause, all tasks are listed even if they are not executed.
This could clutter the output if many tasks are not executed…

root@machine ~/devops/ansible2/projects/test-role-os $ ansible-playbook -i centos.hosts site.yml

PLAY [test-role-hosts] ********************************************************

GATHERING FACTS ***************************************************************
ok: [vps-centos]

TASK: [testrole/roles/debian | say hello from your os] ************************
skipping: [vps-centos]

TASK: [testrole/roles/centos | say hello from your os] ************************
changed: [vps-centos]

PLAY RECAP ********************************************************************
vps-centos : ok=2 changed=1 unreachable=0 failed=0

root@machine ~/devops/ansible2/projects/test-role-os $ ansible-playbook -i debian.hosts site.yml

PLAY [test-role-hosts] ********************************************************

GATHERING FACTS ***************************************************************
ok: [vps-debian]

TASK: [testrole/roles/debian | say hello from your os] ************************
changed: [vps-debian]

TASK: [testrole/roles/centos | say hello from your os] ************************
skipping: [vps-debian]

PLAY RECAP ********************************************************************
vps-debian : ok=2 changed=1 unreachable=0 failed=0

What about a task with a special when clause that doesn’t output anything if it’s not executed ?

Thanks

@sebbrochet

Here's what I do --

Prior to all my roles, I do a role called bootstrap across all of my
nodes. That role performs a group_by task:

- name: Create groups based on distribution
  group_by: key={{ ansible_distribution }}

then I execute my other roles -- first I execute the OS specific
role--assigned to the intersection of my group in inventory and the
group created by group_by task above--followed by the common role,
that works on both OSes.

- hosts: CentOS:RedHat:&riak_cluster
  gather_facts: no
  sudo: True
  roles:

  - { role: riak/redhat, tags: ["riak"] }
  - { role: riak/common, tags: ["riak"] }

- hosts: Ubuntu:&riak_cluster
  gather_facts: no
  sudo: True

  roles:

  - { role: riak/ubuntu, tags: ["riak"] }
  - { role: riak/common, tags: ["riak"] }

- James

You can include tasks conditionally within a role:

[roles/apache/tasks/main.yml]

  • include: debian.yml
    when: ansible_os_family == ‘Debian’
  • include: redhat.yml
    when: ansible_os_family == ‘RedHat’

Now do stuff which is both common to Debian and RedHat

As well as doing OS-specific tasks in there, you can use set_fact to set variables conditionally. e.g.

[roles/apache/tasks/debian.yml]

  • set_fact: service_name=apache2

[roles/apache/tasks/redhat.yml]

  • set_fact: service_name=httpd

In the common config: service name={{service_name}} state=started enabled=yes

(Aside: this is messy compared to dynamically pulling in a vars file as you can do in a playbook, but that feature isn’t available within a role AFAICS. But I’m with you: I believe this stuff belongs in the role, not the playbook)

You will still have the annoying thing that all RedHat tasks will be shown as “skipped” on a Debian host and vice versa. You can reduce the noise a bit using
http://www.ansibleworks.com/docs/intro_configuration.html#display-skipped-hosts

although if all your hosts are the same type, I believe the task header is still shown.