0

I have a file in my Ansible role vars/sonarqube.yml with content

---
lvm_roles:
  sonarqube:
    size: '10g'
    path: '{{ sonar_home }}'

And a file group_vars/all/lvm.yml with content

lvm_roles:
  sonarqube:
    size: '20g'

In ansible.cfg I have a line

hash_behaviour = merge

Without merge the resulting fact will be

lvm_roles:
  sonarqube:
    size: '20g'

With other words I loose the path var.

With merge the result is

lvm_roles:
  sonarqube:
    size: '10g'
    path: '/opt/sonarqube'

The result I want and expected is however

lvm_roles:
  sonarqube:
    size: '20g'
    path: '/opt/sonarqube'

So the desired behavior is that

  1. Ansible merges vars
  2. config in group_vars takes precedence to config in my role.

Can I configure this behavior in Ansible? How?

onknows
  • 6,151
  • 12
  • 65
  • 109
  • 2
    I suggest your read this: [the paragraph on vars precedenc in the doc](https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable). /var in role has a higher precedence than group_vars. – Zeitounator Sep 24 '19 at 10:17
  • That is interesting. IMHO It doesn't make sense to give this kind of priority to vars in vars directory. Vars in role should have low priority because you want roles to be configurable. That it doesn't make sense is easy to illustrate. You end up with "shadow" vars like in https://github.com/geerlingguy/ansible-role-postgresql/blob/master/vars/Debian-8.yml – onknows Sep 24 '19 at 10:46
  • 2
    Variable in role **defaults** has a lower precedence than inventory variable. Variable in role **vars** has a higher precedence than inventory variable. – dgw Sep 24 '19 at 11:14
  • @onknowns as pointed out by dgw, configurable variables of your roles should be in `defaults/main.yml`. If you are not happy with all that well, [ansible is opensource](https://github.com/ansible/ansible) so you can easily file an issue or even propose a PR to see if they are willing to change this (I doubt it though). Your example on geerlingguys role is the exact contrary of what your are trying to demonstrate: those are internal vars you don't want anyone to change. The customizable ones are in https://github.com/geerlingguy/ansible-role-postgresql/blob/master/defaults/main.yml – Zeitounator Sep 24 '19 at 12:15
  • @Zeitounator ok, why then did geerlingguy go to the trouble of making those vars changeable? By using pseudo vars. The point here is that people find ways to change the way PostgreSQL is installed on systems that deviate from the default way. At my site I use a internal hosting provider that install PostgreSQL in custom way and the only way I can use geerlingguy.postgresql role is because geerlingguy took the trouble of making those vars configurable by using pseudo vars. geerlingguy lowered precedence of vars by using pseudo vars. – onknows Sep 24 '19 at 12:33
  • We could probably argue on that for long if our views diverge. My view for geerlinguy roles: in /vars there are files per environment that you should not change, which are loaded and used to create sensible defaults in /defaults depending on your OS. Those defaults can be overriden in your own role/inventory/playbook to meet your needs. – Zeitounator Sep 24 '19 at 13:34

2 Answers2

0

Precedence is not configurable in Ansible, you cannot give configuration in group_vars a higher precedence than configuration in the vars directory in a role.

What you want are conditional default vars which Ansible does not support . As shown for example here Set Ansible role defaults conditionally and here use conditionals in vars ansible.

This is an area where Ansible is definitely lacking as an IaC tool. It is for example a very commonly used feature with Chef. Snippet from attributes/default.rb from the Chef Apache cookbook demonstrates this common pattern

....
case node['platform_family']
when 'rhel', 'fedora', 'amazon'
  if node['platform'] == 'amazon'
    default['apache']['package'] = 'httpd24'
    default['apache']['devel_package'] = 'httpd24-devel'
  else
    default['apache']['package'] = 'httpd'
    default['apache']['devel_package'] = 'httpd-devel'
  end
  default['apache']['service_name'] = 'httpd'
  default['apache']['perl_pkg']    = 'perl'
  default['apache']['apachectl']   = '/usr/sbin/apachectl'
  default['apache']['dir']         = '/etc/httpd'
  default['apache']['log_dir']     = '/var/log/httpd'

Configuration in the vars directory can be compared to "override" attributes in Chef. Configuration in a Chef cookbook has a low precedence but you can use "override" attributes to give a very high precedence. The use of "override" attributes in cookbooks is however very uncommon. They are of very limited practical use. Vice versa the Ansible vars directory for its intended use of creating high precedence configuration that overrides almost all other configuration, is of very limited practical use.

If you disagree please share examples of roles where we absolutely need high precedence configuration in a role. You can for example share a link to a Ansible role that demonstrates practical use.

The vars role directory is useful but not for its intended use. In practice the directory is used to store conditional configuration. The fact that configuration gets a high precedence is a more a problem than a desired or intended result.

This is demonstrated by the geerlingguy.posttgresql role. In this role geerlingguy uses "pseudo variables" to work around the fact that Ansible does not have conditional defaults vars.

For example in vars/Debian-7.yml a variable __postgresql_data_dir is introduced. This variable gets a high precedence.

__postgresql_data_dir: "/var/lib/postgresql/{{ __postgresql_version }}/main" 

It is of no practical use other than that it can be used to mimic a conditional default var postgresql_data_dir as show in tasks/variables.yml

- name: Define postgresql_data_dir.
  set_fact:
    postgresql_data_dir: "{{ __postgresql_data_dir }}"
  when: postgresql_data_dir is not defined

It would make sense if precedence rules could be configured because the vars directory in a Ansible role is typically of limited practical use because of its high precedence. To make practical use of the vars directory trickery is needed as demonstrated by the postgresql_data_dir in the geerlingguy.posttgresql role to lower precedence of configuration in this directory.

If you don't like this trickery you can alternatively use workaround set_fact as described in Set Ansible role defaults conditionally or unholy inline coding as described in use conditionals in vars ansible.

The Ansible community would be well advised to change the intended use of the vars directory from "override" to "conditional" configuration. Giving high precedence to configuration in a role is very uncommon requirement. Conditional configuration is however very very common.

onknows
  • 6,151
  • 12
  • 65
  • 109
  • This role demonstrates that you did not get what you had been looking for. But this does not prove Ansible's lack of functionality. Instead, you might want to post a use-case (with all details) that you think is not possible to solve in Ansible. – Vladimir Botka Sep 27 '19 at 07:46
0

Q: "The desired behavior is that"

1) Ansible merges vars
2) config in group_vars takes precedence over config in my role.

