Why automate Ansible

Ansible can be used for many things. There are only a few things I have on my bucket list of things I would like to do, where Ansible cannot help me.

One of my most urgent things to handle was the increasing complexity of Ansible, its configuration and in particular the role development. As I got deeper into Ansible, more and more factors needed to be taken into consideration when setting up a role: the role structure, linting issues, molecule testing, etc.

When writing a new role, I usually start with the tool ansible-galaxy in order to create the basic structure.

$ ansible-galaxy role init dbv.test
- Role dbv.test was created successfully
$ tree dbv.test
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

This gives me the basic structure, files and directories that I will most likely need. It also gives me some stuff that I do not need. I also lack some things like molecule testing.

It does not only make my life easier, but also gives me some extra work.

If I ran yamllint on the freshly create role, I immediately get a list of problems.

$ yamllint .
./meta/main.yml
  1:1       warning  missing document start "---"  (document-start)
  21:81     error    line too long (84 > 80 characters)  (line-length)
  25:5      warning  comment not indented like content  (comments-indentation)
  25:81     error    line too long (83 > 80 characters)  (line-length)
  26:81     error    line too long (82 > 80 characters)  (line-length)
  29:81     error    line too long (83 > 80 characters)  (line-length)
  33:3      warning  comment not indented like content  (comments-indentation)
  33:81     error    line too long (85 > 80 characters)  (line-length)

The file ./meta/main.yml is the troublemaker here. If I run ansible-lint on top of that against the blank role, I get even more issues with the same file:

$ ansible-lint .
ERROR    Computed fully qualified role name of test does not follow current galaxy requirements.
Please edit meta/main.yml and assure we can correctly determine full role name:

galaxy_info:
role_name: my_name  # if absent directory name hosting role is used instead
namespace: my_galaxy_namespace  # if absent, author is used instead

Namespace: https://old-galaxy.ansible.com/docs/contributing/namespaces.html#galaxy-namespace-limitations
Role: https://old-galaxy.ansible.com/docs/contributing/creating_role.html#role-names

As an alternative, you can add 'role-name' to either skip_list or warn_list.

Computed fully qualified role name of test does not follow current galaxy requirements.
Please edit meta/main.yml and assure we can correctly determine full role name:

galaxy_info:
role_name: my_name  # if absent directory name hosting role is used instead
namespace: my_galaxy_namespace  # if absent, author is used instead

Namespace: https://old-galaxy.ansible.com/docs/contributing/namespaces.html#galaxy-namespace-limitations
Role: https://old-galaxy.ansible.com/docs/contributing/creating_role.html#role-names

As an alternative, you can add 'role-name' to either skip_list or warn_list.

I foolishly tend to share my roles. Without that I could delete the whole subdirectory ./meta and just do not have to care about these initial problems. But since I know how that would make my role less resilient, usable and shareable, I usually fix those problems.

Why the ./meta/main.yml file is not passing the basic yamllint or ansible-lint test, I cannot understand. Surely the Ansible project should be able to create lint-valid code? Spoiler: It isn’t that easy.

Since I am already working on ./meta/main.yml, I also update the role author, platforms, the minimum required Ansible version, add my desired license and delete all the comments.

That makes me pass at least the ansible-lint check for this file.

But I still cannot start working on my role. It would be easy just to write some code, hope it works and call it a day. But since I stepped my toe into Ansible molecule, I have to create the required file-structure there as well.

 tree molecule/
molecule/
└── default
    ├── converge.yml
    ├── molecule.yml
    ├── prepare.yml
    ├── variables.yml
    └── verify.yml

The file variables.yml is my personal little amendment to molecule in order to set the variables I use in a role. For each testing scenario I have to create a new directory with at least these five files again. And again. And again.

The file molecule.yml also has some references to my local Podman containers which I use for development and testing. Each time I test on a new platform/container, I have to update these in all my roles as well. Some additional work that this development brings forth.

I hope you can see how this very quickly starts to get out of hand. Each new feature and functionality increases the initial workload every time I create a role.

But I am not done yet.

While the basic role file structure now passes the linting tests, the structure itself requires some changes.

The directory ./tests and the file ./.travis.yml I usually do not need. I delete them. Instead I add a configuration file for yamllint into the root directory of the role.

A file CHANGELOG.md should not be missing either.

Last but not least I structure my roles in installation, configuration and service tasks. Following that pattern I create corresponding files in ./tasks/, called install.yml, config.yml and service.yml and include those in this order in ./tasks/main.yml.

At this point I have already done quite some changes to the role, without actually writing any application related code:

  • Delete the subdirectory ./tests.
  • Update the role name in ./meta/main.yml.
  • Update the namespace in ./meta/main.yml.
  • Update the author in ./meta/main.yml
  • Update the compatible platforms for the role in ./meta/main.yml.
  • Update the required license in ./meta/main.yml.
  • Update the compatible Ansible version in ./meta/main.yml and put it in hyphens.
  • Add additional task files in ./tasks.
  • Add a Changelog file.
  • Add a basic yamllint configuration to the role.
  • Remove the ./travis.yml file.
  • Create a molecule configuration and add required scenarios.

