Getting Ansible info into your playbook


Early this week a co-worker asked if it was possible to access the Ansible command-line in a playbook. It seems that is not the case, in a normal, clean Ansible environment.

But in the meantime I was creating a playbook that served multiple purposes, stopping and starting services. These playbooks are completely the same, except for the start and stop keywords. Of course I could have solved that with a variabele, either hardcoded or as an extra variable on the commandline. But, where is the fun in that :-)

So the idea arose to let the playbook depend on its name and if it is called start, start all services and if it is called stop just stop them. Something along the line of $0 in shell or sys.argv[0] in Python.

But this idea turned out to exactly the same idea as my co-worker had. They are very related, but it is just not in Ansible.

But, it is open source, so just fix it :-)

I started looking into an action plugin and after a lot of trail, error and Ansible source code reading I have fixed it.

The Ansible source-code contains a helper module called context, that parses the command-line and consumes all options. But luckily, all that’s left are the playbook names and these are in context.CLIARGS['args']. So if I take these I’m done. But when I’m doing this, I can also fix the co-workers problem, if I can access the ansible-playbook parameters. And that turns out to be even simpler, just get sys.argv in Python.

The result of all this craft is this Python script, an action plugin.

#!/usr/bin/python
# Make coding more python3-ish, this is required for contributions to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import sys
import os

# Get Ansible context parser
from ansible import context

# ADT base class for our Ansible Action Plugin
from ansible.plugins.action import ActionBase

# Load the display handler to send logging to CLI or relevant display mechanism
try:
    from __main__ import display
except ImportError:
    from ansible.utils.display import Display
    display = Display()


# Get all Ansible commandline arguments and place these in the
# `facts` dictionairy as `ansible_facts['argv']`
class ActionModule(ActionBase):

    # No file transfer needed
    TRANSFERS_FILES = False

    def run(self, tmp=None, task_vars=None):
        '''Run action plugin'''

        # All checks (file, exists, etc) are already done
        # by the Ansible context
        playbooks = list(map(os.path.abspath, list(context.CLIARGS['args'])))

        # Create the result JSON blob
        result = {
            'changed': False,
            'failed': False,
            'skipped': False,
            'msg': '',
            'ansible_facts': {
                'argv' : sys.argv,
                'playbooks': playbooks,
            }
        }

        return result

This results in two extra Ansible facts, called argv and playbooks, that can be used in your playbooks like this:

- name: lets go
  hosts: localhost
  become: false
  connection: local

  tasks:
    - name: get commandline arguments
      get_argv:

    - debug:
        msg:
          - "{{ ansible_facts['argv'] | default('Nope' ) }}"
          - "{{ ansible_facts['playbooks'] | default('Nope' ) }}"

To use the action plugin, create a directory called action_plugins in your Ansible directory, or set the action_plugins path in the ansible.cfg file and place the get_argv script in this directory.

Enjoy!

See also