Code Monkey home page Code Monkey logo

ansible-styleguide's Introduction

Ansible Styleguide

Table of Contents

  1. Practices
  2. Start of Files
  3. End of Files
  4. Quotes
  5. Environment
  6. Booleans
  7. Key value pairs
  8. Sudo
  9. Hosts Declaration
  10. Task Declaration
  11. Include Declaration
  12. Spacing

Practices

You should follow the Best Practices defined by the Ansible documentation when developing playbooks.

Why?

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.

Why Doesn't Your Style Follow Theirs?

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.

Start of Files

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

Why?

This makes it easier to quickly find out the purpose/usage of a script, either by opening the file or using the head command.

End of Files

You should always end your files with a newline.

Why?

This is common Unix best practice, and avoids any prompt misalignment when printing files in a terminal.

Quotes

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 }}'

Why?

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.

Environment

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

Why?

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.

Booleans

# 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

Why?

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.

Key value pairs

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

Why?

It's easier to read and it's not hard to do. It reduces changeset collisions for version control.

Sudo

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

Why?

Using sudo was deprecated at Ansible version 1.9.1

Hosts Declaration

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 }}'

Why?

A proper definition for how to order these maps produces consistent and easily readable code.

Task Declaration

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'

Why?

Similar to the hosts definition, having a well-defined style here helps us create consistent code.

Include Declaration

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'

Why?

This tends to be the most readable way to have include statements in your code.

Spacing

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.

Why?

This produces nice looking code that is easy to read.

Variable Names

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'

Why?

Ansible uses snake_case for module names so it makes sense to extend this convention to variable names.

ansible-styleguide's People

Contributors

blakedietz avatar brikr avatar elloboblanco avatar matttaylor8910 avatar waded avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ansible-styleguide's Issues

License unclear

Hi,

Before contributing or using it: What are the terms when contributing or using your styleguide?

There is no LICENSE, COPYING file nor anything mentioned in CONTRIBUTING.md.

Apache 2.0 would be reasonable in an Ansible context, I guess.

Thanks,
Andreas

Guidance on tasks that ignore error

Is there any guidance on designing tasks that would ignore errors?

I read task output like this:

TASK [whatever : Shut down something] **********************************************
fatal: [10.140.2.225]: FAILED! => {"changed": false, "failed": true, "msg": "systemd could not find the requested service "'something'": "}
...ignoring

in red and my first thought is WHY is that ignored?

Would it make sense to have guidelines around naming of the task to include the reason for ignored error?, as extra assurance that this is is an 'expected exception' not just a patch around an error condition we don't understand?

E.g. here, if the task was named "Shut down something, ignore since user may have already shut it down" I might feel better.

Or perhaps something is wrong at a higher level with this pattern... perhaps we should have guidance that ignore should never be used UNLESS it is expected exception. It still does end up being red output. :(

Quotes style exception: single quotes don't work for escape characters

As https://github.com/whitecloud/ansible-styleguide#quotes stated, single quotes should be preferred for strings; double quotes are only allowed if embedded in Jinja templates. But it seems that I found an exception from my work where only double quotes work, but single quotes do not.

# test.yml
---

- name: test special quote cases
  hosts: localhost
  vars:
    tabbed_strings:
      - "aaa\t111"
      - "bbb\t222"
      - "ccc\t333"
  tasks:
    - name: double quotes work
      debug:
        msg: "{{ item.split('\t')[0] }}" 
      loop: "{{ tabbed_strings }}"

    - name: single quotes don't work
      debug:
        msg: '{{ item.split("\t")[0] }}'
      loop: '{{ tabbed_strings }}'

Output:

➜ ansible-playbook test.yml -v
Using /home/sqyu/code/greatdb-ansible/ansible.cfg as config file

PLAY [test special quote cases] ***********************************************************************************************************************************************************************************

TASK [double quotes work] *****************************************************************************************************************************************************************************************
ok: [localhost] => (item=aaa    111) => 
  msg: aaa
ok: [localhost] => (item=bbb    222) => 
  msg: bbb
ok: [localhost] => (item=ccc    333) => 
  msg: ccc

TASK [single quotes don't work] ***********************************************************************************************************************************************************************************
ok: [localhost] => (item=aaa    111) => 
  msg: "aaa\t111"
ok: [localhost] => (item=bbb    222) => 
  msg: "bbb\t222"
ok: [localhost] => (item=ccc    333) => 
  msg: "ccc\t333"

PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

The output shows that the split() function doesn't work for single-quoted strings.

Why do I use double quotes for tabbed_strings in the above case? Because I found it is how Ansible's register produces strings from stdout.

The official Ansible YAML syntax documentation stated that single quotes don't work with escape characters.

Role Declaration section deprecation

In practice I find the current "Role Declaration" rule (create a task file named to match the role, then include that file in a main.yml that does nothing else) to be boilerplate-heavy, forcing an include right off the bat to address what's generally an editor issue ("having several main.yml files open at once can get very confusing".) It also encourages a single task file per role, which in larger roles may not be the right structure... as to name a task file otherwise contradicts the role name, so it seems people generally just append more and more to the task file.

I prefer we don't favor boilerplate to fix editor issues in favor of purposeful use of multiple files, so I recommend we strike "Role Declation" in favor of well-documented main.yml, then fragmenting into multiple task .yml files as needed, none of them named the same as the role itself.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.