Creating Ansible Roles from Scratch: Part 1

Creating Ansible Roles from Scratch: Part 1

Within Ansible there are two techniques for reusing a set of configuration management tasks, includes and roles. Although both techniques function in similar ways, roles appear to be the official way forward. Ansible Galaxy was built as a repository for roles, and as we’ll see in this post, ansible-galaxy exists to aid in installing and creating them.

Creating a New Role

Let’s start off by creating a role for Packer.

Packer is a useful tool for producing different machine image types with the same set of configuration management tasks. For example, Packer can be used to take a set of Ansible instructions, funnel them through itself, and produce both an AMI and Docker image.

Enough about Packer though, let’s get back to creating an Ansible role for installing Packer.

The first step in creating a role is creating its directory structure. In order to create the base directory structure, we’re going to use a tool bundled with Ansible (since 1.4.2) called ansible-galaxy:

$ ansible-galaxy init azavea.packer
azavea.packer was created successfully


That command will create an azavea.packer directory with the following structure:

├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
└── vars
└── main.yml


Explaining the Role Directory Structure

A role’s directory structure consists of defaults, vars, files, handlers, meta, tasks, and templates. Let’s take a closer look at each:


Within defaults, there is a main.yml file with the default variables used by a role. For the Packer role, there is only a packer_version default variable. As of this post, the most recent version of Packer is 0.7.1, so we’ll set it to that:

packer_version: "0.7.1"



vars and defaults house variables, but variables in vars have a higher priority, which means that they are more difficult to override. Variables in defaults have the lowest priority of any variables available, which means they’re easy to override. Placing packer_version in defaults instead of vars is desirable because now it is easier to override when you want to install an older or newer version of Packer:

- hosts: all
sudo: yes
- { role: "azavea.packer", packer_version: "0.7.0" }


All of that said, we’re set with packer_version in defaults, so the vars directory is not needed either.


files is where you put files that need to be added to the machine being provisioned, without modification. Most of the time, files in files are referenced by copy tasks.

The Packer role has no need for files, so we’ll delete that directory.


handlers usually contain targets for notify directives, and are almost always associated with services. For example, if you were creating a role for NTP, you might have an entry in handlers/main.yml for restarting NTP after a task finishes altering the NTP configuration file.

Packer isn’t a service, so there is no need for the handlers directory.


meta/main.yml houses one of the biggest differences between includes from roles: metadata. The metadata of an Ansible role consists of attributes such as author, supported platforms, and dependencies. Most of this file is commented out by default, so I usually go through and fill in or uncomment relevant attributes, then delete anything else.

For the Packer role, I trimmed things down to:

author: Hector Castro
description: An Ansible role for installing Packer.
company: Azavea Inc.
license: Apache
min_ansible_version: 1.2
- name: Ubuntu
- trusty
- cloud
- system
- { role: "azavea.unzip" }


Ignore the dependencies bit for right now. We’ll come back to it later.


tasks houses a series of Ansible plays to install, configure, and run software. For Packer, we need to download a specific version, and since it’s packaged as a compiled binary in a ZIP archive, extract it. Accomplishing that with Ansible’s built-in get_url and unarchive modules looks like this:

- name: Download Packer
get_url: >
url={{ packer_version }}
dest=/usr/local/src/packer_{{ packer_version }}

- name: Extract and install Packer
unarchive: src=/usr/local/src/packer_{{ packer_version }}



templates is similar to files except that templates support modification as they’re added to the machine being provisioned. Modifications are achieved through the Jinja2 templating language. Most software configuration files become templates.

Packer takes most of its configuration parameters via command-line arguments, so the templates directory is not needed.


Congratulations! You now have all of the components necessary for an Ansible role.

In part two of this series, we’ll take a look at creating a small playbook to apply the role against a local virtual machine. We’ll also take a closer look at the dependencies listed in the role metadata.

Go to Creating Ansible Roles from Scratch: Part 2 now