1""" 2Roster matching by various criteria (glob, pcre, etc) 3""" 4 5import copy 6import fnmatch 7import functools 8import logging 9import re 10 11# Try to import range from https://github.com/ytoolshed/range 12HAS_RANGE = False 13try: 14 import seco.range 15 16 HAS_RANGE = True 17except ImportError: 18 pass 19# pylint: enable=import-error 20 21 22log = logging.getLogger(__name__) 23 24 25def targets(conditioned_raw, tgt, tgt_type, ipv="ipv4"): 26 rmatcher = RosterMatcher(conditioned_raw, tgt, tgt_type, ipv) 27 return rmatcher.targets() 28 29 30def _tgt_set(tgt): 31 """ 32 Return the tgt as a set of literal names 33 """ 34 try: 35 # A comma-delimited string 36 return set(tgt.split(",")) 37 except AttributeError: 38 # Assume tgt is already a non-string iterable. 39 return set(tgt) 40 41 42class RosterMatcher: 43 """ 44 Matcher for the roster data structure 45 """ 46 47 def __init__(self, raw, tgt, tgt_type, ipv="ipv4"): 48 self.tgt = tgt 49 self.tgt_type = tgt_type 50 self.raw = raw 51 self.ipv = ipv 52 53 def targets(self): 54 """ 55 Execute the correct tgt_type routine and return 56 """ 57 try: 58 return getattr(self, "ret_{}_minions".format(self.tgt_type))() 59 except AttributeError: 60 return {} 61 62 def _ret_minions(self, filter_): 63 """ 64 Filter minions by a generic filter. 65 """ 66 minions = {} 67 for minion in filter_(self.raw): 68 data = self.get_data(minion) 69 if data: 70 minions[minion] = data.copy() 71 return minions 72 73 def ret_glob_minions(self): 74 """ 75 Return minions that match via glob 76 """ 77 fnfilter = functools.partial(fnmatch.filter, pat=self.tgt) 78 return self._ret_minions(fnfilter) 79 80 def ret_pcre_minions(self): 81 """ 82 Return minions that match via pcre 83 """ 84 tgt = re.compile(self.tgt) 85 refilter = functools.partial(filter, tgt.match) 86 return self._ret_minions(refilter) 87 88 def ret_list_minions(self): 89 """ 90 Return minions that match via list 91 """ 92 tgt = _tgt_set(self.tgt) 93 return self._ret_minions(tgt.intersection) 94 95 def ret_nodegroup_minions(self): 96 """ 97 Return minions which match the special list-only groups defined by 98 ssh_list_nodegroups 99 """ 100 nodegroup = __opts__.get("ssh_list_nodegroups", {}).get(self.tgt, []) 101 nodegroup = _tgt_set(nodegroup) 102 return self._ret_minions(nodegroup.intersection) 103 104 def ret_range_minions(self): 105 """ 106 Return minions that are returned by a range query 107 """ 108 if HAS_RANGE is False: 109 raise RuntimeError("Python lib 'seco.range' is not available") 110 111 minions = {} 112 range_hosts = _convert_range_to_list(self.tgt, __opts__["range_server"]) 113 return self._ret_minions(range_hosts.__contains__) 114 115 def get_data(self, minion): 116 """ 117 Return the configured ip 118 """ 119 ret = copy.deepcopy(__opts__.get("roster_defaults", {})) 120 if isinstance(self.raw[minion], str): 121 ret.update({"host": self.raw[minion]}) 122 return ret 123 elif isinstance(self.raw[minion], dict): 124 ret.update(self.raw[minion]) 125 return ret 126 return False 127 128 129def _convert_range_to_list(tgt, range_server): 130 """ 131 convert a seco.range range into a list target 132 """ 133 r = seco.range.Range(range_server) 134 try: 135 return r.expand(tgt) 136 except seco.range.RangeException as err: 137 log.error("Range server exception: %s", err) 138 return [] 139