Let’s build an Ansible Role

Let’s build an Ansible Role

Originally posted on Medium

Our first task

Here is where the fun begins! First thing we want to do is to create our file and we’ll call it config.yml.

It’s a YAML file so… be careful with your spaces and tabs!

As every yml, let’s not forget to add --- at the beginning of the file; then we can write something like this:

- name: Install packages
    name: “{{ item }}”
    update_cache: yes
    state: present
  with_items: packages
  when: packages is defined

Let’s go through every single element of this task!


This is the very beginning of our task which defines its name and it is also very handy because it can be used to describe what that task actually does therefore your code will be more readable!


Apt it’s one of the many Ansible modules which allows us to install packages using the Ubuntu repositories. Amongst all the modules, if you’re running Ansible against RHEL, you’ll also find the yum module.
Right below apt, you can see three indentated attributes and, as you can imagine, they belong to apt!


This is the name of the package we’re going to install; in the example above we used {{ item }} which is nothing more than a parametrization of a list of packages defined in our variables file; but we’ll get to that.

Update Cache

When this options is equal to yes, it runs the equivalent of apt-get update and it can be run as part of the installation or as a separate step.


Defines the package’s state. Using present it won’t upgrade the package if already installed. The other available options are:

  • latest;
  • absent;
  • build-dep.

With Items

This is the variable name you want to use to include the list of items to be installed by the role.


When works exactly like an if statement; it’s not required but eventually is very, very useful!

Spoiler Alert --- One of the packages that we will install, is Apache 2. Why do I spoil it? Because the next step will make much more sense now that you acknowledged this!

- name: Disable Apache’s default vhost
  command: a2dissite 000-default
  when: dissite_default == true

- name: Make sure that document root exists
    path: “{{ item.document_root }}”
    state: directory
    owner: “{{ item.owner }}”
    group: “{{ item.group }}”
    mode: “{{ item.mode }}”
  with_items: site
  when: site is defined

So, what have we got up here? Those are two tasks which manage some of the Apache bits and pieces.

Disable Apache’s default vhost

Let’s say that you don’t care about the Apache default page showing that the webserver is working as it should and you want to replace it with your wonderful, fancy website; the command module will take care of it running the standard a2dissite command exactly as you were typing it from your terminal.

Make sure that document root exists

We are going to use a new type of variable for this tasks called dictionary! Its data structure is very similar to a hash one and it allows you to create nested items.
This task uses a module called file which, despite the name, can manage either files or directory or symlinks. With the case above we:

  • Check whether the directory exists, if not, create it;
  • Set a specific owner and a specific group for that directory; and
  • Set the permissions desired.

We will see later the structure of a dictionary once will go through the variables file.

Why do I use items instead of site?

The power of the dictionary, allows you to create as many item as you want. For instance, let’s say that you want to create more than one site; you surely don’t want to create a task for each site, do you? Item is your friend and Ansible will loop through each of those exactly like it was running a for loop.

Site task file

Here’s where the magic takes action; in the following code block, we will copy the apache vhost for our site, our index.html to our document root and we will enable the site to the webserver.

- name: Add apache vhost
    src: ../templates/vhost.conf.j2
    dest: "/etc/apache2/sites-available/{{item.name}}.conf"
    owner: root
    group: root
    mode: 644
  with_items: site
  when: site is defined

- name: Add our index.html
    src: ../templates/index.html.j2
    dest: "{{ item.document_root }}/index.html"
    owner: “{{ item.owner }}”
    group: “{{ item.group }}”
    mode: 0664
  with_items: site
  when: site is defined

- name: Enable your site
  command: a2ensite "{{ item.name }}"
  with_items: site
  when: site is defined

- name: Reload Apache2
    name: apache2
    state: reloaded
    enabled: yes

This is going to be very quick as I think it is very straight forward and easy to understand; When we add both the apache host and the index.html, we use a module called template which, in fact, allows us to add a certain file to our host using one of our templates. Of course what you need to specify is:

  • The source of the file - it’s usually one level up within the templates directory but any source can be used;
  • The destination of the file on the host;
  • Optionally, stuff like owner, group, mode, etc can be specified.

After that, we need to enable our site and we will use, again, the command module as we did for dissite; at the end of all we have to reload apache and we can do so in two ways:

  1. With the handlers - more elegant and reliable;
  2. With a module called service which is what I use with the example above.

Main task file

The purpose of this file is to execute the tasks one by one with a specific order specified by you; it does make sense to execute first config.yml and right after that site.yml, therefore our file will look like this:

- include: config.yml
- include: site.yml

Easy peasy!!!

Default variables file

Let’s take a break before creating our templates (that’s going to be awesome, I promise you) and let’s focus for a moment on the variables we used in our tasks. First: where are they stored?
In my first post, Ansible - Getting started, I described the role directory’s structure and if you recall, at the top of it, we had a directory called defaults with a yml file called main; everything defined within that file, will be used by the role. However, if you define your variables in your playbook, they will take precedence on the role ones!


dissite_default: true

  - curl
  - git
  - vim
  - apache2

  - name: test
    document_root: /var/www/test
    owner: www-data
    group: www-data
    mode: 2775
    message: "This Is My First Role"
    port: 80
    email: [email protected]


I could have installed just apache but I preferred to use a list to make sure to show you what a list looks like and how we can loop through it using the apt module.


As mentioned before, here we declare a dictionary which contains a bunch of informations. Do you see the ‘-’ before ‘name’? That is the beginning of your item; it means that if you wanted to add another site to test, right below message, you’d have to add another ‘- name: test2’ and so on.


JINJA2 has many, many features and any of those will be part of this post but, if you fancy, take a look at the JINJA2 documentation:


<VirtualHost *:{{ item.port }}>

  ServerAdmin {{ item.email }}
  DocumentRoot {{ item.document_root }}
{% if item.name is defined %}
  ServerName {{ item.name }}
{% endif %}

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined


The concept is as the same as the one used for the task. The new feature used is the if statement which is very simple to read and very useful in cases like the one above!


Here we are with our amazing webpage:

      {%if item.name is defined %}{{ item.name }}{% else %}Test{%endif%}
    <h1>{{ item.message }}</h1>

This is similar to the vhost but this time we want to make sure to have a title either way and in order to do that we can use else within our if block!

There’s nothing more I can tell about templates right now but I’m pretty sure that will write, someday, something about JINJA filtering inside a template and/or inside a role (yes, you got me right! You can use JINJA filtering within your roles!); it’s gonna be challenging, I think!


With a simple role, comes a simple meta file

As I said before, the meta file contains meta data for our role and other stuff such as dependencies. For the purpose of this post’s sake, let’s create some lines for our meta file:

  author: Davide Di Mauro
  description: Ansible Getting Started role
  version: 0.1.0

As you may remember from the previous post, this file needs to be called… want to guess? main.yml

This is pretty much it! This role is complete and ready to work. With the next post I’ll show you how to test it with your test kitchen and how to run some server spec with rspec.

Next article: Test Kitchen - Let's shake this role up!

Spread the love