mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 09:23:56 +00:00
1425 lines
50 KiB
Python
1425 lines
50 KiB
Python
# -*- coding: utf-8 -*-
|
|
'''
|
|
Routines to set up a minion
|
|
'''
|
|
|
|
# Import python libs
|
|
import os
|
|
import imp
|
|
import sys
|
|
import salt
|
|
import logging
|
|
import tempfile
|
|
import time
|
|
|
|
from collections import MutableMapping
|
|
|
|
# Import salt libs
|
|
from salt.exceptions import LoaderError
|
|
from salt.template import check_render_pipe_str
|
|
from salt.utils.decorators import Depends
|
|
from salt.utils.odict import OrderedDict
|
|
|
|
# Solve the Chicken and egg problem where grains need to run before any
|
|
# of the modules are loaded and are generally available for any usage.
|
|
import salt.modules.cmdmod
|
|
|
|
__salt__ = {
|
|
'cmd.run': salt.modules.cmdmod._run_quiet
|
|
}
|
|
log = logging.getLogger(__name__)
|
|
|
|
SALT_BASE_PATH = os.path.abspath(os.path.dirname(salt.__file__))
|
|
LOADED_BASE_NAME = 'salt.loaded'
|
|
|
|
# Because on the cloud drivers we do `from salt.cloud.libcloudfuncs import *`
|
|
# which simplifies code readability, it adds some unsupported functions into
|
|
# the driver's module scope.
|
|
# We list un-supported functions here. These will be removed from the loaded.
|
|
LIBCLOUD_FUNCS_NOT_SUPPORTED = (
|
|
'parallels.avail_sizes',
|
|
'parallels.avail_locations',
|
|
'proxmox.avail_sizes',
|
|
'saltify.destroy',
|
|
'saltify.avail_sizes',
|
|
'saltify.avail_images',
|
|
'saltify.avail_locations',
|
|
'rackspace.reboot',
|
|
'openstack.list_locations',
|
|
'rackspace.list_locations'
|
|
)
|
|
|
|
|
|
def _create_loader(
|
|
opts,
|
|
ext_type,
|
|
tag,
|
|
int_type=None,
|
|
ext_dirs=True,
|
|
ext_type_dirs=None,
|
|
base_path=None,
|
|
loaded_base_name=None,
|
|
mod_type_check=None):
|
|
'''
|
|
Creates Loader instance
|
|
|
|
Order of module_dirs:
|
|
cli flag -m MODULE_DIRS
|
|
opts[ext_type_dirs],
|
|
extension types,
|
|
base types.
|
|
'''
|
|
sys_types = os.path.join(base_path or SALT_BASE_PATH, int_type or ext_type)
|
|
ext_types = os.path.join(opts['extension_modules'], ext_type)
|
|
|
|
ext_type_types = []
|
|
if ext_dirs:
|
|
if ext_type_dirs is None:
|
|
ext_type_dirs = '{0}_dirs'.format(tag)
|
|
if ext_type_dirs in opts:
|
|
ext_type_types.extend(opts[ext_type_dirs])
|
|
|
|
cli_module_dirs = []
|
|
# The dirs can be any module dir, or a in-tree _{ext_type} dir
|
|
for _dir in opts.get('module_dirs', []):
|
|
# Prepend to the list to match cli argument ordering
|
|
maybe_dir = os.path.join(_dir, ext_type)
|
|
if os.path.isdir(maybe_dir):
|
|
cli_module_dirs.insert(0, maybe_dir)
|
|
continue
|
|
|
|
maybe_dir = os.path.join(_dir, '_{0}'.format(ext_type))
|
|
if os.path.isdir(maybe_dir):
|
|
cli_module_dirs.insert(0, maybe_dir)
|
|
|
|
if loaded_base_name is None:
|
|
loaded_base_name = LOADED_BASE_NAME
|
|
|
|
if mod_type_check is None:
|
|
mod_type_check = _mod_type
|
|
|
|
module_dirs = cli_module_dirs + ext_type_types + [ext_types, sys_types]
|
|
_generate_module('{0}.int'.format(loaded_base_name))
|
|
_generate_module('{0}.int.{1}'.format(loaded_base_name, tag))
|
|
_generate_module('{0}.ext'.format(loaded_base_name))
|
|
_generate_module('{0}.ext.{1}'.format(loaded_base_name, tag))
|
|
return Loader(
|
|
module_dirs,
|
|
opts,
|
|
tag,
|
|
loaded_base_name=loaded_base_name,
|
|
mod_type_check=mod_type_check
|
|
)
|
|
|
|
|
|
def minion_mods(opts, context=None, whitelist=None, loaded_base_name=None):
|
|
'''
|
|
Load execution modules
|
|
|
|
Returns a dictionary of execution modules appropriate for the current
|
|
system by evaluating the __virtual__() function in each module.
|
|
|
|
.. code-block:: python
|
|
|
|
import salt.config
|
|
import salt.loader
|
|
|
|
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
|
__grains__ = salt.loader.grains(__opts__)
|
|
__opts__['grains'] = __grains__
|
|
__salt__ = salt.loader.minion_mods(__opts__)
|
|
__salt__['test.ping']()
|
|
'''
|
|
load = _create_loader(
|
|
opts,
|
|
'modules',
|
|
'module',
|
|
loaded_base_name=loaded_base_name)
|
|
if context is None:
|
|
context = {}
|
|
pack = {'name': '__context__',
|
|
'value': context}
|
|
if not whitelist:
|
|
whitelist = opts.get('whitelist_modules', None)
|
|
functions = load.gen_functions(
|
|
pack,
|
|
whitelist=whitelist,
|
|
provider_overrides=True
|
|
)
|
|
# Enforce dependencies of module functions from "functions"
|
|
Depends.enforce_dependencies(functions)
|
|
return functions
|
|
|
|
|
|
def raw_mod(opts, name, functions):
|
|
'''
|
|
Returns a single module loaded raw and bypassing the __virtual__ function
|
|
|
|
.. code-block:: python
|
|
|
|
import salt.config
|
|
import salt.loader
|
|
|
|
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
|
testmod = salt.loader.raw_mod(__opts__, 'test', None)
|
|
testmod['test.ping']()
|
|
'''
|
|
load = _create_loader(opts, 'modules', 'rawmodule')
|
|
return load.gen_module(name, functions)
|
|
|
|
|
|
def proxy(opts, functions, whitelist=None):
|
|
'''
|
|
Returns the proxy module for this salt-proxy-minion
|
|
'''
|
|
load = _create_loader(opts, 'proxy', 'proxy')
|
|
pack = {'name': '__proxy__',
|
|
'value': functions}
|
|
return load.gen_functions(pack, whitelist=whitelist)
|
|
|
|
|
|
def returners(opts, functions, whitelist=None):
|
|
'''
|
|
Returns the returner modules
|
|
'''
|
|
load = _create_loader(opts, 'returners', 'returner')
|
|
pack = {'name': '__salt__',
|
|
'value': functions}
|
|
return LazyLoader(load,
|
|
functions,
|
|
pack,
|
|
whitelist=whitelist,
|
|
)
|
|
|
|
|
|
def pillars(opts, functions):
|
|
'''
|
|
Returns the pillars modules
|
|
'''
|
|
load = _create_loader(opts, 'pillar', 'pillar')
|
|
pack = {'name': '__salt__',
|
|
'value': functions}
|
|
return load.filter_func('ext_pillar', pack)
|
|
|
|
|
|
def tops(opts):
|
|
'''
|
|
Returns the tops modules
|
|
'''
|
|
if 'master_tops' not in opts:
|
|
return {}
|
|
whitelist = opts['master_tops'].keys()
|
|
load = _create_loader(opts, 'tops', 'top')
|
|
topmodules = load.filter_func('top', whitelist=whitelist)
|
|
return topmodules
|
|
|
|
|
|
def wheels(opts, whitelist=None):
|
|
'''
|
|
Returns the wheels modules
|
|
'''
|
|
load = _create_loader(opts, 'wheel', 'wheel')
|
|
return load.gen_functions(whitelist=whitelist)
|
|
|
|
|
|
def outputters(opts):
|
|
'''
|
|
Returns the outputters modules
|
|
'''
|
|
load = _create_loader(
|
|
opts,
|
|
'output',
|
|
'output',
|
|
ext_type_dirs='outputter_dirs')
|
|
return load.filter_func('output')
|
|
|
|
|
|
def auth(opts, whitelist=None):
|
|
'''
|
|
Returns the auth modules
|
|
'''
|
|
load = _create_loader(opts, 'auth', 'auth')
|
|
return load.gen_functions(whitelist=whitelist)
|
|
|
|
|
|
def fileserver(opts, backends):
|
|
'''
|
|
Returns the file server modules
|
|
'''
|
|
load = _create_loader(opts, 'fileserver', 'fileserver')
|
|
return load.gen_functions(whitelist=backends)
|
|
|
|
|
|
def roster(opts, whitelist=None):
|
|
'''
|
|
Returns the roster modules
|
|
'''
|
|
load = _create_loader(opts, 'roster', 'roster')
|
|
return load.gen_functions(whitelist=whitelist)
|
|
|
|
|
|
def states(opts, functions, whitelist=None):
|
|
'''
|
|
Returns the state modules
|
|
|
|
.. code-block:: python
|
|
|
|
import salt.config
|
|
import salt.loader
|
|
|
|
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
|
statemods = salt.loader.states(__opts__, None)
|
|
'''
|
|
load = _create_loader(opts, 'states', 'states')
|
|
pack = {'name': '__salt__',
|
|
'value': functions}
|
|
return load.gen_functions(pack, whitelist=whitelist)
|
|
|
|
|
|
def search(opts, returners, whitelist=None):
|
|
'''
|
|
Returns the search modules
|
|
'''
|
|
load = _create_loader(opts, 'search', 'search')
|
|
pack = {'name': '__ret__',
|
|
'value': returners}
|
|
return LazyLoader(load, pack=pack, whitelist=whitelist)
|
|
|
|
|
|
def log_handlers(opts):
|
|
'''
|
|
Returns the custom logging handler modules
|
|
'''
|
|
load = _create_loader(
|
|
opts,
|
|
'log_handlers',
|
|
'log_handlers',
|
|
int_type='handlers',
|
|
base_path=os.path.join(SALT_BASE_PATH, 'log')
|
|
)
|
|
return load.filter_func('setup_handlers')
|
|
|
|
|
|
def ssh_wrapper(opts, functions=None, context=None):
|
|
'''
|
|
Returns the custom logging handler modules
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
if functions is None:
|
|
functions = {}
|
|
load = _create_loader(
|
|
opts,
|
|
'wrapper',
|
|
'wrapper',
|
|
base_path=os.path.join(SALT_BASE_PATH, os.path.join(
|
|
'client',
|
|
'ssh'))
|
|
)
|
|
pack = [{'name': '__salt__',
|
|
'value': functions},
|
|
{'name': '__context__',
|
|
'value': context}]
|
|
return load.gen_functions(pack)
|
|
|
|
|
|
def render(opts, functions, states=None):
|
|
'''
|
|
Returns the render modules
|
|
'''
|
|
load = _create_loader(
|
|
opts, 'renderers', 'render', ext_type_dirs='render_dirs'
|
|
)
|
|
pack = [{'name': '__salt__',
|
|
'value': functions},
|
|
{'name': '__pillar__',
|
|
'value': opts.get('pillar', {})}]
|
|
|
|
if states:
|
|
pack.append({'name': '__states__', 'value': states})
|
|
rend = load.filter_func('render', pack)
|
|
if not check_render_pipe_str(opts['renderer'], rend):
|
|
err = ('The renderer {0} is unavailable, this error is often because '
|
|
'the needed software is unavailable'.format(opts['renderer']))
|
|
log.critical(err)
|
|
raise LoaderError(err)
|
|
return rend
|
|
|
|
|
|
def grains(opts, force_refresh=False):
|
|
'''
|
|
Return the functions for the dynamic grains and the values for the static
|
|
grains.
|
|
|
|
.. code-block:: python
|
|
|
|
import salt.config
|
|
import salt.loader
|
|
|
|
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
|
__grains__ = salt.loader.grains(__opts__)
|
|
print __grains__['id']
|
|
'''
|
|
if opts.get('skip_grains', False):
|
|
return {}
|
|
if 'conf_file' in opts:
|
|
pre_opts = {}
|
|
pre_opts.update(salt.config.load_config(
|
|
opts['conf_file'], 'SALT_MINION_CONFIG',
|
|
salt.config.DEFAULT_MINION_OPTS['conf_file']
|
|
))
|
|
default_include = pre_opts.get(
|
|
'default_include', opts['default_include']
|
|
)
|
|
include = pre_opts.get('include', [])
|
|
pre_opts.update(salt.config.include_config(
|
|
default_include, opts['conf_file'], verbose=False
|
|
))
|
|
pre_opts.update(salt.config.include_config(
|
|
include, opts['conf_file'], verbose=True
|
|
))
|
|
if 'grains' in pre_opts:
|
|
opts['grains'] = pre_opts['grains']
|
|
else:
|
|
opts['grains'] = {}
|
|
else:
|
|
opts['grains'] = {}
|
|
load = _create_loader(opts, 'grains', 'grain', ext_type_dirs='grains_dirs')
|
|
grains_info = load.gen_grains(force_refresh)
|
|
grains_info.update(opts['grains'])
|
|
return grains_info
|
|
|
|
|
|
def call(fun, **kwargs):
|
|
'''
|
|
Directly call a function inside a loader directory
|
|
'''
|
|
args = kwargs.get('args', [])
|
|
dirs = kwargs.get('dirs', [])
|
|
module_dirs = [os.path.join(SALT_BASE_PATH, 'modules')] + dirs
|
|
load = Loader(module_dirs)
|
|
return load.call(fun, args)
|
|
|
|
|
|
def runner(opts):
|
|
'''
|
|
Directly call a function inside a loader directory
|
|
'''
|
|
load = _create_loader(
|
|
opts, 'runners', 'runner', ext_type_dirs='runner_dirs'
|
|
)
|
|
return load.gen_functions()
|
|
|
|
|
|
def queues(opts):
|
|
'''
|
|
Directly call a function inside a loader directory
|
|
'''
|
|
load = _create_loader(
|
|
opts, 'queues', 'queue', ext_type_dirs='queue_dirs'
|
|
)
|
|
return load.gen_functions()
|
|
|
|
|
|
def sdb(opts, functions=None, whitelist=None):
|
|
'''
|
|
Make a very small database call
|
|
'''
|
|
load = _create_loader(opts, 'sdb', 'sdb')
|
|
pack = {'name': '__sdb__',
|
|
'value': functions}
|
|
return LazyLoader(load,
|
|
functions,
|
|
pack,
|
|
whitelist=whitelist,
|
|
)
|
|
|
|
|
|
def clouds(opts):
|
|
'''
|
|
Return the cloud functions
|
|
'''
|
|
load = _create_loader(opts,
|
|
'clouds',
|
|
'cloud',
|
|
base_path=os.path.join(SALT_BASE_PATH, 'cloud'),
|
|
int_type='clouds')
|
|
|
|
# Let's bring __active_provider_name__, defaulting to None, to all cloud
|
|
# drivers. This will get temporarily updated/overridden with a context
|
|
# manager when needed.
|
|
pack = {
|
|
'name': '__active_provider_name__',
|
|
'value': None
|
|
}
|
|
|
|
functions = load.gen_functions(pack)
|
|
for funcname in LIBCLOUD_FUNCS_NOT_SUPPORTED:
|
|
log.debug(
|
|
'{0!r} has been marked as not supported. Removing from the list '
|
|
'of supported cloud functions'.format(
|
|
funcname
|
|
)
|
|
)
|
|
functions.pop(funcname, None)
|
|
return functions
|
|
|
|
|
|
def netapi(opts):
|
|
'''
|
|
Return the network api functions
|
|
'''
|
|
load = salt.loader._create_loader(opts, 'netapi', 'netapi')
|
|
return load.gen_functions()
|
|
|
|
|
|
def _generate_module(name):
|
|
if name in sys.modules:
|
|
return
|
|
|
|
code = "'''Salt loaded {0} parent module'''".format(name.split('.')[-1])
|
|
module = imp.new_module(name)
|
|
exec code in module.__dict__
|
|
sys.modules[name] = module
|
|
|
|
|
|
def _mod_type(module_path):
|
|
if module_path.startswith(SALT_BASE_PATH):
|
|
return 'int'
|
|
return 'ext'
|
|
|
|
|
|
def in_pack(pack, name):
|
|
'''
|
|
Returns if the passed name is in the pack
|
|
'''
|
|
if isinstance(pack, list):
|
|
for chunk in pack:
|
|
if not isinstance(chunk, dict):
|
|
continue
|
|
try:
|
|
if name == chunk['name']:
|
|
return True
|
|
except KeyError:
|
|
pass
|
|
elif isinstance(pack, dict):
|
|
try:
|
|
if name == pack['name']:
|
|
return True
|
|
except KeyError:
|
|
pass
|
|
return False
|
|
|
|
|
|
class Loader(object):
|
|
'''
|
|
Used to load in arbitrary modules from a directory, the Loader can
|
|
also be used to only load specific functions from a directory, or to
|
|
call modules in an arbitrary directory directly.
|
|
'''
|
|
def __init__(self,
|
|
module_dirs,
|
|
opts=None,
|
|
tag='module',
|
|
loaded_base_name=None,
|
|
mod_type_check=None):
|
|
self.module_dirs = module_dirs
|
|
if opts is None:
|
|
opts = {}
|
|
self.tag = tag
|
|
if 'grains' in opts:
|
|
self.grains = opts['grains']
|
|
else:
|
|
self.grains = {}
|
|
if 'pillar' in opts:
|
|
self.pillar = opts['pillar']
|
|
else:
|
|
self.pillar = {}
|
|
self.opts = self.__prep_mod_opts(opts)
|
|
self.loaded_base_name = loaded_base_name or LOADED_BASE_NAME
|
|
self.mod_type_check = mod_type_check or _mod_type
|
|
if self.opts.get('grains_cache', False):
|
|
self.serial = salt.payload.Serial(self.opts)
|
|
|
|
def __prep_mod_opts(self, opts):
|
|
'''
|
|
Strip out of the opts any logger instance
|
|
'''
|
|
mod_opts = {}
|
|
for key, val in opts.items():
|
|
if key in ('logger', 'grains'):
|
|
continue
|
|
mod_opts[key] = val
|
|
return mod_opts
|
|
|
|
def call(self, fun, arg=None):
|
|
'''
|
|
Call a function in the load path.
|
|
'''
|
|
if arg is None:
|
|
arg = []
|
|
name = fun[:fun.rindex('.')]
|
|
try:
|
|
fn_, path, desc = imp.find_module(name, self.module_dirs)
|
|
mod = imp.load_module(name, fn_, path, desc)
|
|
except ImportError:
|
|
if self.opts.get('cython_enable', True) is True:
|
|
# The module was not found, try to find a cython module
|
|
try:
|
|
import pyximport # pylint: disable=import-error
|
|
pyximport.install()
|
|
|
|
for mod_dir in self.module_dirs:
|
|
for fn_ in os.listdir(mod_dir):
|
|
if name == fn_[:fn_.rindex('.')]:
|
|
# Found it, load the mod and break the loop
|
|
mod = pyximport.load_module(
|
|
name, os.path.join(mod_dir, fn_)
|
|
)
|
|
return getattr(
|
|
mod, fun[fun.rindex('.') + 1:])(*arg)
|
|
except ImportError:
|
|
log.info('Cython is enabled in options though it\'s not '
|
|
'present in the system path. Skipping Cython '
|
|
'modules.')
|
|
return getattr(mod, fun[fun.rindex('.') + 1:])(*arg)
|
|
|
|
def gen_module(self, name, functions, pack=None):
|
|
'''
|
|
Load a single module and pack it with the functions passed
|
|
'''
|
|
full = ''
|
|
mod = None
|
|
for mod_dir in self.module_dirs:
|
|
if not os.path.isabs(mod_dir):
|
|
continue
|
|
if not os.path.isdir(mod_dir):
|
|
continue
|
|
fn_ = os.path.join(mod_dir, name)
|
|
if os.path.isdir(fn_):
|
|
full = fn_
|
|
break
|
|
else:
|
|
for ext in ('.py', '.pyo', '.pyc', '.so'):
|
|
full_test = '{0}{1}'.format(fn_, ext)
|
|
if os.path.isfile(full_test):
|
|
full = full_test
|
|
break
|
|
if full:
|
|
break
|
|
if not full:
|
|
return None
|
|
|
|
cython_enabled = False
|
|
if self.opts.get('cython_enable', True) is True:
|
|
try:
|
|
import pyximport # pylint: disable=import-error
|
|
pyximport.install()
|
|
cython_enabled = True
|
|
except ImportError:
|
|
log.info('Cython is enabled in the options but not present '
|
|
'in the system path. Skipping Cython modules.')
|
|
try:
|
|
if full.endswith('.pyx') and cython_enabled:
|
|
# If there's a name which ends in .pyx it means the above
|
|
# cython_enabled is True. Continue...
|
|
mod = pyximport.load_module(name, full, tempfile.gettempdir())
|
|
else:
|
|
fn_, path, desc = imp.find_module(name, self.module_dirs)
|
|
mod = imp.load_module(
|
|
'{0}.{1}.{2}.{3}'.format(
|
|
self.loaded_base_name,
|
|
self.mod_type_check(path),
|
|
self.tag,
|
|
name
|
|
), fn_, path, desc
|
|
)
|
|
except ImportError:
|
|
log.debug(
|
|
'Failed to import {0} {1}:\n'.format(
|
|
self.tag, name
|
|
),
|
|
exc_info=True
|
|
)
|
|
return mod
|
|
except Exception:
|
|
log.warning(
|
|
'Failed to import {0} {1}, this is due most likely to a '
|
|
'syntax error:\n'.format(
|
|
self.tag, name
|
|
),
|
|
exc_info=True
|
|
)
|
|
return mod
|
|
if hasattr(mod, '__opts__'):
|
|
mod.__opts__.update(self.opts)
|
|
else:
|
|
mod.__opts__ = self.opts
|
|
|
|
mod.__grains__ = self.grains
|
|
|
|
if pack:
|
|
if isinstance(pack, list):
|
|
for chunk in pack:
|
|
try:
|
|
setattr(mod, chunk['name'], chunk['value'])
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
setattr(mod, pack['name'], pack['value'])
|
|
|
|
# Call a module's initialization method if it exists
|
|
if hasattr(mod, '__init__'):
|
|
if callable(mod.__init__):
|
|
try:
|
|
mod.__init__(self.opts)
|
|
except TypeError:
|
|
pass
|
|
funcs = {}
|
|
module_name = mod.__name__[mod.__name__.rindex('.') + 1:]
|
|
if getattr(mod, '__load__', False) is not False:
|
|
log.info(
|
|
'The functions from module {0!r} are being loaded from the '
|
|
'provided __load__ attribute'.format(
|
|
module_name
|
|
)
|
|
)
|
|
for attr in getattr(mod, '__load__', dir(mod)):
|
|
if attr.startswith('_'):
|
|
# private functions are skipped
|
|
continue
|
|
if callable(getattr(mod, attr)):
|
|
func = getattr(mod, attr)
|
|
if hasattr(func, '__bases__'):
|
|
if 'BaseException' in func.__bases__:
|
|
# the callable object is an exception, don't load it
|
|
continue
|
|
|
|
# Let's get the function name.
|
|
# If the module has the __func_alias__ attribute, it must be a
|
|
# dictionary mapping in the form of(key -> value):
|
|
# <real-func-name> -> <desired-func-name>
|
|
#
|
|
# It default's of course to the found callable attribute name
|
|
# if no alias is defined.
|
|
funcname = getattr(mod, '__func_alias__', {}).get(attr, attr)
|
|
funcs['{0}.{1}'.format(module_name, funcname)] = func
|
|
self._apply_outputter(func, mod)
|
|
if not hasattr(mod, '__salt__'):
|
|
mod.__salt__ = functions
|
|
try:
|
|
context = sys.modules[
|
|
functions[functions.iterkeys().next()].__module__
|
|
].__context__
|
|
except (AttributeError, StopIteration):
|
|
context = {}
|
|
mod.__context__ = context
|
|
return funcs
|
|
|
|
def gen_functions(self, pack=None, virtual_enable=True, whitelist=None,
|
|
provider_overrides=False):
|
|
'''
|
|
Return a dict of functions found in the defined module_dirs
|
|
'''
|
|
funcs = OrderedDict()
|
|
self.load_modules()
|
|
for mod in self.modules:
|
|
# If this is a proxy minion then MOST modules cannot work. Therefore, require that
|
|
# any module that does work with salt-proxy-minion define __proxyenabled__ as a list
|
|
# containing the names of the proxy types that the module supports.
|
|
if not hasattr(mod, 'render') and 'proxy' in self.opts:
|
|
if not hasattr(mod, '__proxyenabled__'):
|
|
# This is a proxy minion but this module doesn't support proxy
|
|
# minions at all
|
|
continue
|
|
if not (self.opts['proxy']['proxytype'] in mod.__proxyenabled__ or
|
|
'*' in mod.__proxyenabled__):
|
|
# This is a proxy minion, this module supports proxy
|
|
# minions, but not this particular minion
|
|
log.debug(mod)
|
|
continue
|
|
|
|
if hasattr(mod, '__opts__'):
|
|
mod.__opts__.update(self.opts)
|
|
else:
|
|
mod.__opts__ = self.opts
|
|
|
|
mod.__grains__ = self.grains
|
|
mod.__pillar__ = self.pillar
|
|
|
|
if pack:
|
|
if isinstance(pack, list):
|
|
for chunk in pack:
|
|
if not isinstance(chunk, dict):
|
|
continue
|
|
try:
|
|
setattr(mod, chunk['name'], chunk['value'])
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
setattr(mod, pack['name'], pack['value'])
|
|
|
|
# Call a module's initialization method if it exists
|
|
if hasattr(mod, '__init__'):
|
|
if callable(mod.__init__):
|
|
try:
|
|
mod.__init__(self.opts)
|
|
except TypeError:
|
|
pass
|
|
|
|
# Trim the full pathname to just the module
|
|
# this will be the short name that other salt modules and state
|
|
# will refer to it as.
|
|
module_name = mod.__name__.rsplit('.', 1)[-1]
|
|
|
|
if virtual_enable:
|
|
# if virtual modules are enabled, we need to look for the
|
|
# __virtual__() function inside that module and run it.
|
|
(virtual_ret, virtual_name) = self.process_virtual(mod,
|
|
module_name)
|
|
|
|
# if process_virtual returned a non-True value then we are
|
|
# supposed to not process this module
|
|
if virtual_ret is not True:
|
|
continue
|
|
|
|
# update our module name to reflect the virtual name
|
|
module_name = virtual_name
|
|
|
|
if whitelist:
|
|
# If a whitelist is defined then only load the module if it is
|
|
# in the whitelist
|
|
if module_name not in whitelist:
|
|
continue
|
|
|
|
# load the functions from the module and update our dict
|
|
funcs.update(self.load_functions(mod, module_name))
|
|
|
|
# Handle provider overrides
|
|
if provider_overrides and self.opts.get('providers', False):
|
|
if isinstance(self.opts['providers'], dict):
|
|
for mod, provider in self.opts['providers'].items():
|
|
newfuncs = raw_mod(self.opts, provider, funcs)
|
|
if newfuncs:
|
|
for newfunc in newfuncs:
|
|
f_key = '{0}{1}'.format(
|
|
mod, newfunc[newfunc.rindex('.'):]
|
|
)
|
|
funcs[f_key] = newfuncs[newfunc]
|
|
|
|
# now that all the functions have been collected, iterate back over
|
|
# the available modules and inject the special __salt__ namespace that
|
|
# contains these functions.
|
|
for mod in self.modules:
|
|
if not hasattr(mod, '__salt__') or (
|
|
not in_pack(pack, '__salt__') and
|
|
(not str(mod.__name__).startswith('salt.loaded.int.grain') and
|
|
not str(mod.__name__).startswith('salt.loaded.ext.grain'))
|
|
):
|
|
mod.__salt__ = funcs
|
|
elif not in_pack(pack, '__salt__') and \
|
|
(str(mod.__name__).startswith('salt.loaded.int.grain') or
|
|
str(mod.__name__).startswith('salt.loaded.ext.grain')):
|
|
mod.__salt__.update(funcs)
|
|
return funcs
|
|
|
|
def load_modules(self):
|
|
'''
|
|
Loads all of the modules from module_dirs and returns a list of them
|
|
'''
|
|
|
|
self.modules = []
|
|
|
|
log.trace('loading {0} in {1}'.format(self.tag, self.module_dirs))
|
|
names = OrderedDict()
|
|
disable = set(self.opts.get('disable_{0}s'.format(self.tag), []))
|
|
|
|
cython_enabled = False
|
|
if self.opts.get('cython_enable', True) is True:
|
|
try:
|
|
import pyximport # pylint: disable=import-error
|
|
pyximport.install()
|
|
cython_enabled = True
|
|
except ImportError:
|
|
log.info('Cython is enabled in the options but not present '
|
|
'in the system path. Skipping Cython modules.')
|
|
for mod_dir in self.module_dirs:
|
|
if not os.path.isabs(mod_dir):
|
|
log.trace(
|
|
'Skipping {0}, it is not an absolute path'.format(
|
|
mod_dir
|
|
)
|
|
)
|
|
continue
|
|
if not os.path.isdir(mod_dir):
|
|
log.trace(
|
|
'Skipping {0}, it is not a directory'.format(
|
|
mod_dir
|
|
)
|
|
)
|
|
continue
|
|
for fn_ in os.listdir(mod_dir):
|
|
if fn_.startswith('_'):
|
|
# skip private modules
|
|
# log messages omitted for obviousness
|
|
continue
|
|
if fn_.split('.')[0] in disable:
|
|
log.trace(
|
|
'Skipping {0}, it is disabled by configuration'.format(
|
|
fn_
|
|
)
|
|
)
|
|
continue
|
|
|
|
if fn_.endswith(('.pyc', '.pyo')):
|
|
non_compiled_filename = '{0}.py'.format(os.path.splitext(fn_)[0])
|
|
if os.path.exists(os.path.join(mod_dir, non_compiled_filename)):
|
|
# Let's just process the non compiled python modules
|
|
continue
|
|
|
|
if (fn_.endswith(('.py', '.pyc', '.pyo', '.so'))
|
|
or (cython_enabled and fn_.endswith('.pyx'))
|
|
or os.path.isdir(os.path.join(mod_dir, fn_))):
|
|
|
|
extpos = fn_.rfind('.')
|
|
if extpos > 0:
|
|
_name = fn_[:extpos]
|
|
else:
|
|
_name = fn_
|
|
|
|
if _name in names:
|
|
# Since we load custom modules first, if this logic is true it means
|
|
# that an internal module was shadowed by an external custom module
|
|
log.trace(
|
|
'The {0!r} module from {1!r} was shadowed by '
|
|
'the module in {2!r}'.format(
|
|
_name,
|
|
mod_dir,
|
|
names[_name],
|
|
)
|
|
)
|
|
continue
|
|
|
|
names[_name] = os.path.join(mod_dir, fn_)
|
|
else:
|
|
log.trace(
|
|
'Skipping {0}, it does not end with an expected '
|
|
'extension'.format(
|
|
fn_
|
|
)
|
|
)
|
|
for name in names:
|
|
try:
|
|
if names[name].endswith('.pyx'):
|
|
# If there's a name which ends in .pyx it means the above
|
|
# cython_enabled is True. Continue...
|
|
mod = pyximport.load_module(
|
|
'{0}.{1}.{2}.{3}'.format(
|
|
self.loaded_base_name,
|
|
self.mod_type_check(names[name]),
|
|
self.tag,
|
|
name
|
|
), names[name], tempfile.gettempdir()
|
|
)
|
|
else:
|
|
fn_, path, desc = imp.find_module(name, self.module_dirs)
|
|
mod = imp.load_module(
|
|
'{0}.{1}.{2}.{3}'.format(
|
|
self.loaded_base_name,
|
|
self.mod_type_check(path),
|
|
self.tag,
|
|
name
|
|
), fn_, path, desc
|
|
)
|
|
# reload all submodules if necessary
|
|
submodules = [
|
|
getattr(mod, sname) for sname in dir(mod) if
|
|
isinstance(getattr(mod, sname), mod.__class__)
|
|
]
|
|
|
|
# reload only custom "sub"modules i.e is a submodule in
|
|
# parent module that are still available on disk (i.e. not
|
|
# removed during sync_modules)
|
|
for submodule in submodules:
|
|
try:
|
|
smname = '{0}.{1}.{2}'.format(
|
|
self.loaded_base_name,
|
|
self.tag,
|
|
name
|
|
)
|
|
smfile = '{0}.py'.format(
|
|
os.path.splitext(submodule.__file__)[0]
|
|
)
|
|
if submodule.__name__.startswith(smname) and \
|
|
os.path.isfile(smfile):
|
|
reload(submodule)
|
|
except AttributeError:
|
|
continue
|
|
except ImportError:
|
|
log.debug(
|
|
'Failed to import {0} {1}, this is most likely NOT a '
|
|
'problem:\n'.format(
|
|
self.tag, name
|
|
),
|
|
exc_info=True
|
|
)
|
|
continue
|
|
except Exception:
|
|
log.warning(
|
|
'Failed to import {0} {1}, this is due most likely to a '
|
|
'syntax error. Traceback raised:\n'.format(
|
|
self.tag, name
|
|
),
|
|
exc_info=True
|
|
)
|
|
continue
|
|
self.modules.append(mod)
|
|
|
|
def load_functions(self, mod, module_name):
|
|
'''
|
|
Load functions returns a dict of all the functions from a module
|
|
'''
|
|
funcs = {}
|
|
|
|
if getattr(mod, '__load__', False) is not False:
|
|
log.info(
|
|
'The functions from module {0!r} are being loaded from '
|
|
'the provided __load__ attribute'.format(
|
|
module_name
|
|
)
|
|
)
|
|
|
|
for attr in getattr(mod, '__load__', dir(mod)):
|
|
if attr.startswith('_'):
|
|
# skip private attributes
|
|
# log messages omitted for obviousness
|
|
continue
|
|
|
|
if callable(getattr(mod, attr)):
|
|
# check to make sure this is callable
|
|
func = getattr(mod, attr)
|
|
if isinstance(func, type):
|
|
# skip callables that might be exceptions
|
|
if any(['Error' in func.__name__,
|
|
'Exception' in func.__name__]):
|
|
continue
|
|
|
|
# now that callable passes all the checks, add it to the
|
|
# library of available functions of this type
|
|
|
|
# Let's get the function name.
|
|
# If the module has the __func_alias__ attribute, it must
|
|
# be a dictionary mapping in the form of(key -> value):
|
|
# <real-func-name> -> <desired-func-name>
|
|
#
|
|
# It default's of course to the found callable attribute
|
|
# name if no alias is defined.
|
|
funcname = getattr(mod, '__func_alias__', {}).get(
|
|
attr, attr
|
|
)
|
|
|
|
# functions are namespaced with their module name, unless
|
|
# the module_name is None (this is a special case added for
|
|
# pyobjects), in which case just the function name is used
|
|
if module_name is None:
|
|
module_func_name = funcname
|
|
else:
|
|
module_func_name = '{0}.{1}'.format(module_name, funcname)
|
|
|
|
funcs[module_func_name] = func
|
|
log.trace(
|
|
'Added {0} to {1}'.format(module_func_name, self.tag)
|
|
)
|
|
self._apply_outputter(func, mod)
|
|
return funcs
|
|
|
|
def process_virtual(self, mod, module_name):
|
|
'''
|
|
Given a loaded module and its default name determine its virtual name
|
|
|
|
This function returns a tuple. The first value will be either True or
|
|
False and will indicate if the module should be loaded or not (ie. if
|
|
it threw and exception while processing its __virtual__ function). The
|
|
second value is the determined virtual name, which may be the same as
|
|
the value provided.
|
|
|
|
The default name can be calculated as follows::
|
|
|
|
module_name = mod.__name__.rsplit('.', 1)[-1]
|
|
'''
|
|
|
|
# The __virtual__ function will return either a True or False value.
|
|
# If it returns a True value it can also set a module level attribute
|
|
# named __virtualname__ with the name that the module should be
|
|
# referred to as.
|
|
#
|
|
# This allows us to have things like the pkg module working on all
|
|
# platforms under the name 'pkg'. It also allows for modules like
|
|
# augeas_cfg to be referred to as 'augeas', which would otherwise have
|
|
# namespace collisions. And finally it allows modules to return False
|
|
# if they are not intended to run on the given platform or are missing
|
|
# dependencies.
|
|
try:
|
|
if hasattr(mod, '__virtual__') and callable(mod.__virtual__):
|
|
if self.opts.get('virtual_timer', False):
|
|
start = time.time()
|
|
virtual = mod.__virtual__()
|
|
end = time.time() - start
|
|
msg = 'Virtual function took {0} seconds for {1}'.format(
|
|
end, module_name)
|
|
log.warning(msg)
|
|
else:
|
|
try:
|
|
virtual = mod.__virtual__()
|
|
except Exception as exc:
|
|
log.error('Exception raised when processing __virtual__ function'
|
|
' for {0}. Module will not be loaded {1}'.format(
|
|
module_name, exc))
|
|
virtual = None
|
|
# Get the module's virtual name
|
|
virtualname = getattr(mod, '__virtualname__', virtual)
|
|
if not virtual:
|
|
# if __virtual__() evaluates to False then the module
|
|
# wasn't meant for this platform or it's not supposed to
|
|
# load for some other reason.
|
|
|
|
# Some modules might accidentally return None and are
|
|
# improperly loaded
|
|
if virtual is None:
|
|
log.warning(
|
|
'{0}.__virtual__() is wrongly returning `None`. '
|
|
'It should either return `True`, `False` or a new '
|
|
'name. If you\'re the developer of the module '
|
|
'{1!r}, please fix this.'.format(
|
|
mod.__name__,
|
|
module_name
|
|
)
|
|
)
|
|
|
|
return (False, module_name)
|
|
|
|
# At this point, __virtual__ did not return a
|
|
# boolean value, let's check for deprecated usage
|
|
# or module renames
|
|
if virtual is not True and module_name == virtual:
|
|
# The module was not renamed, it should
|
|
# have returned True instead
|
|
#salt.utils.warn_until(
|
|
# 'Helium',
|
|
# 'The {0!r} module is NOT renaming itself and is '
|
|
# 'returning a string. In this case the __virtual__() '
|
|
# 'function should simply return `True`. This usage will '
|
|
# 'become an error in Salt Helium'.format(
|
|
# mod.__name__,
|
|
# )
|
|
#)
|
|
pass
|
|
|
|
elif virtual is not True and module_name != virtual:
|
|
# The module is renaming itself. Updating the module name
|
|
# with the new name
|
|
log.trace('Loaded {0} as virtual {1}'.format(
|
|
module_name, virtual
|
|
))
|
|
|
|
if not hasattr(mod, '__virtualname__'):
|
|
salt.utils.warn_until(
|
|
'Hydrogen',
|
|
'The {0!r} module is renaming itself in it\'s '
|
|
'__virtual__() function ({1} => {2}). Please '
|
|
'set it\'s virtual name as the '
|
|
'\'__virtualname__\' module attribute. '
|
|
'Example: "__virtualname__ = {2!r}"'.format(
|
|
mod.__name__,
|
|
module_name,
|
|
virtual
|
|
)
|
|
)
|
|
|
|
if virtualname != virtual:
|
|
# The __virtualname__ attribute does not match what's
|
|
# being returned by the __virtual__() function. This
|
|
# should be considered an error.
|
|
log.error(
|
|
'The module {0!r} is showing some bad usage. It\'s '
|
|
'__virtualname__ attribute is set to {1!r} yet the '
|
|
'__virtual__() function is returning {2!r}. These '
|
|
'values should match!'.format(
|
|
mod.__name__,
|
|
virtualname,
|
|
virtual
|
|
)
|
|
)
|
|
|
|
module_name = virtualname
|
|
|
|
# If the __virtual__ function returns True and __virtualname__ is set then use it
|
|
elif virtual is True and virtualname != module_name:
|
|
if virtualname is not True:
|
|
module_name = virtualname
|
|
|
|
except KeyError:
|
|
# Key errors come out of the virtual function when passing
|
|
# in incomplete grains sets, these can be safely ignored
|
|
# and logged to debug, still, it includes the traceback to
|
|
# help debugging.
|
|
log.debug(
|
|
'KeyError when loading {0}'.format(module_name),
|
|
exc_info=True
|
|
)
|
|
|
|
except Exception:
|
|
# If the module throws an exception during __virtual__()
|
|
# then log the information and continue to the next.
|
|
log.error(
|
|
'Failed to read the virtual function for '
|
|
'{0}: {1}'.format(
|
|
self.tag, module_name
|
|
),
|
|
exc_info=True
|
|
)
|
|
return (False, module_name)
|
|
|
|
return (True, module_name)
|
|
|
|
def _apply_outputter(self, func, mod):
|
|
'''
|
|
Apply the __outputter__ variable to the functions
|
|
'''
|
|
if hasattr(mod, '__outputter__'):
|
|
outp = mod.__outputter__
|
|
if func.__name__ in outp:
|
|
func.__outputter__ = outp[func.__name__]
|
|
|
|
def filter_func(self, name, pack=None, whitelist=None):
|
|
'''
|
|
Filter a specific function out of the functions, this is used to load
|
|
the returners for the salt minion
|
|
'''
|
|
funcs = {}
|
|
gen = self.gen_functions(pack=pack, whitelist=whitelist)
|
|
for key, fun in gen.items():
|
|
# if the name (after '.') is "name", then rename to mod_name: fun
|
|
if key[key.index('.') + 1:] == name:
|
|
funcs[key[:key.index('.')]] = fun
|
|
return funcs
|
|
|
|
def chop_mods(self):
|
|
'''
|
|
Chop off the module names so that the raw functions are exposed,
|
|
used to generate the grains
|
|
'''
|
|
funcs = {}
|
|
for key, fun in self.gen_functions().items():
|
|
funcs[key[key.rindex('.')] + 1:] = fun
|
|
return funcs
|
|
|
|
def gen_grains(self, force_refresh=False):
|
|
'''
|
|
Read the grains directory and execute all of the public callable
|
|
members. Then verify that the returns are python dict's and return
|
|
a dict containing all of the returned values.
|
|
'''
|
|
if self.opts.get('grains_cache', False):
|
|
cfn = os.path.join(
|
|
self.opts['cachedir'],
|
|
'{0}.cache.p'.format('grains')
|
|
)
|
|
if os.path.isfile(cfn):
|
|
grains_cache_age = int(time.time() - os.path.getmtime(cfn))
|
|
if self.opts.get('grains_cache_expiration', 300) >= grains_cache_age and not \
|
|
self.opts.get('refresh_grains_cache', False) and not force_refresh:
|
|
log.debug('Retrieving grains from cache')
|
|
try:
|
|
with salt.utils.fopen(cfn, 'rb') as fp_:
|
|
cached_grains = self.serial.load(fp_)
|
|
return cached_grains
|
|
except (IOError, OSError):
|
|
pass
|
|
else:
|
|
if force_refresh:
|
|
log.debug('Grains refresh requested. Refreshing grains.')
|
|
else:
|
|
log.debug('Grains cache last modified {0} seconds ago and '
|
|
'cache expiration is set to {1}. '
|
|
'Grains cache expired. Refreshing.'.format(
|
|
grains_cache_age,
|
|
self.opts.get('grains_cache_expiration', 300)
|
|
))
|
|
else:
|
|
log.debug('Grains cache file does not exist.')
|
|
grains_data = {}
|
|
funcs = self.gen_functions()
|
|
for key, fun in funcs.items():
|
|
try:
|
|
ret = fun()
|
|
except Exception:
|
|
log.critical(
|
|
'Failed to load grains defined in grain file {0} in '
|
|
'function {1}, error:\n'.format(
|
|
key, fun
|
|
),
|
|
exc_info=True
|
|
)
|
|
continue
|
|
if not isinstance(ret, dict):
|
|
continue
|
|
# The OrderedDict of funcs has core grains at the end. So earlier
|
|
# grains are the ones which should override
|
|
ret.update(grains_data)
|
|
grains_data = ret
|
|
# Write cache if enabled
|
|
if self.opts.get('grains_cache', False):
|
|
cumask = os.umask(077)
|
|
try:
|
|
if salt.utils.is_windows():
|
|
# Make sure cache file isn't read-only
|
|
__salt__['cmd.run']('attrib -R "{0}"'.format(cfn))
|
|
with salt.utils.fopen(cfn, 'w+b') as fp_:
|
|
try:
|
|
self.serial.dump(grains_data, fp_)
|
|
except TypeError:
|
|
# Can't serialize pydsl
|
|
pass
|
|
except (IOError, OSError):
|
|
msg = 'Unable to write to grains cache file {0}'
|
|
log.error(msg.format(cfn))
|
|
os.umask(cumask)
|
|
return grains_data
|
|
|
|
|
|
class LazyLoader(MutableMapping):
|
|
'''
|
|
Lazily load things modules. If anyone asks for len or attempts to iterate this
|
|
will load them all.
|
|
|
|
TODO: negative caching? If you ask for 'foo.bar' and it doesn't exist it will
|
|
look EVERY time unless someone calls load_all()
|
|
'''
|
|
def __init__(self,
|
|
loader,
|
|
functions=None,
|
|
pack=None,
|
|
whitelist=None):
|
|
# create a dict to store module functions in
|
|
self._dict = {}
|
|
|
|
self.loader = loader
|
|
if not functions:
|
|
self.functions = {}
|
|
else:
|
|
self.functions = functions
|
|
self.pack = pack
|
|
self.whitelist = whitelist
|
|
|
|
# have we already loded everything?
|
|
self.loaded = False
|
|
|
|
def _load(self, key):
|
|
'''
|
|
Load a single item if you have it
|
|
'''
|
|
# if the key doesn't have a '.' then it isn't valid for this mod dict
|
|
if '.' not in key:
|
|
raise KeyError
|
|
mod_key = key.split('.', 1)[0]
|
|
if self.whitelist:
|
|
# if the modulename isn't in the whitelist, don't bother
|
|
if mod_key not in self.whitelist:
|
|
raise KeyError
|
|
mod_funcs = self.loader.gen_module(mod_key,
|
|
self.functions,
|
|
pack=self.pack,
|
|
)
|
|
# if you loaded nothing, then we don't have it
|
|
if mod_funcs is None:
|
|
# if we couldn't find it, then it could be a virtual or we don't have it
|
|
# until we have a better way, we have to load them all to know
|
|
# TODO: maybe do a load until, with some glob match first?
|
|
self.load_all()
|
|
return self._dict[key]
|
|
self._dict.update(mod_funcs)
|
|
|
|
def load_all(self):
|
|
'''
|
|
Load all of them
|
|
'''
|
|
self._dict.update(self.loader.gen_functions(pack=self.pack,
|
|
whitelist=self.whitelist))
|
|
self.loaded = True
|
|
|
|
def __setitem__(self, key, val):
|
|
self._dict[key] = val
|
|
|
|
def __delitem__(self, key):
|
|
del self._dict[key]
|
|
|
|
def __getitem__(self, key):
|
|
'''
|
|
Check if the key is ttld out, then do the get
|
|
'''
|
|
if key not in self._dict and not self.loaded:
|
|
# load the item
|
|
self._load(key)
|
|
log.debug('LazyLoaded {0}'.format(key))
|
|
return self._dict[key]
|
|
|
|
def __len__(self):
|
|
# if not loaded,
|
|
if not self.loaded:
|
|
self.load_all()
|
|
return len(self._dict)
|
|
|
|
def __iter__(self):
|
|
if not self.loaded:
|
|
self.load_all()
|
|
return iter(self._dict)
|
|
|
|
|
|
class LazyFilterLoader(LazyLoader):
|
|
'''
|
|
Subclass of LazyLoader which filters the module names (for things such as ext_pillar)
|
|
which have all modules with a single function that we care about
|
|
'''
|
|
def __init__(self,
|
|
loader,
|
|
name,
|
|
functions=None,
|
|
pack=None,
|
|
whitelist=None):
|
|
self.name = name
|
|
LazyLoader.__init__(self,
|
|
loader,
|
|
functions=functions,
|
|
pack=pack,
|
|
whitelist=whitelist)
|
|
|
|
def _load(self, key):
|
|
if self.whitelist:
|
|
# if the modulename isn't in the whitelist, don't bother
|
|
if key not in self.whitelist:
|
|
raise KeyError
|
|
mod_funcs = self.loader.gen_module(key,
|
|
self.functions,
|
|
pack=self.pack,
|
|
)
|
|
# if you loaded nothing, then we don't have it
|
|
if mod_funcs is None:
|
|
# if we couldn't find it, then it could be a virtual or we don't have it
|
|
# until we have a better way, we have to load them all to know
|
|
# TODO: maybe do a load until, with some glob match first?
|
|
self.load_all()
|
|
return self._dict[key]
|
|
|
|
# if we got one, now lets check if we have the function name we want
|
|
for mod_key, mod_fun in mod_funcs.iteritems():
|
|
# if the name (after '.') is "name", then rename to mod_name: fun
|
|
if mod_key[mod_key.index('.') + 1:] == self.name:
|
|
self._dict[mod_key[:mod_key.index('.')]] = mod_fun
|
|
|
|
def load_all(self):
|
|
filtered_funcs = self.loader.filter_func(self.name,
|
|
pack=self.pack,
|
|
whitelist=self.whitelist)
|
|
self._dict.update(filtered_funcs)
|