ansible: multiple var files for roles? - ansible

In all ansible docs I have found, the [role]/vars/main.yml file is referenced as the place for role variables.
There is some customization possible by using [role]/defaults/main.yml which then can be overwritten by [role]/vars/main.yml.
What if I'd want to address a multitude of scenarios, and say load a group of vars based on a main variable, and then place the grouped vars in each file like
[role]/vars/main.yml
[role]/vars/scenario1.yml
[role]/vars/scenario2.yml
[role]/vars/scenario3.yml
[role]/vars/scenario4.yml
In other words, is there any use for the [role]/vars directory to contain more than just the main.yml file?
The concrete challenge is, define sets of users with their public keys, so depending on which machine I am, create the appropriate user accounts

The role of vars/main.yml is not really to overwrite variables defined in defaults/main.yml. In fact that would actually be counter-productive as variables defined in vars/main.yml have a quite high precedence level and are harder to override while you usually want vars in defaults/main.yml to be easilly modified by a user.
Regarding the vars folder, it contains by convention files defining variables that are specific to your role. But only vars/main.yml will be loaded by default.
Meanwhile that folder (like the vars folder adjacent to your playbook if you have one) is on the search path when you include_vars from your role. So if I take your example, you can easilly include_vars: scenario1.yml somewhere in you role or even include_vars: "scenario{{ scenario_number }}.yml" if you get that dynamically.
Disclaimer: I am the maintainer of the role taken as example below
Here is a real life example taken from ansible-thoteam/nexus-oss role.
As you can see, this role has a vars folder with several files but no default.yml. So no role variables are loaded by default. Those files are loaded dynamically when needed.
There are 2 files related to different OS famillies supported by the role: configure-Debian.yml and configure-RedHat.yml. They are loaded as needed in tasks/main.yml with the following task:
- name: Include OS specific variables.
include_vars: "configure-{{ ansible_os_family }}.yml"
Alternatively, you can have a look at the first_found lookup which can also be used for this kind of scenario.

Related

Combine Variable Values Rather Than Let Precedence Override

