Writing an Ansible module using PHP

Bootstrap your environment

The first thing you need to do is bootstrap your environment for development. I’m going to assume that you’ve set it up like me

cd ansible
source hacking/env-setup
chmod +x hacking/test-module
source .virtualenv/bin/activate

Next, we want to see if everything’s set up correctly by running the test-module command with no parameters.

./hacking/test-module 
Usage: test-module -[options] (-h for help)

If you see the above, we’re all set!

Creating a module

The first step is to make sure that we can run a custom module. Create a file named my_module with the following contents:

#!/usr/bin/env php
<?php
echo json_encode(array("foo" => "bar", "time" => time()));

Ansible modules talk JSON, so here all we’re doing is returning the current time to see if our module works. You can run it with the following command:

./hacking/test-module -m ./my_module

You should see something that looks like this:

* including generated source, if any, saving to: /Users/michael/.ansible_module_generated
* this may offset any line numbers in tracebacks/debuggers!
***********************************
RAW OUTPUT
{"foo":"bar","time":1397926744}

***********************************
PARSED OUTPUT
{
    "foo": "bar", 
    "time": 1397926744
}

Now, it’s great that we’ve got our module working, but it doesn’t actually do much. The next thing to do is make it read any input parameters:

$args_file = $argv[1];
$args_data = file_get_contents($args_file);

Ansible writes input parameters to a file then passes the filename to our module, so we need to read it to find out what’s going on.

It’s up to each module to decide what format it wants it’s arguments in, but as most of them take key=value pairs, let’s stick with that.

$args_file = $argv[1];
$args_data = file_get_contents($args_file);

$args = explode(" ", $args_data);
$return_value = array();

foreach ($args as $arg){
    // If it doesn't contain an equals sign, skip it
    if (strpos($arg, "=") == false) {
        continue;
    }

    list ($key, $value) = explode("=", $arg, 2);

    $return_value[$key] = $value;

    $path = '/tmp/'.$key;
    file_put_contents($path, $value);
}

echo json_encode($return_value);

Let’s test our module again, this time giving it some parameters (the quoting is important):

./hacking/test-module -m ./my_module -a "foo=bar bee baz=boo"

You’ll see that our output changes to match what we passed in, skipping anything that didn’t contain an equals sign:

***********************************
RAW OUTPUT
{"foo":"bar","baz":"boo"}

***********************************
PARSED OUTPUT
{
    "baz": "boo", 
    "foo": "bar"
}

Thankfully, Ansible takes care of running everything on the target machines for us. If you check your target machine, you should see files created in /tmp with the values you specified.

The only caveat with writing your own modules is that if you want to use PHP, you’ll need PHP installed on the target machine, not just the host machine.

Using the module

For Ansible to know about your module, it needs to live somewhere special. There’s two options for telling Ansible about your module.

The first is by adding a directory containing your modules to $ANSIBLE_LIBRARY like the following:

export ANSIBLE_LIBRARY=”/Users/michael/my_ansible_modules:/usr/share/ansible/”

Then, you just use Ansible like normal.

Alternatively, you can use the --module-path (or -M) flag to specify where to search for modules. Of these two options, I prefer modifying $ANSIBLE_LIBRARY.

Next steps

That’s all there is to it. You’ve created a basic Ansible module. There’s more to add, such as error conditions and exit codes, along with required parameters but that’s a story for another time.

Setting up Ansible for hacking

When trying my hand at Ansible module development, I had a few issues getting Ansible running from a checkout. Here’s how I fixed it:

The Ansible documentation for running from source gives us these instructions:

git clone git://github.com/ansible/ansible.git
cd ./ansible
source ./hacking/env-setup

Unfortunately, this doesn’t give us all the dependencies we need. To keep this completely separate to your normal installs, let’s get Ansible running from inside a virtualenv with it’s own dependencies

sudo pip install virtualenv
virtualenv .virtualenv
source .virtualenv/bin/activate
pip install pyyaml jinja2 pycrypto

If you get an error about “unknown argument ‘-mno-fused-madd’” when installing pycrypto, you can flag it as a warning rather than an error with the following command:

ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future pip install pycrypto

Now, if you run ansible --version, you should see the version that you checked out of source control.

ansible 1.5.2

Disabling comment formatting in Jira

Today, I wanted to share a proposed config file with some coworkers via a Jira ticket. Unfortunately Jira was eating lots of the characters in there, treating them as special formatting characters. The fix is nice and easy, you just need the {noformat} declaration.

{noformat}
// Write your code!
{noformat}

You can see the docs for this (and more!) on this Jira documentation page. I decided to reproduce it here as it took me a while to find that page