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!