3

I'm planning to use Ansible to manage a fairly large set of Linux servers with a lot of variation among them.

The hosts will be grouped into roles with a hierarchy between them, there's a common role, on which every other depends, child-roles which depend on on common, and there may be grandchild-roles which depend on them, with no limit on dependency depth.

common
   |___ role1
   |___ role2
   |___ role3
          |___ role3.1

Each role has its own subset of templates for system configuration files.

roles/
   common/
       templates/
           etc/
               hosts
               resolv.conf
       tasks/
           main.yml
           system-settings.yml # imported in main.yml
   role1/
       templates/
           etc/
               hosts # role-specific version, overrides 'common'
               httpd/
                   conf/
                       httpd.conf
       tasks/
           main.yml
           system-settings.yml # imported in main.yml
   role2/
   role3/
   role3.1/

The tricky part is that I'd like to apply these templates hierarchically, similarly to the way Ansible resolves variable scopes. So, for a given child-role, the most specific version of a template file is applied, if a template does not exist in that role then apply the parent's version, searching up to the root level.

I have (sort of) achieved this by putting the following into the system-settings.yml for each role:

- name: Create directories
  file:
    path: /{{ item.path }}
    state: directory
    mode: '{{ item.mode }}'
  with_filetree: templates/
  when: item.state == 'directory'

- name: Template files
  template:
    src: '{{ item.src }}'
    dest: /{{ item.path }}
    mode: '{{ item.mode }}'
  with_filetree: templates/
  when: item.state == 'file'

- name: Recreate symlinks
  file:
    src: '{{ item.src }}'
    dest: /{{ item.path }}
    state: link
    force: yes
    mode: '{{ item.mode }}'
  with_filetree: templates/
  when: item.state == 'link'

It works, but unfortunately it applies all of the parent role's templates, then all of the child role's templates. This is a waste of work and dangerous in case a playbook execution aborts for some reason, since the hosts may be left with generic versions of the system files.

So, for the above example tree, when applying role1 to a group of hosts Ansible:

  1. applies /etc/hosts and /etc/resolv.conf from the common role
  2. applies /etc/hosts and /etc/httpd/conf/httpd.conf from the role1 role

But I want it to do:

  1. apply /etc/resolv.conf from the common role
  2. apply /etc/hosts from the role1 role (the most specific version)
  3. apply /etc/httpd/conf/httpd.conf from the role1 role

The template files should not be specifically listed in the tasks/playbooks due to management overhead, when adding a new file, or a role-specific version to override a generic one we don't want to have to change any playbooks or other configurations.

Is this achievable with Ansible?

I can see a possible solution using external scripts (as described in https://docs.ansible.com/ansible/2.4/playbooks_loops.html#iterating-over-the-results-of-a-program-execution), but I'm not sure how to pass down the role hierarchy into the script.

Etienne Neveu
  • 12,604
  • 9
  • 36
  • 59
André Fernandes
  • 2,335
  • 3
  • 25
  • 33
  • I don't have the full details of your work, but I think your usage on ansible is not the easiest, as you're trying to modify the same files with different roles. It should be easier and more ansible oriented to create role has the exclusive ownership on modification of files, and put the content inside template based on the fact and the role you assigned to the targets. for instance common files should be only modified in you common roles, and you put the logic based on the roles assigned in the templates. – Baptiste Mille-Mathias Jul 07 '18 at 11:07

1 Answers1

0

My thoughts:

First Choice: Change the "common" file tasks to "common" handlers. The reason I picked handlers is we can conditionally include handlers. Also we have roles pre/post handlers. Notify what all tasks that are not done in role and what must be done can be in pre role handlers.

Second Choice: Conditionally include tasks, I think lot of effort is needed to attach tags to included files. E.g:

---
-name: task inclusion
Hosts: localhost
Gether_facts: false
Tasks:
Include: common.yaml
When: item | bool
A_list:
  -true
  -false

Hope this helps.

Regards Sudhakar

Sudhakar MNSR
  • 594
  • 1
  • 3
  • 17