Hi all!
I need to disallow a role from being executed when it is include more than once. The scenario is like this.
Role A includes as a dependency Role B
Role C includes as a dependency Role B
I include role A and role C in a playbook
The result is.
B is run
A is run
B is run
C is run
Given that B is already executed I would like it not to be run again.
Any idea? Im currently setting a fact in B and use a conditional include in its main.yml file but this is code smell IMHO.
Thanks in advance!
I'd skip the dependency mechanics and just explicitly apply both roles
in the play.
Well, the thing here is a matter of encapsulation, the responsibility of installing B is inside A or C not in the playbook (the playbook should be agnostic). Imagine this scenario:
I have a web server with an nginx with different sites installed (for two different applications). For each application I create a role that installs nginx and creates a site with its configuration, I want those roles to be self sufficient because maybe in the future those two applications wont be installed in the same host.
In the role of each application I could have a:
- name: "Install nginx package
apt: pkg=nginx state=latest
This will be executed twice though only the first time will do stuff.
A good approach to DRY it out a little be would be extracting the nginx installation into a new role where I maybe also do some common tweaks like adding common files to /etc/nginx/conf.d directory etc. In this case, I should use a dependency in my application roles so I ensure nginx is installed before adding the site for the app. Now, after the refactor with the dependencies approach, the nginx tasks will be executed twice (again), first time doing stuff and second not doing anything but spending time with state checking. This is because the whole ‘Dependencies are only installed once unless allow_duplicates is yes’ thing is scoped to the role.
I’m currently creating a guard to ensure it gets executed only once. But this is ugly and I think this scenario is quite common and ansible should implement a way of dealing with such things. Maybe there is a way of doing it but Im missing it. In puppet is straightforward, a simple require would do, because is declarative.
Any thoughts?
As long as you haven’t set the “private_role_vars = yes” option in Ansible.cfg, you could do this by adding a set_fact step at the end of role B (something like “role_b_has_run”: true), and then at the beginning you check for the existence of it, probably using a “when block” on the entire thing.
First time Role B runs, it will toggle the variable to true using set_fact, and next time it runs you can skip the role or parts of it by checking for the existence of that fact.
-Trond
That’s what I’m doing now and it works but I find two trade offs with this approach.
- You pollute the global scope with facts, and you are at risk of name clashing
- You have to explicitly write the guard with code, something that is going to be the same in several roles. That’s more boilerplate and code complexity.
I think this use case is common enough to be implemented in the language itself. Something like a meta key saying ‘this module should only be run once per server, don’t bother to run in several times’ lets say:
I started liking the dependency model but it was generally met with confusion by
other admins, so we started being explicit in the play.
e.g. we'd have an nginx role that setup a 'conf.d' folder to
auto-include vhost definitions.
That would be nginx_confd_dir , set in roles/nginx/defaults/main.yml.
If I 'layer' another role onto that in the same play, it can access
the var directly and
know where to drop it's vhost files.
In practice, this has worked pretty well for us and it makes it
obvious what the play
is putting on each inventory group. It also avoids the 'role gets
applied twice' issue
that dependencies can cause.
If the playbooks are composable and it's obvious what's being deployed
where, it's
a win in my book.
Code reuse makes sense when you have a load of complex libraries of thousands
of lines of code but IMO it's massive overkill wrt. copying and
pasting a dozen lines
of YAML.