1""" 2The match module allows for match routines to be run and determine target specs 3""" 4 5import copy 6import inspect 7import logging 8import sys 9from collections.abc import Mapping 10 11import salt.loader 12from salt.defaults import DEFAULT_TARGET_DELIM 13from salt.exceptions import SaltException 14 15__func_alias__ = {"list_": "list"} 16 17log = logging.getLogger(__name__) 18 19 20def compound(tgt, minion_id=None): 21 """ 22 Return True if the minion ID matches the given compound target 23 24 minion_id 25 Specify the minion ID to match against the target expression 26 27 .. versionadded:: 2014.7.0 28 29 CLI Example: 30 31 .. code-block:: bash 32 33 salt '*' match.compound 'L@cheese,foo and *' 34 """ 35 if minion_id is not None: 36 if not isinstance(minion_id, str): 37 minion_id = str(minion_id) 38 matchers = salt.loader.matchers(__opts__) 39 try: 40 ret = matchers["compound_match.match"](tgt, opts=__opts__, minion_id=minion_id) 41 except Exception as exc: # pylint: disable=broad-except 42 log.exception(exc) 43 ret = False 44 45 return ret 46 47 48def ipcidr(tgt): 49 """ 50 Return True if the minion matches the given ipcidr target 51 52 CLI Example: 53 54 .. code-block:: bash 55 56 salt '*' match.ipcidr '192.168.44.0/24' 57 58 delimiter 59 Pillar Example: 60 61 .. code-block:: yaml 62 63 '172.16.0.0/12': 64 - match: ipcidr 65 - nodeclass: internal 66 67 """ 68 matchers = salt.loader.matchers(__opts__) 69 try: 70 return matchers["ipcidr_match.match"](tgt, opts=__opts__) 71 except Exception as exc: # pylint: disable=broad-except 72 log.exception(exc) 73 return False 74 75 76def pillar_pcre(tgt, delimiter=DEFAULT_TARGET_DELIM): 77 """ 78 Return True if the minion matches the given pillar_pcre target. The 79 ``delimiter`` argument can be used to specify a different delimiter. 80 81 CLI Example: 82 83 .. code-block:: bash 84 85 salt '*' match.pillar_pcre 'cheese:(swiss|american)' 86 salt '*' match.pillar_pcre 'clone_url|https://github\\.com/.*\\.git' delimiter='|' 87 88 delimiter 89 Specify an alternate delimiter to use when traversing a nested dict 90 91 .. versionadded:: 2014.7.0 92 93 delim 94 Specify an alternate delimiter to use when traversing a nested dict 95 96 .. versionadded:: 0.16.4 97 .. deprecated:: 2015.8.0 98 """ 99 matchers = salt.loader.matchers(__opts__) 100 try: 101 return matchers["pillar_pcre_match.match"]( 102 tgt, delimiter=delimiter, opts=__opts__ 103 ) 104 except Exception as exc: # pylint: disable=broad-except 105 log.exception(exc) 106 return False 107 108 109def pillar(tgt, delimiter=DEFAULT_TARGET_DELIM): 110 """ 111 Return True if the minion matches the given pillar target. The 112 ``delimiter`` argument can be used to specify a different delimiter. 113 114 CLI Example: 115 116 .. code-block:: bash 117 118 salt '*' match.pillar 'cheese:foo' 119 salt '*' match.pillar 'clone_url|https://github.com/saltstack/salt.git' delimiter='|' 120 121 delimiter 122 Specify an alternate delimiter to use when traversing a nested dict 123 124 .. versionadded:: 2014.7.0 125 126 delim 127 Specify an alternate delimiter to use when traversing a nested dict 128 129 .. versionadded:: 0.16.4 130 .. deprecated:: 2015.8.0 131 """ 132 matchers = salt.loader.matchers(__opts__) 133 try: 134 return matchers["pillar_match.match"](tgt, delimiter=delimiter, opts=__opts__) 135 except Exception as exc: # pylint: disable=broad-except 136 log.exception(exc) 137 return False 138 139 140def data(tgt): 141 """ 142 Return True if the minion matches the given data target 143 144 CLI Example: 145 146 .. code-block:: bash 147 148 salt '*' match.data 'spam:eggs' 149 """ 150 matchers = salt.loader.matchers(__opts__) 151 try: 152 return matchers["data_match.match"](tgt, opts=__opts__) 153 except Exception as exc: # pylint: disable=broad-except 154 log.exception(exc) 155 return False 156 157 158def grain_pcre(tgt, delimiter=DEFAULT_TARGET_DELIM): 159 """ 160 Return True if the minion matches the given grain_pcre target. The 161 ``delimiter`` argument can be used to specify a different delimiter. 162 163 CLI Example: 164 165 .. code-block:: bash 166 167 salt '*' match.grain_pcre 'os:Fedo.*' 168 salt '*' match.grain_pcre 'ipv6|2001:.*' delimiter='|' 169 170 delimiter 171 Specify an alternate delimiter to use when traversing a nested dict 172 173 .. versionadded:: 2014.7.0 174 175 delim 176 Specify an alternate delimiter to use when traversing a nested dict 177 178 .. versionadded:: 0.16.4 179 .. deprecated:: 2015.8.0 180 """ 181 matchers = salt.loader.matchers(__opts__) 182 try: 183 return matchers["grain_pcre_match.match"]( 184 tgt, delimiter=delimiter, opts=__opts__ 185 ) 186 except Exception as exc: # pylint: disable=broad-except 187 log.exception(exc) 188 return False 189 190 191def grain(tgt, delimiter=DEFAULT_TARGET_DELIM): 192 """ 193 Return True if the minion matches the given grain target. The ``delimiter`` 194 argument can be used to specify a different delimiter. 195 196 CLI Example: 197 198 .. code-block:: bash 199 200 salt '*' match.grain 'os:Ubuntu' 201 salt '*' match.grain 'ipv6|2001:db8::ff00:42:8329' delimiter='|' 202 203 delimiter 204 Specify an alternate delimiter to use when traversing a nested dict 205 206 .. versionadded:: 2014.7.0 207 208 delim 209 Specify an alternate delimiter to use when traversing a nested dict 210 211 .. versionadded:: 0.16.4 212 .. deprecated:: 2015.8.0 213 """ 214 matchers = salt.loader.matchers(__opts__) 215 try: 216 return matchers["grain_match.match"](tgt, delimiter=delimiter, opts=__opts__) 217 except Exception as exc: # pylint: disable=broad-except 218 log.exception(exc) 219 return False 220 221 222def list_(tgt, minion_id=None): 223 """ 224 Return True if the minion ID matches the given list target 225 226 minion_id 227 Specify the minion ID to match against the target expression 228 229 .. versionadded:: 2014.7.0 230 231 CLI Example: 232 233 .. code-block:: bash 234 235 salt '*' match.list 'server1,server2' 236 """ 237 if minion_id is not None: 238 if not isinstance(minion_id, str): 239 minion_id = str(minion_id) 240 matchers = salt.loader.matchers(__opts__) 241 try: 242 return matchers["list_match.match"](tgt, opts=__opts__, minion_id=minion_id) 243 except Exception as exc: # pylint: disable=broad-except 244 log.exception(exc) 245 return False 246 247 248def pcre(tgt, minion_id=None): 249 """ 250 Return True if the minion ID matches the given pcre target 251 252 minion_id 253 Specify the minion ID to match against the target expression 254 255 .. versionadded:: 2014.7.0 256 257 CLI Example: 258 259 .. code-block:: bash 260 261 salt '*' match.pcre '.*' 262 """ 263 if minion_id is not None: 264 if not isinstance(minion_id, str): 265 minion_id = str(minion_id) 266 matchers = salt.loader.matchers(__opts__) 267 try: 268 return matchers["pcre_match.match"](tgt, opts=__opts__, minion_id=minion_id) 269 except Exception as exc: # pylint: disable=broad-except 270 log.exception(exc) 271 return False 272 273 274def glob(tgt, minion_id=None): 275 """ 276 Return True if the minion ID matches the given glob target 277 278 minion_id 279 Specify the minion ID to match against the target expression 280 281 .. versionadded:: 2014.7.0 282 283 CLI Example: 284 285 .. code-block:: bash 286 287 salt '*' match.glob '*' 288 """ 289 if minion_id is not None: 290 if not isinstance(minion_id, str): 291 minion_id = str(minion_id) 292 matchers = salt.loader.matchers(__opts__) 293 294 try: 295 return matchers["glob_match.match"](tgt, opts=__opts__, minion_id=minion_id) 296 except Exception as exc: # pylint: disable=broad-except 297 log.exception(exc) 298 return False 299 300 301def filter_by( 302 lookup, 303 tgt_type="compound", 304 minion_id=None, 305 merge=None, 306 merge_lists=False, 307 default="default", 308): 309 """ 310 Return the first match in a dictionary of target patterns 311 312 .. versionadded:: 2014.7.0 313 314 CLI Example: 315 316 .. code-block:: bash 317 318 salt '*' match.filter_by '{foo*: Foo!, bar*: Bar!}' minion_id=bar03 319 320 Pillar Example: 321 322 .. code-block:: jinja 323 324 # Filter the data for the current minion into a variable: 325 {% set roles = salt['match.filter_by']({ 326 'web*': ['app', 'caching'], 327 'db*': ['db'], 328 }, minion_id=grains['id'], default='web*') %} 329 330 # Make the filtered data available to Pillar: 331 roles: {{ roles | yaml() }} 332 """ 333 expr_funcs = dict( 334 inspect.getmembers(sys.modules[__name__], predicate=inspect.isfunction) 335 ) 336 337 for key in lookup: 338 params = (key, minion_id) if minion_id else (key,) 339 if expr_funcs[tgt_type](*params): 340 if merge: 341 if not isinstance(merge, Mapping): 342 raise SaltException( 343 "filter_by merge argument must be a dictionary." 344 ) 345 346 if lookup[key] is None: 347 return merge 348 else: 349 salt.utils.dictupdate.update( 350 lookup[key], copy.deepcopy(merge), merge_lists=merge_lists 351 ) 352 353 return lookup[key] 354 355 return lookup.get(default, None) 356 357 358def search_by(lookup, tgt_type="compound", minion_id=None): 359 """ 360 Search a dictionary of target strings for matching targets 361 362 This is the inverse of :py:func:`match.filter_by 363 <salt.modules.match.filter_by>` and allows matching values instead of 364 matching keys. A minion can be matched by multiple entries. 365 366 .. versionadded:: 2017.7.0 367 368 CLI Example: 369 370 .. code-block:: bash 371 372 salt '*' match.search_by '{web: [node1, node2], db: [node2, node]}' 373 374 Pillar Example: 375 376 .. code-block:: jinja 377 378 {% set roles = salt.match.search_by({ 379 'web': ['G@os_family:Debian not nodeX'], 380 'db': ['L@node2,node3 and G@datacenter:west'], 381 'caching': ['node3', 'node4'], 382 }) %} 383 384 # Make the filtered data available to Pillar: 385 roles: {{ roles | yaml() }} 386 """ 387 expr_funcs = dict( 388 inspect.getmembers(sys.modules[__name__], predicate=inspect.isfunction) 389 ) 390 391 matches = [] 392 for key, target_list in lookup.items(): 393 for target in target_list: 394 params = (target, minion_id) if minion_id else (target,) 395 if expr_funcs[tgt_type](*params): 396 matches.append(key) 397 398 return matches or None 399