ansible-public/CONVENTIONS.md

7.2 KiB

Conventions

Roles

We can use the ansible-galaxy init command to bootstrap a new role :

$ ansible-galaxy init foo
- foo was created successfully

$ tree foo
foo
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

All main.yml file will be picked up by Ansible automatically, with respect to their own responsibility.

The main directory is tasks. It will contains tasks, either all in the main.yml file, or grouped in files that can be included in the main file.

defaults/main.yml is the place to put the list of all variables for the role with a default value.

vars will hold files with variables definitions. Those differ from the defaults because of a much higher precedence (see below).

files is the directory where we'll put files to copy on hosts. They will be copied "as-is". When a role has multiple logical groups of tasks, it's best to create a sub-directory for each group that needs files. The name of files in these directories doesn't have to be the same as the destination name. Example :

copy:
  src: apt/jessie_backports_preferences
  dest: /etc/apt/apt.conf.d/backports

templates is the twin brother of files, but differs in that it contains files that can be pre-processed by the Jinja2 templating language. It can contain variables that will be extrapolated before copying the file to its destination.

handlers is the place to put special tasks that can be triggered by the notify argument of modules. For example an nginx -s reload command.

meta/main.yml contains … well … "meta" information. There we can define role dependencies, but also some "galaxy" information like the desired Ansible version, supported OS and distributions, a description, author/ownership, license…

tests and .travis.yml are here to help testing with a test matrix, a test inventory and a test playbook.

We can delete parts we don't need.

How much goes into a role

We create roles (instead of a plain tasks files) when it makes sense as a whole, and it is more that a series of tasks. It often has variables, files/templates, handlers…

Syntax

Pure YAML

It's possible to use a compact (Ansible specific) syntax,

- name: Add evomaintenance trap for '{{ user.name }}'
  lineinfile: state=present dest='/home/{{ user.name }}/.profile' insertafter=EOF line='trap "sudo /usr/share/scripts/evomaintenance.sh" 0'
  when: evomaintenance_script.stat.exists

but we prefer the pure-YAML syntax

- name: Add evomaintenance trap for '{{ user.name }}'
  lineinfile:
    state: present
    dest: '/home/{{ user.name }}/.profile'
    insertafter: EOF
    line: 'trap "sudo /usr/share/scripts/evomaintenance.sh" 0'
  when: evomaintenance_script.stat.exists

Here are some reasons :

  • when lines get long, it's easier to read ;
  • it's a pure YAML syntax, so there is no Ansible-specific preprocessing
  • … which means that IDE can show the proper syntax highlighting ;
  • each argument stands on its own.

Variables

defaults

When a role is using variables, they must be defined (for example in the defaults/main.yml) with a default value (possibly Null). That way, there will never be a "foo is undefined" situation.

If a variable can't have a default value, it must be marked as mandatory. Example :

- name: Setting default timezone
  lineinfile:
    line: "{{ evolinux_system_timezone | mandatory }}"

progressive specificity

In many roles, we use a progressive specificity pattern for some variables. The most common is for "alert_email" ; we want to have a default email address where all alerts or messages will be sent, but it can be customized globally, and also customized per task/role.

For the evolinux-base role we have those defaults :

  general_alert_email: "root@localhost"
  reboot_alert_email: Null
  log2mail_alert_email: Null
  raid_alert_email: Null

In the log2mail template, we set the email address like this :

mailto = {{ log2mail_alert_email or general_alert_email | mandatory }}

If nothing is customized, the mail will be sent to root@localhost, if general_alert_email is changed, it will be used, but if log2mail_alert_email is set to a non-null value, it will have precedence.

precedence

There are multiple places where we can define variables and there is a specific precedence order for the resolution. Here is the (ascending) order :

  • role defaults
  • inventory vars
  • inventory group_vars
  • inventory host_vars
  • playbook group_vars
  • playbook host_vars
  • host facts
  • play vars
  • play vars_prompt
  • play vars_files
  • registered vars
  • set_facts
  • role and include vars
  • block vars (only for tasks in block)
  • task vars (only for the task)
  • extra vars (always win precedence)

Configuration patterns

lineinfile vs. blockinfile vs. copy/template

When possible, we prefer using the lineinfile module to make very specific changes. If a regexp argument is specified, every line that matches the pattern will be updated. It's a good way to comment/uncomment variable, or add a piece inside a line.

When it's not possible (multi-line changes, for example), we can use the blockinfile module. It manages blocks of text with begin/end markers. The marker can be customized, mostly to use the proper comment syntax, but also to prevent collisions within a file.

If none of the previous can be used, we can use copy or template modules to copy an entire file.

defaults and custom files

We try not to alter configuration files managed by packages. It makes upgrading easier, so when a piece of software has a "foo.d" configuration directory, we add custom files there.

We usually put a z-evolinux-defaults with our core configuration. This file can be changed later via Ansible and must not be edited by hand. Example :

copy:
  src: evolinux-defaults.cnf
  dest: /etc/mysql/conf.d/z-evolinux-defaults.cnf
  force: yes

We also create a blank zzz-evolinux-custom file, with commented examples, to allow custom configuration that will never be reverted by Ansible. Example :

copy:
  src: evolinux-custom.cnf
  dest: /etc/mysql/conf.d/zzz-evolinux-custom.cnf
  force: no

The source file or template shouldn't to be prefixed for ordering (eg. z- or zzz-). It's the task's responsibility to choose how destination files must be ordered.

packages installation

When making a role or a task the necessary packages must be installed explicitly.

For example for the "mysql" role we obviously need the MySQL packages, but we also need the "apg" package to generate new passwords. This package is installed by "evolinux-base" but the "mysql" role can be executed on a fresh server.