mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Allow compound expressions to begin with "not"
This commit is contained in:
parent
3867950e13
commit
d6fe499f55
5 changed files with 69 additions and 58 deletions
|
@ -41,18 +41,11 @@ That same example expressed in a :term:`top file` looks like the following:
|
|||
- match: compound
|
||||
- webserver
|
||||
|
||||
Note that a leading ``not`` is not supported in compound matches. Instead,
|
||||
something like the following must be done:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt -C '* and not G@kernel:Darwin' test.ping
|
||||
|
||||
Excluding a minion based on its ID is also possible:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt -C '* and not web-dc1-srv' test.ping
|
||||
salt -C 'not web-dc1-srv' test.ping
|
||||
|
||||
Precedence Matching
|
||||
-------------------
|
||||
|
|
|
@ -2327,7 +2327,7 @@ def get_id(opts, cache_minion_id=False):
|
|||
except (IOError, OSError):
|
||||
pass
|
||||
if '__role' in opts and opts.get('__role') == 'minion':
|
||||
log.debug('Guessing ID. The id can be explicitly in set {0}'
|
||||
log.debug('Guessing ID. The id can be explicitly set in {0}'
|
||||
.format(os.path.join(salt.syspaths.CONFIG_DIR, 'minion')))
|
||||
|
||||
newid = salt.utils.network.generate_minion_id()
|
||||
|
|
|
@ -2311,6 +2311,7 @@ class Matcher(object):
|
|||
if not isinstance(tgt, six.string_types):
|
||||
log.debug('Compound target received that is not a string')
|
||||
return False
|
||||
log.debug('compound_match({0})'.format(tgt))
|
||||
ref = {'G': 'grain',
|
||||
'P': 'grain_pcre',
|
||||
'I': 'pillar',
|
||||
|
@ -2325,37 +2326,40 @@ class Matcher(object):
|
|||
tokens = tgt.split()
|
||||
for match in tokens:
|
||||
# Try to match tokens from the compound target, first by using
|
||||
# the 'G, X, I, L, S, E' matcher types, then by hostname glob.
|
||||
# the 'G, X, I, J, L, S, E' matcher types, then by hostname glob.
|
||||
if '@' in match and match[1] == '@':
|
||||
comps = match.split('@')
|
||||
comps = match.split('@', 1)
|
||||
matcher = ref.get(comps[0])
|
||||
if not matcher:
|
||||
# If an unknown matcher is called at any time, fail out
|
||||
log.error('Invalid matcher: {0}'.format(comps[0]))
|
||||
return False
|
||||
results.append(
|
||||
str(
|
||||
getattr(self, '{0}_match'.format(matcher))(
|
||||
'@'.join(comps[1:])
|
||||
)
|
||||
)
|
||||
str(getattr(self, '{0}_match'.format(matcher))(comps[1]))
|
||||
)
|
||||
elif match in opers:
|
||||
# We didn't match a target, so append a boolean operator or
|
||||
# subexpression
|
||||
if results or match in ['(', ')']:
|
||||
if results:
|
||||
if results[-1] == '(' and match in ('and', 'or'):
|
||||
log.error('Invalid beginning operator after "(": {0}'.format(match))
|
||||
return False
|
||||
if match == 'not':
|
||||
match_suffix = results[-1]
|
||||
if not (match_suffix == 'and' or match_suffix == 'or'):
|
||||
if not results[-1] in ('and', 'or', '('):
|
||||
results.append('and')
|
||||
results.append(match)
|
||||
else:
|
||||
# seq start with oper, fail
|
||||
if match not in ['(', ')']:
|
||||
# seq start with binary oper, fail
|
||||
if match not in ['(', 'not']:
|
||||
log.error('Invalid beginning operator: {0}'.format(match))
|
||||
return False
|
||||
results.append(match)
|
||||
|
||||
else:
|
||||
# The match is not explicitly defined, evaluate it as a glob
|
||||
results.append(str(self.glob_match(match)))
|
||||
results = ' '.join(results)
|
||||
log.debug('compound_match "{0}" => "{1}"'.format(tgt, results))
|
||||
try:
|
||||
return eval(results) # pylint: disable=W0123
|
||||
except Exception:
|
||||
|
|
|
@ -342,9 +342,12 @@ class CkMinions(object):
|
|||
'''
|
||||
Return the minions found by looking via compound matcher
|
||||
'''
|
||||
log.debug('_check_compound_minions({0})'.format(expr))
|
||||
minions = set(
|
||||
os.listdir(os.path.join(self.opts['pki_dir'], self.acc))
|
||||
)
|
||||
log.debug('minions: {0}'.format(minions))
|
||||
|
||||
if self.opts.get('minion_data_cache', False):
|
||||
ref = {'G': self._check_grain_minions,
|
||||
'P': self._check_grain_pcre_minions,
|
||||
|
@ -365,30 +368,31 @@ class CkMinions(object):
|
|||
# Try to match tokens from the compound target, first by using
|
||||
# the 'G, X, I, J, L, S, E' matcher types, then by hostname glob.
|
||||
if '@' in match and match[1] == '@':
|
||||
comps = match.split('@')
|
||||
comps = match.split('@', 1)
|
||||
matcher = ref.get(comps[0])
|
||||
|
||||
matcher_args = ['@'.join(comps[1:])]
|
||||
matcher_args = [comps[1]]
|
||||
if comps[0] in ('G', 'P', 'I', 'J'):
|
||||
matcher_args.append(delimiter)
|
||||
matcher_args.append(True)
|
||||
|
||||
if not matcher:
|
||||
# If an unknown matcher is called at any time, fail out
|
||||
log.error('Unknown matcher: {0}'.format(comps[0]))
|
||||
return []
|
||||
results.append(str(set(matcher(*matcher_args))))
|
||||
if unmatched and unmatched[-1] == '-':
|
||||
results.append(str(set(matcher(*matcher_args))))
|
||||
results.append(')')
|
||||
unmatched.pop()
|
||||
else:
|
||||
results.append(str(set(matcher(*matcher_args))))
|
||||
elif match in opers:
|
||||
# We didn't match a target, so append a boolean operator or
|
||||
# subexpression
|
||||
if results:
|
||||
if results[-1] == '(' and match in ('and', 'or'):
|
||||
log.error('Invalid beginning operator after "(": {0}'.format(match))
|
||||
return []
|
||||
if match == 'not':
|
||||
result_suffix = results[-1]
|
||||
if not (result_suffix == '&' or result_suffix == '|'):
|
||||
if not results[-1] in ('&', '|', '('):
|
||||
results.append('&')
|
||||
results.append('(')
|
||||
results.append(str(set(minions)))
|
||||
|
@ -418,10 +422,16 @@ class CkMinions(object):
|
|||
return []
|
||||
else:
|
||||
# seq start with oper, fail
|
||||
if match == '(':
|
||||
if match == 'not':
|
||||
results.append('(')
|
||||
results.append(str(set(minions)))
|
||||
results.append('-')
|
||||
unmatched.append('-')
|
||||
elif match == '(':
|
||||
results.append(match)
|
||||
unmatched.append(match)
|
||||
else:
|
||||
log.error('Expression cannot begin with binary operator: {0}'.format(match))
|
||||
return []
|
||||
else:
|
||||
# The match is not explicitly defined, evaluate as a glob
|
||||
|
@ -536,7 +546,7 @@ class CkMinions(object):
|
|||
infinite.append('grain_pcre')
|
||||
|
||||
if '@' in valid and valid[1] == '@':
|
||||
comps = valid.split('@')
|
||||
comps = valid.split('@', 1)
|
||||
v_matcher = ref.get(comps[0])
|
||||
v_expr = comps[1]
|
||||
else:
|
||||
|
|
|
@ -16,6 +16,10 @@ import integration
|
|||
import salt.utils
|
||||
|
||||
|
||||
def minion_in_returns(minion, lines):
|
||||
return bool([True for line in lines if line == '{0}:'.format(minion)])
|
||||
|
||||
|
||||
class MatchTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
|
||||
'''
|
||||
Test salt matchers
|
||||
|
@ -40,58 +44,58 @@ class MatchTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
|
|||
test salt compound matcher
|
||||
'''
|
||||
data = self.run_salt('-C "min* and G@test_grain:cheese" test.ping')
|
||||
data = '\n'.join(data)
|
||||
self.assertIn('minion', data)
|
||||
self.assertNotIn('sub_minion', data)
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-C "min* and not G@test_grain:foo" test.ping')
|
||||
data = '\n'.join(data)
|
||||
self.assertIn('minion', data)
|
||||
self.assertNotIn('sub_minion', data)
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-C "min* not G@test_grain:foo" test.ping')
|
||||
data = '\n'.join(data)
|
||||
self.assertIn('minion', data)
|
||||
self.assertNotIn('sub_minion', data)
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
match = 'P@test_grain:^cheese$ and * and G@test_grain:cheese'
|
||||
data = self.run_salt('-t 1 -C \'{0}\' test.ping'.format(match))
|
||||
data = '\n'.join(data)
|
||||
self.assertIn('minion', data)
|
||||
self.assertNotIn('sub_minion', data)
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
match = 'L@sub_minion and E@.*'
|
||||
data = self.run_salt('-t 1 -C "{0}" test.ping'.format(match))
|
||||
data = '\n'.join(data)
|
||||
self.assertIn('sub_minion', data)
|
||||
self.assertNotIn('minion', data.replace('sub_minion', 'stub'))
|
||||
self.assertTrue(minion_in_returns('sub_minion', data))
|
||||
self.assertFalse(minion_in_returns('minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt("-C 'not sub_minion' test.ping")
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt("-C '* and ( not G@test_grain:cheese )' test.ping")
|
||||
self.assertFalse(minion_in_returns('minion', data))
|
||||
self.assertTrue(minion_in_returns('sub_minion', data))
|
||||
|
||||
def test_nodegroup(self):
|
||||
'''
|
||||
test salt nodegroup matcher
|
||||
'''
|
||||
def minion_in_target(minion, lines):
|
||||
return sum([line == '{0}:'.format(minion) for line in lines])
|
||||
|
||||
data = self.run_salt('-N min test.ping')
|
||||
self.assertTrue(minion_in_target('minion', data))
|
||||
self.assertFalse(minion_in_target('sub_minion', data))
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-N sub_min test.ping')
|
||||
self.assertFalse(minion_in_target('minion', data))
|
||||
self.assertTrue(minion_in_target('sub_minion', data))
|
||||
self.assertFalse(minion_in_returns('minion', data))
|
||||
self.assertTrue(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-N mins test.ping')
|
||||
self.assertTrue(minion_in_target('minion', data))
|
||||
self.assertTrue(minion_in_target('sub_minion', data))
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertTrue(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-N unknown_nodegroup test.ping')
|
||||
self.assertFalse(minion_in_target('minion', data))
|
||||
self.assertFalse(minion_in_target('sub_minion', data))
|
||||
self.assertFalse(minion_in_returns('minion', data))
|
||||
self.assertFalse(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = self.run_salt('-N redundant_minions test.ping')
|
||||
self.assertTrue(minion_in_target('minion', data))
|
||||
self.assertTrue(minion_in_target('sub_minion', data))
|
||||
self.assertTrue(minion_in_returns('minion', data))
|
||||
self.assertTrue(minion_in_returns('sub_minion', data))
|
||||
time.sleep(2)
|
||||
data = '\n'.join(self.run_salt('-N nodegroup_loop_a test.ping'))
|
||||
self.assertIn('No minions matched', data)
|
||||
|
|
Loading…
Add table
Reference in a new issue