- Ansible StyleGuide
This is an attempt to standardize ansible and the format of commit messages, for the sake of uniformity in git log, best practices for writing commit messages.
You should follow the Best Practices defined by the Ansible documentation when developing playbooks.
The Ansible developers have a good understanding of how the playbooks work and where they look for certain files. Following these practices will avoid a lot of problems.
The script examples are inconsistent in style throughout the Ansible documentation; the purpose of this document is to define a consistent style that can be used throughout Ansible scripts to create robust, readable code.
You should start your scripts with some comments explaining what the script's purpose does (and an example usage, if necessary), followed by ---
with blank lines around it, then followed by the rest of the script.
#bad
- name: 'Change s1m0n3's status'
service:
enabled: true
name: 's1m0ne'
state: '{{ state }}'
become: true
#good
# Example usage: ansible-playbook -e state=started playbook.yml
# This playbook changes the state of s1m0n3 the robot
---
- name: 'Change s1m0n3's status'
service:
enabled: true
name: 's1m0ne'
state: '{{ state }}'
become: true
This makes it easier to quickly find out the purpose/usage of a script, either by opening the file or using the head
command.
You should always end your files with a newline.
This is common Unix best practice, and avoids any prompt misalignment when printing files in a terminal.
We always quote strings and prefer single quotes over double quotes. The only time you should use double quotes is when they are nested within single quotes (e.g. Jinja map reference), or when your string requires escaping characters (e.g. using \n
to represent a newline). If you must write a long string, we use the "folded scalar" style and omit all special quoting. The only things you should avoid quoting are booleans (e.g. true/false), numbers (e.g. 42), and things referencing the local Ansible environemnt (e.g. boolean logic or names of variables we are assigning values to).
# bad
- name: start robot named S1m0ne
service:
name: s1m0ne
state: started
enabled: true
become: true
# good
- name: 'start robot named S1m0ne'
service:
name: 's1m0ne'
state: 'started'
enabled: true
become: true
# double quotes w/ nested single quotes
- name: 'start all robots'
service:
name: '{{ item["robot_name"] }}''
state: 'started'
enabled: true
with_items: '{{ robots }}'
become: true
# double quotes to escape characters
- name 'print some text on two lines'
debug:
msg: "This text is on\ntwo lines"
# folded scalar style
- name: 'robot infos'
debug:
msg: >
Robot {{ item['robot_name'] }} is {{ item['status'] }} and in {{ item['az'] }}
availability zone with a {{ item['curiosity_quotient'] }} curiosity quotient.
with_items: robots
# folded scalar when the string has nested quotes already
- name: 'print some text'
debug:
msg: >
“I haven’t the slightest idea,” said the Hatter.
# don't quote booleans/numbers
- name: 'download google homepage'
get_url:
dest: '/tmp'
timeout: 60
url: 'https://google.com'
validate_certs: true
# variables example 1
- name: 'set a variable'
set_fact:
my_var: 'test'
# variables example 2
- name: 'print my_var'
debug:
var: my_var
when: ansible_os_family == 'Darwin'
# variables example 3
- name: 'set another variable'
set_fact:
my_second_var: '{{ my_var }}'
Even though strings are the default type for YAML, syntax highlighting looks better when explicitly set types. This also helps troubleshoot malformed strings when they should be properly escaped to have the desired effect.
When provisioning a server with environment variables add the environment variables to /etc/environment
with lineinfile. Do this from the ansible role that is associated with the service or application that is being installed. For example, for Tomcat installation the CATALINA_HOME
environment variable is often used to reference the folder that contains Tomcat and its associated webapps.
- name: 'add line CATALINA_HOME to /etc/environment'
lineinfile:
dest: '/etc/environment'
line: 'CATALINA_HOME={{ tomcat_home }}'
state: 'present'
sudo: true
Environment definition files are typically shared so blowing them away by templating them can cause problems. Having the specific environment variable included by lineinfile
makes it easier to track which applications are dependent upon the environment variable.
# bad
- name: 'start sensu-client'
service:
name: 'sensu-client'
state: 'restarted'
enabled: 1
become: 'yes'
# good
- name: 'start sensu-client'
service:
name: 'sensu-client'
state: 'restarted'
enabled: true
become: true
There are many different ways to specify a boolean value in ansible, True/False
, true/false
, yes/no
, 1/0
. While it is cute to see all those options we prefer to stick to one : true/false
. The main reasoning behind this is that Java and JavaScript have similar designations for boolean values.
Use only one space after the colon when designating a key value pair
# bad
- name : 'start sensu-client'
service:
name : 'sensu-client'
state : 'restarted'
enabled : true
become : true
# good
- name: 'start sensu-client'
service:
name: 'sensu-client'
state: 'restarted'
enabled: true
become: true
Always use the map syntax, regardless of how many pairs exist in the map.
# bad
- name: 'create checks directory to make it easier to look at checks vs handlers'
file: 'path=/etc/sensu/conf.d/checks state=directory mode=0755 owner=sensu group=sensu'
become: true
- name: 'copy check-memory.json to /etc/sensu/conf.d'
copy: 'dest=/etc/sensu/conf.d/checks/ src=checks/check-memory.json'
become: true
# good
- name: 'create checks directory to make it easier to look at checks vs handlers'
file:
group: 'sensu'
mode: '0755'
owner: 'sensu'
path: '/etc/sensu/conf.d/checks'
state: 'directory'
become: true
- name: 'copy check-memory.json to /etc/sensu/conf.d'
copy:
dest: '/etc/sensu/conf.d/checks/'
src: 'checks/check-memory.json'
become: true
It's easier to read and it's not hard to do. It reduces changeset collisions for version control.
Use the new become
syntax when designating that a task needs to be run with sudo
privileges
# bad
- name: 'template client.json to /etc/sensu/conf.d/'
template:
dest: '/etc/sensu/conf.d/client.json'
src: 'client.json.j2'
sudo: true
# good
- name: 'template client.json to /etc/sensu/conf.d/'
template:
dest: '/etc/sensu/conf.d/client.json'
src: 'client.json.j2'
become: true
Using sudo
was deprecated at Ansible version 1.9.1
host
sections should follow this general order:
# host declaration
# host options in alphabetical order
# pre_tasks
# roles
# tasks
# example
- hosts: 'webservers'
remote_user: 'centos'
vars:
tomcat_state: 'started'
pre_tasks:
- name: 'set the timezone to America/Boise'
lineinfile:
dest: '/etc/environment'
line: 'TZ=America/Boise'
state: 'present'
become: true
roles:
- { role: 'tomcat', tags: 'tomcat' }
tasks:
- name: 'start the tomcat service'
service:
name: 'tomcat'
state: '{{ tomcat_state }}'
A proper definition for how to order these maps produces consistent and easily readable code.
A task should be defined in such a way that it follows this general order:
# task name
# tags
# task map declaration (e.g. service:)
# task parameters in alphabetical order (remember to always use multi-line map syntax)
# loop operators (e.g. with_items)
# task options in alphabetical order (e.g. become, ignore_errors, register)
# example
- name: 'create some ec2 instances'
tags: 'ec2'
ec2:
assign_public_ip: true
image: 'ami-c7d092f7'
instance_tags:
Name: '{{ item }}'
key_name: 'my_key'
with_items: '{{ instance_names }}'
ignore_errors: true
register: ec2_output
when: ansible_os_family == 'Darwin'
Similar to the hosts definition, having a well-defined style here helps us create consistent code.
For include
statements, make sure to quote filenames and only use blank lines between include
statements if they are multi-line (e.g. they have tags).
# bad
- include: other_file.yml
- include: 'second_file.yml'
- include: third_file.yml tags=third
# good
- include: 'other_file.yml'
- include: 'second_file.yml'
- include: 'third_file.yml'
tags: 'third'
This tends to be the most readable way to have include
statements in your code.
You should have blank lines between two host blocks, between two task blocks, and between host and include blocks. When indenting, you should use 2 spaces to represent sub-maps, and multi-line maps should start with a -
). For a more in-depth example of how spacing (and other things) should look, consult style.yml.
This produces nice looking code that is easy to read.
Use snake_case
for variable names in your scripts.
# bad
- name: 'set some facts'
set_fact:
myBoolean: true
myint: 20
MY_STRING: 'test'
# good
- name: 'set some facts'
set_fact:
my_boolean: true
my_int: 20
my_string: 'test'
Ansible uses snake_case
for module names so it makes sense to extend this convention to variable names.
For all bool operators true or false must be used (Lower case). It is not allowed to use yes or no.
Example:
- name: enable nginx service
service:
name: nginx
state: started
enabled: true
Use lint checks on all ansible scripts.
ansible-lint
checks playbooks for practices and behaviour that could potentially be improved.
A linter for YAML files. yamllint
does not only check for syntax validity, but for weirdnesses like key repetition and cosmetic problems such as lines length, trailing spaces, indentation, etc.
So that errors are found and a certain standard is maintained.
Create an Undo/Rollback
function for all playbooks/roles.
So that all changes can be undone again.
Please make as many commits as possible. The principle here is small commits. A version tag must then be set for each related change.
- Every change must commit.
- All related changes must be combined in a new version.
- Every new version must be tagged.
- Every project name must must be lower case.
- Every project must have an easily recognizable name. The blanks in the project name are filled in with the character _ (snake_case).
- Every project must have a description.
- Projects that belong together must be marked with a project description. For example: ansible_example_role/playbook/module
Please use the README_ansible_template for ansible projects.
Please use the GNU Affero General Public License v3.0 for normal projects.
Please use the CONTRIBUTING for for projects.
All Git Commit Messages MUST be signed!
All Git Commit Messages MUST meet with this Text Format:
Subject
(Only One NewLine)
Message Body
(Only One NewLine)
Ref <###>
All Git Tag Messages MUST be signed!
All Git Tag Messages MUST meet with this Text Format:
Release vSemVer
- Capitalize the Subject.
- Do not end the Subject line with a period.
- Message Body SHOULD End with at-least One Issue Tracking ID Reference(GitHub Issues/GitLab Issues/Phabricator Tasks), Ex.
Issue #27
,Ref T27
orRef T27, T56
orFixes T8
. It's also recommanded to use Full URL to Issues, instead of just Issue ID Number; Doing so will ease browsing issues from terminal. - Total Characters of the Subject Line MUST be Less than or Equal to 50 Chars Long.
- Use Valid MarkDown format in Message Body.
- Use the Present Tense ("Add feature" not "Added feature").
- Use the Imperative Mood ("Move cursor to..." not "Moves cursor to...").
- Use the Message body to explain what and why vs. how.
- All WIP Commits Should be Avoided!.
- Refrencing Issues by using special keywords like
Fixes
orResolves
will mark them as closed automatically! For more information about automatic issue closing using ketwords see: GitHub/GitLab/Phabricator. - There is NO New-Line After the Task ID Reference Line.
- See ToDo Grammar StyleGuide for more Information on
@XXX
Comment Tags.
All commit messages and tags must be signed!
gpg --list-secret-keys --keyid-format LONG
sec ed25519 2018-02-26 [SC] [expires: 2022-02-25]
101D2615211421D8D22218DFD68251B5B79A051A
uid [ultimate] Robert Ressl <robert.ressl@ibm.com>
ssb cv25519 2018-02-26 [E] [expires: 2022-02-25]
git config --global user.signingkey 101D2615211421D8D22218DFD68251B5B79A051A
git config --global user.name "Ressl Robert"
git config --global user.email "robert.ressl@ibm.com"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
- ansible: Ansible is the simplest way to automate apps and IT infrastructure.
- yamllint: A linter for YAML files.
- ansible-lint: A linter for ansible files.
- git: Git is a free and open source distributed version control system.
Please read CONTRIBUTING for details on our code of conduct, and the process for submitting pull requests to us.
- v1.0.0 - Robert Ressl - Initial work
- Robert Ressl - Initial work & improvement - Robert Ressl
The Code is licensed under the GNU Affero General Public License v3.0