This repository has been archived by the owner on Feb 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
endpoint_report_callback.py
165 lines (140 loc) · 5.47 KB
/
endpoint_report_callback.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/python3
from ansible.plugins.callback import CallbackBase
import uuid
import json
from requests import post
from functools import partial
from ansible.inventory.host import Host
from ansible.module_utils._text import to_native
from ansible.errors import AnsibleError
import datetime
import os
DOCUMENTATION = '''
callback: NOTIFY_ENDPOINT
callback_type: notification
requirements:
- whitelist in configuration
short_description: Sends play report to endpoint
version_added: "2.4"
description:
- This is an ansible callback plugin that sends play result to endpoint.
- Before 2.4 only environment variables were available for configuring this plugin
options:
endpoint_url:
required: True
description: endpoint URL
env:
- name: ENDPOINT_URL
ini:
- section: callback_endpoint
key: endpoint_url
endpoint_token:
required: True
description: Authentication token.
env:
- name: ENDPOINT_TOKEN
ini:
- section: callback_endpoint
key: endpoint_token
'''
def current_time():
return '%sZ' % datetime.datetime.utcnow().isoformat()
class CallbackModule(CallbackBase):
"""
This is an ansible callback plugin that
sends play results to an endpoint.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'endpoint_report_callback'
CALLBACK_NEEDS_WHITELIST = False
def __init__(self, display=None):
super(CallbackModule, self).__init__(display=display)
self.results = []
self.playbook_name = None
# This is a 6 character identifier provided with each message
# This makes it easier to correlate messages when there are more
# than 1 simultaneous playbooks running
self.guid = uuid.uuid4().hex[:6]
def set_options(self, task_keys=None, var_options=None, direct=None):
super(CallbackModule, self).set_options(
task_keys=task_keys, var_options=var_options, direct=direct)
self.url = self.get_option('endpoint_url')
self.token = self.get_option('endpoint_token')
self.show_invocation = (self._display.verbosity > 1)
if self.url is None:
self.disabled = True
self._display.warning('endpoint URL was not provided. The '
'endpoint URL can be provided using '
'the `ENDPOINT_URL` environment '
'variable.')
def send_msg(self, payload):
try:
req = post(self.url, headers={
'Authentication': f'Bearer {self.token}'}, json=payload)
except Exception as e:
self._display.warning(f'POST failed with exception {e}')
self.disabled = True
raise AnsibleError(to_native(e))
if req.status_code != 200:
self._display.warning(
f'Failure to post JSON to endpoint. Response: {req.text}')
def v2_playbook_on_stats(self, stats):
"""Display info about playbook statistics"""
hostname = os.environ['HOSTNAME']
"""if run locally change hostname"""
plays = json.loads(json.dumps(self.results).replace('localhost',hostname).replace('127.0.0.1',hostname))
payload = {'plays': plays, }
self.send_msg(payload)
def _new_play(self, play):
return {
'play': {
'name': play.get_name(),
'id': str(play._uuid),
'duration': {
'start': current_time()
}
},
'tasks': []
}
def _new_task(self, task):
return {
'task': {
'name': task.get_name(),
'id': str(task._uuid),
'duration': {
'start': current_time()
}
},
'hosts': {}
}
def v2_playbook_on_play_start(self, play):
self.results.append(self._new_play(play))
def v2_playbook_on_task_start(self, task, is_conditional):
self.results[-1]['tasks'].append(self._new_task(task))
def v2_playbook_on_handler_task_start(self, task):
self.results[-1]['tasks'].append(self._new_task(task))
def _convert_host_to_name(self, key):
if isinstance(key, (Host,)):
return key.get_name()
return key
def _record_task_result(self, on_info, result, **kwargs):
"""This function is used as a partial to add failed/skipped info in a single method"""
host = result._host
task = result._task
task_result = result._result.copy()
task_result.update(on_info)
task_result['action'] = task.action
self.results[-1]['tasks'][-1]['hosts'][host.name] = task_result
end_time = current_time()
self.results[-1]['tasks'][-1]['task']['duration']['end'] = end_time
self.results[-1]['play']['duration']['end'] = end_time
def __getattribute__(self, name):
"""Return ``_record_task_result`` partial with a dict containing skipped/failed if necessary"""
if name not in ('v2_runner_on_ok', 'v2_runner_on_failed', 'v2_runner_on_unreachable', 'v2_runner_on_skipped'):
return object.__getattribute__(self, name)
on = name.rsplit('_', 1)[1]
on_info = {}
if on in ('failed', 'skipped'):
on_info[on] = True
return partial(self._record_task_result, on_info)