mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Merge pull request #48809 from cro/matcher_in_loader2
Matcher in loader, take 4
This commit is contained in:
commit
1db2d2e4d0
81
doc/topics/matchers/index.rst
Normal file
81
doc/topics/matchers/index.rst
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
.. _matchers:
|
||||||
|
|
||||||
|
========
|
||||||
|
Matchers
|
||||||
|
========
|
||||||
|
|
||||||
|
.. versionadded:: Flourine
|
||||||
|
|
||||||
|
Matchers are modules that provide Salt's targeting abilities. As of the
|
||||||
|
Flourine release, matchers can be dynamically loaded. Currently new matchers
|
||||||
|
cannot be created because the required plumbing for the CLI does not exist yet.
|
||||||
|
Existing matchers may have their functionality altered or extended.
|
||||||
|
|
||||||
|
For details of targeting methods, see the :ref:`Targeting <targeting>` topic.
|
||||||
|
|
||||||
|
A matcher module must have a function called ``match()``. This function ends up
|
||||||
|
becoming a method on the Matcher class. All matcher functions require at least
|
||||||
|
two arguments, ``self`` (because the function will be turned into a method), and
|
||||||
|
``tgt``, which is the actual target string. The grains and pillar matchers also
|
||||||
|
take a ``delimiter`` argument and should default to ``DEFAULT_TARGET_DELIM``.
|
||||||
|
|
||||||
|
Like other Salt loadable modules, modules that override built-in functionality
|
||||||
|
can be placed in ``file_roots`` in a special directory and then copied to the
|
||||||
|
minion through the normal sync process. :py:func:`saltutil.sync_all <salt.modules.saltutil.sync_all>`
|
||||||
|
will transfer all loadable modules, and the Flourine release introduces
|
||||||
|
:py:func:`saltutil.sync_matchers <salt.modules.saltutil.sync_matchers>`. For matchers, the directory is
|
||||||
|
``/srv/salt/_matchers`` (assuming your ``file_roots`` is set to the default
|
||||||
|
``/srv/salt``).
|
||||||
|
|
||||||
|
As an example, let's modify the ``list`` matcher to have the separator be a
|
||||||
|
'``/``' instead of the default '``,``'.
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
def match(self, tgt):
|
||||||
|
'''
|
||||||
|
Determines if this host is on the list
|
||||||
|
'''
|
||||||
|
if isinstance(tgt, six.string_types):
|
||||||
|
# The stock matcher splits on `,`. Change to `/` below.
|
||||||
|
tgt = tgt.split('/')
|
||||||
|
return bool(self.opts['id'] in tgt)
|
||||||
|
|
||||||
|
|
||||||
|
Place this code in a file called ``list_matcher.py`` in ``_matchers`` in your
|
||||||
|
``file_roots``. Sync this down to your minions with
|
||||||
|
:py:func:`saltutil.sync_matchers <salt.modules.saltutil.sync_matchers>`.
|
||||||
|
Then attempt to match with the following, replacing ``minionX`` with three of your minions.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
salt -L 'minion1/minion2/minion3' test.ping
|
||||||
|
|
||||||
|
|
||||||
|
Three of your minions should respond.
|
||||||
|
|
||||||
|
The current supported matchers and associated filenames are
|
||||||
|
|
||||||
|
=============== ====================== ===================
|
||||||
|
Salt CLI Switch Match Type Filename
|
||||||
|
=============== ====================== ===================
|
||||||
|
<none> Glob glob_match.py
|
||||||
|
-C Compound compound_match.py
|
||||||
|
-E Perl-Compatible pcre_match.py
|
||||||
|
Regular Expressions
|
||||||
|
-L List list_match.py
|
||||||
|
-G Grain grain_match.py
|
||||||
|
-P Grain Perl-Compatible grain_pcre_match.py
|
||||||
|
Regular Expressions
|
||||||
|
-N Nodegroup nodegroup_match.py
|
||||||
|
-R Range range_match.py
|
||||||
|
-I Pillar pillar_match.py
|
||||||
|
-J Pillar Perl-Compatible pillar_pcre.py
|
||||||
|
Regular Expressions
|
||||||
|
-S IP-Classless Internet ipcidr_match.py
|
||||||
|
Domain Routing
|
||||||
|
=============== ====================== ===================
|
@ -111,3 +111,19 @@ There are many ways to target individual minions or groups of minions in Salt:
|
|||||||
nodegroups
|
nodegroups
|
||||||
batch
|
batch
|
||||||
range
|
range
|
||||||
|
|
||||||
|
|
||||||
|
Loadable Matchers
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. versionadded:: Flourine
|
||||||
|
|
||||||
|
Internally targeting is implemented with chunks of code called Matchers. As of
|
||||||
|
the Flourine release, matchers can be loaded dynamically. Currently new matchers
|
||||||
|
cannot be created, but existing matchers can have their functionality altered or
|
||||||
|
extended. For more information on Matchers see
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
Loadable Matchers <../matchers/index.rst>
|
||||||
|
@ -74,7 +74,7 @@ class SSHHighState(salt.state.BaseHighState):
|
|||||||
self.client = fsclient
|
self.client = fsclient
|
||||||
salt.state.BaseHighState.__init__(self, opts)
|
salt.state.BaseHighState.__init__(self, opts)
|
||||||
self.state = SSHState(opts, pillar, wrapper)
|
self.state = SSHState(opts, pillar, wrapper)
|
||||||
self.matcher = salt.minion.Matcher(self.opts)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.tops = salt.loader.tops(self.opts)
|
self.tops = salt.loader.tops(self.opts)
|
||||||
|
|
||||||
self._pydsl_all_decls = {}
|
self._pydsl_all_decls = {}
|
||||||
|
@ -294,6 +294,17 @@ def raw_mod(opts, name, functions, mod='modules'):
|
|||||||
return dict(loader._dict) # return a copy of *just* the funcs for `name`
|
return dict(loader._dict) # return a copy of *just* the funcs for `name`
|
||||||
|
|
||||||
|
|
||||||
|
def matchers(opts):
|
||||||
|
'''
|
||||||
|
Return the matcher services plugins
|
||||||
|
'''
|
||||||
|
return LazyLoader(
|
||||||
|
_module_dirs(opts, 'matchers'),
|
||||||
|
opts,
|
||||||
|
tag='matchers'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def engines(opts, functions, runners, utils, proxy=None):
|
def engines(opts, functions, runners, utils, proxy=None):
|
||||||
'''
|
'''
|
||||||
Return the master services plugins
|
Return the master services plugins
|
||||||
|
97
salt/matchers/__init__.py
Normal file
97
salt/matchers/__init__.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Salt package
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Import Python libs
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# All salt related deprecation warnings should be shown once each!
|
||||||
|
warnings.filterwarnings(
|
||||||
|
'once', # Show once
|
||||||
|
'', # No deprecation message match
|
||||||
|
DeprecationWarning, # This filter is for DeprecationWarnings
|
||||||
|
r'^(salt|salt\.(.*))$' # Match module(s) 'salt' and 'salt.<whatever>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# While we are supporting Python2.6, hide nested with-statements warnings
|
||||||
|
warnings.filterwarnings(
|
||||||
|
'ignore',
|
||||||
|
'With-statements now directly support multiple context managers',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
# Filter the backports package UserWarning about being re-imported
|
||||||
|
warnings.filterwarnings(
|
||||||
|
'ignore',
|
||||||
|
'^Module backports was already imported from (.*), but (.*) is being added to sys.path$',
|
||||||
|
UserWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __define_global_system_encoding_variable__():
|
||||||
|
import sys
|
||||||
|
# This is the most trustworthy source of the system encoding, though, if
|
||||||
|
# salt is being imported after being daemonized, this information is lost
|
||||||
|
# and reset to None
|
||||||
|
encoding = None
|
||||||
|
|
||||||
|
if not sys.platform.startswith('win') and sys.stdin is not None:
|
||||||
|
# On linux we can rely on sys.stdin for the encoding since it
|
||||||
|
# most commonly matches the filesystem encoding. This however
|
||||||
|
# does not apply to windows
|
||||||
|
encoding = sys.stdin.encoding
|
||||||
|
|
||||||
|
if not encoding:
|
||||||
|
# If the system is properly configured this should return a valid
|
||||||
|
# encoding. MS Windows has problems with this and reports the wrong
|
||||||
|
# encoding
|
||||||
|
import locale
|
||||||
|
try:
|
||||||
|
encoding = locale.getdefaultlocale()[-1]
|
||||||
|
except ValueError:
|
||||||
|
# A bad locale setting was most likely found:
|
||||||
|
# https://github.com/saltstack/salt/issues/26063
|
||||||
|
pass
|
||||||
|
|
||||||
|
# This is now garbage collectable
|
||||||
|
del locale
|
||||||
|
if not encoding:
|
||||||
|
# This is most likely ascii which is not the best but we were
|
||||||
|
# unable to find a better encoding. If this fails, we fall all
|
||||||
|
# the way back to ascii
|
||||||
|
encoding = sys.getdefaultencoding()
|
||||||
|
if not encoding:
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
# Mac OS X uses UTF-8
|
||||||
|
encoding = 'utf-8'
|
||||||
|
elif sys.platform.startswith('win'):
|
||||||
|
# Windows uses a configurable encoding; on Windows, Python uses the name “mbcs”
|
||||||
|
# to refer to whatever the currently configured encoding is.
|
||||||
|
encoding = 'mbcs'
|
||||||
|
else:
|
||||||
|
# On linux default to ascii as a last resort
|
||||||
|
encoding = 'ascii'
|
||||||
|
|
||||||
|
# We can't use six.moves.builtins because these builtins get deleted sooner
|
||||||
|
# than expected. See:
|
||||||
|
# https://github.com/saltstack/salt/issues/21036
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
import __builtin__ as builtins # pylint: disable=incompatible-py3-code
|
||||||
|
else:
|
||||||
|
import builtins # pylint: disable=import-error
|
||||||
|
|
||||||
|
# Define the detected encoding as a built-in variable for ease of use
|
||||||
|
setattr(builtins, '__salt_system_encoding__', encoding)
|
||||||
|
|
||||||
|
# This is now garbage collectable
|
||||||
|
del sys
|
||||||
|
del builtins
|
||||||
|
del encoding
|
||||||
|
|
||||||
|
|
||||||
|
__define_global_system_encoding_variable__()
|
||||||
|
|
||||||
|
# This is now garbage collectable
|
||||||
|
del __define_global_system_encoding_variable__
|
30
salt/matchers/cache_match.py
Normal file
30
salt/matchers/cache_match.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default cache matcher function. It only exists for the master,
|
||||||
|
this is why there is only a ``mmatch()`` but not ``match()``.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.utils.minions # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def mmatch(expr,
|
||||||
|
delimiter,
|
||||||
|
greedy,
|
||||||
|
search_type,
|
||||||
|
regex_match=False,
|
||||||
|
exact_match=False):
|
||||||
|
'''
|
||||||
|
Helper function to search for minions in master caches
|
||||||
|
If 'greedy' return accepted minions that matched by the condition or absent in the cache.
|
||||||
|
If not 'greedy' return the only minions have cache data and matched by the condition.
|
||||||
|
'''
|
||||||
|
ckminions = salt.utils.minions.CkMinions(__opts__)
|
||||||
|
|
||||||
|
return ckminions._check_cache_minions(expr, delimiter, greedy,
|
||||||
|
search_type, regex_match=regex_match,
|
||||||
|
exact_match=exact_match)
|
112
salt/matchers/compound_match.py
Normal file
112
salt/matchers/compound_match.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default compound matcher function.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.loader
|
||||||
|
import salt.utils.minions # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
HAS_RANGE = False
|
||||||
|
try:
|
||||||
|
import seco.range # pylint: disable=unused-import
|
||||||
|
HAS_RANGE = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Runs the compound target check
|
||||||
|
'''
|
||||||
|
nodegroups = __opts__.get('nodegroups', {})
|
||||||
|
matchers = salt.loader.matchers(__opts__)
|
||||||
|
|
||||||
|
if not isinstance(tgt, six.string_types) and not isinstance(tgt, (list, tuple)):
|
||||||
|
log.error('Compound target received that is neither string, list nor tuple')
|
||||||
|
return False
|
||||||
|
log.debug('compound_match: %s ? %s', __opts__['id'], tgt)
|
||||||
|
ref = {'G': 'grain',
|
||||||
|
'P': 'grain_pcre',
|
||||||
|
'I': 'pillar',
|
||||||
|
'J': 'pillar_pcre',
|
||||||
|
'L': 'list',
|
||||||
|
'N': None, # Nodegroups should already be expanded
|
||||||
|
'S': 'ipcidr',
|
||||||
|
'E': 'pcre'}
|
||||||
|
if HAS_RANGE:
|
||||||
|
ref['R'] = 'range'
|
||||||
|
|
||||||
|
results = []
|
||||||
|
opers = ['and', 'or', 'not', '(', ')']
|
||||||
|
|
||||||
|
if isinstance(tgt, six.string_types):
|
||||||
|
words = tgt.split()
|
||||||
|
else:
|
||||||
|
# we make a shallow copy in order to not affect the passed in arg
|
||||||
|
words = tgt[:]
|
||||||
|
|
||||||
|
while words:
|
||||||
|
word = words.pop(0)
|
||||||
|
target_info = salt.utils.minions.parse_target(word)
|
||||||
|
|
||||||
|
# Easy check first
|
||||||
|
if word in opers:
|
||||||
|
if results:
|
||||||
|
if results[-1] == '(' and word in ('and', 'or'):
|
||||||
|
log.error('Invalid beginning operator after "(": %s', word)
|
||||||
|
return False
|
||||||
|
if word == 'not':
|
||||||
|
if not results[-1] in ('and', 'or', '('):
|
||||||
|
results.append('and')
|
||||||
|
results.append(word)
|
||||||
|
else:
|
||||||
|
# seq start with binary oper, fail
|
||||||
|
if word not in ['(', 'not']:
|
||||||
|
log.error('Invalid beginning operator: %s', word)
|
||||||
|
return False
|
||||||
|
results.append(word)
|
||||||
|
|
||||||
|
elif target_info and target_info['engine']:
|
||||||
|
if 'N' == target_info['engine']:
|
||||||
|
# if we encounter a node group, just evaluate it in-place
|
||||||
|
decomposed = salt.utils.minions.nodegroup_comp(target_info['pattern'], nodegroups)
|
||||||
|
if decomposed:
|
||||||
|
words = decomposed + words
|
||||||
|
continue
|
||||||
|
|
||||||
|
engine = ref.get(target_info['engine'])
|
||||||
|
if not engine:
|
||||||
|
# If an unknown engine is called at any time, fail out
|
||||||
|
log.error(
|
||||||
|
'Unrecognized target engine "%s" for target '
|
||||||
|
'expression "%s"', target_info['engine'], word
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
engine_args = [target_info['pattern']]
|
||||||
|
engine_kwargs = {}
|
||||||
|
if target_info['delimiter']:
|
||||||
|
engine_kwargs['delimiter'] = target_info['delimiter']
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
six.text_type(matchers['{0}_match.match'.format(engine)](*engine_args, **engine_kwargs))
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# The match is not explicitly defined, evaluate it as a glob
|
||||||
|
results.append(six.text_type(matchers['glob_match.match'](word)))
|
||||||
|
|
||||||
|
results = ' '.join(results)
|
||||||
|
log.debug('compound_match %s ? "%s" => "%s"', __opts__['id'], tgt, results)
|
||||||
|
try:
|
||||||
|
return eval(results) # pylint: disable=W0123
|
||||||
|
except Exception:
|
||||||
|
log.error(
|
||||||
|
'Invalid compound target: %s for results: %s', tgt, results)
|
||||||
|
return False
|
||||||
|
return False
|
23
salt/matchers/compound_pillar_exact_match.py
Normal file
23
salt/matchers/compound_pillar_exact_match.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default pillar exact matcher for compound matches.
|
||||||
|
|
||||||
|
There is no minion-side equivalent for this, so consequently there is no ``match()``
|
||||||
|
function below, only an ``mmatch()``
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import salt.utils.minions # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def mmatch(expr, delimiter, greedy):
|
||||||
|
'''
|
||||||
|
Return the minions found by looking via pillar
|
||||||
|
'''
|
||||||
|
ckminions = salt.utils.minions.CkMinions(__opts__)
|
||||||
|
return ckminions._check_compound_minions(expr, delimiter, greedy,
|
||||||
|
pillar_exact=True)
|
38
salt/matchers/confirm_top.py
Normal file
38
salt/matchers/confirm_top.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
The matcher subsystem needs a function called 'confirm_top', which
|
||||||
|
takes the data passed to a top file environment and determines if that
|
||||||
|
data matches this minion.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import salt.loader
|
||||||
|
|
||||||
|
log = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_top(match, data, nodegroups=None):
|
||||||
|
'''
|
||||||
|
Takes the data passed to a top file environment and determines if the
|
||||||
|
data matches this minion
|
||||||
|
'''
|
||||||
|
matcher = 'compound'
|
||||||
|
if not data:
|
||||||
|
log.error('Received bad data when setting the match from the top '
|
||||||
|
'file')
|
||||||
|
return False
|
||||||
|
for item in data:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
if 'match' in item:
|
||||||
|
matcher = item['match']
|
||||||
|
|
||||||
|
matchers = salt.loader.matchers(__opts__)
|
||||||
|
funcname = matcher + '_match.match'
|
||||||
|
if matcher == 'nodegroup':
|
||||||
|
return matchers[funcname](match, nodegroups)
|
||||||
|
else:
|
||||||
|
m = matchers[funcname]
|
||||||
|
return m(match)
|
||||||
|
# except TypeError, KeyError:
|
||||||
|
# log.error('Attempting to match with unknown matcher: %s', matcher)
|
46
salt/matchers/data_match.py
Normal file
46
salt/matchers/data_match.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default data matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import fnmatch
|
||||||
|
import logging
|
||||||
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.utils.minions # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.utils.network # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.loader # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, functions=None):
|
||||||
|
'''
|
||||||
|
Match based on the local data store on the minion
|
||||||
|
'''
|
||||||
|
if functions is None:
|
||||||
|
utils = salt.loader.utils(__opts__)
|
||||||
|
functions = salt.loader.minion_mods(__opts__, utils=utils)
|
||||||
|
comps = tgt.split(':')
|
||||||
|
if len(comps) < 2:
|
||||||
|
return False
|
||||||
|
val = functions['data.getval'](comps[0])
|
||||||
|
if val is None:
|
||||||
|
# The value is not defined
|
||||||
|
return False
|
||||||
|
if isinstance(val, list):
|
||||||
|
# We are matching a single component to a single list member
|
||||||
|
for member in val:
|
||||||
|
if fnmatch.fnmatch(six.text_type(member).lower(), comps[1].lower()):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if isinstance(val, dict):
|
||||||
|
if comps[1] in val:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
return bool(fnmatch.fnmatch(
|
||||||
|
val,
|
||||||
|
comps[1],
|
||||||
|
))
|
18
salt/matchers/glob_match.py
Normal file
18
salt/matchers/glob_match.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default glob matcher function.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import fnmatch
|
||||||
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Returns true if the passed glob matches the id
|
||||||
|
'''
|
||||||
|
if not isinstance(tgt, six.string_types):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return fnmatch.fnmatch(__opts__['id'], tgt)
|
26
salt/matchers/grain_match.py
Normal file
26
salt/matchers/grain_match.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default grains matcher function.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.defaults import DEFAULT_TARGET_DELIM # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
||||||
|
'''
|
||||||
|
Reads in the grains glob match
|
||||||
|
'''
|
||||||
|
log.debug('grains target: %s', tgt)
|
||||||
|
if delimiter not in tgt:
|
||||||
|
log.error('Got insufficient arguments for grains match '
|
||||||
|
'statement from master')
|
||||||
|
return False
|
||||||
|
return salt.utils.data.subdict_match(
|
||||||
|
__opts__['grains'], tgt, delimiter=delimiter
|
||||||
|
)
|
25
salt/matchers/grain_pcre_match.py
Normal file
25
salt/matchers/grain_pcre_match.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default grains PCRE matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.defaults import DEFAULT_TARGET_DELIM # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
||||||
|
'''
|
||||||
|
Matches a grain based on regex
|
||||||
|
'''
|
||||||
|
log.debug('grains pcre target: %s', tgt)
|
||||||
|
if delimiter not in tgt:
|
||||||
|
log.error('Got insufficient arguments for grains pcre match '
|
||||||
|
'statement from master')
|
||||||
|
return False
|
||||||
|
return salt.utils.data.subdict_match(
|
||||||
|
__opts__['grains'], tgt, delimiter=delimiter, regex_match=True)
|
45
salt/matchers/ipcidr_match.py
Normal file
45
salt/matchers/ipcidr_match.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default ipcidr matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.ext import six # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
import salt.utils.network # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
import ipaddress
|
||||||
|
else:
|
||||||
|
import salt.ext.ipaddress as ipaddress
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Matches based on IP address or CIDR notation
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
# Target is an address?
|
||||||
|
tgt = ipaddress.ip_address(tgt)
|
||||||
|
except: # pylint: disable=bare-except
|
||||||
|
try:
|
||||||
|
# Target is a network?
|
||||||
|
tgt = ipaddress.ip_network(tgt)
|
||||||
|
except: # pylint: disable=bare-except
|
||||||
|
log.error('Invalid IP/CIDR target: %s', tgt)
|
||||||
|
return []
|
||||||
|
proto = 'ipv{0}'.format(tgt.version)
|
||||||
|
|
||||||
|
grains = __opts__['grains']
|
||||||
|
|
||||||
|
if proto not in grains:
|
||||||
|
match = False
|
||||||
|
elif isinstance(tgt, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
|
||||||
|
match = six.text_type(tgt) in grains[proto]
|
||||||
|
else:
|
||||||
|
match = salt.utils.network.in_subnet(tgt, grains[proto])
|
||||||
|
|
||||||
|
return match
|
39
salt/matchers/list_match.py
Normal file
39
salt/matchers/list_match.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default list matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Determines if this host is on the list
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
if ',' + __opts__['id'] + ',' in tgt \
|
||||||
|
or tgt.startswith(__opts__['id'] + ',') \
|
||||||
|
or tgt.endswith(',' + __opts__['id']):
|
||||||
|
return True
|
||||||
|
# tgt is a string, which we know because the if statement above did not
|
||||||
|
# cause one of the exceptions being caught. Therefore, look for an
|
||||||
|
# exact match. (e.g. salt -L foo test.ping)
|
||||||
|
return __opts__['id'] == tgt
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# tgt is not a string, maybe it's a sequence type?
|
||||||
|
try:
|
||||||
|
return __opts__['id'] in tgt
|
||||||
|
except Exception:
|
||||||
|
# tgt was likely some invalid type
|
||||||
|
return False
|
||||||
|
|
||||||
|
# We should never get here based on the return statements in the logic
|
||||||
|
# above. If we do, it is because something above changed, and should be
|
||||||
|
# considered as a bug. Log a warning to help us catch this.
|
||||||
|
log.warning(
|
||||||
|
'List matcher unexpectedly did not return, for target %s, '
|
||||||
|
'this is probably a bug.', tgt
|
||||||
|
)
|
||||||
|
return False
|
22
salt/matchers/nodegroup_match.py
Normal file
22
salt/matchers/nodegroup_match.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default nodegroup matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import salt.utils.minions # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.loader
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, nodegroups):
|
||||||
|
'''
|
||||||
|
This is a compatibility matcher and is NOT called when using
|
||||||
|
nodegroups for remote execution, but is called when the nodegroups
|
||||||
|
matcher is used in states
|
||||||
|
'''
|
||||||
|
if tgt in nodegroups:
|
||||||
|
matchers = salt.loader.matchers(__opts__)
|
||||||
|
return matchers['compound_match.match'](
|
||||||
|
salt.utils.minions.nodegroup_comp(tgt, nodegroups)
|
||||||
|
)
|
||||||
|
return False
|
14
salt/matchers/pcre_match.py
Normal file
14
salt/matchers/pcre_match.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default pcre matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Returns true if the passed pcre regex matches
|
||||||
|
'''
|
||||||
|
return bool(re.match(tgt, __opts__['id']))
|
25
salt/matchers/pillar_exact_match.py
Normal file
25
salt/matchers/pillar_exact_match.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default pillar exact matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, delimiter=':'):
|
||||||
|
'''
|
||||||
|
Reads in the pillar match, no globbing, no PCRE
|
||||||
|
'''
|
||||||
|
log.debug('pillar target: %s', tgt)
|
||||||
|
if delimiter not in tgt:
|
||||||
|
log.error('Got insufficient arguments for pillar match '
|
||||||
|
'statement from master')
|
||||||
|
return False
|
||||||
|
return salt.utils.data.subdict_match(__opts__['pillar'],
|
||||||
|
tgt,
|
||||||
|
delimiter=delimiter,
|
||||||
|
exact_match=True)
|
25
salt/matchers/pillar_match.py
Normal file
25
salt/matchers/pillar_match.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default pillar matcher function.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.defaults import DEFAULT_TARGET_DELIM # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
||||||
|
'''
|
||||||
|
Reads in the pillar glob match
|
||||||
|
'''
|
||||||
|
log.debug('pillar target: %s', tgt)
|
||||||
|
if delimiter not in tgt:
|
||||||
|
log.error('Got insufficient arguments for pillar match '
|
||||||
|
'statement from master')
|
||||||
|
return False
|
||||||
|
return salt.utils.data.subdict_match(
|
||||||
|
__opts__['pillar'], tgt, delimiter=delimiter
|
||||||
|
)
|
25
salt/matchers/pillar_pcre_match.py
Normal file
25
salt/matchers/pillar_pcre_match.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default pillar PCRE matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from salt.defaults import DEFAULT_TARGET_DELIM # pylint: disable=3rd-party-module-not-gated
|
||||||
|
import salt.utils.data # pylint: disable=3rd-party-module-not-gated
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
||||||
|
'''
|
||||||
|
Reads in the pillar pcre match
|
||||||
|
'''
|
||||||
|
log.debug('pillar PCRE target: %s', tgt)
|
||||||
|
if delimiter not in tgt:
|
||||||
|
log.error('Got insufficient arguments for pillar PCRE match '
|
||||||
|
'statement from master')
|
||||||
|
return False
|
||||||
|
return salt.utils.data.subdict_match(
|
||||||
|
__opts__['pillar'], tgt, delimiter=delimiter, regex_match=True
|
||||||
|
)
|
30
salt/matchers/range_match.py
Normal file
30
salt/matchers/range_match.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
This is the default range matcher.
|
||||||
|
'''
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
HAS_RANGE = False
|
||||||
|
try:
|
||||||
|
import seco.range
|
||||||
|
HAS_RANGE = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def match(tgt):
|
||||||
|
'''
|
||||||
|
Matches based on range cluster
|
||||||
|
'''
|
||||||
|
if HAS_RANGE:
|
||||||
|
range_ = seco.range.Range(__opts__['range_server'])
|
||||||
|
try:
|
||||||
|
return __opts__['grains']['fqdn'] in range_.expand(tgt)
|
||||||
|
except seco.range.RangeException as exc:
|
||||||
|
log.debug('Range exception in compound match: %s', exc)
|
||||||
|
return False
|
||||||
|
return False
|
331
salt/minion.py
331
salt/minion.py
@ -6,14 +6,12 @@ Routines to set up a minion
|
|||||||
from __future__ import absolute_import, print_function, with_statement, unicode_literals
|
from __future__ import absolute_import, print_function, with_statement, unicode_literals
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import copy
|
import copy
|
||||||
import time
|
import time
|
||||||
import types
|
import types
|
||||||
import signal
|
import signal
|
||||||
import random
|
import random
|
||||||
import fnmatch
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
@ -27,10 +25,6 @@ from binascii import crc32
|
|||||||
# Import Salt Libs
|
# Import Salt Libs
|
||||||
# pylint: disable=import-error,no-name-in-module,redefined-builtin
|
# pylint: disable=import-error,no-name-in-module,redefined-builtin
|
||||||
from salt.ext import six
|
from salt.ext import six
|
||||||
if six.PY3:
|
|
||||||
import ipaddress
|
|
||||||
else:
|
|
||||||
import salt.ext.ipaddress as ipaddress
|
|
||||||
from salt.ext.six.moves import range
|
from salt.ext.six.moves import range
|
||||||
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO
|
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO
|
||||||
import salt.defaults.exitcodes
|
import salt.defaults.exitcodes
|
||||||
@ -40,13 +34,6 @@ from salt.utils.ctx import RequestContext
|
|||||||
# pylint: enable=no-name-in-module,redefined-builtin
|
# pylint: enable=no-name-in-module,redefined-builtin
|
||||||
import tornado
|
import tornado
|
||||||
|
|
||||||
HAS_RANGE = False
|
|
||||||
try:
|
|
||||||
import seco.range
|
|
||||||
HAS_RANGE = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
HAS_PSUTIL = False
|
HAS_PSUTIL = False
|
||||||
try:
|
try:
|
||||||
import salt.utils.psutil_compat as psutil
|
import salt.utils.psutil_compat as psutil
|
||||||
@ -862,7 +849,8 @@ class SMinion(MinionBase):
|
|||||||
self.utils,
|
self.utils,
|
||||||
self.serializers)
|
self.serializers)
|
||||||
self.rend = salt.loader.render(self.opts, self.functions)
|
self.rend = salt.loader.render(self.opts, self.functions)
|
||||||
self.matcher = Matcher(self.opts, self.functions)
|
# self.matcher = Matcher(self.opts, self.functions)
|
||||||
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.functions['sys.reload_modules'] = self.gen_modules
|
self.functions['sys.reload_modules'] = self.gen_modules
|
||||||
self.executors = salt.loader.executors(self.opts)
|
self.executors = salt.loader.executors(self.opts)
|
||||||
|
|
||||||
@ -924,7 +912,7 @@ class MasterMinion(object):
|
|||||||
if self.mk_rend:
|
if self.mk_rend:
|
||||||
self.rend = salt.loader.render(self.opts, self.functions)
|
self.rend = salt.loader.render(self.opts, self.functions)
|
||||||
if self.mk_matcher:
|
if self.mk_matcher:
|
||||||
self.matcher = Matcher(self.opts, self.functions)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.functions['sys.reload_modules'] = self.gen_modules
|
self.functions['sys.reload_modules'] = self.gen_modules
|
||||||
|
|
||||||
|
|
||||||
@ -2158,6 +2146,13 @@ class Minion(MinionBase):
|
|||||||
log.debug('Refreshing beacons.')
|
log.debug('Refreshing beacons.')
|
||||||
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
||||||
|
|
||||||
|
def matchers_refresh(self):
|
||||||
|
'''
|
||||||
|
Refresh the matchers
|
||||||
|
'''
|
||||||
|
log.debug('Refreshing matchers.')
|
||||||
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
|
|
||||||
# TODO: only allow one future in flight at a time?
|
# TODO: only allow one future in flight at a time?
|
||||||
@tornado.gen.coroutine
|
@tornado.gen.coroutine
|
||||||
def pillar_refresh(self, force_refresh=False):
|
def pillar_refresh(self, force_refresh=False):
|
||||||
@ -2334,6 +2329,8 @@ class Minion(MinionBase):
|
|||||||
)
|
)
|
||||||
elif tag.startswith('beacons_refresh'):
|
elif tag.startswith('beacons_refresh'):
|
||||||
self.beacons_refresh()
|
self.beacons_refresh()
|
||||||
|
elif tag.startswith('matchers_refresh'):
|
||||||
|
self.matchers_refresh()
|
||||||
elif tag.startswith('manage_schedule'):
|
elif tag.startswith('manage_schedule'):
|
||||||
self.manage_schedule(tag, data)
|
self.manage_schedule(tag, data)
|
||||||
elif tag.startswith('manage_beacons'):
|
elif tag.startswith('manage_beacons'):
|
||||||
@ -2531,7 +2528,8 @@ class Minion(MinionBase):
|
|||||||
self.functions, self.returners, self.function_errors, self.executors = self._load_modules()
|
self.functions, self.returners, self.function_errors, self.executors = self._load_modules()
|
||||||
self.serial = salt.payload.Serial(self.opts)
|
self.serial = salt.payload.Serial(self.opts)
|
||||||
self.mod_opts = self._prep_mod_opts()
|
self.mod_opts = self._prep_mod_opts()
|
||||||
self.matcher = Matcher(self.opts, self.functions)
|
# self.matcher = Matcher(self.opts, self.functions)
|
||||||
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
||||||
uid = salt.utils.user.get_uid(user=self.opts.get('user', None))
|
uid = salt.utils.user.get_uid(user=self.opts.get('user', None))
|
||||||
self.proc_dir = get_proc_dir(self.opts['cachedir'], uid=uid)
|
self.proc_dir = get_proc_dir(self.opts['cachedir'], uid=uid)
|
||||||
@ -2741,8 +2739,7 @@ class Minion(MinionBase):
|
|||||||
# publication if the master does not determine that it should.
|
# publication if the master does not determine that it should.
|
||||||
|
|
||||||
if 'tgt_type' in load:
|
if 'tgt_type' in load:
|
||||||
match_func = getattr(self.matcher,
|
match_func = self.matchers.get('{0}_match.match'.format(load['tgt_type']), None)
|
||||||
'{0}_match'.format(load['tgt_type']), None)
|
|
||||||
if match_func is None:
|
if match_func is None:
|
||||||
return False
|
return False
|
||||||
if load['tgt_type'] in ('grain', 'grain_pcre', 'pillar'):
|
if load['tgt_type'] in ('grain', 'grain_pcre', 'pillar'):
|
||||||
@ -2752,7 +2749,7 @@ class Minion(MinionBase):
|
|||||||
elif not match_func(load['tgt']):
|
elif not match_func(load['tgt']):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if not self.matcher.glob_match(load['tgt']):
|
if not self.matchers['glob_match.match'](load['tgt']):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -3250,298 +3247,6 @@ class SyndicManager(MinionBase):
|
|||||||
del self.job_rets[master]
|
del self.job_rets[master]
|
||||||
|
|
||||||
|
|
||||||
class Matcher(object):
|
|
||||||
'''
|
|
||||||
Use to return the value for matching calls from the master
|
|
||||||
'''
|
|
||||||
def __init__(self, opts, functions=None):
|
|
||||||
self.opts = opts
|
|
||||||
self.functions = functions
|
|
||||||
|
|
||||||
def confirm_top(self, match, data, nodegroups=None):
|
|
||||||
'''
|
|
||||||
Takes the data passed to a top file environment and determines if the
|
|
||||||
data matches this minion
|
|
||||||
'''
|
|
||||||
matcher = 'compound'
|
|
||||||
if not data:
|
|
||||||
log.error('Received bad data when setting the match from the top '
|
|
||||||
'file')
|
|
||||||
return False
|
|
||||||
for item in data:
|
|
||||||
if isinstance(item, dict):
|
|
||||||
if 'match' in item:
|
|
||||||
matcher = item['match']
|
|
||||||
if hasattr(self, matcher + '_match'):
|
|
||||||
funcname = '{0}_match'.format(matcher)
|
|
||||||
if matcher == 'nodegroup':
|
|
||||||
return getattr(self, funcname)(match, nodegroups)
|
|
||||||
return getattr(self, funcname)(match)
|
|
||||||
else:
|
|
||||||
log.error('Attempting to match with unknown matcher: %s', matcher)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def glob_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Returns true if the passed glob matches the id
|
|
||||||
'''
|
|
||||||
if not isinstance(tgt, six.string_types):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return fnmatch.fnmatch(self.opts['id'], tgt)
|
|
||||||
|
|
||||||
def pcre_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Returns true if the passed pcre regex matches
|
|
||||||
'''
|
|
||||||
return bool(re.match(tgt, self.opts['id']))
|
|
||||||
|
|
||||||
def list_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Determines if this host is on the list
|
|
||||||
'''
|
|
||||||
if isinstance(tgt, six.string_types):
|
|
||||||
tgt = tgt.split(',')
|
|
||||||
return bool(self.opts['id'] in tgt)
|
|
||||||
|
|
||||||
def grain_match(self, tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|
||||||
'''
|
|
||||||
Reads in the grains glob match
|
|
||||||
'''
|
|
||||||
log.debug('grains target: %s', tgt)
|
|
||||||
if delimiter not in tgt:
|
|
||||||
log.error('Got insufficient arguments for grains match '
|
|
||||||
'statement from master')
|
|
||||||
return False
|
|
||||||
return salt.utils.data.subdict_match(
|
|
||||||
self.opts['grains'], tgt, delimiter=delimiter
|
|
||||||
)
|
|
||||||
|
|
||||||
def grain_pcre_match(self, tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|
||||||
'''
|
|
||||||
Matches a grain based on regex
|
|
||||||
'''
|
|
||||||
log.debug('grains pcre target: %s', tgt)
|
|
||||||
if delimiter not in tgt:
|
|
||||||
log.error('Got insufficient arguments for grains pcre match '
|
|
||||||
'statement from master')
|
|
||||||
return False
|
|
||||||
return salt.utils.data.subdict_match(
|
|
||||||
self.opts['grains'], tgt, delimiter=delimiter, regex_match=True)
|
|
||||||
|
|
||||||
def data_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Match based on the local data store on the minion
|
|
||||||
'''
|
|
||||||
if self.functions is None:
|
|
||||||
utils = salt.loader.utils(self.opts)
|
|
||||||
self.functions = salt.loader.minion_mods(self.opts, utils=utils)
|
|
||||||
comps = tgt.split(':')
|
|
||||||
if len(comps) < 2:
|
|
||||||
return False
|
|
||||||
val = self.functions['data.getval'](comps[0])
|
|
||||||
if val is None:
|
|
||||||
# The value is not defined
|
|
||||||
return False
|
|
||||||
if isinstance(val, list):
|
|
||||||
# We are matching a single component to a single list member
|
|
||||||
for member in val:
|
|
||||||
if fnmatch.fnmatch(six.text_type(member).lower(), comps[1].lower()):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
if isinstance(val, dict):
|
|
||||||
if comps[1] in val:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
return bool(fnmatch.fnmatch(
|
|
||||||
val,
|
|
||||||
comps[1],
|
|
||||||
))
|
|
||||||
|
|
||||||
def pillar_match(self, tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|
||||||
'''
|
|
||||||
Reads in the pillar glob match
|
|
||||||
'''
|
|
||||||
log.debug('pillar target: %s', tgt)
|
|
||||||
if delimiter not in tgt:
|
|
||||||
log.error('Got insufficient arguments for pillar match '
|
|
||||||
'statement from master')
|
|
||||||
return False
|
|
||||||
return salt.utils.data.subdict_match(
|
|
||||||
self.opts['pillar'], tgt, delimiter=delimiter
|
|
||||||
)
|
|
||||||
|
|
||||||
def pillar_pcre_match(self, tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|
||||||
'''
|
|
||||||
Reads in the pillar pcre match
|
|
||||||
'''
|
|
||||||
log.debug('pillar PCRE target: %s', tgt)
|
|
||||||
if delimiter not in tgt:
|
|
||||||
log.error('Got insufficient arguments for pillar PCRE match '
|
|
||||||
'statement from master')
|
|
||||||
return False
|
|
||||||
return salt.utils.data.subdict_match(
|
|
||||||
self.opts['pillar'], tgt, delimiter=delimiter, regex_match=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def pillar_exact_match(self, tgt, delimiter=':'):
|
|
||||||
'''
|
|
||||||
Reads in the pillar match, no globbing, no PCRE
|
|
||||||
'''
|
|
||||||
log.debug('pillar target: %s', tgt)
|
|
||||||
if delimiter not in tgt:
|
|
||||||
log.error('Got insufficient arguments for pillar match '
|
|
||||||
'statement from master')
|
|
||||||
return False
|
|
||||||
return salt.utils.data.subdict_match(self.opts['pillar'],
|
|
||||||
tgt,
|
|
||||||
delimiter=delimiter,
|
|
||||||
exact_match=True)
|
|
||||||
|
|
||||||
def ipcidr_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Matches based on IP address or CIDR notation
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
# Target is an address?
|
|
||||||
tgt = ipaddress.ip_address(tgt)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
# Target is a network?
|
|
||||||
tgt = ipaddress.ip_network(tgt)
|
|
||||||
except Exception:
|
|
||||||
log.error('Invalid IP/CIDR target: %s', tgt)
|
|
||||||
return []
|
|
||||||
proto = 'ipv{0}'.format(tgt.version)
|
|
||||||
|
|
||||||
grains = self.opts['grains']
|
|
||||||
|
|
||||||
if proto not in grains:
|
|
||||||
match = False
|
|
||||||
elif isinstance(tgt, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
|
|
||||||
match = six.text_type(tgt) in grains[proto]
|
|
||||||
else:
|
|
||||||
match = salt.utils.network.in_subnet(tgt, grains[proto])
|
|
||||||
|
|
||||||
return match
|
|
||||||
|
|
||||||
def range_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Matches based on range cluster
|
|
||||||
'''
|
|
||||||
if HAS_RANGE:
|
|
||||||
range_ = seco.range.Range(self.opts['range_server'])
|
|
||||||
try:
|
|
||||||
return self.opts['grains']['fqdn'] in range_.expand(tgt)
|
|
||||||
except seco.range.RangeException as exc:
|
|
||||||
log.debug('Range exception in compound match: %s', exc)
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
|
|
||||||
def compound_match(self, tgt):
|
|
||||||
'''
|
|
||||||
Runs the compound target check
|
|
||||||
'''
|
|
||||||
nodegroups = self.opts.get('nodegroups', {})
|
|
||||||
|
|
||||||
if not isinstance(tgt, six.string_types) and not isinstance(tgt, (list, tuple)):
|
|
||||||
log.error('Compound target received that is neither string, list nor tuple')
|
|
||||||
return False
|
|
||||||
log.debug('compound_match: %s ? %s', self.opts['id'], tgt)
|
|
||||||
ref = {'G': 'grain',
|
|
||||||
'P': 'grain_pcre',
|
|
||||||
'I': 'pillar',
|
|
||||||
'J': 'pillar_pcre',
|
|
||||||
'L': 'list',
|
|
||||||
'N': None, # Nodegroups should already be expanded
|
|
||||||
'S': 'ipcidr',
|
|
||||||
'E': 'pcre'}
|
|
||||||
if HAS_RANGE:
|
|
||||||
ref['R'] = 'range'
|
|
||||||
|
|
||||||
results = []
|
|
||||||
opers = ['and', 'or', 'not', '(', ')']
|
|
||||||
|
|
||||||
if isinstance(tgt, six.string_types):
|
|
||||||
words = tgt.split()
|
|
||||||
else:
|
|
||||||
# we make a shallow copy in order to not affect the passed in arg
|
|
||||||
words = tgt[:]
|
|
||||||
|
|
||||||
while words:
|
|
||||||
word = words.pop(0)
|
|
||||||
target_info = salt.utils.minions.parse_target(word)
|
|
||||||
|
|
||||||
# Easy check first
|
|
||||||
if word in opers:
|
|
||||||
if results:
|
|
||||||
if results[-1] == '(' and word in ('and', 'or'):
|
|
||||||
log.error('Invalid beginning operator after "(": %s', word)
|
|
||||||
return False
|
|
||||||
if word == 'not':
|
|
||||||
if not results[-1] in ('and', 'or', '('):
|
|
||||||
results.append('and')
|
|
||||||
results.append(word)
|
|
||||||
else:
|
|
||||||
# seq start with binary oper, fail
|
|
||||||
if word not in ['(', 'not']:
|
|
||||||
log.error('Invalid beginning operator: %s', word)
|
|
||||||
return False
|
|
||||||
results.append(word)
|
|
||||||
|
|
||||||
elif target_info and target_info['engine']:
|
|
||||||
if 'N' == target_info['engine']:
|
|
||||||
# if we encounter a node group, just evaluate it in-place
|
|
||||||
decomposed = salt.utils.minions.nodegroup_comp(target_info['pattern'], nodegroups)
|
|
||||||
if decomposed:
|
|
||||||
words = decomposed + words
|
|
||||||
continue
|
|
||||||
|
|
||||||
engine = ref.get(target_info['engine'])
|
|
||||||
if not engine:
|
|
||||||
# If an unknown engine is called at any time, fail out
|
|
||||||
log.error(
|
|
||||||
'Unrecognized target engine "%s" for target '
|
|
||||||
'expression "%s"', target_info['engine'], word
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
engine_args = [target_info['pattern']]
|
|
||||||
engine_kwargs = {}
|
|
||||||
if target_info['delimiter']:
|
|
||||||
engine_kwargs['delimiter'] = target_info['delimiter']
|
|
||||||
|
|
||||||
results.append(
|
|
||||||
six.text_type(getattr(self, '{0}_match'.format(engine))(*engine_args, **engine_kwargs))
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# The match is not explicitly defined, evaluate it as a glob
|
|
||||||
results.append(six.text_type(self.glob_match(word)))
|
|
||||||
|
|
||||||
results = ' '.join(results)
|
|
||||||
log.debug('compound_match %s ? "%s" => "%s"', self.opts['id'], tgt, results)
|
|
||||||
try:
|
|
||||||
return eval(results) # pylint: disable=W0123
|
|
||||||
except Exception:
|
|
||||||
log.error(
|
|
||||||
'Invalid compound target: %s for results: %s', tgt, results)
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
|
|
||||||
def nodegroup_match(self, tgt, nodegroups):
|
|
||||||
'''
|
|
||||||
This is a compatibility matcher and is NOT called when using
|
|
||||||
nodegroups for remote execution, but is called when the nodegroups
|
|
||||||
matcher is used in states
|
|
||||||
'''
|
|
||||||
if tgt in nodegroups:
|
|
||||||
return self.compound_match(
|
|
||||||
salt.utils.minions.nodegroup_comp(tgt, nodegroups)
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyMinionManager(MinionManager):
|
class ProxyMinionManager(MinionManager):
|
||||||
'''
|
'''
|
||||||
Create the multi-minion interface but for proxy minions
|
Create the multi-minion interface but for proxy minions
|
||||||
@ -3677,7 +3382,7 @@ class ProxyMinion(Minion):
|
|||||||
|
|
||||||
self.serial = salt.payload.Serial(self.opts)
|
self.serial = salt.payload.Serial(self.opts)
|
||||||
self.mod_opts = self._prep_mod_opts()
|
self.mod_opts = self._prep_mod_opts()
|
||||||
self.matcher = Matcher(self.opts, self.functions)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
self.beacons = salt.beacons.Beacon(self.opts, self.functions)
|
||||||
uid = salt.utils.user.get_uid(user=self.opts.get('user', None))
|
uid = salt.utils.user.get_uid(user=self.opts.get('user', None))
|
||||||
self.proc_dir = get_proc_dir(self.opts['cachedir'], uid=uid)
|
self.proc_dir = get_proc_dir(self.opts['cachedir'], uid=uid)
|
||||||
@ -3887,7 +3592,7 @@ class SProxyMinion(SMinion):
|
|||||||
|
|
||||||
self.functions = salt.loader.minion_mods(self.opts, utils=self.utils, notify=False, proxy=self.proxy)
|
self.functions = salt.loader.minion_mods(self.opts, utils=self.utils, notify=False, proxy=self.proxy)
|
||||||
self.returners = salt.loader.returners(self.opts, self.functions, proxy=self.proxy)
|
self.returners = salt.loader.returners(self.opts, self.functions, proxy=self.proxy)
|
||||||
self.matcher = Matcher(self.opts, self.functions)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.functions['sys.reload_modules'] = self.gen_modules
|
self.functions['sys.reload_modules'] = self.gen_modules
|
||||||
self.executors = salt.loader.executors(self.opts, self.functions, proxy=self.proxy)
|
self.executors = salt.loader.executors(self.opts, self.functions, proxy=self.proxy)
|
||||||
|
|
||||||
|
@ -8,9 +8,11 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import copy
|
||||||
|
|
||||||
# Import salt libs
|
# Import salt libs
|
||||||
import salt.minion
|
import salt.minion
|
||||||
|
import salt.loader
|
||||||
from salt.defaults import DEFAULT_TARGET_DELIM
|
from salt.defaults import DEFAULT_TARGET_DELIM
|
||||||
from salt.ext import six
|
from salt.ext import six
|
||||||
|
|
||||||
@ -36,16 +38,16 @@ def compound(tgt, minion_id=None):
|
|||||||
|
|
||||||
salt '*' match.compound 'L@cheese,foo and *'
|
salt '*' match.compound 'L@cheese,foo and *'
|
||||||
'''
|
'''
|
||||||
opts = {'grains': __grains__, 'pillar': __pillar__}
|
|
||||||
if minion_id is not None:
|
if minion_id is not None:
|
||||||
|
opts = copy.copy(__opts__)
|
||||||
if not isinstance(minion_id, six.string_types):
|
if not isinstance(minion_id, six.string_types):
|
||||||
minion_id = six.text_type(minion_id)
|
minion_id = six.text_type(minion_id)
|
||||||
|
opts['id'] = minion_id
|
||||||
else:
|
else:
|
||||||
minion_id = __grains__['id']
|
opts = __opts__
|
||||||
opts['id'] = minion_id
|
matchers = salt.loader.matchers(opts)
|
||||||
matcher = salt.minion.Matcher(opts, __salt__)
|
|
||||||
try:
|
try:
|
||||||
return matcher.compound_match(tgt)
|
return matchers['compound_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -71,9 +73,9 @@ def ipcidr(tgt):
|
|||||||
- nodeclass: internal
|
- nodeclass: internal
|
||||||
|
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher({'grains': __grains__}, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.ipcidr_match(tgt)
|
return matchers['ipcidr_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -102,9 +104,9 @@ def pillar_pcre(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|||||||
.. versionadded:: 0.16.4
|
.. versionadded:: 0.16.4
|
||||||
.. deprecated:: 2015.8.0
|
.. deprecated:: 2015.8.0
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher({'pillar': __pillar__}, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.pillar_pcre_match(tgt, delimiter=delimiter)
|
return matchers['pillar_pcre_match.match'](tgt, delimiter=delimiter)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -133,9 +135,9 @@ def pillar(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|||||||
.. versionadded:: 0.16.4
|
.. versionadded:: 0.16.4
|
||||||
.. deprecated:: 2015.8.0
|
.. deprecated:: 2015.8.0
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher({'pillar': __pillar__}, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.pillar_match(tgt, delimiter=delimiter)
|
return matchers['pillar_match.match'](tgt, delimiter=delimiter)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -151,9 +153,9 @@ def data(tgt):
|
|||||||
|
|
||||||
salt '*' match.data 'spam:eggs'
|
salt '*' match.data 'spam:eggs'
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher(__opts__, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.data_match(tgt)
|
return matchers['data_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -182,9 +184,9 @@ def grain_pcre(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|||||||
.. versionadded:: 0.16.4
|
.. versionadded:: 0.16.4
|
||||||
.. deprecated:: 2015.8.0
|
.. deprecated:: 2015.8.0
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher({'grains': __grains__}, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.grain_pcre_match(tgt, delimiter=delimiter)
|
return matchers['grain_pcre_match.match'](tgt, delimiter=delimiter)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -213,9 +215,9 @@ def grain(tgt, delimiter=DEFAULT_TARGET_DELIM):
|
|||||||
.. versionadded:: 0.16.4
|
.. versionadded:: 0.16.4
|
||||||
.. deprecated:: 2015.8.0
|
.. deprecated:: 2015.8.0
|
||||||
'''
|
'''
|
||||||
matcher = salt.minion.Matcher({'grains': __grains__}, __salt__)
|
matchers = salt.loader.matchers(__opts__)
|
||||||
try:
|
try:
|
||||||
return matcher.grain_match(tgt, delimiter=delimiter)
|
return matchers['grain_match.match'](tgt, delimiter=delimiter)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -237,13 +239,15 @@ def list_(tgt, minion_id=None):
|
|||||||
salt '*' match.list 'server1,server2'
|
salt '*' match.list 'server1,server2'
|
||||||
'''
|
'''
|
||||||
if minion_id is not None:
|
if minion_id is not None:
|
||||||
|
opts = copy.copy(__opts__)
|
||||||
if not isinstance(minion_id, six.string_types):
|
if not isinstance(minion_id, six.string_types):
|
||||||
minion_id = six.text_type(minion_id)
|
minion_id = six.text_type(minion_id)
|
||||||
|
opts['id'] = minion_id
|
||||||
else:
|
else:
|
||||||
minion_id = __grains__['id']
|
opts = __opts__
|
||||||
matcher = salt.minion.Matcher({'id': minion_id}, __salt__)
|
matchers = salt.loader.matchers(opts)
|
||||||
try:
|
try:
|
||||||
return matcher.list_match(tgt)
|
return matchers['list_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -265,13 +269,15 @@ def pcre(tgt, minion_id=None):
|
|||||||
salt '*' match.pcre '.*'
|
salt '*' match.pcre '.*'
|
||||||
'''
|
'''
|
||||||
if minion_id is not None:
|
if minion_id is not None:
|
||||||
|
opts = copy.copy(__opts__)
|
||||||
if not isinstance(minion_id, six.string_types):
|
if not isinstance(minion_id, six.string_types):
|
||||||
minion_id = six.text_type(minion_id)
|
minion_id = six.text_type(minion_id)
|
||||||
|
opts['id'] = minion_id
|
||||||
else:
|
else:
|
||||||
minion_id = __grains__['id']
|
opts = __opts__
|
||||||
matcher = salt.minion.Matcher({'id': minion_id}, __salt__)
|
matchers = salt.loader.matchers(opts)
|
||||||
try:
|
try:
|
||||||
return matcher.pcre_match(tgt)
|
return matchers['pcre_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
@ -293,13 +299,16 @@ def glob(tgt, minion_id=None):
|
|||||||
salt '*' match.glob '*'
|
salt '*' match.glob '*'
|
||||||
'''
|
'''
|
||||||
if minion_id is not None:
|
if minion_id is not None:
|
||||||
|
opts = copy.copy(__opts__)
|
||||||
if not isinstance(minion_id, six.string_types):
|
if not isinstance(minion_id, six.string_types):
|
||||||
minion_id = six.text_type(minion_id)
|
minion_id = six.text_type(minion_id)
|
||||||
|
opts['id'] = minion_id
|
||||||
else:
|
else:
|
||||||
minion_id = __grains__['id']
|
opts = __opts__
|
||||||
matcher = salt.minion.Matcher({'id': minion_id}, __salt__)
|
matchers = salt.loader.matchers(opts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return matcher.glob_match(tgt)
|
return matchers['glob_match.match'](tgt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(exc)
|
log.exception(exc)
|
||||||
return False
|
return False
|
||||||
|
@ -547,6 +547,44 @@ def sync_proxymodules(saltenv=None, refresh=False, extmod_whitelist=None, extmod
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def sync_matchers(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blacklist=None):
|
||||||
|
'''
|
||||||
|
.. versionadded:: Flourine
|
||||||
|
|
||||||
|
Sync engine modules from ``salt://_matchers`` to the minion
|
||||||
|
|
||||||
|
saltenv
|
||||||
|
The fileserver environment from which to sync. To sync from more than
|
||||||
|
one environment, pass a comma-separated list.
|
||||||
|
|
||||||
|
If not passed, then all environments configured in the :ref:`top files
|
||||||
|
<states-top>` will be checked for engines to sync. If no top files are
|
||||||
|
found, then the ``base`` environment will be synced.
|
||||||
|
|
||||||
|
refresh : True
|
||||||
|
If ``True``, refresh the available execution modules on the minion.
|
||||||
|
This refresh will be performed even if no new matcher modules are synced.
|
||||||
|
Set to ``False`` to prevent this refresh.
|
||||||
|
|
||||||
|
extmod_whitelist : None
|
||||||
|
comma-separated list of modules to sync
|
||||||
|
|
||||||
|
extmod_blacklist : None
|
||||||
|
comma-separated list of modules to blacklist based on type
|
||||||
|
|
||||||
|
CLI Examples:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt '*' saltutil.sync_matchers
|
||||||
|
salt '*' saltutil.sync_matchers saltenv=base,dev
|
||||||
|
'''
|
||||||
|
ret = _sync('matchers', saltenv, extmod_whitelist, extmod_blacklist)
|
||||||
|
if refresh:
|
||||||
|
refresh_modules()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def sync_engines(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blacklist=None):
|
def sync_engines(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blacklist=None):
|
||||||
'''
|
'''
|
||||||
.. versionadded:: 2016.3.0
|
.. versionadded:: 2016.3.0
|
||||||
@ -943,6 +981,7 @@ def sync_all(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist
|
|||||||
ret['engines'] = sync_engines(saltenv, False, extmod_whitelist, extmod_blacklist)
|
ret['engines'] = sync_engines(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||||
ret['thorium'] = sync_thorium(saltenv, False, extmod_whitelist, extmod_blacklist)
|
ret['thorium'] = sync_thorium(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||||
ret['serializers'] = sync_serializers(saltenv, False, extmod_whitelist, extmod_blacklist)
|
ret['serializers'] = sync_serializers(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||||
|
ret['matchers'] = sync_matchers(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||||
if __opts__['file_client'] == 'local':
|
if __opts__['file_client'] == 'local':
|
||||||
ret['pillar'] = sync_pillar(saltenv, False, extmod_whitelist, extmod_blacklist)
|
ret['pillar'] = sync_pillar(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||||
if refresh:
|
if refresh:
|
||||||
@ -969,6 +1008,24 @@ def refresh_beacons():
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_matchers():
|
||||||
|
'''
|
||||||
|
Signal the minion to refresh its matchers.
|
||||||
|
|
||||||
|
CLI Example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt '*' saltutil.refresh_matchers
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
ret = __salt__['event.fire']({}, 'matchers_refresh')
|
||||||
|
except KeyError:
|
||||||
|
log.error('Event module not available. Matcher refresh failed.')
|
||||||
|
ret = False # Effectively a no-op, since we can't really return without an event system
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def refresh_pillar():
|
def refresh_pillar():
|
||||||
'''
|
'''
|
||||||
Signal the minion to refresh the pillar data.
|
Signal the minion to refresh the pillar data.
|
||||||
|
@ -366,7 +366,7 @@ class Pillar(object):
|
|||||||
else:
|
else:
|
||||||
self.functions = functions
|
self.functions = functions
|
||||||
|
|
||||||
self.matcher = salt.minion.Matcher(self.opts, self.functions)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.rend = salt.loader.render(self.opts, self.functions)
|
self.rend = salt.loader.render(self.opts, self.functions)
|
||||||
ext_pillar_opts = copy.deepcopy(self.opts)
|
ext_pillar_opts = copy.deepcopy(self.opts)
|
||||||
# Fix self.opts['file_roots'] so that ext_pillars know the real
|
# Fix self.opts['file_roots'] so that ext_pillars know the real
|
||||||
@ -649,7 +649,7 @@ class Pillar(object):
|
|||||||
if saltenv != self.opts['pillarenv']:
|
if saltenv != self.opts['pillarenv']:
|
||||||
continue
|
continue
|
||||||
for match, data in six.iteritems(body):
|
for match, data in six.iteritems(body):
|
||||||
if self.matcher.confirm_top(
|
if self.matchers['confirm_top.confirm_top'](
|
||||||
match,
|
match,
|
||||||
data,
|
data,
|
||||||
self.opts.get('nodegroups', {}),
|
self.opts.get('nodegroups', {}),
|
||||||
|
@ -3379,7 +3379,7 @@ class BaseHighState(object):
|
|||||||
def _filter_matches(_match, _data, _opts):
|
def _filter_matches(_match, _data, _opts):
|
||||||
if isinstance(_data, six.string_types):
|
if isinstance(_data, six.string_types):
|
||||||
_data = [_data]
|
_data = [_data]
|
||||||
if self.matcher.confirm_top(
|
if self.matchers['confirm_top.confirm_top'](
|
||||||
_match,
|
_match,
|
||||||
_data,
|
_data,
|
||||||
_opts
|
_opts
|
||||||
@ -4068,7 +4068,7 @@ class HighState(BaseHighState):
|
|||||||
mocked=mocked,
|
mocked=mocked,
|
||||||
loader=loader,
|
loader=loader,
|
||||||
initial_pillar=initial_pillar)
|
initial_pillar=initial_pillar)
|
||||||
self.matcher = salt.minion.Matcher(self.opts)
|
self.matchers = salt.loader.matchers(self.opts)
|
||||||
self.proxy = proxy
|
self.proxy = proxy
|
||||||
|
|
||||||
# tracks all pydsl state declarations globally across sls files
|
# tracks all pydsl state declarations globally across sls files
|
||||||
|
@ -515,8 +515,8 @@ def subdict_match(data,
|
|||||||
Check for a match in a dictionary using a delimiter character to denote
|
Check for a match in a dictionary using a delimiter character to denote
|
||||||
levels of subdicts, and also allowing the delimiter character to be
|
levels of subdicts, and also allowing the delimiter character to be
|
||||||
matched. Thus, 'foo:bar:baz' will match data['foo'] == 'bar:baz' and
|
matched. Thus, 'foo:bar:baz' will match data['foo'] == 'bar:baz' and
|
||||||
data['foo']['bar'] == 'baz'. The former would take priority over the
|
data['foo']['bar'] == 'baz'. The latter would take priority over the
|
||||||
latter.
|
former, as more deeply-nested matches are tried first.
|
||||||
'''
|
'''
|
||||||
def _match(target, pattern, regex_match=False, exact_match=False):
|
def _match(target, pattern, regex_match=False, exact_match=False):
|
||||||
if regex_match:
|
if regex_match:
|
||||||
@ -568,8 +568,15 @@ def subdict_match(data,
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for idx in range(1, expr.count(delimiter) + 1):
|
splits = expr.split(delimiter)
|
||||||
splits = expr.split(delimiter)
|
num_splits = len(splits)
|
||||||
|
if num_splits == 1:
|
||||||
|
# Delimiter not present, this can't possibly be a match
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If we have 4 splits, then we have three delimiters. Thus, the indexes we
|
||||||
|
# want to use are 3, 2, and 1, in that order.
|
||||||
|
for idx in range(num_splits - 1, 0, -1):
|
||||||
key = delimiter.join(splits[:idx])
|
key = delimiter.join(splits[:idx])
|
||||||
matchstr = delimiter.join(splits[idx:])
|
matchstr = delimiter.join(splits[idx:])
|
||||||
log.debug("Attempting to match '%s' in '%s' using delimiter '%s'",
|
log.debug("Attempting to match '%s' in '%s' using delimiter '%s'",
|
||||||
|
@ -1234,19 +1234,16 @@ def in_subnet(cidr, addr=None):
|
|||||||
try:
|
try:
|
||||||
cidr = ipaddress.ip_network(cidr)
|
cidr = ipaddress.ip_network(cidr)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
log.error('Invalid CIDR \'{0}\''.format(cidr))
|
log.error('Invalid CIDR \'%s\'', cidr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if addr is None:
|
if addr is None:
|
||||||
addr = ip_addrs()
|
addr = ip_addrs()
|
||||||
addr.extend(ip_addrs6())
|
addr.extend(ip_addrs6())
|
||||||
elif isinstance(addr, six.string_types):
|
elif not isinstance(addr, (list, tuple)):
|
||||||
return ipaddress.ip_address(addr) in cidr
|
addr = (addr,)
|
||||||
|
|
||||||
for ip_addr in addr:
|
return any(ipaddress.ip_address(item) in cidr for item in addr)
|
||||||
if ipaddress.ip_address(ip_addr) in cidr:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _ip_addrs(interface=None, include_loopback=False, interface_data=None, proto='inet'):
|
def _ip_addrs(interface=None, include_loopback=False, interface_data=None, proto='inet'):
|
||||||
|
@ -93,6 +93,7 @@ class SaltUtilSyncModuleTest(ModuleCase):
|
|||||||
'modules.salttest'],
|
'modules.salttest'],
|
||||||
'renderers': [],
|
'renderers': [],
|
||||||
'log_handlers': [],
|
'log_handlers': [],
|
||||||
|
'matchers': [],
|
||||||
'states': [],
|
'states': [],
|
||||||
'sdb': [],
|
'sdb': [],
|
||||||
'proxymodules': [],
|
'proxymodules': [],
|
||||||
@ -115,6 +116,7 @@ class SaltUtilSyncModuleTest(ModuleCase):
|
|||||||
'modules': ['modules.salttest'],
|
'modules': ['modules.salttest'],
|
||||||
'renderers': [],
|
'renderers': [],
|
||||||
'log_handlers': [],
|
'log_handlers': [],
|
||||||
|
'matchers': [],
|
||||||
'states': [],
|
'states': [],
|
||||||
'sdb': [],
|
'sdb': [],
|
||||||
'proxymodules': [],
|
'proxymodules': [],
|
||||||
@ -140,6 +142,7 @@ class SaltUtilSyncModuleTest(ModuleCase):
|
|||||||
'modules.salttest'],
|
'modules.salttest'],
|
||||||
'renderers': [],
|
'renderers': [],
|
||||||
'log_handlers': [],
|
'log_handlers': [],
|
||||||
|
'matchers': [],
|
||||||
'states': [],
|
'states': [],
|
||||||
'sdb': [],
|
'sdb': [],
|
||||||
'proxymodules': [],
|
'proxymodules': [],
|
||||||
@ -162,6 +165,7 @@ class SaltUtilSyncModuleTest(ModuleCase):
|
|||||||
'modules': [],
|
'modules': [],
|
||||||
'renderers': [],
|
'renderers': [],
|
||||||
'log_handlers': [],
|
'log_handlers': [],
|
||||||
|
'matchers': [],
|
||||||
'states': [],
|
'states': [],
|
||||||
'sdb': [],
|
'sdb': [],
|
||||||
'proxymodules': [],
|
'proxymodules': [],
|
||||||
|
@ -279,7 +279,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
|
|||||||
request_timeout=30,
|
request_timeout=30,
|
||||||
)
|
)
|
||||||
response_obj = salt.utils.json.loads(response.body)
|
response_obj = salt.utils.json.loads(response.body)
|
||||||
self.assertEqual(response_obj['return'], [{'localhost': True, 'minion': True, 'sub_minion': True}])
|
self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
|
||||||
|
|
||||||
# runner tests
|
# runner tests
|
||||||
def test_simple_local_runner_post(self):
|
def test_simple_local_runner_post(self):
|
||||||
|
@ -9,19 +9,43 @@
|
|||||||
|
|
||||||
# Import python libs
|
# Import python libs
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
# Import Salt Testing libs
|
# Import Salt Testing libs
|
||||||
|
from tests.support.helpers import with_tempdir
|
||||||
from tests.support.unit import skipIf, TestCase
|
from tests.support.unit import skipIf, TestCase
|
||||||
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
|
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
|
||||||
from tests.support.paths import TMP
|
from tests.support.paths import TMP
|
||||||
|
|
||||||
# Import salt libs
|
# Import salt libs
|
||||||
|
import salt.fileclient
|
||||||
import salt.pillar
|
import salt.pillar
|
||||||
import salt.utils.stringutils
|
import salt.utils.stringutils
|
||||||
import salt.exceptions
|
import salt.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class MockFileclient(object):
|
||||||
|
def __init__(self, cache_file=None, get_state=None, list_states=None):
|
||||||
|
if cache_file is not None:
|
||||||
|
self.cache_file = lambda *x, **y: cache_file
|
||||||
|
if get_state is not None:
|
||||||
|
self.get_state = lambda sls, env: get_state[sls]
|
||||||
|
if list_states is not None:
|
||||||
|
self.list_states = lambda *x, **y: list_states
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument,no-method-argument,method-hidden
|
||||||
|
def cache_file(*args, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_state(*args, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def list_states(*args, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
# pylint: enable=unused-argument,no-method-argument,method-hidden
|
||||||
|
|
||||||
|
|
||||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||||
class PillarTestCase(TestCase):
|
class PillarTestCase(TestCase):
|
||||||
|
|
||||||
@ -509,40 +533,57 @@ class PillarTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_topfile_order(self):
|
def test_topfile_order(self):
|
||||||
with patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) as get_file_client, \
|
opts = {
|
||||||
patch('salt.pillar.salt.minion.Matcher') as Matcher: # autospec=True disabled due to py3 mock bug
|
'optimization_order': [0, 1, 2],
|
||||||
opts = {
|
'renderer': 'yaml',
|
||||||
'optimization_order': [0, 1, 2],
|
'renderer_blacklist': [],
|
||||||
'renderer': 'yaml',
|
'renderer_whitelist': [],
|
||||||
'renderer_blacklist': [],
|
'state_top': '',
|
||||||
'renderer_whitelist': [],
|
'pillar_roots': [],
|
||||||
'state_top': '',
|
'extension_modules': '',
|
||||||
'pillar_roots': [],
|
'saltenv': 'base',
|
||||||
'extension_modules': '',
|
'file_roots': [],
|
||||||
'saltenv': 'base',
|
}
|
||||||
'file_roots': [],
|
grains = {
|
||||||
}
|
'os': 'Ubuntu',
|
||||||
grains = {
|
'os_family': 'Debian',
|
||||||
'os': 'Ubuntu',
|
'oscodename': 'raring',
|
||||||
'os_family': 'Debian',
|
'osfullname': 'Ubuntu',
|
||||||
'oscodename': 'raring',
|
'osrelease': '13.04',
|
||||||
'osfullname': 'Ubuntu',
|
'kernel': 'Linux'
|
||||||
'osrelease': '13.04',
|
}
|
||||||
'kernel': 'Linux'
|
|
||||||
}
|
|
||||||
# glob match takes precedence
|
|
||||||
self._setup_test_topfile_mocks(Matcher, get_file_client, 1, 2)
|
|
||||||
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
|
||||||
self.assertEqual(pillar.compile_pillar()['ssh'], 'bar')
|
|
||||||
# nodegroup match takes precedence
|
|
||||||
self._setup_test_topfile_mocks(Matcher, get_file_client, 2, 1)
|
|
||||||
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
|
||||||
self.assertEqual(pillar.compile_pillar()['ssh'], 'foo')
|
|
||||||
|
|
||||||
def _setup_test_topfile_mocks(self, Matcher, get_file_client,
|
def _run_test(nodegroup_order, glob_order, expected):
|
||||||
nodegroup_order, glob_order):
|
tempdir = tempfile.mkdtemp(dir=TMP)
|
||||||
|
try:
|
||||||
|
sls_files = self._setup_test_topfile_sls(
|
||||||
|
tempdir,
|
||||||
|
nodegroup_order,
|
||||||
|
glob_order)
|
||||||
|
fc_mock = MockFileclient(
|
||||||
|
cache_file=sls_files['top']['dest'],
|
||||||
|
list_states=['top', 'ssh', 'ssh.minion',
|
||||||
|
'generic', 'generic.minion'],
|
||||||
|
get_state=sls_files)
|
||||||
|
with patch.object(salt.fileclient, 'get_file_client',
|
||||||
|
MagicMock(return_value=fc_mock)):
|
||||||
|
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
||||||
|
# Make sure that confirm_top.confirm_top returns True
|
||||||
|
pillar.matchers['confirm_top.confirm_top'] = lambda *x, **y: True
|
||||||
|
self.assertEqual(pillar.compile_pillar()['ssh'], expected)
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tempdir, ignore_errors=True)
|
||||||
|
|
||||||
|
# test case where glob match happens second and therefore takes
|
||||||
|
# precedence over nodegroup match.
|
||||||
|
_run_test(nodegroup_order=1, glob_order=2, expected='bar')
|
||||||
|
# test case where nodegroup match happens second and therefore takes
|
||||||
|
# precedence over glob match.
|
||||||
|
_run_test(nodegroup_order=2, glob_order=1, expected='foo')
|
||||||
|
|
||||||
|
def _setup_test_topfile_sls(self, tempdir, nodegroup_order, glob_order):
|
||||||
# Write a simple topfile and two pillar state files
|
# Write a simple topfile and two pillar state files
|
||||||
self.top_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
top_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
s = '''
|
s = '''
|
||||||
base:
|
base:
|
||||||
group:
|
group:
|
||||||
@ -557,22 +598,22 @@ base:
|
|||||||
- ssh.minion
|
- ssh.minion
|
||||||
- generic.minion
|
- generic.minion
|
||||||
'''.format(nodegroup_order=nodegroup_order, glob_order=glob_order)
|
'''.format(nodegroup_order=nodegroup_order, glob_order=glob_order)
|
||||||
self.top_file.write(salt.utils.stringutils.to_bytes(s))
|
top_file.write(salt.utils.stringutils.to_bytes(s))
|
||||||
self.top_file.flush()
|
top_file.flush()
|
||||||
self.ssh_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
ssh_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
self.ssh_file.write(b'''
|
ssh_file.write(b'''
|
||||||
ssh:
|
ssh:
|
||||||
foo
|
foo
|
||||||
''')
|
''')
|
||||||
self.ssh_file.flush()
|
ssh_file.flush()
|
||||||
self.ssh_minion_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
ssh_minion_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
self.ssh_minion_file.write(b'''
|
ssh_minion_file.write(b'''
|
||||||
ssh:
|
ssh:
|
||||||
bar
|
bar
|
||||||
''')
|
''')
|
||||||
self.ssh_minion_file.flush()
|
ssh_minion_file.flush()
|
||||||
self.generic_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
generic_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
self.generic_file.write(b'''
|
generic_file.write(b'''
|
||||||
generic:
|
generic:
|
||||||
key1:
|
key1:
|
||||||
- value1
|
- value1
|
||||||
@ -580,67 +621,65 @@ generic:
|
|||||||
key2:
|
key2:
|
||||||
sub_key1: []
|
sub_key1: []
|
||||||
''')
|
''')
|
||||||
self.generic_file.flush()
|
generic_file.flush()
|
||||||
self.generic_minion_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
generic_minion_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
self.generic_minion_file.write(b'''
|
generic_minion_file.write(b'''
|
||||||
generic:
|
generic:
|
||||||
key1:
|
key1:
|
||||||
- value3
|
- value3
|
||||||
key2:
|
key2:
|
||||||
sub_key2: []
|
sub_key2: []
|
||||||
''')
|
''')
|
||||||
self.generic_minion_file.flush()
|
generic_minion_file.flush()
|
||||||
|
|
||||||
# Setup Matcher mock
|
return {
|
||||||
matcher = Matcher.return_value
|
'top': {'path': '', 'dest': top_file.name},
|
||||||
matcher.confirm_top.return_value = True
|
'ssh': {'path': '', 'dest': ssh_file.name},
|
||||||
|
'ssh.minion': {'path': '', 'dest': ssh_minion_file.name},
|
||||||
|
'generic': {'path': '', 'dest': generic_file.name},
|
||||||
|
'generic.minion': {'path': '', 'dest': generic_minion_file.name},
|
||||||
|
}
|
||||||
|
|
||||||
# Setup fileclient mock
|
@with_tempdir()
|
||||||
client = get_file_client.return_value
|
def test_include(self, tempdir):
|
||||||
client.cache_file.return_value = self.top_file.name
|
opts = {
|
||||||
|
'optimization_order': [0, 1, 2],
|
||||||
def get_state(sls, env):
|
'renderer': 'yaml',
|
||||||
return {
|
'renderer_blacklist': [],
|
||||||
'ssh': {'path': '', 'dest': self.ssh_file.name},
|
'renderer_whitelist': [],
|
||||||
'ssh.minion': {'path': '', 'dest': self.ssh_minion_file.name},
|
'state_top': '',
|
||||||
'generic': {'path': '', 'dest': self.generic_file.name},
|
'pillar_roots': [],
|
||||||
'generic.minion': {'path': '', 'dest': self.generic_minion_file.name},
|
'extension_modules': '',
|
||||||
}[sls]
|
'saltenv': 'base',
|
||||||
|
'file_roots': [],
|
||||||
client.get_state.side_effect = get_state
|
}
|
||||||
|
grains = {
|
||||||
def test_include(self):
|
'os': 'Ubuntu',
|
||||||
with patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) as get_file_client, \
|
'os_family': 'Debian',
|
||||||
patch('salt.pillar.salt.minion.Matcher') as Matcher: # autospec=True disabled due to py3 mock bug
|
'oscodename': 'raring',
|
||||||
opts = {
|
'osfullname': 'Ubuntu',
|
||||||
'optimization_order': [0, 1, 2],
|
'osrelease': '13.04',
|
||||||
'renderer': 'yaml',
|
'kernel': 'Linux'
|
||||||
'renderer_blacklist': [],
|
}
|
||||||
'renderer_whitelist': [],
|
sls_files = self._setup_test_include_sls(tempdir)
|
||||||
'state_top': '',
|
fc_mock = MockFileclient(
|
||||||
'pillar_roots': [],
|
cache_file=sls_files['top']['dest'],
|
||||||
'extension_modules': '',
|
get_state=sls_files,
|
||||||
'saltenv': 'base',
|
list_states=['top', 'test.init', 'test.sub1',
|
||||||
'file_roots': [],
|
'test.sub2', 'test.sub_wildcard_1'],
|
||||||
}
|
)
|
||||||
grains = {
|
with patch.object(salt.fileclient, 'get_file_client',
|
||||||
'os': 'Ubuntu',
|
MagicMock(return_value=fc_mock)):
|
||||||
'os_family': 'Debian',
|
|
||||||
'oscodename': 'raring',
|
|
||||||
'osfullname': 'Ubuntu',
|
|
||||||
'osrelease': '13.04',
|
|
||||||
'kernel': 'Linux'
|
|
||||||
}
|
|
||||||
|
|
||||||
self._setup_test_include_mocks(Matcher, get_file_client)
|
|
||||||
pillar = salt.pillar.Pillar(opts, grains, 'minion', 'base')
|
pillar = salt.pillar.Pillar(opts, grains, 'minion', 'base')
|
||||||
|
# Make sure that confirm_top.confirm_top returns True
|
||||||
|
pillar.matchers['confirm_top.confirm_top'] = lambda *x, **y: True
|
||||||
compiled_pillar = pillar.compile_pillar()
|
compiled_pillar = pillar.compile_pillar()
|
||||||
self.assertEqual(compiled_pillar['foo_wildcard'], 'bar_wildcard')
|
self.assertEqual(compiled_pillar['foo_wildcard'], 'bar_wildcard')
|
||||||
self.assertEqual(compiled_pillar['foo1'], 'bar1')
|
self.assertEqual(compiled_pillar['foo1'], 'bar1')
|
||||||
self.assertEqual(compiled_pillar['foo2'], 'bar2')
|
self.assertEqual(compiled_pillar['foo2'], 'bar2')
|
||||||
|
|
||||||
def _setup_test_include_mocks(self, Matcher, get_file_client):
|
def _setup_test_include_sls(self, tempdir):
|
||||||
self.top_file = top_file = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
top_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
top_file.write(b'''
|
top_file.write(b'''
|
||||||
base:
|
base:
|
||||||
'*':
|
'*':
|
||||||
@ -651,50 +690,40 @@ base:
|
|||||||
- test
|
- test
|
||||||
''')
|
''')
|
||||||
top_file.flush()
|
top_file.flush()
|
||||||
self.init_sls = init_sls = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
init_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
init_sls.write(b'''
|
init_sls.write(b'''
|
||||||
include:
|
include:
|
||||||
- test.sub1
|
- test.sub1
|
||||||
- test.sub_wildcard*
|
- test.sub_wildcard*
|
||||||
''')
|
''')
|
||||||
init_sls.flush()
|
init_sls.flush()
|
||||||
self.sub1_sls = sub1_sls = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
sub1_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
sub1_sls.write(b'''
|
sub1_sls.write(b'''
|
||||||
foo1:
|
foo1:
|
||||||
bar1
|
bar1
|
||||||
''')
|
''')
|
||||||
sub1_sls.flush()
|
sub1_sls.flush()
|
||||||
self.sub2_sls = sub2_sls = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
sub2_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
sub2_sls.write(b'''
|
sub2_sls.write(b'''
|
||||||
foo2:
|
foo2:
|
||||||
bar2
|
bar2
|
||||||
''')
|
''')
|
||||||
sub2_sls.flush()
|
sub2_sls.flush()
|
||||||
|
|
||||||
self.sub_wildcard_1_sls = sub_wildcard_1_sls = tempfile.NamedTemporaryFile(dir=TMP, delete=False)
|
sub_wildcard_1_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
|
||||||
sub_wildcard_1_sls.write(b'''
|
sub_wildcard_1_sls.write(b'''
|
||||||
foo_wildcard:
|
foo_wildcard:
|
||||||
bar_wildcard
|
bar_wildcard
|
||||||
''')
|
''')
|
||||||
sub_wildcard_1_sls.flush()
|
sub_wildcard_1_sls.flush()
|
||||||
# Setup Matcher mock
|
|
||||||
matcher = Matcher.return_value
|
|
||||||
matcher.confirm_top.return_value = True
|
|
||||||
|
|
||||||
# Setup fileclient mock
|
return {
|
||||||
client = get_file_client.return_value
|
'top': {'path': '', 'dest': top_file.name},
|
||||||
client.cache_file.return_value = self.top_file.name
|
'test': {'path': '', 'dest': init_sls.name},
|
||||||
client.list_states.return_value = ['top', 'test.init', 'test.sub1', 'test.sub2', 'test.sub_wildcard_1']
|
'test.sub1': {'path': '', 'dest': sub1_sls.name},
|
||||||
|
'test.sub2': {'path': '', 'dest': sub2_sls.name},
|
||||||
def get_state(sls, env):
|
'test.sub_wildcard_1': {'path': '', 'dest': sub_wildcard_1_sls.name},
|
||||||
return {
|
}
|
||||||
'test': {'path': '', 'dest': init_sls.name},
|
|
||||||
'test.sub1': {'path': '', 'dest': sub1_sls.name},
|
|
||||||
'test.sub2': {'path': '', 'dest': sub2_sls.name},
|
|
||||||
'test.sub_wildcard_1': {'path': '', 'dest': sub_wildcard_1_sls.name},
|
|
||||||
}[sls]
|
|
||||||
|
|
||||||
client.get_state.side_effect = get_state
|
|
||||||
|
|
||||||
|
|
||||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||||
|
Loading…
Reference in New Issue
Block a user