I have a playbook that is running some roles supplied by a vendor that I don't want to modify. There is a variable being defined in the 'role defaults' (roles/jira_config/default/main.yml) that I want to keep the value of and add onto from my 'play vars'. Normal variable precedence means that my 'play vars' definition overwrites the 'role defaults'. How can I combine the two?
jira.yml
vars:
atl_catalina_opts: >-
-myAdditionA
-myAdditionB
roles:
- role: jira_config
roles/jira_config/default/main.yml
atl_catalina_opts_extra: >-
-something1
-something2
As it stands, the value for atl_catalina_opts ends up
"atl_catalina_opts": "myAdditionA myAdditionB"
Whereas I want a combination of the two (order doesn't matter)
"atl_catalina_opts": "something1 something2 myAdditionA myAdditionB"
I've tried something like
vars:
atl_catalina_opts: >-
-{{ atl_catalina_opts }}
-myAdditionA
-myAdditionB
but that throws an unhandled exception.
Again, I don't want to modify anything in the jira_config role because that is being supplied by the vendor. Also, that role is the one using the variable so I can't do any tricks like creating another variable that combines the 'play vars' and 'role defaults'. Also, I don't want to search the vendor role for the atl_catalina_opts and add them into my 'play vars'. At some point in the future the vendor may update the role and I don't want to search for changes the vendor has made each time there is an update.

How to take the role default value if a variable is undefined in the playbook vars with ansible?

I have the following structure ..
playbook/main.yml:
- role: my-role
vars:
src_path: "{{ src_path_from_inventory }}"
my-role/defaults/main.yml:
src_path: /opt/src
So I'm wondering if there is any way to use my role default if my "src_path_from_inventory" is not defined in my inventory.
It seems the role default is taken only if I don't specify "src_path" at all in the vars section of my playbook.
I tried with default(omit), but it doesn't take the role default. Only considers my variable as empty instead of undefined.
I tried with default(''), but it doesn't take the role default neither.
EDIT (more precision) :
I want to do something like this because we are deploying a big system (reverse-proxy, several frontend application and many backend services) and we also have a lot of clients. To keep the inventory light and clean, we don't want to specify something in it if we can avoid it. Mainly by these 2 ways :
Using the same value in the inventory to feed many role variables instead of having multiple variables in the inventory.
Using (or trying to use) role default if 90% of the time we can use the default value instead of defining the variable in the inventory.
If I go deeper in my example :
Reverse Proxy
rp_order_app_src_path: "{{ order_app_src_path_from_inventory }}"
Order App
order_app_src_path: "{{ order_app_src_path_from_inventory }}"
I want to avoid to define rp_order_app_src_path and order_app_src_path in my inventory.
I don't even want to define order_app_src_path_from_inventory 90% of the time.
I certainly can use the default() keyword in my playbook, but then I will lose the default value for my most generic role (like authentication) that can be used by other playbook in the company. And it will force us to hardcode the default in each playbook. It seems to me that the role default have been made for that.

How can I skip role in Ansible?

I have this playbook:
roles:
- { role: common}
- { role: mariadb}
- { role: wordpress}
What is want is in every role I want to have first task as some conditional which if true then I want to skip the whole role and playbook continues to next.
Now I can use when like this:
roles:
- { role: common, when: mvar is False }
But for that I have to evaluate the mvar in playbook itself but for that mvar I need all other vars etc. stuff which is in that role itself. So it will be easier for me to do in role.
Is there any way?
In each role, in tasks/main.yml include a file with tasks only when the condition is met:
- include: real_tasks.yml
when: condition_is_met
In addition to #techraf's answer, one can also use include_tasks instead of include.
- include_tasks: real_tasks.yml
when: condition_is_met
Ansible include documentation says:
Notes
Include has some unintuitive behaviours depending on if it is running in a static or dynamic in play or in playbook context, in an effort to clarify behaviours we are moving to a new set modules (include_tasks, include_role, import_playbook, import_tasks) that have well established and clear behaviours. This module will still be supported for some time but we are looking at deprecating it in the near future.
In addition to the answers above, you could also consider using Tags to skip specific tasks or roles depending on how you are calling the roles.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html
This will work well if the condition is determined in a less dynamic manner - so if you have a large playbook but know at the point of execution that you want to skip certain parts.
Tags can also be used to selectively run specific tasks.
Note: using tags in this way is often a great way to test specific parts of a playbook during development without running the whole thing.

How can I have a playbook initiate other playbooks, based on user input?

I would like to have a starter playbook, say go.yml -- and within this, I would like to be able to prompt the user for which products they would like to perform actions on (say proxy, lb, etc.).
Is there a way I can query the user for input, and then based on that input initiate playbooks? Basically, I want something like the following:
- hosts: "prod"
sudo: yes
vars_prompt:
product: "What product do you want to deploy?"
if product == proxy:
- include proxy.yml
if product == lb:
- include lb.yml
...
I'm not very comfortable with how playbooks flow, and how they differ distinctly from task/handler files. Is anything like this possible? My goal is to avoid having a dozen different playbook files to choose from, and instead just have a default initiation type playbook which can always be used.
A playbook can include other playbooks. So you can have multiple playbooks which are triggered through a single entry point playbook. But on playbook level there are not conditions. All included playbooks will be executed.
Your conditions on roles theoretically would work. The correct syntax would be:
roles:
- role: proxy
when: product == "proxy"
- role: lb
when: product == "lb"
But this is kind of re-inventing the wheel. To limit which roles should be executed one usually uses tags (and skip-tags).
roles:
- role: proxy
tags: proxy
- role: lb
tags: lb
Then, for example, call you playbook with --tags "proxy". This also works with included playbooks.
The downside of using vars_prompt and conditions on roles is, you'll get tons of skipped tasks when executing the playbook. When filtered by tag the non-matching roles/tasks will not be shown with a skipped state.

Appending to an ansible task's variables in another role

(This is a cross-post from the mailing list, since I haven't gotten any answers there.)
I've recently set up fail2ban for ssh on one of my servers using https://galaxy.ansible.com/tersmitten/fail2ban/ . This was very easy, and I'm loving galaxy.
Now, I'm wanting to extend fail2ban to also block IPs that have failed basic auth checks in Apache too many times. Since all my hosts run ssh, but not all run Apache, I've created a new role for this (cleverly named 'apache', while the first is 'common'). However, as shown in the fail2ban role's README, configuration of individual services is done with an array, fail2ban_services, and redefining that in my apache role overwrites what was defined in common (or, due to timing, vice versa).
I've seen that I can change the hash behavior, which would seem to do what I want. I'm wary of this, however, since it's a global change.
There is also the combine filter for jinja, but reports indicate I can't do what I want, which would be
fail2ban_services: "{{ fail2ban_services | combine({...}) }}"
Is there another method to do this combination? Or perhaps am I going about this the wrong way, and should be architecting this differently?
I'm using ansible 2.0.1.0.
hash_behavior: merge and the combine filter would not be helpful, because those are for hashes. If I get the docs of fail2ban right, the fail2ban_services variable though is a list of hashes:
fail2ban_services:
- name: serviceA
...
- name: serviceB
...
- name: serviceC
...
You can easily merge lists:
listA: "{{ listB + listC }}"
... but the same limitations apply as for the combine filter. You can not override a variable which depends on itself, Ansible will complain about some "recursive loop detected" or so. No clue why this is such a big problem for Ansible, there should nothing be recursive in doing a = a + b. But unfortunately that's how it is.
If I understood your description, you define the fail2ban_services list in your role common. Then later you want to re-define it in the role apache. How is actually the fail2ban role implemented? Do you have 3 roles then in your playbook like so?
roles:
- common
- apache
- fail2ban
And the goal is, the first roles define what the last role then is using?
Given the mentioned limitations, my only idea then would be to rename your variables. The role common will define fail2ban_services_common, the role apache will define fail2ban_services_apache and then you pass the combined value to fail2ban:
roles:
- common
- apache
- role: fail2ban
fail2ban_services: "{{ fail2ban_services_common + fail2ban_services_apache}}"

Resources