Developing Roles with TripleO-Ansible

Developing Roles with TripleO-Ansible

Developing roles with TripleO has never been more accessible. The TripleO-Ansible project has been recently gone through a renaissance which has focused on operators, developers, simplicity, and ease of understanding. In this blog post, I'm going to cover the creation of a new role using the role-addition.yml playbook and testing the role before submitting the review. [1]

To get started I'm going to make a couple of assumptions
  1. The development environment is GNU/Linux (Fedora)
  2. ansible and tox are installed in a virtual environment

Preparing the Development Environment

The following steps are optional and only mirror what I typically have on a development workstation. If the development environment already has these requirements installed, feel free to skip to the next section.

Make sure our base requirements are installed. [2]

# dnf install python3-virtualenv docker git git-review

For the user to gain access to docker, create a group named docker and add the current user to the newly created group.

# groupadd docker
# useradd -aG docker $(whoami)

Validate the user has been added to the group.

$ id $(whoami)

Once the user has been added to the newely created group, logout and log back in to reload the credentials.

Start the docker daemon.[3]

# systemctl start docker
# systemctl enable docker

Create a virtual environment where ansible and tox are being installed.

$ python3 -m virtualenv ~/venvs/tripleo-development

Activate the virtual environment.

$ source ~/venvs/tripleo-development/bin/activate

Install tox and ansible.

(tripleo-development) $ pip install tox ansible

Getting the TripleO-Ansible source code

The TripleO-Ansible source code is a git repository. It is free, it is open source, and we love contributions.

Clone the tripleo-ansible repository.

(tripleo-development) $ git clone

Creating a new role

Change into the tripleo-ansible directory.

(tripleo-development) $ cd tripleo-ansible

Create a development branch. While creating development branches is my recommendation, it is not required.

(tripleo-development) $ git checkout -b role-hello_world

Create a new hello_world role. For this, we're using the built-in ansible playbook which automates the creation of everything. For a more extensive overview of the process of contributing a role to the TripleO-Ansible project see the upstream contributing guild for a detailed breakdown.

(tripleo-development) $ ansible-playbook -i 'localhost,' role-addition.yml \
                                         -e ansible_connection=local \
                                         -e role_name=hello_world

The playbook generated several files and directories. Check the status of the git repository to see what's been changed and modified.

(tripleo-development) $ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   tox.ini
        modified:   zuul.d/jobs.yaml
        modified:   zuul.d/layout.yaml

Untracked files:
  (use "git add <file>..." to include in what will be committed)


no changes added to commit (use "git add" and/or "git commit -a")

Now add everything into the git repository.

git add -A

With the role skeleton created, edit the files in tripleo_ansible/roles/hello_world.

For this role, we're focusing on the following
  • The role prints a debug message
  • The role has appropriate documentation hooks
  • The role is loaded into Zuul CI
  • The role has molecule tests for validating the functionality

Because we generated the role using the role-addition.yml playbook, all of the mundane things are done.

  • The role-addition.yml playbook creates the shell of the role which includes a basic molecule configuration.
  • All of the Zuul CI configuration options are set within the zuul.d/layout.yaml and zuul.d/jobs.yaml files. Once the role has been sent for a review, an isolated test is run against the role to ensure its functionality. When the role merges, these tests are run when something changes within the scope of the role.
  • The documentation requirements are set within the doc/source/role path. When the changes have been merged, the documentation jobs rebuild automatically and are published on the official TripleO-Ansible documentation site.

So with all of the mundane tasks taken care of, we (the role developer) can focus on making the best role possible. To achieve the goals of this post we'll update a single task file adding one task which will print our debug message.

$ git diff tripleo_ansible/roles/hello_world/tasks/main.yml

diff --git a/tripleo_ansible/roles/hello_world/tasks/main.yml b/tripleo_ansible/roles/hello_world/tasks/main.yml
index 749bfdf..b70a59e 100644
--- a/tripleo_ansible/roles/hello_world/tasks/main.yml
+++ b/tripleo_ansible/roles/hello_world/tasks/main.yml
@@ -30,3 +30,10 @@
         - "{{ ansible_os_family | lower }}.yml"
     - always
