-
Notifications
You must be signed in to change notification settings - Fork 361
/
Copy pathawx_display.py
819 lines (710 loc) · 30 KB
/
awx_display.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=W0212
from __future__ import (absolute_import, division, print_function)
# Python
import json
import stat
import multiprocessing
import threading
import base64
import functools
import collections
import contextlib
import datetime
import os
import sys
import uuid
from copy import copy
# Ansible
from ansible import constants as C
from ansible.plugins.callback import CallbackBase
from ansible.plugins.loader import callback_loader
from ansible.utils.display import Display
DOCUMENTATION = '''
callback: awx_display
short_description: Playbook event dispatcher for ansible-runner
version_added: "2.0"
description:
- This callback is necessary for ansible-runner to work
type: stdout
extends_documentation_fragment:
- default_callback
requirements:
- Set as stdout in config
'''
IS_ADHOC = os.getenv('AD_HOC_COMMAND_ID') or False
# Dynamically construct base classes for our callback module, to support custom stdout callbacks.
if os.getenv('ORIGINAL_STDOUT_CALLBACK'):
default_stdout_callback = os.getenv('ORIGINAL_STDOUT_CALLBACK')
elif IS_ADHOC:
default_stdout_callback = 'minimal'
else:
default_stdout_callback = 'default'
DefaultCallbackModule: CallbackBase = callback_loader.get(default_stdout_callback).__class__
CENSORED = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
def current_time():
return datetime.datetime.now(datetime.timezone.utc)
# use a custom JSON serializer so we can properly handle !unsafe and !vault
# objects that may exist in events emitted by the callback plugin
# see: https://github.com/ansible/ansible/pull/38759
class AnsibleJSONEncoderLocal(json.JSONEncoder):
'''
The class AnsibleJSONEncoder exists in Ansible core for this function
this performs a mostly identical function via duck typing
'''
def default(self, o):
'''
Returns JSON-valid representation for special Ansible python objects
which including vault objects and datetime objects
'''
if getattr(o, 'yaml_tag', None) == '!vault':
encrypted_form = o._ciphertext
if isinstance(encrypted_form, bytes):
encrypted_form = encrypted_form.decode('utf-8')
return {'__ansible_vault': encrypted_form}
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
return super().default(o)
class IsolatedFileWrite:
'''
Class that will write partial event data to a file
'''
def __init__(self):
self.private_data_dir = os.getenv('AWX_ISOLATED_DATA_DIR')
def set(self, key, value):
# Strip off the leading key identifying characters :1:ev-
event_uuid = key[len(':1:ev-'):]
# Write data in a staging area and then atomic move to pickup directory
filename = f"{event_uuid}-partial.json"
if not os.path.exists(os.path.join(self.private_data_dir, 'job_events')):
os.mkdir(os.path.join(self.private_data_dir, 'job_events'), 0o700)
dropoff_location = os.path.join(self.private_data_dir, 'job_events', filename)
write_location = '.'.join([dropoff_location, 'tmp'])
partial_data = json.dumps(value, cls=AnsibleJSONEncoderLocal)
with os.fdopen(os.open(write_location, os.O_WRONLY | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR), 'w') as f:
f.write(partial_data)
os.rename(write_location, dropoff_location)
class EventContext:
'''
Store global and local (per thread/process) data associated with callback
events and other display output methods.
'''
def __init__(self):
self.display_lock = multiprocessing.RLock()
self._global_ctx = {}
self._local = threading.local()
if os.getenv('AWX_ISOLATED_DATA_DIR'):
self.cache = IsolatedFileWrite()
def add_local(self, **kwargs):
tls = vars(self._local)
ctx = tls.setdefault('_ctx', {})
ctx.update(kwargs)
def remove_local(self, **kwargs):
for key in kwargs:
self._local._ctx.pop(key, None)
@contextlib.contextmanager
def set_local(self, **kwargs):
try:
self.add_local(**kwargs)
yield
finally:
self.remove_local(**kwargs)
def get_local(self):
return getattr(getattr(self, '_local', None), '_ctx', {})
def add_global(self, **kwargs):
self._global_ctx.update(kwargs)
def remove_global(self, **kwargs):
for key in kwargs:
self._global_ctx.pop(key, None)
@contextlib.contextmanager
def set_global(self, **kwargs):
try:
self.add_global(**kwargs)
yield
finally:
self.remove_global(**kwargs)
def get_global(self):
return self._global_ctx
def get(self):
ctx = {}
ctx.update(self.get_global())
ctx.update(self.get_local())
return ctx
def get_begin_dict(self):
omit_event_data = os.getenv("RUNNER_OMIT_EVENTS", "False").lower() == "true"
include_only_failed_event_data = os.getenv("RUNNER_ONLY_FAILED_EVENTS", "False").lower() == "true"
event_data = self.get()
event = event_data.pop('event', None)
if not event:
event = 'verbose'
for key in ('debug', 'verbose', 'deprecated', 'warning', 'system_warning', 'error'):
if event_data.get(key, False):
event = key
break
event_dict = {'event': event}
should_process_event_data = (include_only_failed_event_data and event in ('runner_on_failed', 'runner_on_async_failed', 'runner_on_item_failed')) \
or not include_only_failed_event_data
if os.getenv('JOB_ID', ''):
event_dict['job_id'] = int(os.getenv('JOB_ID', '0'))
if os.getenv('AD_HOC_COMMAND_ID', ''):
event_dict['ad_hoc_command_id'] = int(os.getenv('AD_HOC_COMMAND_ID', '0'))
if os.getenv('PROJECT_UPDATE_ID', ''):
event_dict['project_update_id'] = int(os.getenv('PROJECT_UPDATE_ID', '0'))
event_dict['pid'] = event_data.get('pid', os.getpid())
event_dict['uuid'] = event_data.get('uuid', str(uuid.uuid4()))
event_dict['created'] = event_data.get('created', current_time().isoformat())
if not event_data.get('parent_uuid', None):
for key in ('task_uuid', 'play_uuid', 'playbook_uuid'):
parent_uuid = event_data.get(key, None)
if parent_uuid and parent_uuid != event_data.get('uuid', None):
event_dict['parent_uuid'] = parent_uuid
break
else:
event_dict['parent_uuid'] = event_data.get('parent_uuid', None)
if "verbosity" in event_data:
event_dict["verbosity"] = event_data.pop("verbosity")
if not omit_event_data and should_process_event_data:
max_res = int(os.getenv("MAX_EVENT_RES", "700000"))
if event not in ('playbook_on_stats',) and "res" in event_data and len(str(event_data['res'])) > max_res:
event_data['res'] = {}
else:
event_data = {}
event_dict['event_data'] = event_data
return event_dict
def get_end_dict(self):
return {}
def dump(self, fileobj, data, max_width=78, flush=False):
b64data = base64.b64encode(json.dumps(data).encode('utf-8')).decode()
with self.display_lock:
# pattern corresponding to OutputEventFilter expectation
fileobj.write('\x1b[K')
for offset in range(0, len(b64data), max_width):
chunk = b64data[offset:offset + max_width]
escaped_chunk = f'{chunk}\x1b[{len(chunk)}D'
fileobj.write(escaped_chunk)
fileobj.write('\x1b[K')
if flush:
fileobj.flush()
def dump_begin(self, fileobj):
begin_dict = self.get_begin_dict()
self.cache.set(f":1:ev-{begin_dict['uuid']}", begin_dict)
self.dump(fileobj, {'uuid': begin_dict['uuid']})
def dump_end(self, fileobj):
self.dump(fileobj, self.get_end_dict(), flush=True)
event_context = EventContext()
def with_context(**context):
global event_context # pylint: disable=W0602
def wrap(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
with event_context.set_local(**context):
return f(*args, **kwargs)
return wrapper
return wrap
for attr in dir(Display):
if attr.startswith('_') or 'cow' in attr or 'prompt' in attr:
continue
if attr in ('display', 'v', 'vv', 'vvv', 'vvvv', 'vvvvv', 'vvvvvv', 'verbose'):
continue
if not callable(getattr(Display, attr)):
continue
setattr(Display, attr, with_context(**{attr: True})(getattr(Display, attr)))
def with_verbosity(f):
global event_context # pylint: disable=W0602
@functools.wraps(f)
def wrapper(*args, **kwargs):
host = args[2] if len(args) >= 3 else kwargs.get('host', None)
caplevel = args[3] if len(args) >= 4 else kwargs.get('caplevel', 2)
context = {'verbose': True, 'verbosity': (caplevel + 1)}
if host is not None:
context['remote_addr'] = host
with event_context.set_local(**context):
return f(*args, **kwargs)
return wrapper
Display.verbose = with_verbosity(Display.verbose)
def display_with_context(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
log_only = args[5] if len(args) >= 6 else kwargs.get('log_only', False)
stderr = args[3] if len(args) >= 4 else kwargs.get('stderr', False)
event_uuid = event_context.get().get('uuid', None)
with event_context.display_lock:
# If writing only to a log file or there is already an event UUID
# set (from a callback module method), skip dumping the event data.
if log_only or event_uuid:
return f(*args, **kwargs)
try:
fileobj = sys.stderr if stderr else sys.stdout
event_context.add_local(uuid=str(uuid.uuid4()))
event_context.dump_begin(fileobj)
return f(*args, **kwargs)
finally:
event_context.dump_end(fileobj)
event_context.remove_local(uuid=None)
return wrapper
Display.display = display_with_context(Display.display)
class CallbackModule(DefaultCallbackModule):
'''
Callback module for logging ansible/ansible-playbook events.
'''
CALLBACK_NAME = 'awx_display'
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
# These events should never have an associated play.
EVENTS_WITHOUT_PLAY = [
'playbook_on_start',
'playbook_on_stats',
]
# These events should never have an associated task.
EVENTS_WITHOUT_TASK = EVENTS_WITHOUT_PLAY + [
'playbook_on_setup',
'playbook_on_notify',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
]
def __init__(self):
super().__init__()
self._host_start = {}
self.task_uuids = set()
self.duplicate_task_counts = collections.defaultdict(lambda: 1)
self.play_uuids = set()
self.duplicate_play_counts = collections.defaultdict(lambda: 1)
# NOTE: Ansible doesn't generate a UUID for playbook_on_start so do it for them.
self.playbook_uuid = str(uuid.uuid4())
@contextlib.contextmanager
def capture_event_data(self, event, **event_data):
event_data.setdefault('uuid', str(uuid.uuid4()))
if event not in self.EVENTS_WITHOUT_TASK:
task = event_data.pop('task', None)
else:
task = None
if event_data.get('res'):
if event_data['res'].get('_ansible_no_log', False):
event_data['res'] = {'censored': CENSORED}
if event_data['res'].get('results', []):
event_data['res']['results'] = copy(event_data['res']['results'])
for i, item in enumerate(event_data['res'].get('results', [])):
if isinstance(item, dict) and item.get('_ansible_no_log', False):
event_data['res']['results'][i] = {'censored': CENSORED}
with event_context.display_lock:
try:
event_context.add_local(event=event, **event_data)
if task:
self.set_task(task, local=True)
event_context.dump_begin(sys.stdout)
yield
finally:
event_context.dump_end(sys.stdout)
if task:
self.clear_task(local=True)
event_context.remove_local(event=None, **event_data)
def set_playbook(self, playbook):
file_name = getattr(playbook, '_file_name', '???')
event_context.add_global(playbook=file_name, playbook_uuid=self.playbook_uuid)
self.clear_play()
def set_play(self, play):
if hasattr(play, 'hosts'):
if isinstance(play.hosts, list):
pattern = ','.join(play.hosts)
else:
pattern = play.hosts
else:
pattern = ''
name = play.get_name().strip() or pattern
event_context.add_global(play=name, play_uuid=str(play._uuid), play_pattern=pattern)
self.clear_task()
def clear_play(self):
event_context.remove_global(play=None, play_uuid=None, play_pattern=None)
self.clear_task()
def set_task(self, task, local=False):
self.clear_task(local)
# FIXME: Task is "global" unless using free strategy!
task_ctx = {
'task': (task.name or task.action),
'task_uuid': str(task._uuid),
'task_action': task.action,
'resolved_action': getattr(task, 'resolved_action', task.action),
'task_args': '',
}
try:
task_ctx['task_path'] = task.get_path()
except AttributeError:
pass
if C.DISPLAY_ARGS_TO_STDOUT: # pylint: disable=E1101
if task.no_log:
task_ctx['task_args'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
else:
task_args = ', '.join((f'{k}={v}' for k, v in task.args.items()))
task_ctx['task_args'] = task_args
if getattr(task, '_role', None):
task_role = task._role._role_name
if hasattr(task._role, 'get_name'):
resolved_role = task._role.get_name()
if resolved_role != task_role:
task_ctx['resolved_role'] = resolved_role
else:
task_role = getattr(task, 'role_name', '')
if task_role:
task_ctx['role'] = task_role
if local:
event_context.add_local(**task_ctx)
else:
event_context.add_global(**task_ctx)
def clear_task(self, local=False):
task_ctx = {
'task': None, 'task_path': None, 'task_uuid': None, 'task_action': None, 'task_args': None, 'resolved_action': None,
'role': None, 'resolved_role': None
}
if local:
event_context.remove_local(**task_ctx)
else:
event_context.remove_global(**task_ctx)
def v2_playbook_on_start(self, playbook):
self.set_playbook(playbook)
event_data = {
'uuid': self.playbook_uuid,
}
with self.capture_event_data('playbook_on_start', **event_data):
super().v2_playbook_on_start(playbook)
def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None,
encrypt=None, confirm=False, salt_size=None,
salt=None, default=None, unsafe=None):
event_data = {
'varname': varname,
'private': private,
'prompt': prompt,
'encrypt': encrypt,
'confirm': confirm,
'salt_size': salt_size,
'salt': salt,
'default': default,
'unsafe': unsafe,
}
with self.capture_event_data('playbook_on_vars_prompt', **event_data):
super().v2_playbook_on_vars_prompt(
varname, private, prompt, encrypt, confirm, salt_size, salt,
default,
)
def v2_playbook_on_include(self, included_file):
event_data = {
'included_file': included_file._filename if included_file is not None else None,
}
with self.capture_event_data('playbook_on_include', **event_data):
super().v2_playbook_on_include(included_file)
def v2_playbook_on_play_start(self, play):
if IS_ADHOC:
return
play_uuid = str(play._uuid)
if play_uuid in self.play_uuids:
# When this play UUID repeats, it means the play is using the
# free strategy (or serial:1) so different hosts may be running
# different tasks within a play (where duplicate UUIDS are common).
#
# When this is the case, modify the UUID slightly to append
# a counter so we can still _track_ duplicate events, but also
# avoid breaking the display in these scenarios.
self.duplicate_play_counts[play_uuid] += 1
play_uuid = '_'.join([
play_uuid,
str(self.duplicate_play_counts[play_uuid])
])
self.play_uuids.add(play_uuid)
play._uuid = play_uuid
self.set_play(play)
if hasattr(play, 'hosts'):
if isinstance(play.hosts, list):
pattern = ','.join(play.hosts)
else:
pattern = play.hosts
else:
pattern = ''
name = play.get_name().strip() or pattern
event_data = {
'name': name,
'pattern': pattern,
'uuid': str(play._uuid),
}
with self.capture_event_data('playbook_on_play_start', **event_data):
super().v2_playbook_on_play_start(play)
def v2_playbook_on_import_for_host(self, result, imported_file):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_import_for_host'):
super().v2_playbook_on_import_for_host(result, imported_file)
def v2_playbook_on_not_import_for_host(self, result, missing_file):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_not_import_for_host'):
super().v2_playbook_on_not_import_for_host(result, missing_file)
def v2_playbook_on_setup(self):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_setup'):
super().v2_playbook_on_setup()
def v2_playbook_on_task_start(self, task, is_conditional):
if IS_ADHOC:
self.set_task(task)
return
# FIXME: Flag task path output as vv.
task_uuid = str(task._uuid)
if task_uuid in self.task_uuids:
# When this task UUID repeats, it means the play is using the
# free strategy (or serial:1) so different hosts may be running
# different tasks within a play (where duplicate UUIDS are common).
#
# When this is the case, modify the UUID slightly to append
# a counter so we can still _track_ duplicate events, but also
# avoid breaking the display in these scenarios.
self.duplicate_task_counts[task_uuid] += 1
task_uuid = '_'.join([
task_uuid,
str(self.duplicate_task_counts[task_uuid])
])
self.task_uuids.add(task_uuid)
self.set_task(task)
event_data = {
'task': task,
'name': task.get_name(),
'is_conditional': is_conditional,
'uuid': task_uuid,
}
with self.capture_event_data('playbook_on_task_start', **event_data):
super().v2_playbook_on_task_start(task, is_conditional)
def v2_playbook_on_cleanup_task_start(self, task):
# NOTE: Not used by Ansible 2.x.
self.set_task(task)
event_data = {
'task': task,
'name': task.get_name(),
'uuid': str(task._uuid),
'is_conditional': True,
}
with self.capture_event_data('playbook_on_task_start', **event_data):
super().v2_playbook_on_cleanup_task_start(task)
def v2_playbook_on_handler_task_start(self, task):
# NOTE: Re-using playbook_on_task_start event for this v2-specific
# event, but setting is_conditional=True, which is how v1 identified a
# task run as a handler.
self.set_task(task)
event_data = {
'task': task,
'name': task.get_name(),
'uuid': str(task._uuid),
'is_conditional': True,
}
with self.capture_event_data('playbook_on_task_start', **event_data):
super().v2_playbook_on_handler_task_start(task)
def v2_playbook_on_no_hosts_matched(self):
with self.capture_event_data('playbook_on_no_hosts_matched'):
super().v2_playbook_on_no_hosts_matched()
def v2_playbook_on_no_hosts_remaining(self):
with self.capture_event_data('playbook_on_no_hosts_remaining'):
super().v2_playbook_on_no_hosts_remaining()
def v2_playbook_on_notify(self, handler, host):
# NOTE: Not used by Ansible < 2.5.
event_data = {
'host': host.get_name(),
'handler': handler.get_name(),
}
with self.capture_event_data('playbook_on_notify', **event_data):
super().v2_playbook_on_notify(handler, host)
# ansible_stats is, retroactively, added in 2.2
def v2_playbook_on_stats(self, stats):
self.clear_play()
# FIXME: Add count of plays/tasks.
event_data = {
'changed': stats.changed,
'dark': stats.dark,
'failures': stats.failures,
'ignored': getattr(stats, 'ignored', 0),
'ok': stats.ok,
'processed': stats.processed,
'rescued': getattr(stats, 'rescued', 0),
'skipped': stats.skipped,
'artifact_data': stats.custom.get('_run', {}) if hasattr(stats, 'custom') else {}
}
with self.capture_event_data('playbook_on_stats', **event_data):
super().v2_playbook_on_stats(stats)
@staticmethod
def _get_event_loop(task):
if hasattr(task, 'loop_with'): # Ansible >=2.5
return task.loop_with
if hasattr(task, 'loop'): # Ansible <2.4
return task.loop
return None
def _get_result_timing_data(self, result):
host_start = self._host_start.get(result._host.get_name())
if host_start:
end_time = current_time()
return host_start, end_time, (end_time - host_start).total_seconds()
return None, None, None
def v2_runner_on_ok(self, result):
# FIXME: Display detailed results or not based on verbosity.
# strip environment vars from the job event; it already exists on the
# job and sensitive values are filtered there
if result._task.action in ('setup', 'gather_facts'):
result._result.get('ansible_facts', {}).pop('ansible_env', None)
host_start, end_time, duration = self._get_result_timing_data(result)
event_data = {
'host': result._host.get_name(),
'remote_addr': result._host.address,
'task': result._task,
'res': result._result,
'start': host_start,
'end': end_time,
'duration': duration,
'event_loop': self._get_event_loop(result._task),
}
with self.capture_event_data('runner_on_ok', **event_data):
super().v2_runner_on_ok(result)
def v2_runner_on_failed(self, result, ignore_errors=False):
# FIXME: Add verbosity for exception/results output.
host_start, end_time, duration = self._get_result_timing_data(result)
event_data = {
'host': result._host.get_name(),
'remote_addr': result._host.address,
'res': result._result,
'task': result._task,
'start': host_start,
'end': end_time,
'duration': duration,
'ignore_errors': ignore_errors,
'event_loop': self._get_event_loop(result._task),
}
with self.capture_event_data('runner_on_failed', **event_data):
super().v2_runner_on_failed(result, ignore_errors)
def v2_runner_on_skipped(self, result):
host_start, end_time, duration = self._get_result_timing_data(result)
event_data = {
'host': result._host.get_name(),
'remote_addr': result._host.address,
'task': result._task,
'start': host_start,
'end': end_time,
'duration': duration,
'event_loop': self._get_event_loop(result._task),
}
with self.capture_event_data('runner_on_skipped', **event_data):
super().v2_runner_on_skipped(result)
def v2_runner_on_unreachable(self, result):
host_start, end_time, duration = self._get_result_timing_data(result)
event_data = {
'host': result._host.get_name(),
'remote_addr': result._host.address,
'task': result._task,
'start': host_start,
'end': end_time,
'duration': duration,
'res': result._result,
}
with self.capture_event_data('runner_on_unreachable', **event_data):
super().v2_runner_on_unreachable(result)
def v2_runner_on_no_hosts(self, task):
# NOTE: Not used by Ansible 2.x.
event_data = {
'task': task,
}
with self.capture_event_data('runner_on_no_hosts', **event_data):
super().v2_runner_on_no_hosts(task)
def v2_runner_on_async_poll(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
'jid': result._result.get('ansible_job_id'),
}
with self.capture_event_data('runner_on_async_poll', **event_data):
super().v2_runner_on_async_poll(result)
def v2_runner_on_async_ok(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
'jid': result._result.get('ansible_job_id'),
}
with self.capture_event_data('runner_on_async_ok', **event_data):
super().v2_runner_on_async_ok(result)
def v2_runner_on_async_failed(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
'jid': result._result.get('ansible_job_id'),
}
with self.capture_event_data('runner_on_async_failed', **event_data):
super().v2_runner_on_async_failed(result)
def v2_runner_on_file_diff(self, result, diff):
# NOTE: Not used by Ansible 2.x.
event_data = {
'host': result._host.get_name(),
'task': result._task,
'diff': diff,
}
with self.capture_event_data('runner_on_file_diff', **event_data):
super().v2_runner_on_file_diff(result, diff)
def v2_on_file_diff(self, result):
# NOTE: Logged as runner_on_file_diff.
event_data = {
'host': result._host.get_name(),
'task': result._task,
'diff': result._result.get('diff'),
}
with self.capture_event_data('runner_on_file_diff', **event_data):
super().v2_on_file_diff(result)
def v2_runner_item_on_ok(self, result):
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
}
with self.capture_event_data('runner_item_on_ok', **event_data):
super().v2_runner_item_on_ok(result)
def v2_runner_item_on_failed(self, result):
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
}
with self.capture_event_data('runner_item_on_failed', **event_data):
super().v2_runner_item_on_failed(result)
def v2_runner_item_on_skipped(self, result):
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
}
with self.capture_event_data('runner_item_on_skipped', **event_data):
super().v2_runner_item_on_skipped(result)
def v2_runner_retry(self, result):
event_data = {
'host': result._host.get_name(),
'task': result._task,
'res': result._result,
}
with self.capture_event_data('runner_retry', **event_data):
super().v2_runner_retry(result)
def v2_runner_on_start(self, host, task):
event_data = {
'host': host.get_name(),
'task': task
}
self._host_start[host.get_name()] = current_time()
with self.capture_event_data('runner_on_start', **event_data):
super().v2_runner_on_start(host, task)