mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Add matchers directory and move default set of matchers to separate .py
This commit is contained in:
parent
4555497583
commit
497327dd93
300
salt/matchers/default.py
Normal file
300
salt/matchers/default.py
Normal file
@ -0,0 +1,300 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
This is the default set of matcher functions.
|
||||
|
||||
NOTE: These functions are converted to methods on the Matcher module during master and minion startup.
|
||||
This is why they all take `self` but are not defined inside a `class:` declaration.
|
||||
'''
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import re
|
||||
import fnmatch
|
||||
from salt.defaults import DEFAULT_TARGET_DELIM
|
||||
from salt.ext import six
|
||||
if six.PY3:
|
||||
import ipaddress
|
||||
else:
|
||||
import salt.ext.ipaddress as ipaddres
|
||||
|
||||
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: # 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 = 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
|
Loading…
Reference in New Issue
Block a user