"Can I configure this behavior in Ansible?"

A: No. The precedence can't be changed. "role vars"(15) override "group_vars"(7). Use "group_vars" to override "role defaults"(2).

Q: "What you want are conditional default vars which Ansible does not support."

A: If Ansible would not support conditional default it would not be possible to write roles for multiple systems. It is possible to create conditional defaults, e.g.

# Defaults variables
- name: "os_vars_playbook_dir: Vars from {{ playbook_dir }}/vars/defaults"
  include_vars: "{{ item }}"
  with_first_found:
    - files:
        - "{{ ansible_distribution }}-{{ ansible_distribution_release }}.yml"
        - "{{ ansible_distribution }}.yml"
        - "{{ ansible_os_family }}.yml"
        - "default.yml"
        - "defaults.yml"
      paths: "{{ playbook_dir }}/vars/defaults"
    
# Custom variables
- name: "os_vars_playbook_dir: Vars from {{ playbook_dir }}/vars"
  include_vars: "{{ item }}"
  with_first_found:
    - files:
        - "{{ ansible_distribution }}-{{ ansible_distribution_release }}.yml"
        - "{{ ansible_distribution }}.yml"
        - "{{ ansible_os_family }}.yml"
        - "default.yml"
        - "defaults.yml"
      paths: "{{ playbook_dir }}/vars"

(available at GitHub)


Notes

  1. OS-specific variables can be included after a play setup found out what system is running on the host.

  2. Role's vars/defaults have very high precedence "include_vars"(18) (because of 1.). But this directory should comprise system variables only. The user does not want to change these variables under standard circumstances.

  3. If necessary, the role may be customized in vars/. Custom configuration files in vars/ will survive the role's update while vars/defaults may be updated.

Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • My use case is to have OS/distro specific variables but to have host_vars/group_vars takes precedence. Not sure how to do it... Ie. to minic missing feature to load defaults based on OS/distro, eg. `defaults/main.yml`, then `defaults/{{ ansible_distribution }}.yml` etc... – Jiri B Mar 05 '21 at 19:28