Using a functional programming language to develop Ansible extensions

As you may already know, I’m a big fan of automation and functional programming. I wanted to combine these two things and develop something that I can apply within my automation process and implement in some functional programming language. That’s why I decided to write Ansible module in Clojure.

Introduction

If you are reading this blog post, I assume you are familiar with either Ansible, Clojure or even both so let me skip an introduction of what Clojure or Ansible are, what features they have and what the Ansible modules are. However, if you don’t know any of them, have a look at my introduction to Ansible or Clojure features.

Use case

Let’s assume we have a company organized in the following way:

We have a bunch of machines in our infrastructure. We have a couple of developers with the same access rights. Every machine has particular users’ accounts (like deployer, website, …). Each developer has an SSH key (or even more) associated with GitHub account. We have a GitHub organization with all developers as members.

Recently I came across the following problem:

I want to give all developers from our organization access to individual users on particular machines. In other words — I want to add developers’ SSH keys to authorized_keys file for some remote users on selected machines. Let’s begin.

Prerequisites

To run an Ansible module on a remote machine, we can use any language. However, this language has to be installed there, and its executable must exist. So the only thing we actually need is:

#!/usr/bin/env executable

and this executable can be:

ruby

python

mix

lein-exec

…

To make it happen, we need to have a role with the language we want to use so that it will be available for a remote script. As we agreed on Clojure, we need to install it first.

Clojure runs on JVM, so we need Java installed:

Now, we can download leiningen, which is basically a Clojure build tool and dependency management system:

When it seems like everything is done, we still need one more step. We need to install lein-exec — leiningen plugin, which is required to execute a single Clojure script with its dependencies.

We are all done! Now is the time to write the actual Ansible module.

Module

There are some requirements of Ansible modules. The most important are:

The shebang that allows an interpreter to work must exist. All module actions should be idempotent. Modules must not output anything on standard error. The output is supposed to be a valid JSON.

So following these requirements we can build a simple Clojure script.

What it basically does is fetching all developers’ keys from a particular GitHub organization (Clojure in that case) and returning them as a JSON array. Nothing more, leave the rest for Ansible.

Usage

How to use our module on remote machines now? We need to build a simple playbook:

We provide a bunch of required environmental variables to be read by our module.

LEIN_ROOT=true # allow to run leinningen as `root` user

2. We clear current authoried_keys for a particular user.

3. We add fetched developers’ keys to remote users.

Summary

And that’s it. With these simple steps, we are now able to manage and provision our machines in a very simple way. Try it on your own!

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

You can find the complete example on GitHub:

Resources

Acknowledgements

Many thanks to Gavin-John Noonan for his patience during code review.