Writing an Ansible module using PHP

19 Apr 2014 in Tech

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

bash
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.

bash
./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:

php
#!/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:

php
./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:

php
$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.

php
$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):

bash
./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:

bash
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.