DRY CI

So I have talked before about what CI actually means. That post was, admittedly, rather dry and academic. I think it’s important to sometimes make the distinction between what is just regular automation and what is strictly CI. When there is automation but no CI it is still very easy for errors to slip into a supposedly stable code base, as not every commit is tested before it is merged to the main. However, with the complexity of modern software, it is pragmatically impossible to separate these two concerns, as CI without automation quickly overwhelms any manual system.

To that point, I have long had a fascination with Ansible, which I encountered early in my experience with CI. Ansible is more suited to Continuous Deployment than specifically to Continuous Integration, but if you give someone CD tools when they are fascinated by CI, you are going to end up with a strange assortment of mashups.

CD for your CI

Ansible, as configuration management software, is great at deploying software and services. I don’t want to try and convince anyone to switch from their favorite system to Ansible, that’s not what I’m about. But, maybe, there are some things I’ve learned and done with Ansible that you can use either in your own Ansible journey or in your own configuration management and continuous deployment tools.

Naturally, CD sits after any sort of test and, most commonly, after CI. If a build system is relatively mature and can produce well tested code with a high confidence, it is a great candidate for Continuous Deployment. Some tools perform this natively (such as Kubernetes). Others can be used to perform deployments for generalized code. However, either way, you have code deploying code.

So how can you be sure your code is doing what you expect?

CI for your CD/Configuration

Well, you test it in CI, of course! That’s right, if you are unfamiliar with writing good Ansbile, there are testing tools that exist specifically to test Ansible code. There are two main tools for this:

  • Ansible Test exists to specifically test things like modules and plugins to Ansible to ensure that they are behaving as expected
  • Molecule exists to specifically test playbooks, roles, and the YAML side of Ansible code

So the automated tools exist to run automated CI for Ansible code. The question becomes, how do we configure our CI system to find and execute these tests on each merge? My answers will focus primarily on Github Actions, as that is where I execute the tests for my own Ansible collections, but a similar technique can be leveraged in most other modern CI executors.

First Version

The first times I wrote CI scripts for my Ansible code using Molecule there were not yet Collections available, so each role existed to be tested on its own. Whenever a change to my CI process arose, or an update to a version was available, I would need to update 30+ separate repositories. This was obviously unweildy, so the first step towards sanity was moving to collection once those were available in Ansible 2.9. Now, I only had one repository to manage.

However, I had 3 different places tests needed to be defined. I would need to write a test, first, in the roles/my_role/molecule/scenario_name folder. This was the actual Molecule test definition. I would then need to add the new scenario name, folder, and job to my tox.ini file, which I used to manage the Python package installation for Ansible, Molecule, pytest, testinfra, yamllint, and more. Tox was a natural place to manage all of these, but it meant a second place to add the test definition. And, thirdly, I had to update my Github Actions workflow file and tell it to execute the particular tox target.

In the next post I will write about how I tamed the beast before it got out of hand.