+- name: Print Hello Message
+  debug:
+    msg: >-
+      {{ hello_world_message }}

Notice this diff is using a variable to print the message. For this to work, we're going to need to define the variable in our defaults/main.yml file.

$ git diff tripleo_ansible/roles/hello_world/defaults/main.yml

diff --git a/tripleo_ansible/roles/hello_world/defaults/main.yml b/tripleo_ansible/roles/hello_world/defaults/main.yml
index ad75c9b..2493c8d 100644
--- a/tripleo_ansible/roles/hello_world/defaults/main.yml
+++ b/tripleo_ansible/roles/hello_world/defaults/main.yml
@@ -18,4 +18,6 @@
 # All variables intended for modification should place placed in this file.

 # All variables within this role should have a prefix of "hello_world"
-hello_world_debug: false
\ No newline at end of file
+hello_world_debug: false
+hello_world_message: "Franky Says Relax"

Now that we have the hello_world_message variable defined, we can test the role using molecule to ensure it's working as expected. Remember, because we created a new role using our tooling, everything is ready for testing; all we need to do is execute the tests on our local workstation.

scripts/run-local-test hello_world

This tox command is using the environment defined in the tox.ini. The tox environment functionally tests the role using containers. By default, the tests are run against Fedora 28 and CentOS 7. [4]

If tests begin failing on the molecule build step you may be running into an issue with selinux. If you happen to run into selinux issues, you can either fix them or set selinux to permissive mode.

Assuming the functional tests pass, run the linter checks to ensure everything created is in alignment with the projects basic code requirements.

tox -e linters

Assuming the lint tests pass we're ready to add the changes into the development branch and commit them.

git add -A 
git commit -a -m "Create a new hello world role"

Submitting code assumes you have an account setup within the OpenStack code review system. An account can be created quickly, follow the basic instructions outlined here.

Submit the new role to the OpenDev review system.

git review -t role-hello_world

That's it, you're done

Once the review has been submitted folks will begin looking over the code submitted. It may take a few iterations to get the submission merged, howekver, that's all par for the course when it comes to contributing to an open source project.

Essentially role creation can be broken-down into a 4 step process
  1. Generate a new role using the role addition playbook
  2. Create new role content (tasks, templates, files, variables, etc)
  3. Locally test the new role (optional: Zuul CI will do this for us)
  4. Submit a review

Here's a real world example of the tools covered in this post being used to create a role for aide, which is being ported to Ansible from tripleo-heat-templates and puppet-tripleo.

But wait there's more!

Tweaking the role we created for a more tailored deployment.

Now that the hello_world role works, it is time to extend it. The framework used to generate the role puts a lot of beautiful little things in place which are incredibly helpful to folks looking to develop roles. In this quick tweaking follow up we'll extend the role to print different messages based on discovered operating systems. We'll also add an operating system to our support matrix.

Here's the basic structure of the hello_world role.
$ tree tripleo_ansible/roles/hello_world/

├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── Dockerfile
│       ├── molecule.yml
│       ├── playbook.yml
│       ├── prepare.yml
│       └── verify.yml
├── tasks
│   └── main.yml
└── vars
    └── main.yml

The very first task run in the new role, which was generated by the role skeleton is named, Gather variables for each operating system. This task loads operating system specific variable files from the vars/ directory if found. So to extend our role so that it prints operating system specific messages, all we need to do is to create a compatible operating system specific variable file. For this, we'll add a new variable file called fedora.yml and debian.yml. These two files will serve both the same and different purposes.

First, let's create the fedora.yaml file.

$ git diff tripleo_ansible/roles/hello_world/vars/fedora.yml

diff --git a/tripleo_ansible/roles/hello_world/vars/fedora.yml b/tripleo_ansible/roles/hello_world/vars/fedora.yml
index e69de29..45ab6dc 100644
--- a/tripleo_ansible/roles/hello_world/vars/fedora.yml
+++ b/tripleo_ansible/roles/hello_world/vars/fedora.yml
@@ -0,0 +1,4 @@
+hello_world_message: relax

This file has a single variable within it, which overrides the default option when loaded. Because the variable load task is the first thing executed and the file is named fedora.yml the fedora variable is loaded anytime the Fedora distribution is detected.

The second variable file created is the debian.yml file.

