Ansible's module development page describes how to create your own Ansible modules in Python, and makes a mention of using shell scripts to do so. As I'm "into" Ansible at the moment, I thought I'd give it a try. I'll also mention now that Ansible 0.5 has been released: one of the nice new features is it supports native SSH connections, so I can use Kerberos authentication.

Consider the following playbook which calls a module named m01:

---
- hosts: mynodes
  user: f2
  sudo: True
  vars:
     name: JP Mens
  tasks:
  - name: Run module m01
    action: m01 dest=/etc/config.jpm name="{{name}}" value=17

The module is contained in a file of the name name which lives in a directory named library/ alongside the playbook file. Ansible transports the shell script to nodes on which it is to be executed at the moment the action is invoked; I don't have to pre-install the module on nodes!

The shell script parses arguments passed it in the playbook (read the comments below carefully) does things and returns arguments in the form key=value ... to Ansible.

#!/bin/sh
  
set -e

# Ansible transports arguments to modules in a file. The
# path to the arguments file is in $1, and the file
# contains the module's arguments like this:
#
#       name="JP Mens" value=15 dest=/tmp/destfile
#
# This is a little dangerous (!?!), but I'm going to ask
# the current shell to parse that file; it will set
# variables accordingly.

source ${1}   # Very, *very*, dangerous!

t=`mktemp /tmp/amod.XXXXXX`

trap "rm -f $t" 0 1 2 15

if [ -z "$dest" ]; then
        echo 'failed=True msg="Module needs dest= argument"'
        exit 0;
fi

# Build content of the temporary file so that we can compare
# its content to the existing destination file.

cat > ${t} << EndConfig
# configuration for foo
username=$name
number=$value
EndConfig


# If destination doesn't exist, install it.

if [ ! -f "$dest" ]; then
        install -m644 ${t} ${dest}
        echo "changed=True msg=OK"
        exit 0
fi

# Destination file exists. Obtain a hash of the content of
# both destination file and the temporary file and compare
# them. If they differ, clobber destination file and tell
# Ansible the file changed.

oldhash=`md5sum ${t} | awk '{print $1;}'`
newhash=`md5sum ${dest} | awk '{print $1;}'`

if [ "$oldhash" != "$newhash" ]; then
        install -m644 ${t} ${dest}
        echo "changed=True msg=OK"
        exit 0
fi


echo "changed=False"
exit 0

I execute the playbook just as I execute any other, here using Kerberos authentication as an example:

$ kinit f2
Password for f2@MENS.DE: 

$ export ANSIBLE_SSH_ARGS=""
$ ansible-playbook --connection=ssh mini.yaml
[...]

Notes:

  • This can be useful for prototyping, but creating Python modules is doubtless the better way.
  • All programs invoked from the script must already exist on the node. If necessary, you can use the copy module to transport them to the node before calling your module. The same goes for files required by the module, which can also be created with the template module.
  • The way I "parse" the arguments using source can be pretty deadly for the remote node; think about that!
  • Modules don't have access to Ansible "facts". They could parse the setup file but there's been a bit of discussion on the Ansible mailing-list whether that file will remain; I hope it does in some form or other.

Comments

blog comments powered by Disqus