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