Merge "Revert "Fallback added for: pillar.galera.(master|slave).admin.user pillar.galera.(master|slave).admin.password""
diff --git a/README.rst b/README.rst
index 5cfa7ae..b89cb51 100644
--- a/README.rst
+++ b/README.rst
@@ -896,6 +896,86 @@
.. literalinclude:: tests/pillar/control_virt_custom.sls
:language: yaml
+Salt shared library
+-------------------
+
+This formula includes 'sharedlib' execution module which is a kind
+of 'library' of function and / or classes to be used in Jinja templates
+or directly as execution module.
+
+'sharedlib' implements a loader that is able to scan nested directories
+and import Python classes / functions from nested modules. Salt doesn't
+allow this as it only imports top-level modules:
+
+https://github.com/saltstack/salt/issues/37273
+
+'sharedlib' implements 4 main functions:
+
+* 'sharedlib.list' - search and print functions / classes found in nested directories
+* 'sharedlib.info' - print docstring of a function (if it exists)
+* 'sharedlib.get' - get function / class object, but not execute it immediately
+* 'sharedlib.call' - get function / class and execute / initialize it with
+ arguments given.
+
+Each of the commands above also have it's own docstring so it's possible to
+use them on a system:
+
+.. code-block:: text
+
+ # salt-call sys.doc sharedlib.list
+ local:
+ ----------
+ sharedlib.list:
+
+ List available functions.
+
+ .. code-block::
+
+ salt-call sharedlib.list
+
+Usage examples:
+
+.. code-block:: text
+
+ # salt-call sharedlib.list
+ local:
+ ----------
+ sharedlib.list:
+ ----------
+ classes:
+ - misc.Test
+ - misc2.Test
+ functions:
+ - misc.cast_dict_keys_to_int
+
+.. code-block:: text
+
+ # salt-call sharedlib.info misc.cast_dict_keys_to_int
+ local:
+ ----------
+ sharedlib.info:
+ ----------
+ misc.cast_dict_keys_to_int:
+
+ Return a dictionary with keys casted to int.
+ This usually is required when you want sort the dict later.
+
+ Jinja example:
+
+ .. code-block: jinja
+
+ {%- set ruleset = salt['sharedlib.call']('misc.cast_dict_keys_to_int', c.get('ruleset', {})) %}
+
+ .. code-block:: jinja
+
+ {%- set func = salt['sharedlib.get']('misc.cast_dict_keys_to_int') %}
+ {%- for c_name, c in t.chains.items() %}
+ {%- set ruleset = func(c.get('ruleset', {})) %}
+ {%- for rule_id, r in ruleset | dictsort %}
+ ...
+ {%- endfor %}
+
+
Usage
=====
diff --git a/_modules/sharedlib/__init__.py b/_modules/sharedlib/__init__.py
new file mode 100644
index 0000000..609d13c
--- /dev/null
+++ b/_modules/sharedlib/__init__.py
@@ -0,0 +1,110 @@
+from inspect import getmembers, isclass, isfunction
+from functools import wraps
+
+import importlib
+import pkgutil
+import sys
+
+
+class Loader(object):
+ def __init__(self, name):
+ self.name = name
+ self.loader = pkgutil.get_loader(self.name)
+ self.path = self.loader.filename
+ sys.path.append(self.path)
+
+ def get(self, name, *args, **kwargs):
+ parts = name.split('.')
+ module_name = '.'.join(parts[:-1])
+ module = importlib.import_module(module_name)
+ module.__salt__ = __salt__
+ return getattr(module, parts[-1])
+
+ def info(self, name, *args, **kwargs):
+ return {name: getattr(self.get(name), '__doc__')}
+
+ def call(self, name, *args, **kwargs):
+ return self.get(name)(*args, **kwargs)
+
+ def list(self, *args, **kwargs):
+ classes = set()
+ functions = set()
+ for _,name,ispkg in pkgutil.walk_packages([self.path,]):
+ if ispkg:
+ continue
+ try:
+ module = importlib.import_module(name)
+ for member_name,_ in getmembers(module, isfunction):
+ functions.add('.'.join((name, member_name)))
+ for member_name,_ in getmembers(module, isclass):
+ classes.add('.'.join((name, member_name)))
+ except:
+ pass
+ return {'functions': sorted(functions), 'classes': sorted(classes)}
+
+
+LOADER = Loader('sharedlib')
+
+
+def loader_attr(name):
+ def wrapper(f):
+ @wraps(f)
+ def func_wrapper(*args, **kwargs):
+ return getattr(LOADER, name)(*args, **kwargs)
+ return func_wrapper
+ return wrapper
+
+
+def wrap_output(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ return {kwargs.get('__pub_fun'): f(*args, **kwargs)}
+ return wrapper
+
+
+@wrap_output
+@loader_attr('list')
+def list(*args, **kwargs):
+ '''
+ List available functions.
+
+ .. code-block:: text
+
+ salt-call sharedlib.list
+ '''
+
+@wrap_output
+@loader_attr('info')
+def info(name, *args, **kwargs):
+ '''
+ Returns info about function.
+
+ .. code-block:: text
+
+ salt-call sharedlib.info function.name
+ '''
+
+@loader_attr('get')
+def get(name):
+ '''
+ Returns function / class object (but not calls it) for later use.
+ This is mostly useful in jinja templates.
+
+ .. code-block:: jinja
+
+ {%- set func = salt['sharedlib.get']('function.name') %}
+ {%- func(*args, **kwargs) %}
+ '''
+
+@loader_attr('call')
+def call(name, *args, **kwargs):
+ '''
+ Calls a function from library.
+
+ For more information about a function being called use 'sharedlib.info'.
+
+ .. code-block:: jinja
+
+ {%- set x = salt['sharedlib.call']('function.name', *args, **kwargs) %}
+ '''
+
diff --git a/_modules/sharedlib/misc.py b/_modules/sharedlib/misc.py
new file mode 100644
index 0000000..5a6f5c4
--- /dev/null
+++ b/_modules/sharedlib/misc.py
@@ -0,0 +1,22 @@
+def cast_dict_keys_to_int(x, *args, **kwargs):
+ '''
+ Return a dictionary with keys casted to int.
+ This usually is required when you want sort the dict later.
+
+ Jinja examples:
+
+ .. code-block: jinja
+
+ {%- set ruleset = salt['sharedlib.call']('misc.cast_dict_keys_to_int', c.get('ruleset', {})) %}
+
+ .. code-block:: jinja
+
+ {%- set func = salt['sharedlib.get']('misc.cast_dict_keys_to_int') %}
+ {%- for c_name, c in t.chains.items() %}
+ {%- set ruleset = func(c.get('ruleset', {})) %}
+ {%- for rule_id, r in ruleset | dictsort %}
+ ...
+ {%- endfor %}
+ '''
+ return dict([(int(key),value) for key,value in x.items()])
+