One of the very important aspects of infrastructure as a code approach is automated testing of code standards. In my case the code is ansible playbooks, tasks and variables. Looking for available solution I found a great tool announced by Whill Thames on his blog  – ansible-review . Unfortunately the example standard shown on  is very simple and other standards rely on
ansible-lint which doesn’t show full capabilities of
ansible-review. In this post I’d like to share my experience with standards development, based on a few examples. I’m not going to discuss basic usage of
ansible-review – you can check that on one of cited sites.
How to develop ansible-review standards?
In all examples below I assume you have imported packages from the example
Example1: Don’t allow spaces in task name.
To make it easier to copy task name (double click on text to mark the word) and use it in
--start-at-task it’s convenient to forbid spaces in task names. To achieve this with
ansible-review standards.py you may use code similar to one below:
Reading of the code should start in line 14 where we define the standard called
task_name_should_not_have_spaces. As required by
ansible-review framework standard is an association of:
- name – the string describing the standard,
- check – python function that will verify if the standard is matched,
- types of ansible elements where the standard applies (tasks, defaults, playbook, etc.),
- and version in which the standard is enforced.
In the example above evaluation is performed by function called
check_task_name defined in 1st line of the code snippet. Check functions receive 2 arguments:
- candidate – a file being reviewed
- and setting – a dictionary with lintdir, config file and a few other settings. Personally I don’t have any particular use case for them in check function.
What happens in our 1st check function is straightforward. We open the file, read it line by line. If the second word is name: (The first is just –) then we check if the number of words in the line is greater than 3, since this indicates space in name. If this is the case we add an
Error to the errors list in the
Result object returned by check function.
Example 2: Check if variables defined in vaulted defaults/main.yml file are prefixed with role name.
If you have some experience in maintaining an ansible repository with multiple roles you probably know that having variables defined in different places sometimes make it difficult to predict the result. Because of that it may be a good practice to prefix variables defined in defaults with the name of the role, where it was defined. Even if you’ll overwrite this variable in group settings it will always remind you that initial idea was to use it within a role. It’s quite common to store some secrets like passwords and API tokens, so the good practice is to encrypt those files. In the example below additionally to standard
ansible-review imports we use
ansible-vault python package to handle data decryption.
As you can see in the listing above we define the standard called
all_defaults_start_with_rolename that will be applied only to defaults. In the check function we parse out the role name from file path (actually doing a double verification of the file being a defaults/main.yml). As you see in line 9 it’s assumed that vault password is stored in
/etc/ansible/vault-password than we use
Vault.load method to decrypt and read the file. This method internally executes yaml parser that stores the input into defaults dictionary. Our next step is simple iteration over the keys in dictionary to check their names. If one is missing appropriate prefix another entry is added into
Example 3: Make sure that all tasks in role have a standard “role” tag.
One of the very important differences of ansible and puppet is that it’s more natural to push configuration changes to hosts than automatically execute everything on hosts. When you’re deploying changes you normally use playbooks containing much more than latest modification. I find it convenient to have standard tags for all tasks in role. This is something you can achieve with the standard defined below:
It’s quite similar to the previous example. Input data is not encrypted, so instead of
Vault.load loading of YAML is done by
parse_yaml_linenumbers function, beside that the logic is easy to understand – the loop in line 9 iterates over all tasks, checks if they are tagged with
Example 4: Fail on services restarted in tasks.
Personally I think that services should never be restarted in tasks, this should be always done by handler. Tasks should only use services states like started or stopped. Implementation of this may look like:
The most crucial part is the
if statement in line 7. It checks if the task has service key indicating the module used. In this case we check if state was specified and report an error when it’s restarted. As for exmaple 3, there are some cases where this standard will not work like task without full YAML format (in line module arguments) or use of
I hope those examples will help you developing your own