TL; DR
With Ansible I am trying to define some kind of default variable at playbook level - let's call it playbook defaults - which have precedence over roles defaults but can be overridden by the inventories inventory group_vars/all variables.
How to define some kind of playbook defaults variables which would have precedence over roles defaults but being overridable by inventories (environments) at the same time?
Currently Ansible 2.x variable precedence is such as:
- role defaults
- inventory file or script group vars
- inventory group_vars/all
- playbook group_vars/all
What I am looking to achieve is something like:
- role defaults
- playbook defaults
- inventory file or script group vars
- inventory group_vars/all
- playbook group_vars/all
From most tools and apps I used before, variables defined at environment level (dev, test, QA, prod, etc.) have precedence over "application" variables, which themselves have precedence over "external" components. The precedence order would be then as following:
- "External" components vars (Roles from Galaxy)
- "Application" vars (Playbook vars)
- "Environment" vars (dev, test, etc.)
With Environment having the highest precedence. But I cannot find a way to reproduce this pattern with Ansible. Such things are easy in Chef (with external cookbooks defaults being overridden by wrapper cookbook default) and Puppet, but I can't find a way to achieve the same with Ansible.
Example with a simple application
Let's consider an application consisting of several services interacting with a webserver. These services are generic and re-used in other parts of my organization, but in my case I used apache2. I have these roles:
- apache
- green_service
- blue_service
- red_service
And these default vars:
# roles/apache/defaults/main.yml
apache_listen_port: 80
# roles/green_service/defaults/main.yml
green_service_port: 80
# roles/blue_service/defaults/main.yml
blue_service_port: 80
# roles/red_service/defaults/main.yml
red_service_port: 80
Each service must know the apache port and this is represented in the default. If other applications need the green service with Nginx or another webserver, they just have to include the related roles.
Now, what if I want to change the port from 80 ot 8081 in one or more environments?
The "straightforward" way
.. I end-up having to duplicate all these variables in each environment such as:
# production/group_vars/all/all.yml
apache_listen_port: 8081
green_service_port: 8081
blue_service_port: 8081
red_service_port: 8081
# ... and so on in each environments inventory!
This may be fine in this simple example, but when you have 10+ services with 5+ environment, it becomes a nightmare... Updating a simple variable may have impacts in lots of services and it becomes hard to maintain and understand.
Aside from the answer "Your service design may be wrong..." I am looking for a way to avoid this.
What I would like to achieve
What I would like to achieve is being able to represent the variables links between my services and the webserver at playbook level such as:
# somewhere in my playbook .yml
# these variables override roles default
# but can be overriden in my inventories
myapp_port: 80
apache_listen_port: "{{ myapp_port }}"
green_service_port: "{{ myapp_port }}"
blue_service_port: "{{ myapp_port }}"
red_service_port: "{{ myapp_port }}"
Now in each environment I just have to override one and only one variable such as:
# production/group_vars/all/all.yml
myapp_port: 8081
I did not find a sensible way without over-complicating my playbooks to achieve something similar.
With Ansible, is there a proper way to accomplish that Playbook vars having precedence over Defaults vars but not Inventories vars?
vars
inside a tasks file? – Prana