"Fossies" - the Fresh Open Source Software Archive 
Member "salt-3002.2/salt/cli/salt.py" (18 Nov 2020, 17333 Bytes) of package /linux/misc/salt-3002.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "salt.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
3002.1_vs_3002.2.
1 import os
2 import sys
3
4 import salt.defaults.exitcodes
5 import salt.log
6 import salt.utils.job
7 import salt.utils.parsers
8 import salt.utils.stringutils
9 from salt.exceptions import (
10 AuthenticationError,
11 AuthorizationError,
12 EauthAuthenticationError,
13 LoaderError,
14 SaltClientError,
15 SaltInvocationError,
16 SaltSystemExit,
17 )
18 from salt.utils.args import yamlify_arg
19 from salt.utils.verify import verify_log
20
21 sys.modules["pkg_resources"] = None
22
23
24 class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
25 """
26 The execution of a salt command happens here
27 """
28
29 def run(self):
30 """
31 Execute the salt command line
32 """
33 import salt.client
34
35 self.parse_args()
36
37 if self.config["log_level"] not in ("quiet",):
38 # Setup file logging!
39 self.setup_logfile_logger()
40 verify_log(self.config)
41
42 try:
43 # We don't need to bail on config file permission errors
44 # if the CLI process is run with the -a flag
45 skip_perm_errors = self.options.eauth != ""
46
47 self.local_client = salt.client.get_local_client(
48 self.get_config_file_path(),
49 skip_perm_errors=skip_perm_errors,
50 auto_reconnect=True,
51 )
52 except SaltClientError as exc:
53 self.exit(2, "{}\n".format(exc))
54 return
55
56 if self.options.batch or self.options.static:
57 # _run_batch() will handle all output and
58 # exit with the appropriate error condition
59 # Execution will not continue past this point
60 # in batch mode.
61 self._run_batch()
62 return
63
64 if self.options.preview_target:
65 minion_list = self._preview_target()
66 self._output_ret(minion_list, self.config.get("output", "nested"))
67 return
68
69 if self.options.timeout <= 0:
70 self.options.timeout = self.local_client.opts["timeout"]
71
72 kwargs = {
73 "tgt": self.config["tgt"],
74 "fun": self.config["fun"],
75 "arg": self.config["arg"],
76 "timeout": self.options.timeout,
77 "show_timeout": self.options.show_timeout,
78 "show_jid": self.options.show_jid,
79 }
80
81 if "token" in self.config:
82 import salt.utils.files
83
84 try:
85 with salt.utils.files.fopen(
86 os.path.join(self.config["cachedir"], ".root_key"), "r"
87 ) as fp_:
88 kwargs["key"] = fp_.readline()
89 except OSError:
90 kwargs["token"] = self.config["token"]
91
92 kwargs["delimiter"] = self.options.delimiter
93
94 if self.selected_target_option:
95 kwargs["tgt_type"] = self.selected_target_option
96 else:
97 kwargs["tgt_type"] = "glob"
98
99 # If batch_safe_limit is set, check minions matching target and
100 # potentially switch to batch execution
101 if self.options.batch_safe_limit > 1:
102 if len(self._preview_target()) >= self.options.batch_safe_limit:
103 salt.utils.stringutils.print_cli(
104 "\nNOTICE: Too many minions targeted, switching to batch execution."
105 )
106 self.options.batch = self.options.batch_safe_size
107 self._run_batch()
108 return
109
110 if getattr(self.options, "return"):
111 kwargs["ret"] = getattr(self.options, "return")
112
113 if getattr(self.options, "return_config"):
114 kwargs["ret_config"] = getattr(self.options, "return_config")
115
116 if getattr(self.options, "return_kwargs"):
117 kwargs["ret_kwargs"] = yamlify_arg(getattr(self.options, "return_kwargs"))
118
119 if getattr(self.options, "module_executors"):
120 kwargs["module_executors"] = yamlify_arg(
121 getattr(self.options, "module_executors")
122 )
123
124 if getattr(self.options, "executor_opts"):
125 kwargs["executor_opts"] = yamlify_arg(
126 getattr(self.options, "executor_opts")
127 )
128
129 if getattr(self.options, "metadata"):
130 kwargs["metadata"] = yamlify_arg(getattr(self.options, "metadata"))
131
132 # If using eauth and a token hasn't already been loaded into
133 # kwargs, prompt the user to enter auth credentials
134 if "token" not in kwargs and "key" not in kwargs and self.options.eauth:
135 # This is expensive. Don't do it unless we need to.
136 import salt.auth
137
138 resolver = salt.auth.Resolver(self.config)
139 res = resolver.cli(self.options.eauth)
140 if self.options.mktoken and res:
141 tok = resolver.token_cli(self.options.eauth, res)
142 if tok:
143 kwargs["token"] = tok.get("token", "")
144 if not res:
145 sys.stderr.write("ERROR: Authentication failed\n")
146 sys.exit(2)
147 kwargs.update(res)
148 kwargs["eauth"] = self.options.eauth
149
150 if self.config["async"]:
151 jid = self.local_client.cmd_async(**kwargs)
152 salt.utils.stringutils.print_cli(
153 "Executed command with job ID: {}".format(jid)
154 )
155 return
156
157 # local will be None when there was an error
158 if not self.local_client:
159 return
160
161 retcodes = []
162 errors = []
163
164 try:
165 if self.options.subset:
166 cmd_func = self.local_client.cmd_subset
167 kwargs["sub"] = self.options.subset
168 kwargs["cli"] = True
169 else:
170 cmd_func = self.local_client.cmd_cli
171
172 if self.options.progress:
173 kwargs["progress"] = True
174 self.config["progress"] = True
175 ret = {}
176 for progress in cmd_func(**kwargs):
177 out = "progress"
178 try:
179 self._progress_ret(progress, out)
180 except LoaderError as exc:
181 raise SaltSystemExit(exc)
182 if "return_count" not in progress:
183 ret.update(progress)
184 self._progress_end(out)
185 self._print_returns_summary(ret)
186 elif self.config["fun"] == "sys.doc":
187 ret = {}
188 out = ""
189 for full_ret in self.local_client.cmd_cli(**kwargs):
190 ret_, out, retcode = self._format_ret(full_ret)
191 ret.update(ret_)
192 self._output_ret(ret, out, retcode=retcode)
193 else:
194 if self.options.verbose:
195 kwargs["verbose"] = True
196 ret = {}
197 for full_ret in cmd_func(**kwargs):
198 try:
199 ret_, out, retcode = self._format_ret(full_ret)
200 retcodes.append(retcode)
201 self._output_ret(ret_, out, retcode=retcode)
202 ret.update(full_ret)
203 except KeyError:
204 errors.append(full_ret)
205
206 # Returns summary
207 if self.config["cli_summary"] is True:
208 if self.config["fun"] != "sys.doc":
209 if self.options.output is None:
210 self._print_returns_summary(ret)
211 self._print_errors_summary(errors)
212
213 # NOTE: Return code is set here based on if all minions
214 # returned 'ok' with a retcode of 0.
215 # This is the final point before the 'salt' cmd returns,
216 # which is why we set the retcode here.
217 if not all(
218 exit_code == salt.defaults.exitcodes.EX_OK for exit_code in retcodes
219 ):
220 sys.stderr.write("ERROR: Minions returned with non-zero exit code\n")
221 sys.exit(salt.defaults.exitcodes.EX_GENERIC)
222
223 except (
224 AuthenticationError,
225 AuthorizationError,
226 SaltInvocationError,
227 EauthAuthenticationError,
228 SaltClientError,
229 ) as exc:
230 ret = str(exc)
231 self._output_ret(ret, "", retcode=1)
232
233 def _preview_target(self):
234 """
235 Return a list of minions from a given target
236 """
237 return self.local_client.gather_minions(
238 self.config["tgt"], self.selected_target_option or "glob"
239 )
240
241 def _run_batch(self):
242 import salt.cli.batch
243
244 eauth = {}
245 if "token" in self.config:
246 eauth["token"] = self.config["token"]
247
248 # If using eauth and a token hasn't already been loaded into
249 # kwargs, prompt the user to enter auth credentials
250 if "token" not in eauth and self.options.eauth:
251 # This is expensive. Don't do it unless we need to.
252 import salt.auth
253
254 resolver = salt.auth.Resolver(self.config)
255 res = resolver.cli(self.options.eauth)
256 if self.options.mktoken and res:
257 tok = resolver.token_cli(self.options.eauth, res)
258 if tok:
259 eauth["token"] = tok.get("token", "")
260 if not res:
261 sys.stderr.write("ERROR: Authentication failed\n")
262 sys.exit(2)
263 eauth.update(res)
264 eauth["eauth"] = self.options.eauth
265
266 if self.options.static:
267
268 if not self.options.batch:
269 self.config["batch"] = "100%"
270
271 try:
272 batch = salt.cli.batch.Batch(self.config, eauth=eauth, quiet=True)
273 except SaltClientError:
274 sys.exit(2)
275
276 ret = {}
277
278 for res in batch.run():
279 ret.update(res)
280
281 self._output_ret(ret, "")
282
283 else:
284 try:
285 self.config["batch"] = self.options.batch
286 batch = salt.cli.batch.Batch(
287 self.config, eauth=eauth, parser=self.options
288 )
289 except SaltClientError:
290 # We will print errors to the console further down the stack
291 sys.exit(1)
292 # Printing the output is already taken care of in run() itself
293 retcode = 0
294 for res in batch.run():
295 for ret in res.values():
296 job_retcode = salt.utils.job.get_retcode(ret)
297 if job_retcode > retcode:
298 # Exit with the highest retcode we find
299 retcode = job_retcode
300 sys.exit(retcode)
301
302 def _print_errors_summary(self, errors):
303 if errors:
304 salt.utils.stringutils.print_cli("\n")
305 salt.utils.stringutils.print_cli("---------------------------")
306 salt.utils.stringutils.print_cli("Errors")
307 salt.utils.stringutils.print_cli("---------------------------")
308 for error in errors:
309 salt.utils.stringutils.print_cli(self._format_error(error))
310
311 def _print_returns_summary(self, ret):
312 """
313 Display returns summary
314 """
315 return_counter = 0
316 not_return_counter = 0
317 not_return_minions = []
318 not_response_minions = []
319 not_connected_minions = []
320 failed_minions = []
321 for each_minion in ret:
322 minion_ret = ret[each_minion]
323 if isinstance(minion_ret, dict) and "ret" in minion_ret:
324 minion_ret = ret[each_minion].get("ret")
325 if isinstance(minion_ret, str) and minion_ret.startswith(
326 "Minion did not return"
327 ):
328 if "Not connected" in minion_ret:
329 not_connected_minions.append(each_minion)
330 elif "No response" in minion_ret:
331 not_response_minions.append(each_minion)
332 not_return_counter += 1
333 not_return_minions.append(each_minion)
334 else:
335 return_counter += 1
336 if self._get_retcode(ret[each_minion]):
337 failed_minions.append(each_minion)
338 salt.utils.stringutils.print_cli("\n")
339 salt.utils.stringutils.print_cli("-------------------------------------------")
340 salt.utils.stringutils.print_cli("Summary")
341 salt.utils.stringutils.print_cli("-------------------------------------------")
342 salt.utils.stringutils.print_cli(
343 "# of minions targeted: {}".format(return_counter + not_return_counter)
344 )
345 salt.utils.stringutils.print_cli(
346 "# of minions returned: {}".format(return_counter)
347 )
348 salt.utils.stringutils.print_cli(
349 "# of minions that did not return: {}".format(not_return_counter)
350 )
351 salt.utils.stringutils.print_cli(
352 "# of minions with errors: {}".format(len(failed_minions))
353 )
354 if self.options.verbose:
355 if not_connected_minions:
356 salt.utils.stringutils.print_cli(
357 "Minions not connected: {}".format(" ".join(not_connected_minions))
358 )
359 if not_response_minions:
360 salt.utils.stringutils.print_cli(
361 "Minions not responding: {}".format(" ".join(not_response_minions))
362 )
363 if failed_minions:
364 salt.utils.stringutils.print_cli(
365 "Minions with failures: {}".format(" ".join(failed_minions))
366 )
367 salt.utils.stringutils.print_cli("-------------------------------------------")
368
369 def _progress_end(self, out):
370 import salt.output
371
372 salt.output.progress_end(self.progress_bar)
373
374 def _progress_ret(self, progress, out):
375 """
376 Print progress events
377 """
378 import salt.output
379
380 # Get the progress bar
381 if not hasattr(self, "progress_bar"):
382 try:
383 self.progress_bar = salt.output.get_progress(self.config, out, progress)
384 except Exception: # pylint: disable=broad-except
385 raise LoaderError(
386 "\nWARNING: Install the `progressbar` python package. "
387 "Requested job was still run but output cannot be displayed.\n"
388 )
389 salt.output.update_progress(self.config, progress, self.progress_bar, out)
390
391 def _output_ret(self, ret, out, retcode=0):
392 """
393 Print the output from a single return to the terminal
394 """
395 import salt.output
396
397 # Handle special case commands
398 if self.config["fun"] == "sys.doc" and not isinstance(ret, Exception):
399 self._print_docs(ret)
400 else:
401 # Determine the proper output method and run it
402 salt.output.display_output(ret, out=out, opts=self.config, _retcode=retcode)
403 if not ret:
404 sys.stderr.write("ERROR: No return received\n")
405 sys.exit(2)
406
407 def _format_ret(self, full_ret):
408 """
409 Take the full return data and format it to simple output
410 """
411 ret = {}
412 out = ""
413 retcode = 0
414 for key, data in full_ret.items():
415 ret[key] = data["ret"]
416 if "out" in data:
417 out = data["out"]
418 ret_retcode = self._get_retcode(data)
419 if ret_retcode > retcode:
420 retcode = ret_retcode
421 return ret, out, retcode
422
423 def _get_retcode(self, ret):
424 """
425 Determine a retcode for a given return
426 """
427 retcode = 0
428 # if there is a dict with retcode, use that
429 if isinstance(ret, dict) and ret.get("retcode", 0) != 0:
430 if isinstance(ret.get("retcode", 0), dict):
431 return max(ret.get("retcode", {0: 0}).values())
432 return ret["retcode"]
433 # if its a boolean, False means 1
434 elif isinstance(ret, bool) and not ret:
435 return 1
436 return retcode
437
438 def _format_error(self, minion_error):
439 for minion, error_doc in minion_error.items():
440 error = "Minion [{}] encountered exception '{}'".format(
441 minion, error_doc["message"]
442 )
443 return error
444
445 def _print_docs(self, ret):
446 """
447 Print out the docstrings for all of the functions on the minions
448 """
449 import salt.output
450
451 docs = {}
452 if not ret:
453 self.exit(2, "No minions found to gather docs from\n")
454 if isinstance(ret, str):
455 self.exit(2, "{}\n".format(ret))
456 for host in ret:
457 if isinstance(ret[host], str) and (
458 ret[host].startswith("Minion did not return")
459 or ret[host] == "VALUE_TRIMMED"
460 ):
461 continue
462 for fun in ret[host]:
463 if fun not in docs and ret[host][fun]:
464 docs[fun] = ret[host][fun]
465 if self.options.output:
466 for fun in sorted(docs):
467 salt.output.display_output({fun: docs[fun]}, "nested", self.config)
468 else:
469 for fun in sorted(docs):
470 salt.utils.stringutils.print_cli("{}:".format(fun))
471 salt.utils.stringutils.print_cli(docs[fun])
472 salt.utils.stringutils.print_cli("")