Creating an Ansible module with Go (Golang) or Bash

Networking, Cloud & Automation
codeburst
Published in
6 min readSep 25, 2020

--

Modified image from Maria Letta’s Free Gophers Pack

In a previous post, we explored how Ansible can integrate with Google Calendar for Change Management, without getting into the details of the Ansible module that was built for this purpose. In this post, we will cover the nuts and bolts of it.

While most Ansible modules are written in Python (see this example), that’s not the only option you have. You can use other programming languages as well if you prefer so. And if you like Go, this post is for you!

If you are new to Go on the other hand, here are some pointers to get started.

Ansible and Go

There are at least four different ways that you can run a Go program from Ansible. You can either:

1. Install Go and run your Go code with the go run command from Ansible.

2. Cross compile your Go code for different platforms before execution. Then call the proper binary from Ansible, based on the facts you collect from the host.

3. Run your Go code or compiled binary from a container with the containers.podman collection. Something along the lines of:

4. Create an RPM package of your Go code with something like NFPM and install it in the system of the target host. You can then call it with Ansible modules shell or command.

Running an RPM package or container is not Go specific (cases 3 and 4), so we will focus on the first two options.

Google Calendar API

We will need to interact with the Google Calendar API, which provides code examples for different programming languages. The one for Go will be the base for our Ansible module.

The tricky part is enabling the Calendar API and downloading the credentials you generate in the Google API Console (Credentials > + CREATE CREDENTIALS > OAuth client ID > Desktop App).

The above image shows where to download your OAuth 2.0 client credentials (JSON file) once created in API Credentials.

Calling the module from Ansible

The calendar module takes the time to validate as an argument. In the example below, the current time is provided. You can typically get this from Ansible facts (ansible_date_time). The JSON output of the module is registered in a variable named output, to be used in a subsequent task.

You might wonder where the calendar module code lives and how Ansible executes it. Please bear with me for a moment; we’ll soon get to this after covering other pieces of the puzzle first.

The time logic

With the Calendar API nuances out of the way, we can proceed to interact with the API and build a Go function to capture the module logic. The time is taken from the input arguments — in the Playbook above — as the initial time (min) of the time window to validate (a 1-hour duration has been arbitrarily chosen).

A FreeBusyRequest is sent to Google Calendar with the initial and finish time (min and max) of the time window. A Calendar client (srv) is created to authenticate the account correctly using the JSON file with the OAuth 2.0 client credentials. In return, we get a list of events during this time window.

If you get zero events, the function returns busy=false. However, if there is at least one event during this time window, it means busy=true. You can check out the full code here.

Processing the input and creating a response

Now, how does the Go code read the inputs arguments from Ansible and, in turn, generate a response that Ansible can process? The answer to this depends on whether you are running the Go CLI or just executing a pre-compiled Go program binary (options 1 and 2 in the list).

Both options have their pros and cons. If you use the Go CLI, you can pass the arguments the way you prefer. However, to make it consistent with how it works for binaries you run from Ansible, both alternatives will read a JSON file in the examples presented in this post.

Reading the arguments

As displayed in the Go code snippet below, an input file is processed. Ansible provides a path to it when it calls a binary.

The content of the file is unmarshaled into an instance (moduleArg) of a previously defined struct (ModuleArgs). Here is how you tell the Go code which data you expect to receive. This method enables you to gain access to the time specified in the Playbook via moduleArg.time, which is then passed to the time logic function (isItBusy) for processing.

Generating a response

The values to return are assigned to an instance of a Response object. Ansible will need this response to include a couple of Boolean flags (Changed and Failed). You can add any other field you need, in this case a Busy Boolean value is carried to signal the response of the time logic function.

The response is marshaled into a message that we print out. Ansible will be able to read it.

You can check out the full code here.

Executing a binary or Go code on the fly?

One of the cool things about Go is that you can cross-compile a Go program to run on different target operating systems and architectures. These binary files you compile can be executed in the target host without installing Go or any dependency.

This flexibility plays nicely with Ansible, which provides you with the target host details ( ansible_system and ansible_architecture) via Ansible facts. In this example, the target architecture is fixed when compiling (x86_64) but binaries for Mac, Linux, and Windows are generated (via make compile). This produces the three files that are included in the library folder of the go_role role with the form of: calendar_$system.

The go_role role that packages the calendar module is structured to replace $system from the executing filename based on the ansible_system discovered during runtime, this way this role can run on any of these target operating systems. How cool is that!? You can test this with make test-ansible.

Alternatively, if Go is installed in the target system, then you can run the go run command to execute the code. If you want to install Go first as part of your playbook, you can use this role (see this example).

How do you run the go run command from Ansible? One option is to use the shell or command modules. However, a bonus Bash module is used in this case to extend this exercise to include another programing language.

Bonus module: Bash

The file go_run in the library folder go_role role is the actual Bash code used to run the Go code on systems that have Go installed. When Ansible runs this Bash module, it will pass a file to it with the module arguments defined in the Playbook, which you can import in Bash with source $1. This provides access to the variable time. Otherwise, you get it from the system with date --iso-8601=seconds.

ISO 8601 and RFC 3339 make timestamps interoperable between Ansible, Bash, and Go. There is no need to parse or transform data between them.

With the inputs at hand, a JSON file is generated with printf. This file is passed as an argument to the Go code via the go run command. You can test this with make test-manual-bash. Check out the full code here.

Using the module output as a conditional

The response from thecalendar module (output) can now be used as a conditional to determine whether the next task should run or not.

If you wanted to avoid running the previous task to get the response and instead use the module’s output directly in your when statement, then an Action plugin might help.

Another alternative, especially if Go is not your thing, is something like this plugin that my good friend Paulo wrote after we discussed this specific use-case (calendar integration for change management)

Conclusions

While Ansible and most of its modules are written in Python, it can seamlessly integrate with tools or scripts that use another programming language. This is key to improving efficiency and operational agility, without the need to rip and replace.

--

--

Proud dad working at Red Hat (CCIE, CCDE). Sharing content I create about: networking, automation, programming, golang, ipv6, and open source software.