def output(data, **kwargs): # pylint: disable=unused-argument ''' The HighState Outputter is only meant to be used with the state.highstate function, or a function that returns highstate return data. ''' # Discard retcode in dictionary as present in orchestrate data local_masters = [ key for key in data.keys() if key.endswith('.local_master') ] orchestrator_output = 'retcode' in data.keys() and len(local_masters) == 1 if orchestrator_output: del data['retcode'] # If additional information is passed through via the "data" dictionary to # the highstate outputter, such as "outputter" or "retcode", discard it. # We only want the state data that was passed through, if it is wrapped up # in the "data" key, as the orchestrate runner does. See Issue #31330, # pull request #27838, and pull request #27175 for more information. if 'data' in data: data = data.pop('data') indent_level = kwargs.get('indent_level', 1) ret = [ _format_host(host, hostdata, indent_level=indent_level)[0] for host, hostdata in six.iteritems(data) ] if ret: return "\n".join(ret) log.error( 'Data passed to highstate outputter is not a valid highstate return: %s', data) # We should not reach here, but if we do return empty string return ''
def output(data, **kwargs): # pylint: disable=unused-argument ''' The HighState Outputter is only meant to be used with the state.highstate function, or a function that returns highstate return data. ''' if len(data.keys()) == 1: # account for nested orchs via saltutil.runner if 'return' in data: data = data['return'] # account for envelope data if being passed lookup_jid ret if isinstance(data, dict): _data = next(iter(data.values())) if 'jid' in _data and 'fun' in _data: data = _data['return'] # output() is recursive, if we aren't passed a dict just return it if isinstance(data, int) or isinstance(data, six.string_types): return data # Discard retcode in dictionary as present in orchestrate data local_masters = [key for key in data.keys() if key.endswith('_master')] orchestrator_output = 'retcode' in data.keys() and len(local_masters) == 1 if orchestrator_output: del data['retcode'] # If additional information is passed through via the "data" dictionary to # the highstate outputter, such as "outputter" or "retcode", discard it. # We only want the state data that was passed through, if it is wrapped up # in the "data" key, as the orchestrate runner does. See Issue #31330, # pull request #27838, and pull request #27175 for more information. if 'data' in data: data = data.pop('data') indent_level = kwargs.get('indent_level', 1) ret = [ _format_host(host, hostdata, indent_level=indent_level)[0] for host, hostdata in six.iteritems(data) ] if ret: return "\n".join(ret) log.error( 'Data passed to highstate outputter is not a valid highstate return: %s', data) # We should not reach here, but if we do return empty string return ''
def format_call(fun, data, initial_ret=None, expected_extra_kws=(), is_class_method=None): ''' Build the required arguments and keyword arguments required for the passed function. :param fun: The function to get the argspec from :param data: A dictionary containing the required data to build the arguments and keyword arguments. :param initial_ret: The initial return data pre-populated as dictionary or None :param expected_extra_kws: Any expected extra keyword argument names which should not trigger a :ref:`SaltInvocationError` :param is_class_method: Pass True if you are sure that the function being passed is a class method. The reason for this is that on Python 3 ``inspect.ismethod`` only returns ``True`` for bound methods, while on Python 2, it returns ``True`` for bound and unbound methods. So, on Python 3, in case of a class method, you'd need the class to which the function belongs to be instantiated and this is not always wanted. :returns: A dictionary with the function required arguments and keyword arguments. ''' ret = initial_ret is not None and initial_ret or {} ret['args'] = [] ret['kwargs'] = {} aspec = get_function_argspec(fun, is_class_method=is_class_method) arg_data = arg_lookup(fun, aspec) args = arg_data['args'] kwargs = arg_data['kwargs'] # Since we WILL be changing the data dictionary, let's change a copy of it data = data.copy() missing_args = [] for key in kwargs: try: kwargs[key] = data.pop(key) except KeyError: # Let's leave the default value in place pass while args: arg = args.pop(0) try: ret['args'].append(data.pop(arg)) except KeyError: missing_args.append(arg) if missing_args: used_args_count = len(ret['args']) + len(args) args_count = used_args_count + len(missing_args) raise SaltInvocationError( '{0} takes at least {1} argument{2} ({3} given)'.format( fun.__name__, args_count, args_count > 1 and 's' or '', used_args_count)) ret['kwargs'].update(kwargs) if aspec.keywords: # The function accepts **kwargs, any non expected extra keyword # arguments will made available. for key, value in six.iteritems(data): if key in expected_extra_kws: continue ret['kwargs'][key] = value # No need to check for extra keyword arguments since they are all # **kwargs now. Return return ret # Did not return yet? Lets gather any remaining and unexpected keyword # arguments extra = {} for key, value in six.iteritems(data): if key in expected_extra_kws: continue extra[key] = copy.deepcopy(value) if extra: # Found unexpected keyword arguments, raise an error to the user if len(extra) == 1: msg = '\'{0[0]}\' is an invalid keyword argument for \'{1}\''.format( list(extra.keys()), ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__))) else: msg = '{0} and \'{1}\' are invalid keyword arguments for \'{2}\''.format( ', '.join(['\'{0}\''.format(e) for e in extra][:-1]), list(extra.keys())[-1], ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__))) raise SaltInvocationError(msg) return ret
def format_call(fun, data, initial_ret=None, expected_extra_kws=(), is_class_method=None): ''' Build the required arguments and keyword arguments required for the passed function. :param fun: The function to get the argspec from :param data: A dictionary containing the required data to build the arguments and keyword arguments. :param initial_ret: The initial return data pre-populated as dictionary or None :param expected_extra_kws: Any expected extra keyword argument names which should not trigger a :ref:`SaltInvocationError` :param is_class_method: Pass True if you are sure that the function being passed is a class method. The reason for this is that on Python 3 ``inspect.ismethod`` only returns ``True`` for bound methods, while on Python 2, it returns ``True`` for bound and unbound methods. So, on Python 3, in case of a class method, you'd need the class to which the function belongs to be instantiated and this is not always wanted. :returns: A dictionary with the function required arguments and keyword arguments. ''' ret = initial_ret is not None and initial_ret or {} ret['args'] = [] ret['kwargs'] = {} aspec = get_function_argspec(fun, is_class_method=is_class_method) arg_data = arg_lookup(fun, aspec) args = arg_data['args'] kwargs = arg_data['kwargs'] # Since we WILL be changing the data dictionary, let's change a copy of it data = data.copy() missing_args = [] for key in kwargs: try: kwargs[key] = data.pop(key) except KeyError: # Let's leave the default value in place pass while args: arg = args.pop(0) try: ret['args'].append(data.pop(arg)) except KeyError: missing_args.append(arg) if missing_args: used_args_count = len(ret['args']) + len(args) args_count = used_args_count + len(missing_args) raise SaltInvocationError( '{0} takes at least {1} argument{2} ({3} given)'.format( fun.__name__, args_count, args_count > 1 and 's' or '', used_args_count ) ) ret['kwargs'].update(kwargs) if aspec.keywords: # The function accepts **kwargs, any non expected extra keyword # arguments will made available. for key, value in six.iteritems(data): if key in expected_extra_kws: continue ret['kwargs'][key] = value # No need to check for extra keyword arguments since they are all # **kwargs now. Return return ret # Did not return yet? Lets gather any remaining and unexpected keyword # arguments extra = {} for key, value in six.iteritems(data): if key in expected_extra_kws: continue extra[key] = copy.deepcopy(value) # We'll be showing errors to the users until Salt Fluorine comes out, after # which, errors will be raised instead. salt.utils.versions.warn_until( 'Fluorine', 'It\'s time to start raising `SaltInvocationError` instead of ' 'returning warnings', # Let's not show the deprecation warning on the console, there's no # need. _dont_call_warnings=True ) if extra: # Found unexpected keyword arguments, raise an error to the user if len(extra) == 1: msg = '\'{0[0]}\' is an invalid keyword argument for \'{1}\''.format( list(extra.keys()), ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__) ) ) else: msg = '{0} and \'{1}\' are invalid keyword arguments for \'{2}\''.format( ', '.join(['\'{0}\''.format(e) for e in extra][:-1]), list(extra.keys())[-1], ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__) ) ) # Return a warning to the user explaining what's going on ret.setdefault('warnings', []).append( '{0}. If you were trying to pass additional data to be used ' 'in a template context, please populate \'context\' with ' '\'key: value\' pairs. Your approach will work until Salt ' 'Fluorine is out.{1}'.format( msg, '' if 'full' not in ret else ' Please update your state files.' ) ) # Lets pack the current extra kwargs as template context ret.setdefault('context', {}).update(extra) return ret
def format_call(fun, data, initial_ret=None, expected_extra_kws=(), is_class_method=None): ''' Build the required arguments and keyword arguments required for the passed function. :param fun: The function to get the argspec from :param data: A dictionary containing the required data to build the arguments and keyword arguments. :param initial_ret: The initial return data pre-populated as dictionary or None :param expected_extra_kws: Any expected extra keyword argument names which should not trigger a :ref:`SaltInvocationError` :param is_class_method: Pass True if you are sure that the function being passed is a class method. The reason for this is that on Python 3 ``inspect.ismethod`` only returns ``True`` for bound methods, while on Python 2, it returns ``True`` for bound and unbound methods. So, on Python 3, in case of a class method, you'd need the class to which the function belongs to be instantiated and this is not always wanted. :returns: A dictionary with the function required arguments and keyword arguments. ''' ret = initial_ret is not None and initial_ret or {} ret['args'] = [] ret['kwargs'] = {} aspec = get_function_argspec(fun, is_class_method=is_class_method) arg_data = arg_lookup(fun, aspec) args = arg_data['args'] kwargs = arg_data['kwargs'] # Since we WILL be changing the data dictionary, let's change a copy of it data = data.copy() missing_args = [] for key in kwargs: try: kwargs[key] = data.pop(key) except KeyError: # Let's leave the default value in place pass while args: arg = args.pop(0) try: ret['args'].append(data.pop(arg)) except KeyError: missing_args.append(arg) if missing_args: used_args_count = len(ret['args']) + len(args) args_count = used_args_count + len(missing_args) raise SaltInvocationError( '{0} takes at least {1} argument{2} ({3} given)'.format( fun.__name__, args_count, args_count > 1 and 's' or '', used_args_count ) ) ret['kwargs'].update(kwargs) if aspec.keywords: # The function accepts **kwargs, any non expected extra keyword # arguments will made available. for key, value in six.iteritems(data): if key in expected_extra_kws: continue ret['kwargs'][key] = value # No need to check for extra keyword arguments since they are all # **kwargs now. Return return ret # Did not return yet? Lets gather any remaining and unexpected keyword # arguments extra = {} for key, value in six.iteritems(data): if key in expected_extra_kws: continue extra[key] = copy.deepcopy(value) if extra: # Found unexpected keyword arguments, raise an error to the user if len(extra) == 1: msg = '\'{0[0]}\' is an invalid keyword argument for \'{1}\''.format( list(extra.keys()), ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__) ) ) else: msg = '{0} and \'{1}\' are invalid keyword arguments for \'{2}\''.format( ', '.join(['\'{0}\''.format(e) for e in extra][:-1]), list(extra.keys())[-1], ret.get( # In case this is being called for a state module 'full', # Not a state module, build the name '{0}.{1}'.format(fun.__module__, fun.__name__) ) ) raise SaltInvocationError(msg) return ret