Appending to an ansible task's variables in another role - ansible

(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}}"

Related

ansible: multiple var files for roles?

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.

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.

Create group with multiple names - Ansible GCE inventory plugin

I want to create a dynamic inventory, and create a group to put the nodes where I scrape data from.I'm using the gcp_compute plugin in my dynamic inventory.
What I want to achieve:
I want to retrieve multiple hostnames/ip and put them in the same group. I want it to be quite modular, so I can switch nodenames easily.
This how it looks so far:
plugin: gcp_compute
auth_kind: serviceaccount
cache: true
gather_facts: false
retrieve_image_info: true
service_account_file: servicefile.json
groups:
node_exporter: "'db-vm' in name"
I want to use something like: node_exporter: "'db-vm' OR 'prometheus-vm' in name" or something similar. How can I achieve this.
This is the command I use too see if it works or not: ansible-inventory -i gce_inventory.gcp.yml --graph. All my nodes gets ungrouped.
I solved it very easily...
I believe I made some mistakes before when doing the conditional, but now accidentally solved it.
To be able to retrieve multiple, here how:
node_exporter: "'db-vm' in name or 'prometheus-vm' in name"

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.

Resources