Skip to content

Commit 0624e3c

Browse files
committed
Merge pull request #16 from DataDog/olivielpeau/ansible-v2
Ansible 2 compatibility
2 parents d0e7672 + 06e5463 commit 0624e3c

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A callback to send Ansible events and metrics to Datadog.
44

55
## Requirements
66

7-
Ansible >=1.1,<2.0
7+
Ansible >=1.1
88

99
The following python libraries are required on the Ansible server:
1010

datadog_callback.py

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
1+
import getpass
12
import os.path
23
import time
34

45
import datadog
56
import yaml
67

8+
try:
9+
# Ansible v2
10+
from ansible.plugins.callback import CallbackBase
11+
from __main__ import cli
12+
except ImportError:
13+
# Ansible v1
14+
CallbackBase = object
15+
cli = None
716

8-
class CallbackModule(object):
17+
18+
class CallbackModule(CallbackBase):
919
def __init__(self):
1020
# Read config and set up API client
1121
api_key, url = self._load_conf(os.path.join(os.path.dirname(__file__), "datadog_callback.yml"))
1222
datadog.initialize(api_key=api_key, api_host=url)
1323

1424
self._playbook_name = None
1525
self._start_time = time.time()
26+
self._options = None
27+
if cli:
28+
self._options = cli.options
29+
30+
# self.playbook is either set by Ansible (v1), or by us in the `playbook_start` callback method (v2)
31+
self.playbook = None
32+
# self.play is either set by Ansible (v1), or by us in the `playbook_on_play_start` callback method (v2)
33+
self.play = None
1634

1735
# Load parameters from conf file
1836
def _load_conf(self, file_path):
@@ -47,7 +65,6 @@ def _send_event(self, title, alert_type=None, text=None, tags=None, host=None, e
4765

4866
# Send event, aggregated with other task-level events from the same host
4967
def send_task_event(self, title, alert_type='info', text='', tags=None, host=None):
50-
# self.play is set by ansible
5168
if getattr(self, 'play', None):
5269
if tags is None:
5370
tags = []
@@ -98,6 +115,23 @@ def start_timer(self):
98115
def get_elapsed_time(self):
99116
return time.time() - self._start_time
100117

118+
# Handle `playbook_on_start` callback, common to Ansible v1 & v2
119+
def _handle_playbook_on_start(self, playbook_file_name, inventory):
120+
self.start_timer()
121+
122+
# Set the playbook name from its filename
123+
self._playbook_name, _ = os.path.splitext(
124+
os.path.basename(playbook_file_name))
125+
inventory_name = os.path.basename(os.path.realpath(inventory))
126+
127+
self.send_playbook_event(
128+
'Ansible playbook "{0}" started by "{1}" against "{2}"'.format(
129+
self._playbook_name,
130+
getpass.getuser(),
131+
inventory_name),
132+
event_type='start',
133+
)
134+
101135
# Default tags sent with events and metrics
102136
@property
103137
def default_tags(self):
@@ -121,9 +155,15 @@ def format_result(res):
121155
elif not res.get('invocation'):
122156
event_text = msg
123157
else:
124-
event_text = "$$$\n{0}[{1}]\n$$$\n".format(res['invocation']['module_name'], res['invocation']['module_args'])
158+
invocation = res['invocation']
159+
event_text = "$$$\n{0}[{1}]\n$$$\n".format(invocation['module_name'], invocation.get('module_args', ''))
125160
event_text += msg
126-
module_name = 'module:{0}'.format(res['invocation']['module_name'])
161+
module_name = 'module:{0}'.format(invocation['module_name'])
162+
if 'module_stdout' in res:
163+
# On Ansible v2, details on internal failures of modules are not reported in the `msg`,
164+
# so we have to extract the info differently
165+
event_text += "$$$\n{0}\n{1}\n$$$\n".format(
166+
res.get('module_stdout', ''), res.get('module_stderr', ''))
127167

128168
return event_text, module_name
129169

@@ -159,20 +199,26 @@ def runner_on_unreachable(self, host, res):
159199
host=host,
160200
)
161201

202+
# Implementation compatible with Ansible v1 only
162203
def playbook_on_start(self):
163-
# Retrieve the playbook name from its filename
164-
self._playbook_name, _ = os.path.splitext(
165-
os.path.basename(self.playbook.filename))
166-
self.start_timer()
167-
host_list = self.playbook.inventory.host_list
168-
inventory = os.path.basename(os.path.realpath(host_list))
169-
self.send_playbook_event(
170-
'Ansible playbook "{0}" started by "{1}" against "{2}"'.format(
171-
self._playbook_name,
172-
self.playbook.remote_user,
173-
inventory),
174-
event_type='start',
175-
)
204+
playbook_file_name = self.playbook.filename
205+
inventory = self.playbook.inventory.host_list
206+
207+
self._handle_playbook_on_start(playbook_file_name, inventory)
208+
209+
# Implementation compatible with Ansible v2 only
210+
def v2_playbook_on_start(self, playbook):
211+
# On Ansible v2, Ansible doesn't set `self.playbook` automatically
212+
self.playbook = playbook
213+
214+
playbook_file_name = self.playbook._file_name
215+
inventory = self._options.inventory
216+
217+
self._handle_playbook_on_start(playbook_file_name, inventory)
218+
219+
def v2_playbook_on_play_start(self, play):
220+
# On Ansible v2, Ansible doesn't set `self.play` automatically
221+
self.play = play
176222

177223
def playbook_on_stats(self, stats):
178224
total_tasks = 0

0 commit comments

Comments
 (0)