"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("")