Ansible - using Woodpecker as an alternative to Semaphore

Ansible can be used in many ways: most people likely execute their playbooks on their local machines. Then there is Ansible Automation Platform (AAP) (formerly Ansible Tower) and its small brother AWX. Both are the standard in larger organizations to allow for a more controlled way of running Ansible playbooks and offer RBAC capabilities. These two are also really “heavy” and require a lot of resources to run. Since some time both also require a Kubernetes backend.

These facts are not attractive for homelabs or other small-scale use cases. Luckily, Semaphore exists, which is a project aiming to provide a lightweight alternative to AAP. Maybe there are also even more alternatives, yet I was not able to find any others when researching the topic until today.

I have experience with all of the above tools (AAP/AWX for business, Semaphore for homelab). This points particularly addresses the “homelab side”, i.e., searching for a tool that executes Ansible code remotely while providing access to execution logs and some kind of RBAC.

In my homelab, I had some hickups every few months with Semaphore: Webhooks did not function properly, some runs were stuck forever as pending in the UI and I couldn’t delete them, etc. Also, I was not so happy with the development speed of Semaphore: it is mainly a one-person project for a while now, and while occasionally some PRs get merged, the maintainer is often away for several weeks, and in this time, nothing happens. (Disclaimer: this is of course not meant as a critique, I am very greatful for any open-source work being done, and I am aware that maintaining a project is a lot of work and that the time of anyone is limited. 🤝️)

Link to this section  Secret handling in Ansible

When looking at the technical challenges of running Ansible playbooks remotely, especially when doing so in a public space (e.g. for the infrastructure of FOSS projects), secret handling is a big issue 1 2 3. Yes, one can set no_log: true but to me, this defeats the whole purpose of running Ansible remotely with log output: I want to have log output and see what is changing/has changed.

On top, I don’t want to fear having overlooked one task where I should have set no_log: true which then exposes a secret. This is not an issue with any of the tools mentioned above, but rather a general issue with Ansible (and also, the built-in “Ansible vault” does not provide this). For to me unknown reasons, there is still not yet any solution to the problem of exposing secrets in Ansible’s diff mode, i.e., when telling Ansible to show what (would) change in a certain task. As there are dozens of integrations for secret injection into Ansible, it might not be so easy to find a common way of declaring which parts of a task content are actually secrets. Yet to me, this is one of the most essential features of an automation tool: do not expose your secrets.

OK, as long as you’re in a private environment, you might not care so much about this. Yet this fact is a big issue when you are not. And given the wide adoption of ansible in the devops space in the last year, having a central place to execute (and colloborate) on Ansible projects in a public space is becoming more and more popular.

Another important point for such public environments is usually cost. You want a tool that has no license costs and is low on resources. Semaphore checks these boxes (as it is written in Go), but does not solve the secret problem.

Link to this section  Woodpecker for Ansible execution (and secret masking)

And at this point, I want to showcase another alternative that solves the secret problem “by accident,” is low on resources, and has no license costs: Woodpecker CI (WP). Yes, a CI tool - it does not aim to be an Ansible frontend and follow the project/environment/inventory structure of AWX/Semaphore, but is very good at running Ansible playbooks efficiently thanks to its Ansible plugin. Disclaimer: I initiated the WP plugin and ported it over from the (unmaintained) Drone CI Ansible plugin.

A quick tl;dr on Woodpecker: WP is a fork of Drone CI, written in Go. It has minimal resource needs (< 100 MB), is very fast and versatile.

And now let’s talk about how Woodpecker solves the “ansible secret issue”: WP executes the playbooks as they would be logged in the console. However, when a sensitive value is added as a Woodpecker secret and then assigned to an ansible variable, the value is not shown in the output but masked:

Example of masked ansible variable in Woodpecker run

🤯️ This is great! So without aiming for it, Woodpecker solves one of the biggest issues of remote Ansible execution. To get a bit more specific, let’s showcase how this works in a technical way:

In a Woodpecker pipeline, one can declare which secrets are injected into a pipeline step:

steps:
  - name: docker # random name
    image: docker # random image
    secrets: [my_ansible_secret] # arbitrary secret name

The secret is then available as an env var in the task environment. Now to parse this env var correctly into ansible, variables should be declared using the lookup function:

my_ansible_secret: "{{ lookup('env', 'MY_ANSIBLE_SECRET') | default('') }}"

Now, whenever the value of variable my_ansible_secret is used in a task, it will be masked in the log output using *****.

Let’s also briefly address the downsides of using Woodpecker for Ansible execution:

Link to this section  Summary

Overall, Woodpecker is a great alternative to execute Ansible playbooks remotely. Especially if a CI tool is needed in general, the resource costs of running ansible for both CI and Ansible tasks are close to zero. This is great for homelabs and other resource-sensitive environments.

The approach shown in this post should also be achievable with other CI tools: the WP Ansible plugin only provides a convenient way of running playbooks, but the same can be done by starting out from a bare-bones Alpine image.

PS: If you want to see a public repo where Ansible playbooks are executed using Woodpecker, check out Codeberg-Infrastructure/ansible-configuration.