The file structure of the (still simple) role now looks like this:

$ tree
~/git/dbv.test/
├── CHANGELOG.md
├── defaults
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── converge.yml
│       ├── molecule.yml
│       ├── prepare.yml
│       ├── variables.yml
│       └── verify.yml
├── README.md
├── tasks
│   ├── config.yml
│   ├── install.yml
│   ├── main.yml
│   └── requirements.yml
├── templates
└── vars
    └── main.yml

Roughly after the third time when I had to do this, I realized that this amount of work will in the end stop me from writing good roles. The tasks were repetitive, stupid and simple. They were begging for automation.

That’s what I did.

I wrote an Ansible role to create Ansible roles.

I regret nothing. Nothing.

The idea is simple. Instead of using the ansible-galaxy command to create a new role, I use an Ansible ad-hoc command instead.

The Ansible role I created to handle this I called Fule.1

Now, this command will create a new role in /data/ansible_roles:

ansible -i localhost, all \
  --connection local \
  -m include_role \
  -a "name=dbv.fule" \
  -e fule_working_dir=/data/ansible_roles \
  -e fule_role_name=dbv.test

The role has become quite complex itself, considering all the steps it has to complete to create and adjust new roles. In order to simplify using the role, I also had to add some features I did originally not think of.

Let me take you through it:

The only two required parameter are fule_working_dir which specifies in which directory to create Ansible role and fule_role_name, setting the name of the new role.

These two parameters are also the only ones that I usually specify. If I forget them , the role outputs an error message reminding me to specify these two parameters.

For any other configuration of a new role I have two options:

  1. Specify a global configuration file in e.g. ~/.ansible/fule.yml (or any other five allowed filenames), or
  2. Specify local configuration settings in ./molecule/fule_variables.yml within the newly created role.

The personal configuration file in my home directory ~/.ansible/fule.yml is used to specify most of the content of the file molecule.yml in each molecule scenario. It contains the basic specifications of each container and which Podman/Docker image to use. Any update here will also update all scenarios in all managed Ansible rules with new containers to test on if I choose to run the role on an existing role again.

Because existing roles are not being destroyed, but sensible updated. Anything else would be tremendously stupid.

The more role specific settings like which scenarios a role should have are stored in ./molecule/fule_variables.yml. Additional scenarios that I need are simply listed in there and fule will just create those scenarios if they are missing.

The local configuration file keeps data like the specify molecule scenarios required.

Remember that I had to create five files in a specific directory within ./molecule every time I require a new scenario? Fule does that for me. The role reads ./molecule/fule_variables and will create additional scenarios if they are missing (beside the default scenario, of course).

As mentioned Fule does not modify existing code and leaves the role in a worse state than it was before. Therefore Fule will not

  • Remove files or directories (unless instructed to).
  • Overwrite existing file (unless instructed to).

I have some further adjustments to make to Fule to address even more issues during the development, but just using the role makes it very easy to update any existing role that already has been used with this setup.[^2]

I also tried to address the linting issues at the source: I filed some change request for Ansible and suggested to fix some of the issue, but I got shot down. The reasoning followed these three points:

  • The Ansible project and the tools yamllint/Ansible-lint are not part of the same projects and disagree to some point about what is considered to be acceptable and what not. This occasionally leads to contrary opinions about what is preferable in terms of coding styles.
  • The file templates are meant as a starting point to make further edits and are not required to be perfekt form the start.
  • It does not make sense to fix some issues, but leave other (e.g. linting issues) unaddressed.

I partly disagree with these points from the Ansible team, but understand the angle where they are coming from. So, so long thee issues, that show up every time I create a new role, are being fixed, I will use other solutions like this Ansible role to work around it.

  1. Originally it was called folecule (fix molecule), but that took forever to write. In the end it became fule. No special meaning attached. [^2]: The only nightmare I had during the development of Fule, where I had to write molecule testing scenarios, that test and apply changes to the development containers and everything got very meta: Writing code that writes code or changes new existing code makes my head hurt. 

Daniel Buøy-Vehn

Senior Systems Consultant at Redpill Linpro

Daniel works with automation in the realm of Ansible, AWX, Tower, Terraform and Puppet. He rolls out mainly to our customer in Norway to assist them with the integration and automation projects.

Comparison of different compression tools

Working with various compressed files on a daily basis, I found I didn’t actually know how the different tools performed compared to each other. I know different compression will best fit different types of data, but I wanted to compare using a large generic file.

The setup

The file I chose was a 4194304000 byte (4.0 GB) Ubuntu installation disk image.

The machine tasked with doing of the bit-mashing was an Ubuntu with a AMD Ryzen 9 5900X 12-Core ... [continue reading]

Why TCP keepalive may be important

Published on December 17, 2024

The irony of insecure security software

Published on December 09, 2024