Article

Being a Star in Galaxy

This article is about the Ansible Galaxy. It describes a few things best practices I discovered while
trying to write my roles as flexible as possible.

Use common sense

First of all, I'd say that it's important to name your role well. Or at least provide a nice description.
Searching for "nginx" on Galaxy will show you what I mean, there
are at least 40 roles to "install and configure Nginx"...

readers who are paying attention will see an F500 role that violates this very rule. We'll try to do better!

Also, please follow Semantic Versioning for your role updates. Ansible's galaxy install
command is not very sophisticated but you can keep a fixed version of a role. When using other people's roles
you really should audit the code and lock the version. You are after all running their code on your servers,
perhaps even with escalated privileges...

Finally, prefix all your variables with the name of your role. You do not want to cause a variable clash
with some other role by using is_secure or something generic:

my_role_is_secure: true (I hope)

Besides those three common-sense tips, here are 6 things you could to to increase re-usability:

Tip 1. Use plenty of defaults and switches

In a role, you can create a defaults/main.yml file with default values for all variables in your role.
Besides variables in your tasks, if there is a template, create defaults for as many of the settings as you can.
You might never change any of them yourself, but others might find it extremely frustrating to have to duplicate
your role just because you left a value hardcoded.

# In a certain unnamed .ini template file
display_errors=true

Should be:

display_errors={{ role_name_display_errors }}

Don't set defaults where it doesn't make sense though. If a consumer needs to provide certain info, document
it and let the role break if they fail to RTFM.

Adding plenty of switches to turn certain behaviour on/off is also nice. Simply add a variable and a when
statement to your tasks:

command: "do-something-cool.sh"
when: something_cool_should_run

Tip 2. Set variable paths for all files and templates

When you have templates in your role, more often than not those are based on program defaults or best practices.
Having every option in a default variables allows most templates to be used for all purposes, but sometimes it's just
too much work to make everything configurable.

Having a variable path for templates allows your uses to override them quite easily, without breaking anyting:

This defaults to your role/templates/template-name.j2, so nothing changes. But if a consumer of your role wants to
override the template, they can do so by setting the some_template_path variable and creating their own version of
the template.

Tip 3. Don't create a monolith

Roles should be small, and do one job. If there are multiple things to install, don't put those into a single role
because they will be married forever. And you really only ever want to marry the right one, right? If you make roles
small, you can easily compose them differently for different servers.

But sometimes, even a single program can benefit from multiple roles. If the program requires extensive
configuration, or the configuration changes often, it can pay to create an installation and a configuration role:

role: install_nginx
role: configure_nginx (depends on install_nginx)

(to make it idempotent, your installation role could contain an option to remove the default configuration)

Another version of this is the settings role. In this case, you create an installation role that can install
multiple versions of something - based on variables. You then create multiple settings roles, that contain the
proper values to install a certain version:

Don't forget to set the list_of_commands variable as an empty list in your defaults!

As an added bonus, add an "environment" variable so their commands can behave different depending on external information.

environment: project_environment

Only use this technique if you explicitly want to limit the consumers to a set of commands. If you want to let them
execute arbitrary tasks/ansible modules, use the next tip.

Tip 5. Create hooks by including task files

To have consumers of your role to write their own tasks, to be executed inside your role, add an include statement
where you want to allow this. The include statement should take a variable that can be set to full path to a tasks
file:

Because of how Ansible works, the 'default' and 'lookup' filters have to be used to point to an existing empty file.
You need to create this empty file inside your role, inside the 'tasks' folder. In the example above, the path would
be [role]/tasks/hooks/empty.yml

This include statement will do nothing by default, but if the consumer sets the hook_variable then their tasks
are executed (using the playbok_dir variable makes it possible to use a relative path):

hook_variable: "{{ playbook_dir }}/hooks/tasks-file.yml"

This is the most powerful way of allowing your role to be adapted to personal taste, but it's also complex to explain
to the target audience of your role. Use it wisely.

Tip 6. Separate modules into roles

If you role contains a module, chances are some people will like the module but not the role. Ansible does not
provide a way to load modules inside roles without running the role. In order to allow others to benefit from your
module outside of the role, break out the module into it's own role, and depend on it yourself.

your_role_module
your_role (depends on your_role_module)

Now, your module is available separately with zero cost to you. You can see an example of this in our
project_deploy role, that depends on
project_deploy_module. The module role contains only the
galaxy meta information and a library folder.

So, there you have it.

Now you've got some pointers on how to make better and more re-usable roles. I
sincerely hope to save myself some time by using them in the future!