Date Thu 28 July 2016 Tags ansible , python , devops

The Problem

Whenever I'm building a server or simple local infrastructure for testing code, I start by looking at the available ansible roles, hoping to find one that JustWorks for my use-case right out of the box. One thing that annoys me though is that even for roles without extra variables, a playbook is still required. Sure it's a small file, but creating it still annoys me when I'm just testing stuff.

Typically these one-off playbooks look something like this:

- hosts : all become : yes become_method : sudo roles : - { role : username.rolename }

There's a regular need for "anonymous roles", ie role application without using playbooks, even if only for simple use-cases.

Prior Art

There's already a few projects that work around this, but none of them fit my use-case exactly (see the spec below).

All implementations seem to agree with my conclusion on one point though: while it's very attractive to consider using the ansible API directly (if only to have a practical task in mind while you get to know the API), doing this is awkward. The simplest approach is to just create a temporary playbook.yml file, then run an appropriate ansible-playbook command on that file and get rid of it afterwards.

The Specification

I specifically wanted the following behaviour from the missing ansible-role command:

The ansible-role invocation should respect data intended for the ansible-playbook command, i.e. ANSIBLE_* shell variables should be passed through, and the unparsed aspect of the ansible-role command line also should be passed through to ansible-playbook .

Despite pass-through, the ansible-role command should also parse and understand the --module-path=FOO argument if it is given, and by default look for roles inside FOO/roles .

In the event no matching role is available locally then the role should be downloaded for me automatically using the ansible-galaxy command.

In case a galaxy download is necessary and --modulepath=FOO is specified, it is downloaded to FOO/roles and not cleaned afterwards. File-system caching makes sense here because the ansible-role invocation is supposed to be quick and easy, but the usage of the role itself is less experimental.

In case a galaxy download is necessary and --modulepath is NOT given, then there is no caching. File system cleaning for the role download makes sense here because this is a one-off experiment and it's no use cluttering the file system. Thus the role will be downloaded to a temporary directory and will be deleted afterwards (regardless of whether the application of the role succeeds).

Apart from the standard ansible-playbook arguments, the ansible-role command line understands exactly 2 other arguments: the primary positional argument role_name (which is required and which specifies an ansible-galaxy role), and the host argument (which defaults to localhost ). Again, everything else on the command line will be passed through to the ansible-playbook invocation.

After cleanup, the ansible-role command should return the same exit code as the implied ansible-playbook invocation.

Finally, the ansible-role command should be available on pypi so it can be installed with pip.

Examples

Most of the requirements above are straightforward, but let's look at a few examples. The invocation ansible-role username.rolename --module-path ./ansible --become will download the role if it doesn't exist, and result in the command ansible-playbook --module-path ./ansible --become $random_tmp_playbook.yml , and the role will not be cleaned. The temporary playbook will be cleaned after it is used, and the contents of it are:

- hosts : localhost roles : - { role : ./ansible/roles/username.rolename }

The invocation ansible-role username.rolename test_server will definitely download the role, and result in the command ansible-playbook $random_tmp_playbook.yml , and the role will be deleted after it is used. The temporary playbook will also be cleaned after it is used, and the contents of the playbook are:

- hosts : test_server roles : - { role : $some_tmpdir/roles/username.rolename }

Overrides

If you need to override variables using this approach to "anonymous" roles, then honestly it might be time to consider writing and maintaining an actual playbook file. But if you insist, then you can always take advantage of ansible variable preference precedence and pass --extra-vars "var=val" to your ansible-role command.

The Code

All this is of course available on github and pypi.