­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ """ :codeauthor: Pedro Algarvio (pedro@algarvio.me) salt.utils.parsers ~~~~~~~~~~~~~~~~~~ This is where all the black magic happens on all of salt's CLI tools. """ # pylint: disable=missing-docstring,protected-access,too-many-ancestors,too-few-public-methods # pylint: disable=attribute-defined-outside-init,no-self-use import copy import getpass import logging import optparse # pylint: disable=deprecated-module import os import signal import sys import traceback import types from functools import partial import salt._logging import salt.config as config import salt.defaults.exitcodes import salt.exceptions import salt.syspaths as syspaths import salt.utils.args import salt.utils.data import salt.utils.files import salt.utils.jid import salt.utils.platform import salt.utils.process import salt.utils.stringutils import salt.utils.user import salt.utils.win_functions import salt.utils.xdg import salt.utils.yaml import salt.version as version from salt.defaults import DEFAULT_TARGET_DELIM from salt.utils.validate.path import is_writeable from salt.utils.verify import insecure_log, verify_log, verify_log_files log = logging.getLogger(__name__) def _sorted(mixins_or_funcs): return sorted(mixins_or_funcs, key=lambda mf: getattr(mf, "_mixin_prio_", 1000)) class MixinFuncsContainer(list): def append(self, func): if isinstance(func, types.MethodType): # We only care about unbound methods func = func.__func__ if func not in self: # And no duplicates please list.append(self, func) class MixInMeta(type): # This attribute here won't actually do anything. But, if you need to # specify an order or a dependency within the mix-ins, please define the # attribute on your own MixIn _mixin_prio_ = 0 def __new__(mcs, name, bases, attrs): instance = super().__new__(mcs, name, bases, attrs) if not hasattr(instance, "_mixin_setup"): raise RuntimeError( "Don't subclass {} in {} if you're not going " "to use it as a salt parser mix-in.".format(mcs.__name__, name) ) return instance class OptionParserMeta(MixInMeta): def __new__(mcs, name, bases, attrs): instance = super().__new__(mcs, name, bases, attrs) if not hasattr(instance, "_mixin_setup_funcs"): instance._mixin_setup_funcs = MixinFuncsContainer() if not hasattr(instance, "_mixin_process_funcs"): instance._mixin_process_funcs = MixinFuncsContainer() if not hasattr(instance, "_mixin_after_parsed_funcs"): instance._mixin_after_parsed_funcs = MixinFuncsContainer() if not hasattr(instance, "_mixin_before_exit_funcs"): instance._mixin_before_exit_funcs = MixinFuncsContainer() for base in _sorted(bases + (instance,)): func = getattr(base, "_mixin_setup", None) if func is not None and func not in instance._mixin_setup_funcs: instance._mixin_setup_funcs.append(func) func = getattr(base, "_mixin_after_parsed", None) if func is not None and func not in instance._mixin_after_parsed_funcs: instance._mixin_after_parsed_funcs.append(func) func = getattr(base, "_mixin_before_exit", None) if func is not None and func not in instance._mixin_before_exit_funcs: instance._mixin_before_exit_funcs.append(func) # Mark process_ functions with the base priority for sorting for func in dir(base): if not func.startswith("process_"): continue func = getattr(base, func) if getattr(func, "_mixin_prio_", None) is not None: # Function already has the attribute set, don't override it continue func._mixin_prio_ = getattr(base, "_mixin_prio_", 1000) return instance class CustomOption(optparse.Option): def take_action( self, action, dest, *args, **kwargs ): # pylint: disable=arguments-differ # see https://github.com/python/cpython/blob/master/Lib/optparse.py#L786 self.explicit = True return optparse.Option.take_action(self, action, dest, *args, **kwargs) class OptionParser(optparse.OptionParser): VERSION = version.__saltstack_version__.formatted_version usage = "%prog [options]" epilog = ( 'You can find additional help about %prog issuing "man %prog" ' "or on https://docs.saltproject.io" ) description = None # Private attributes # We want this class order to be right before LogLevelMixIn _mixin_prio_ = sys.maxsize - 200 def __init__(self, *args, **kwargs): kwargs.setdefault("version", f"%prog {self.VERSION}") kwargs.setdefault("usage", self.usage) if self.description: kwargs.setdefault("description", self.description) if self.epilog: kwargs.setdefault("epilog", self.epilog) kwargs.setdefault("option_class", CustomOption) optparse.OptionParser.__init__(self, *args, **kwargs) if self.epilog and "%prog" in self.epilog: self.epilog = self.epilog.replace("%prog", self.get_prog_name()) def add_option_group(self, *args, **kwargs): option_group = optparse.OptionParser.add_option_group(self, *args, **kwargs) option_group.option_class = CustomOption return option_group def parse_args(self, args=None, values=None): options, args = optparse.OptionParser.parse_args(self, args, values) if "args_stdin" in options.__dict__ and options.args_stdin is True: # Read additional options and/or arguments from stdin and combine # them with the options and arguments from the command line. new_inargs = sys.stdin.readlines() new_inargs = [arg.rstrip("\r\n") for arg in new_inargs] new_options, new_args = optparse.OptionParser.parse_args(self, new_inargs) options.__dict__.update(new_options.__dict__) args.extend(new_args) if options.versions_report: self.print_versions_report() self.options, self.args = options, args # Let's get some proper sys.stderr logging as soon as possible!!! # This logging handler will be removed once the proper console or # logfile logging is setup. temp_log_level = getattr(self.options, "log_level", None) salt._logging.setup_temp_handler( "error" if temp_log_level is None else temp_log_level ) # Gather and run the process_