Ansible Style Guidelines
This guide lays out some must and should rules for how to write Ansible
playbooks, rules etc. It covers both Ansible style and convention as well as
common patterns such as software installation, systemd service configuration
etc. Rules that are already covered by ansible-lint are not included.
YAML Basics
- All Ansible YAML files must have the extension
.yml, not.yaml. -
If required, a comment explaining what a YAML file is for (full sentence description, empty line, longer description) can be places above the document start marker like this:
# Install and configure foobar # # This is a rule that configures and installs the popular foobar program.
-
Only quote strings when YAML syntax requires it.
- When quotes are required, use double quotes unless single quotes are required.
Ansible Basics
- Follow Ansible best practices.
- Don't prefix task names with some role identifier or similar, Ansible automatically prepends this.
- Use blocks to group tasks that you want to execute/skip in unison based on some condition.
- Prefer
copy/templateovercontent/lineinfile/blockinfile. - Prefer
synchronizeovercopyfor moving larger files (ifrsyncis present both locally and on target). - ALWAYS use
no_logwhen handling sensitive output in a task. - Prefer not to use
debug:in committed playbooks.
Playbooks
Currently every playbook completely sets up a certain machine type. This may change in the future.
Add these to a playbook in the following order (if used):
hostsbecomevarsvars_filespre_tasksrolestasks
Prefer handling things in reusable roles instead of adding playbook-specific
tasks. Most playbooks should likely include the basic_tools which installs
utilities for live-debugging/maintenance. For consistencty always prefix with
role: even when not specifying any variables, e.g.:
roles:
- role: docker
- role: cuda
- role: consul
vars:
consul_node_name: # ...
Roles
Structure
Every role should most likely (perhaps with special exceptions such as roles that don't contain tasks but only handlers) contain at minimum the following files:
foobar_client
|- README.md
|- meta
| |- main.yml
| `- argument_specs.yml
|- defaults
| `- main.yml
`- tasks
`- main.yml
README
README.md should contain a title (the role name in title case), a brief
description of what the role does and a usage example, e.g.:
# Foobar Client
This installs a client for the Foobar service. The client can be used to invoke
various foobar functionalities.
Dependencies, options etc. should not be listed here as they are documented elsewhere (see next sections).
meta/main.yml
meta/main.yml should contain a short galaxy block with the following fields,
all of which except description (the role name in sentence case) should
exactly match this example unless there is a very good reason to deviate from
it:
---
galaxy_info:
author: Lyceum GmbH
description: Foobar client
company: Lyceum GmbH
license: proprietary
min_ansible_version: 2.18
platforms:
- name: Ubuntu
versions:
- noble
After this there may follow a dependencies block that lists local and
third-party dependencies. One dependency that you'll probably want to make
available in most roles is systemd_restarter which defines common handlers
for restarting systemd services. For example:
dependencies:
- role: systemd_restarter
vars:
systemd_service: foobar-client
Which you can then use to notify e.g.:
notify: "systemd_restart : Restart foobar-client systemd service"
This helps keep handler naming and usage patterns consistent across roles.
For other dependencies it usually makes more sense to directly add them in a
task via ansible.builtin.import_role to make its usage more explicit.
meta/argument_specs.yml and defaults/main.yml
meta/argument_specs.yml must contain types and descriptions of all options
that the role expects. No variables a role consumed must be explicitly declared
in argument_specs.yml. We try not to go overboard with the number of
available options so certain potentially configurable settings can be
"hardcoded" within the role, commonly:
- Installation/config directories
- Systemd service names
On the other hand these should be options for future-proofing:
- Binary versions
- Users/groups under which services run
- Data/log directories
- Systemd service states (but defaulting to
enabled: true,state: started)
By convention all options are prefixed by the role name and sorted
alphabetically within argument_specs.yml, Descriptions are mandatory.
---
argument_specs:
main:
options:
foobar_client_dockerized:
type: bool
default: false
description: Whether to run Foobar client dockerized
foobar_client_unsafe:
type: bool
default: false
decription: Whether to enable unsafe (HTTP) communication for Foobar client
foobar_client_version:
type: str
default: 1.0.0
description: Foobar client version
Any default must then also be present in defaults/main.yml, again sorted
alphabetically. Additional comments are not required here, all information
should be centralized in argument_specs.yml. For example:
---
foobar_client_dockerized: false
foobar_client_unsafe: false
foobar_client_version: 1.0.0
Great care must be taken to keep defaults here and in argument_specs.yml in
sync, Ansible unfortunately cannot derive one from the other by itself.
Tasks
If you have a long list of tasks for a given role that can be logically split up into several larger steps, consider splitting them up into several task files, e.g.:
tasks
|- main.yml
|- install_client.yml
`- configure_client.yml
With main.yml then containing:
---
- name: Install Client
import_tasks: install_client.yml
- name: Configure Client
import_tasks: configure_client.yml
Handlers
Some roles might require additional handlers not provided by
systemd_restarter. These should all be declared in handlers and not inside
of task files. When referring to these handlers always prefix them with the
role name to avoid any ambiguity, e.g. foobar_client : Reload client config.
Files and Templates
If your role needs to create files (configurations etc.), do not manage them
inline in a task with content: but instead via the files and templates
directories. The former contains files that don't need to be processed by Jinja
and the latter all other files. Make sure your template files thus have a .j2
extension, e.g. foobar-client.service.j2