$ git diff tripleo_ansible/roles/hello_world/vars/debian.yml

diff --git a/tripleo_ansible/roles/hello_world/vars/debian.yml b/tripleo_ansible/roles/hello_world/vars/debian.yml
index e69de29..9e4f275 100644
--- a/tripleo_ansible/roles/hello_world/vars/debian.yml
+++ b/tripleo_ansible/roles/hello_world/vars/debian.yml
@@ -0,0 +1,4 @@
+hello_world_message: Don't do it

This file has a dual purpose as Debian is both an operating system and an Operating System Family. So anytime the role encounters Debian or any Debian based operating system, the file is loaded resulting in the Debian specific message being printed.

Why you should care

The neat part of this solution is that we have both operating systems specific variables and defaults, which is crucial when building robust roles. We want to be able to have sensible defaults and smart overrides as needed, and this gives us a framework to make that happen. While this example is simple, it becomes compelling when needing to support things like multiple package lists across operating systems (RHEL7 and RHEL8) or loading network config when the underlying network functions may be different (systemd-networkd, NetworkManager, Wickd, ENI, etc).

Now that the role has been updated to support both Fedora and Debian, let's make a change to the meta/main.yml file, so our supported operating systems are reflected in our role.

$ git diff tripleo_ansible/roles/hello_world/meta/main.yml

diff --git a/tripleo_ansible/roles/hello_world/meta/main.yml b/tripleo_ansible/roles/hello_world/meta/main.yml
index 53395a6..1fbb8b3 100644
--- a/tripleo_ansible/roles/hello_world/meta/main.yml
+++ b/tripleo_ansible/roles/hello_world/meta/main.yml
@@ -34,6 +34,7 @@ galaxy_info:
     - name: CentOS
         - 7
+    - name: Debian

     - tripleo
@@ -41,4 +42,5 @@ galaxy_info:

 # List your role dependencies here, one per line. Be sure to remove the '[]' above,
 # if you add dependencies to this list.
-dependencies: []
\ No newline at end of file
+dependencies: []

Given we're able to test our role on centos and fedora, it makes sense to add Debian as a tested operating system in our molecule config.

$ git diff tripleo_ansible/roles/hello_world/molecule/default/molecule.yml

diff --git a/tripleo_ansible/roles/hello_world/molecule/default/molecule.yml b/tripleo_ansible/roles/hello_world/molecule/default/molecule.yml
index 3a32890..8d2023a 100644
--- a/tripleo_ansible/roles/hello_world/molecule/default/molecule.yml
+++ b/tripleo_ansible/roles/hello_world/molecule/default/molecule.yml
@@ -24,6 +24,15 @@ platforms:
       <<: *env

+  - name: debian9
+    hostname: debian9
+    image: debian:9
+    dockerfile: Dockerfile
+    pkg_extras: python*-setuptools
+    environment:
+      <<: *env
   name: ansible
   log: true

Finally, test the role functions to ensure everything is working as expected.

$ scripts/run-local-test hello_world

Assuming all of the tests pass, commit the changes, and submit a review.

That's all folks

This simple blog post covered some new content, though, through the power of automation, it handwaved over even more. In this tutorial, we walked through the creation of a new role in TripleO-Ansible and showed how easy it is to make new roles. This post also showed how to extend a role to use dynamic variable loading, which leverages Ansible facts, giving us the ability to create robust roles in exciting ways. Finally, we covered running tests with molecule across all of our supported operating systems. All in all, I'm hoping this post helps folks understand the structure of TripleO-Ansible and highlights how easy it is to contribute to the project. If this post was helpful or if you have questions, let me know, reach out on twitter or IRC.

Chao, until next time!

  1. Main image credit from - ↩︎

  2. Docker will be removed as soon as molecule supports podman and is determined to be stable. ↩︎

  3. At the time of this righting, TripleO-Ansible is dependant on Docker for testing, however, as soon as molecule supports podman we'll no longer need, or want, docker to test roles. ↩︎

  4. At the time of this writting the default operating systems used by TripleO-Ansible are CentOS 7 and Fedora 28. Work is currently underway to replace Fedora 28 with the Red Hat Universal Base Image (UBI8). ↩︎