Ansible variable validation with ansible.utils.assert

October 6, 2022 – Samuli Seppänen
Ansible variable validation helps you avoid accidentally hammering square pegs going into round holes. Photo by Julij Vanello Premru (https://medium.com/@julijVP/square-pegs-in-round-holes-6b3d1a3aa58a)

Overview of Ansible quality assurance

Ansible is an IT automation engine which you can use for configuration management, orchestration and device management, among other things. While you can get started fast with Ansible, ensuring high-quality, bug-free code can be challenging. Moreover, there's not that much official, high-quality or coherent documentation available on Ansible quality assurance best practices. While low hanging fruit like Ansible variable validation are available, they are not emphasized in official documentation.

This is the first part of our "Ansible quality assurance" series of blog posts.

Why you should do Ansible variable validation?

Here in part 1 we cover validation of variables. In particular, we focus on variable validation in Ansible roles, although the same approach works anywhere. Variable validation helps avoid playbook failures and hard to debug runtime errors and side-effects caused by

  1. Undefined variables
  2. Invalid variable values

Ansible variable validation with ansible.builtin.assert

Ansible does not a have built-in data types. As a result, you need to construct assertions manually. This is contrast to typed languages like Puppet where data types are first class citizens. The main tool you can use for Ansible variable validation is the ansible.builtin.assert module which builds on top of Jinja2 tests and filters.

Fail as early as possible

You can minimize misconfigurations by failing as early as possible. Typically misconfigurations are caused by missing or wrongly time variable validation and fall into two categories

  • Partial configurations: failure happens after Ansible has already executed some tasks. Sometimes you may have to do cleanup if this happens. For example, if your tasks are not idempotent, you may not be able to run them twice in a row without side-effects.
  • Hidden misconfigurations: this happens when Ansible thinks everything is ok, but a wrong (or missing) variable value ended up in, say, a configuration file. These can be very tricky to debug afterwards.

For this reason you should not only fail on invalid variables, but fail as early as possible. Correct location for Ansible variable validation depends on your use-case:

  • Roles: top of <role>/tasks/main.yml
  • (Orchestration) playbooks: top of the playbook

This is why you should not in most cases rely on variable validation done at task execution time.

Additionally you can avoid late failures by preferring imports over includes. When you Import a role or task it is added to your code statically. In other words, importing is about the same as if you had copied-and-pasted the imported role or task into your own code. A positive side-effect of an import is that you can validate variables before the Ansible run starts. In contrast includes are evaluated at runtime when the playbook is already running, so missing/invalid variables may go unnoticed until they cause a failure.

If you want to learn more about includes and imports please refer to official Ansible documentation.

Minimal Ansible variable validation: is the variable defined?

The minimal check you should do for every variable is to check for variable presence. We focus on roles, of which here is an example:

- ansible.builtin.include_role:
    name: myrole
  vars:
    myrole_myvar: foobar

The myrole/tasks/main.yml has the assert(s) at top. You should at minimum check that you've set all the variables a role needs:

---
- name: validate parameters
  ansible.builtin.assert:
    that:
      - myrole_myvar is defined

Now if you forget to pass a value to the role Ansible will error out immediately with a reasonable error message. This is definitely progress, but it is far from optimal. As you can see, you could still pass, for example, "foobar" instead of an IP address.

Validating that a variable is of certain type

Validating that a variable is of certain type is one step up from the "is variable defined" check:

- name: validate parameters
  ansible.builtin.assert:
    that:
      - my_string is string
      - my_integer is number
      - my_float is float
      - my_boolean is boolean

Validating that a variable belongs to a predefined set

For string variables that take a limited set of values using a regular expression matches are very useful:

- name: validate parameters
  ansible.builtin.assert:
    that:
      - myrole_state is match ('^(present|absent)$')

Regular expressions for complex string validation

Regular expressions also help you validate DNS names, for example like this:

- name: validate parameters
  ansible.builtin.assert:
    that:
      - myrole_dnsname is match ('^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)[A-Za-z]{2,6}')

As a regular expression grows more complex, the likelihood of it being buggy grows.

Validating numeric values

Checking numeric values like port numbers is easy:

- name: validate parameters
  ansible.builtin.assert:
    that:
      - myrole_port >= 1 and myrole_port <= 65535

Doing asserts on multiple variable values in one place

Validating multiple asserts in one place is trivial as well:

---
- name: validate parameters
  ansible.builtin.assert:
    that:
      - myrole_state is match ('^(present|absent)$')
      - myrole_dnsname is match ('^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)[A-Za-z]{2,6}')
      - myrole_port >= 1 and myrole_port <= 65535

More on Ansible quality assurance from Puppeteers

External resources

Want to talk to an expert?

If you want to reach us, just send us a message or book a free call!
Categories

Tags

#aad #Access #acl #alertmanager #ansible #ansible module development #Apache #API #augeas #authentication #authorization #automation #automatization #aws #azure #backup #bash #bitbucket #buildbot #cache #centos #cloud #cloud-init #cloudflare #cloudfront #cluster #connectionsJpa #control repo #custom fact #database #debian #devops #digital sovereignty #DNS #docker #domain mode #duplo #ejabberd #email #encryption #erb #europe #eyaml #fabric #facter #facts #fargate #fedora #file #finnish #foreman #freeipa #git #github #gitlab #gnome #google #grafana #hammer #hiera #IAM #import #infinispan #Infrastructure as Code #ipmi #irc #jboss #jdk #jenkins #JMESPath #kanban #keycloak #librarian-puppet #librenms #linkedin #Linux #Location #loop #marketing #mautic #Mellon #mfa #monitoring #mysql #nagios #network-manager #oauth #oauth2 #office365 #open source #openvpn #oxygen #packer #paranormal #pdk #people #php #pkcs7 #pomodoro #Powershell #preseed #presentation #profiles #prometheus #provisioning #puppet #puppet-bolt #puppet-litmus #puppetboard #puppetdb #Puppetfile #puppetserver #puppet types and providers #pxeboot #qemu #quality #r10k #recruitment #redirect #Restrict #Reverse Proxy #roles #rspec #ruby #SAML #sem #shell #showsql #snmp #snmpd #software developement #spam #ssh #sso #standardization #systemd #systemd-resolved #teams #terraform #ubuntu #user-data #vagrant #vanity awards #variable #vim #virtualbox #visualstudio #webdevelopment #wildfly #Windows #wireguard #wordpress #workflow #x11 #xmpp #zimbra
We are
 Puppeteers
menucross-circle