blob: 2615346a72dc9969272e09800a7ea9d317e06f3d [file] [log] [blame]
Scott Bakerbba67b62019-01-28 17:38:21 -08001#!/usr/bin/env python
2
3# Copyright 2017-present Open Networking Foundation
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Zack Williams5c2ea232019-01-30 15:23:01 -070017from __future__ import absolute_import
18
Scott Bakerbba67b62019-01-28 17:38:21 -080019import json
Zack Williams5c2ea232019-01-30 15:23:01 -070020import os
Scott Bakerbba67b62019-01-28 17:38:21 -080021import uuid
22
23from ansible import constants
Zack Williams5c2ea232019-01-30 15:23:01 -070024from ansible.executor import playbook_executor
25from ansible.inventory.manager import InventoryManager
26from ansible.parsing.dataloader import DataLoader
27from ansible.plugins.callback import CallbackBase
28from ansible.utils.display import Display
29from ansible.vars.manager import VariableManager
30
31from multistructlog import create_logger
32from xosconfig import Config
33
34try:
35 # Python 2: "reload" is built-in
36 # pylint: disable=W1626
37 reload
38except NameError:
39 # Python 3: "reload" is part of importlib
40 from importlib import reload
Scott Bakerbba67b62019-01-28 17:38:21 -080041
42constants = reload(constants)
43
44
45log = create_logger(Config().get("logging"))
46
47
48class ResultCallback(CallbackBase):
49
50 CALLBACK_VERSION = 2.0
51 CALLBACK_NAME = "resultcallback"
52 CALLBACK_TYPE = "programmatic"
53
54 def __init__(self):
55 super(ResultCallback, self).__init__()
56 self.results = []
57 self.uuid = str(uuid.uuid1())
58 self.playbook_status = "OK"
59
60 def v2_playbook_on_start(self, playbook):
61 self.playbook = playbook._file_name
62 log_extra = {
63 "xos_type": "ansible",
64 "ansible_uuid": self.uuid,
65 "ansible_type": "playbook start",
66 "ansible_status": "OK",
67 "ansible_playbook": self.playbook,
68 }
69 log.info("PLAYBOOK START", playbook=self.playbook, **log_extra)
70
71 def v2_playbook_on_stats(self, stats):
72 host_stats = {}
73 for host in stats.processed.keys():
74 host_stats[host] = stats.summarize(host)
75
76 log_extra = {
77 "xos_type": "ansible",
78 "ansible_uuid": self.uuid,
79 "ansible_type": "playbook stats",
80 "ansible_status": self.playbook_status,
81 "ansible_playbook": self.playbook,
82 "ansible_result": json.dumps(host_stats),
83 }
84
85 if self.playbook_status == "OK":
86 log.info("PLAYBOOK END", playbook=self.playbook, **log_extra)
87 else:
88 log.error("PLAYBOOK END", playbook=self.playbook, **log_extra)
89
90 def v2_playbook_on_play_start(self, play):
91 log_extra = {
92 "xos_type": "ansible",
93 "ansible_uuid": self.uuid,
94 "ansible_type": "play start",
95 "ansible_status": self.playbook_status,
96 "ansible_playbook": self.playbook,
97 }
98 log.debug("PLAY START", play_name=play.name, **log_extra)
99
100 def v2_runner_on_ok(self, result, **kwargs):
101 log_extra = {
102 "xos_type": "ansible",
103 "ansible_uuid": self.uuid,
104 "ansible_type": "task",
105 "ansible_status": "OK",
106 "ansible_result": json.dumps(result._result),
107 "ansible_task": result._task,
108 "ansible_playbook": self.playbook,
109 "ansible_host": result._host.get_name(),
110 }
111 log.debug("OK", task=str(result._task), **log_extra)
112 self.results.append(result)
113
114 def v2_runner_on_failed(self, result, **kwargs):
115 self.playbook_status = "FAILED"
116 log_extra = {
117 "xos_type": "ansible",
118 "ansible_uuid": self.uuid,
119 "ansible_type": "task",
120 "ansible_status": "FAILED",
121 "ansible_result": json.dumps(result._result),
122 "ansible_task": result._task,
123 "ansible_playbook": self.playbook,
124 "ansible_host": result._host.get_name(),
125 }
126 log.error("FAILED", task=str(result._task), **log_extra)
127 self.results.append(result)
128
129 def v2_runner_on_async_failed(self, result, **kwargs):
130 self.playbook_status = "FAILED"
131 log_extra = {
132 "xos_type": "ansible",
133 "ansible_uuid": self.uuid,
134 "ansible_type": "task",
135 "ansible_status": "ASYNC FAILED",
136 "ansible_result": json.dumps(result._result),
137 "ansible_task": result._task,
138 "ansible_playbook": self.playbook,
139 "ansible_host": result._host.get_name(),
140 }
141 log.error("ASYNC FAILED", task=str(result._task), **log_extra)
142
143 def v2_runner_on_skipped(self, result, **kwargs):
144 log_extra = {
145 "xos_type": "ansible",
146 "ansible_uuid": self.uuid,
147 "ansible_type": "task",
148 "ansible_status": "SKIPPED",
149 "ansible_result": json.dumps(result._result),
150 "ansible_task": result._task,
151 "ansible_playbook": self.playbook,
152 "ansible_host": result._host.get_name(),
153 }
154 log.debug("SKIPPED", task=str(result._task), **log_extra)
155 self.results.append(result)
156
157 def v2_runner_on_unreachable(self, result, **kwargs):
158 log_extra = {
159 "xos_type": "ansible",
160 "ansible_uuid": self.uuid,
161 "ansible_type": "task",
162 "ansible_status": "UNREACHABLE",
163 "ansible_result": json.dumps(result._result),
164 "ansible_task": result._task,
165 "ansible_playbook": self.playbook,
166 "ansible_host": result._host.get_name(),
167 }
168 log.error("UNREACHABLE", task=str(result._task), **log_extra)
169 self.results.append(result)
170
171 def v2_runner_retry(self, result, **kwargs):
172 log_extra = {
173 "xos_type": "ansible",
174 "ansible_uuid": self.uuid,
175 "ansible_type": "task",
176 "ansible_status": "RETRY",
177 "ansible_result": json.dumps(result._result),
178 "ansible_task": result._task,
179 "ansible_playbook": self.playbook,
180 "ansible_host": result._host.get_name(),
181 }
182 log.warning(
183 "RETRYING - attempt",
184 task=str(result._task),
185 attempt=result._result["attempts"],
186 **log_extra
187 )
188 self.results.append(result)
189
190 def v2_playbook_on_handler_task_start(self, task, **kwargs):
191 log_extra = {
192 "xos_type": "ansible",
193 "ansible_uuid": self.uuid,
194 "ansible_type": "task",
195 "ansible_status": "HANDLER",
196 "ansible_task": task.get_name().strip(),
197 "ansible_playbook": self.playbook,
198 # 'ansible_host': result._host.get_name()
199 }
200 log.debug("HANDLER", task=task.get_name().strip(), **log_extra)
201
202 def v2_playbook_on_import_for_host(self, result, imported_file):
203 log_extra = {
204 "xos_type": "ansible",
205 "ansible_uuid": self.uuid,
206 "ansible_type": "import",
207 "ansible_status": "IMPORT",
208 "ansible_result": json.dumps(result._result),
209 "ansible_playbook": self.playbook,
210 "ansible_host": result._host.get_name(),
211 }
212 log.debug("IMPORT", imported_file=imported_file, **log_extra)
213 self.results.append(result)
214
215 def v2_playbook_on_not_import_for_host(self, result, missing_file):
216 log_extra = {
217 "xos_type": "ansible",
218 "ansible_uuid": self.uuid,
219 "ansible_type": "import",
220 "ansible_status": "MISSING IMPORT",
221 "ansible_result": json.dumps(result._result),
222 "ansible_playbook": self.playbook,
223 "ansible_host": result._host.get_name(),
224 }
225 log.debug("MISSING IMPORT", missing=missing_file, **log_extra)
226 self.results.append(result)
227
228
229class Options(object):
230 """
231 Options class to replace Ansible OptParser
232 """
233
234 def __init__(
235 self,
236 ask_pass=None,
237 ask_su_pass=None,
238 ask_sudo_pass=None,
239 become=None,
240 become_ask_pass=None,
241 become_method=None,
242 become_user=None,
243 check=None,
244 connection=None,
245 diff=None,
246 flush_cache=None,
247 force_handlers=None,
248 forks=1,
249 listtags=None,
250 listtasks=None,
251 module_path=None,
252 new_vault_password_file=None,
253 one_line=None,
254 output_file=None,
255 poll_interval=None,
256 private_key_file=None,
257 remote_user=None,
258 scp_extra_args=None,
259 seconds=None,
260 sftp_extra_args=None,
261 skip_tags=None,
262 ssh_common_args=None,
263 ssh_extra_args=None,
264 sudo=None,
265 sudo_user=None,
266 syntax=None,
267 tags=None,
268 timeout=None,
269 tree=None,
270 vault_password_files=None,
271 ask_vault_pass=None,
272 extra_vars=None,
273 inventory=None,
274 listhosts=None,
275 module_paths=None,
276 subset=None,
277 verbosity=None,
278 ):
279
280 if tags:
281 self.tags = tags
282
283 if skip_tags:
284 self.skip_tags = skip_tags
285
286 self.ask_pass = ask_pass
287 self.ask_su_pass = ask_su_pass
288 self.ask_sudo_pass = ask_sudo_pass
289 self.ask_vault_pass = ask_vault_pass
290 self.become = become
291 self.become_ask_pass = become_ask_pass
292 self.become_method = become_method
293 self.become_user = become_user
294 self.check = check
295 self.connection = connection
296 self.diff = diff
297 self.extra_vars = extra_vars
298 self.flush_cache = flush_cache
299 self.force_handlers = force_handlers
300 self.forks = forks
301 self.inventory = inventory
302 self.listhosts = listhosts
303 self.listtags = listtags
304 self.listtasks = listtasks
305 self.module_path = module_path
306 self.module_paths = module_paths
307 self.new_vault_password_file = new_vault_password_file
308 self.one_line = one_line
309 self.output_file = output_file
310 self.poll_interval = poll_interval
311 self.private_key_file = private_key_file
312 self.remote_user = remote_user
313 self.scp_extra_args = scp_extra_args
314 self.seconds = seconds
315 self.sftp_extra_args = sftp_extra_args
316 self.ssh_common_args = ssh_common_args
317 self.ssh_extra_args = ssh_extra_args
318 self.subset = subset
319 self.sudo = sudo
320 self.sudo_user = sudo_user
321 self.syntax = syntax
322 self.timeout = timeout
323 self.tree = tree
324 self.vault_password_files = vault_password_files
325 self.verbosity = verbosity
326
327
328class Runner(object):
329 def __init__(
330 self, playbook, run_data, private_key_file=None, verbosity=0, host_file=None
331 ):
332
333 self.playbook = playbook
334 self.run_data = run_data
335
336 self.options = Options()
337 self.options.output_file = playbook + ".result"
338 self.options.private_key_file = private_key_file
339 self.options.verbosity = verbosity
340 self.options.connection = "ssh" # Need a connection type "smart" or "ssh"
341 # self.options.become = True
342 self.options.become_method = "sudo"
343 self.options.become_user = "root"
344
345 # Set global verbosity
346 self.display = Display()
347 self.display.verbosity = self.options.verbosity
348 # Executor appears to have it's own
349 # verbosity object/setting as well
350 playbook_executor.verbosity = self.options.verbosity
351
352 # Become Pass Needed if not logging in as user root
353 # passwords = {'become_pass': become_pass}
354
355 # Gets data from YAML/JSON files
356 self.loader = DataLoader()
357 try:
358 self.loader.set_vault_password(os.environ["VAULT_PASS"])
359 except AttributeError:
360 pass
361
362 # Set inventory, using most of above objects
363 if host_file:
364 self.inventory = InventoryManager(loader=self.loader, sources=host_file)
365 else:
366 self.inventory = InventoryManager(loader=self.loader)
367
368 # All the variables from all the various places
369 self.variable_manager = VariableManager(
370 loader=self.loader, inventory=self.inventory
371 )
372 self.variable_manager.extra_vars = {} # self.run_data
373
374 # Setup playbook executor, but don't run until run() called
375 self.pbex = playbook_executor.PlaybookExecutor(
376 playbooks=[playbook],
377 inventory=self.inventory,
378 variable_manager=self.variable_manager,
379 loader=self.loader,
380 options=self.options,
381 passwords={},
382 )
383
384 def run(self):
385 os.environ[
386 "REQUESTS_CA_BUNDLE"
387 ] = "/usr/local/share/ca-certificates/local_certs.crt"
388 callback = ResultCallback()
389 self.pbex._tqm._stdout_callback = callback
390
391 self.pbex.run()
392 stats = self.pbex._tqm._stats
393
394 # os.remove(self.hosts.name)
395
396 return stats, callback.results