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