defaults files per os and release

In the current Ansible setup, you can have 1 file in defaults (i.e. defaults/main.yml) and it is used for all hosts that run that task, no matter what the host.

In our use case, we have found it would be highly advantageous to be able to have os-specific and release-specific defaults files. For instance, some applications change directives between releases – using a directive in a release that doesn’t support it, in the best case just issues a warning (which is not ideal), but in the far more likely case, breaks something.

We cannot use include_vars as this completely ruins precedence: http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable

To illustrate, here is an example/outline of our setup:

file structure:

  • inventory/production/host_vars/mail-server-1
  • inventory/production/group_vars/mail-servers
  • roles/servers/mail-server
  • roles/postfix
  • mail-server.yml

mail-server.yml:
roles:

  • roles/servers/mail-server

roles/servers/mail-server/meta/main.yml:
dependencies:

  • { role: postfix }

summary: What the above is trying to illustrate, is that we have our re-usable building blocks in “roles” and server-specific setups in “roles/servers”, e.g. the “mail-server” role runs through postfix as meta, and then runs through its own tasks. This gives us the flexibility to:

  • set host-specific vars in the inventory host_vars variables (e.g. relayhost=“”)

  • set vars common to particular groupings of mail servers in inventory group_vars variables (e.g. relay_domains=domain.foo)

  • set vars common to mail-servers in general in roles/servers/mail-server/vars (e.g. content-filter=smtp-amavis:[127.0.0.1]:10024)

  • set vars for everything that runs the postfix role (e.g. heloname=“{{ ansible_hostname }}”)

If for example, we were to use include_vars to try to include variables specific to CentOS, it would be the highest precedence and override host specific settings with generic CentOS stuff.

Our current working solution is to do a large amount of “union-ing” in the defaults file based on variables, i.e. to get the proper postfix configuration options trusty v. xenial:
postfix_configuration_options: “{{ postfix_common_configuration_options | union( (ansible_distribution_release==‘xenial’) | ternary(postfix_xenial_configuration_options,) ) | union( (ansible_distribution_release==‘trusty’) | ternary(postfix_trusty_configuration_options,) ) }}”

This is not ideal for a number of reasons:

  • unless you already know what it does, it can take some time to figure out
  • it gets messier and messier the more distributions you support
  • it’ll get slower to run the more distributions are supported

But is still useful as you can have a common template for everything then:
main.cf.j2:

{{ ansible_managed }}

{% for item in postfix_configuration_options %}

{% if item.option is defined and item.value is defined %}
{% if item.comment is defined %}
{{ item.comment }}
{% endif %}
{{ item.option }} = {{ item.value }}
{% endif %}
{% endfor %}

What we had planned on doing was using the meta structure to include specific roles with particular defaults based on os and release, e.g.:

  • mail-server.yml:
    roles:

  • meta/postfix

  • meta/postfix/meta/main.yml:
    dependencies:

  • { role: os/CentOS/postfix, when: ansible_distribution == ‘CentOS’ }

  • { role: releases/trusty/postfix, when: ansible_distribution_release == ‘trusty’ }

  • { role: releases/xenial/postfix, when: ansible_distribution_release == ‘xenial’ }

  • { role: postfix }

  • release/trusty/postfix/defaults/main.yml:

postfix_configuration_options:
postscreen_dnsbl_ttl

  • release/trusty
    postfix_configuration_options:
    address_verify_pending_request_limit

And then in the aforementioned main.cf.j2, xenial hosts would define “address_verify_pending_request_limit” (which is not a thing available in the postfix release for trusty) and trusty hosts would define “postscreen_dnsbl_ttl” (which is not a thing available in the postfix release for xenial).

While it works for that case, I’ve found that even when a meta role is skipped, the defaults file is still read, so for situations where you had the same directive, but wanted different values based on os/release, it would not work (i.e. in the following example, “biff” would always be set to “yes”):

  • release/trusty/postfix/defaults/main.yml:

postfix_configuration_options:
biff = no

  • release/trusty
    postfix_configuration_options:
    biff = yes

Does anyone have any ideas for how to have a defaults file per os/release like I want? Does anyone have alternative suggestions on setups such that I would not need/want this anymore?

I’ve got the same issue (in various roles) and still cannot foresee a good enough fix exists (or planned)

Hi,

Have you tried include_vars:
https://docs.ansible.com/ansible/latest/modules/include_vars_module.html

It has such example:

- name: Load a variable file based on the OS type, or a default if not
found. Using free-form to specify the file.
  include_vars: "{{ lookup('first_found', possible_files) }}"
  vars:
    possible_files:
      - "{{ ansible_distribution }}.yaml"
      - "{{ ansible_os_family }}.yaml"
      - default.yaml