Licenses: Updated the list of licenses and added a PDF containing all license texts
[check_mk.git] / cmk_base / check_api.py
blob8ecf4da741dd2dcedf2f42eab9fb78fab48c0485
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 """
28 The things in this module specify the official Check_MK check API. Meaning all
29 variables, functions etc. and default modules that are available to checks.
31 Modules available by default (pre imported by Check_MK):
32 collections
33 enum
34 fnmatch
35 functools
36 math
39 socket
40 sys
41 time
42 pprint
44 Global variables:
45 from cmk.utils.regex import regex
46 import cmk.utils.render as render
47 core_state_names Names of states. Usually used to convert numeric states
48 to their name for adding it to the plugin output.
49 The mapping is like this:
51 -1: 'PEND'
52 0: 'OK'
53 1: 'WARN'
54 2: 'CRIT'
55 3: 'UNKN'
57 state_markers Symbolic representations of states in plugin output.
58 Will be displayed colored by the Check_MK GUI.
59 The mapping is like this:
61 0: ''
62 1: '(!)'
63 2: '(!!)'
64 3: '(?)'
66 nagios_illegal_chars Characters not allowed to be used in service
67 descriptions. Can be used in discovery functions to
68 remove unwanted characters from a string. The unwanted
69 chars default are: `;~!$%^&*|\'"<>?,()=
72 OID_BIN TODO
73 OID_END TODO
74 OID_END_BIN TODO
75 OID_END_OCTET_STRING TODO
76 OID_STRING TODO
78 MGMT_ONLY Check is only executed for management boards.
79 HOST_PRECEDENCE Use host address/credentials eg. when it's a SNMP HOST.
80 HOST_ONLY Check is only executed for real SNMP hosts.
82 RAISE Used as value for the "onwrap" argument of the get_rate()
83 function. See get_rate() documentation for details
84 SKIP Used as value for the "onwrap" argument of the get_rate()
85 function. See get_rate() documentation for details
86 ZERO Used as value for the "onwrap" argument of the get_rate()
87 function. See get_rate() documentation for details
88 """ # # pylint: disable=pointless-string-statement
90 # NOTE: The above suppression is necessary because our testing framework blindly
91 # concatenates lots of files, including this one.
93 # We import several modules here for the checks
95 # TODO: Move imports directly to checks?
96 import collections # pylint: disable=unused-import
97 import enum # pylint: disable=unused-import
98 import fnmatch # pylint: disable=unused-import
99 import functools
100 import math # pylint: disable=unused-import
101 import os
102 import re # pylint: disable=unused-import
103 import socket # pylint: disable=unused-import
104 import sys # pylint: disable=unused-import
105 import time
106 # NOTE: We do not use pprint in this module, but it is part of the check API.
107 import pprint # pylint: disable=unused-import
109 from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union # pylint: disable=unused-import
111 import six
113 import cmk.utils.debug as _debug
114 import cmk.utils.defines as _defines
115 import cmk.utils.paths as _paths
116 from cmk.utils.exceptions import MKGeneralException
117 from cmk.utils.regex import regex # pylint: disable=unused-import
118 import cmk.utils.render as render
120 # These imports are not meant for use in the API. So we prefix the names
121 # with an underscore. These names will be skipped when loading into the
122 # check context.
123 import cmk_base.utils as _utils
124 import cmk_base.config as _config
125 import cmk_base.console as _console # pylint: disable=unused-import
126 import cmk_base.snmp_utils as _snmp_utils
127 import cmk_base.item_state as _item_state
128 import cmk_base.prediction as _prediction
129 import cmk_base.check_api_utils as _check_api_utils
132 def get_check_api_context():
133 """This is called from cmk_base code to get the Check API things. Don't
134 use this from checks."""
135 return {k: v for k, v in globals().items() if not k.startswith("_")}
139 # .--Check API-----------------------------------------------------------.
140 # | ____ _ _ _ ____ ___ |
141 # | / ___| |__ ___ ___| | __ / \ | _ \_ _| |
142 # | | | | '_ \ / _ \/ __| |/ / / _ \ | |_) | | |
143 # | | |___| | | | __/ (__| < / ___ \| __/| | |
144 # | \____|_| |_|\___|\___|_|\_\ /_/ \_\_| |___| |
145 # | |
146 # +----------------------------------------------------------------------+
147 # | Helper API for being used in checks |
148 # '----------------------------------------------------------------------'
150 # Names of texts usually output by checks
151 core_state_names = _defines.short_service_state_names()
153 # Symbolic representations of states in plugin output
154 state_markers = _check_api_utils.state_markers
156 BINARY = _snmp_utils.BINARY
157 CACHED_OID = _snmp_utils.CACHED_OID
159 OID_END = _snmp_utils.OID_END
160 OID_STRING = _snmp_utils.OID_STRING
161 OID_BIN = _snmp_utils.OID_BIN
162 OID_END_BIN = _snmp_utils.OID_END_BIN
163 OID_END_OCTET_STRING = _snmp_utils.OID_END_OCTET_STRING
164 binstring_to_int = _snmp_utils.binstring_to_int
166 # Management board checks
167 MGMT_ONLY = _check_api_utils.MGMT_ONLY # Use host address/credentials when it's a SNMP HOST
168 HOST_PRECEDENCE = _check_api_utils.HOST_PRECEDENCE # Check is only executed for mgmt board (e.g. Managegment Uptime)
169 HOST_ONLY = _check_api_utils.HOST_ONLY # Check is only executed for real SNMP host (e.g. interfaces)
171 host_name = _check_api_utils.host_name
172 service_description = _check_api_utils.service_description
173 check_type = _check_api_utils.check_type
175 network_interface_scan_registry = _snmp_utils.MutexScanRegistry()
178 def saveint(i):
179 """Tries to cast a string to an integer and return it. In case this
180 fails, it returns 0.
182 Advice: Please don't use this function in new code. It is understood as
183 bad style these days, because in case you get 0 back from this function,
184 you can not know whether it is really 0 or something went wrong."""
185 try:
186 return int(i)
187 except:
188 return 0
191 def savefloat(f):
192 """Tries to cast a string to an float and return it. In case this fails,
193 it returns 0.0.
195 Advice: Please don't use this function in new code. It is understood as
196 bad style these days, because in case you get 0.0 back from this function,
197 you can not know whether it is really 0.0 or something went wrong."""
198 try:
199 return float(f)
200 except:
201 return 0.0
204 service_extra_conf = _config.service_extra_conf
205 host_extra_conf = _config.host_extra_conf
206 in_binary_hostlist = _config.in_binary_hostlist
207 host_extra_conf_merged = _config.host_extra_conf_merged
209 # TODO: Only used by logwatch check. Can we clean this up?
210 get_rule_options = _config.get_rule_options
212 # These functions were used in some specific checks until 1.6. Don't add it to
213 # the future check API. It's kept here for compatibility reasons for now.
214 in_extraconf_hostlist = _config.in_extraconf_hostlist
215 hosttags_match_taglist = _config.hosttags_match_taglist
216 all_matching_hosts = _config.all_matching_hosts
219 # These functions were used in some specific checks until 1.6. Don't add it to
220 # the future check API. It's kept here for compatibility reasons for now.
221 def tags_of_host(hostname):
222 return _config.get_config_cache().get_host_config(hostname).tags
225 nagios_illegal_chars = _config.nagios_illegal_chars
226 is_ipv6_primary = _config.is_ipv6_primary
227 is_cmc = _config.is_cmc
229 get_age_human_readable = lambda secs: str(render.Age(secs))
230 get_bytes_human_readable = render.fmt_bytes
231 quote_shell_string = _utils.quote_shell_string
234 def get_checkgroup_parameters(group, deflt=None):
235 return _config.checkgroup_parameters.get(group, deflt)
238 # TODO: Replace by some render.* function / move to render module?
239 def get_filesize_human_readable(size):
240 """Format size of a file for humans.
242 Similar to get_bytes_human_readable, but optimized for file
243 sizes. Really only use this for files. We assume that for smaller
244 files one wants to compare the exact bytes of a file, so the
245 threshold to show the value as MB/GB is higher as the one of
246 get_bytes_human_readable()."""
247 if size < 4 * 1024 * 1024:
248 return "%d B" % int(size)
249 elif size < 4 * 1024 * 1024 * 1024:
250 return "%.2f MB" % (float(size) / (1024 * 1024))
251 return "%.2f GB" % (float(size) / (1024 * 1024 * 1024))
254 # TODO: Replace by some render.* function / move to render module?
255 def get_nic_speed_human_readable(speed):
256 """Format network speed (bit/s) for humans."""
257 try:
258 speedi = int(speed)
259 if speedi == 10000000:
260 speed = "10 Mbit/s"
261 elif speedi == 100000000:
262 speed = "100 Mbit/s"
263 elif speedi == 1000000000:
264 speed = "1 Gbit/s"
265 elif speedi < 1500:
266 speed = "%d bit/s" % speedi
267 elif speedi < 1000000:
268 speed = "%.1f Kbit/s" % (speedi / 1000.0)
269 elif speedi < 1000000000:
270 speed = "%.2f Mbit/s" % (speedi / 1000000.0)
271 else:
272 speed = "%.2f Gbit/s" % (speedi / 1000000000.0)
273 except:
274 pass
275 return speed
278 # TODO: Replace by some render.* function / move to render module?
279 def get_timestamp_human_readable(timestamp):
280 """Format a time stamp for humans in "%Y-%m-%d %H:%M:%S" format.
281 In case None is given or timestamp is 0, it returns "never"."""
282 if timestamp:
283 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(float(timestamp)))
284 return "never"
287 # TODO: Replace by some render.* function / move to render module?
288 def get_relative_date_human_readable(timestamp):
289 """Formats the given timestamp for humans "in ..." for future times
290 or "... ago" for past timestamps."""
291 now = time.time()
292 if timestamp > now:
293 return "in " + get_age_human_readable(timestamp - now)
294 return get_age_human_readable(now - timestamp) + " ago"
297 # TODO: Replace by some render.* function / move to render module?
298 def get_percent_human_readable(perc, precision=2):
299 """Format perc (0 <= perc <= 100 + x) so that precision
300 digits are being displayed. This avoids a "0.00%" for
301 very small numbers."""
302 if abs(perc) > 4:
303 return "%.1f%%" % perc
304 return "%.3g%%" % perc
308 # Counter handling
311 set_item_state = _item_state.set_item_state
312 get_item_state = _item_state.get_item_state
313 get_all_item_states = _item_state.get_all_item_states
314 clear_item_state = _item_state.clear_item_state
315 clear_item_states_by_full_keys = _item_state.clear_item_states_by_full_keys
316 get_rate = _item_state.get_rate
317 get_average = _item_state.get_average
318 # TODO: Cleanup checks and deprecate this
319 last_counter_wrap = _item_state.last_counter_wrap
321 SKIP = _item_state.SKIP
322 RAISE = _item_state.RAISE
323 ZERO = _item_state.ZERO
325 MKCounterWrapped = _item_state.MKCounterWrapped
328 def _normalize_bounds(levels):
329 if len(levels) == 2: # upper warn and crit
330 warn_upper, crit_upper = levels[0], levels[1]
331 warn_lower, crit_lower = None, None
333 else: # upper and lower warn and crit
334 warn_upper, crit_upper = levels[0], levels[1]
335 warn_lower, crit_lower = levels[2], levels[3]
337 return warn_upper, crit_upper, warn_lower, crit_lower
340 def _check_boundaries(value, levels, human_readable_func, unit_info):
341 warn_upper, crit_upper, warn_lower, crit_lower = _normalize_bounds(levels)
342 # Critical cases
343 if crit_upper is not None and value >= crit_upper:
344 return 2, _levelsinfo_ty("at", warn_upper, crit_upper, human_readable_func, unit_info)
345 if crit_lower is not None and value < crit_lower:
346 return 2, _levelsinfo_ty("below", warn_lower, crit_lower, human_readable_func, unit_info)
348 # Warning cases
349 if warn_upper is not None and value >= warn_upper:
350 return 1, _levelsinfo_ty("at", warn_upper, crit_upper, human_readable_func, unit_info)
351 if warn_lower is not None and value < warn_lower:
352 return 1, _levelsinfo_ty("below", warn_lower, crit_lower, human_readable_func, unit_info)
353 return 0, ""
356 def _levelsinfo_ty(ty, warn, crit, human_readable_func, unit_info):
357 return " (warn/crit {0} {1}{3}/{2}{3})".format(ty, human_readable_func(warn),
358 human_readable_func(crit), unit_info)
361 def check_levels(value,
362 dsname,
363 params,
364 unit="",
365 factor=1.0,
366 scale=1.0,
367 statemarkers=False,
368 human_readable_func=None,
369 infoname=None):
370 """Generic function for checking a value against levels
372 This also supports predictive levels.
374 value: currently measured value
375 dsname: name of the datasource in the RRD that corresponds to this value
376 or None in order to skip perfdata
377 params: None or Tuple(None, None) -> no level checking.
378 Tuple variants with non-None values:
379 Tuple[warn_upper, crit_upper] -> upper level checking only.
380 Tuple[warn_upper, crit_upper, warn_lower, crit_lower]
381 -> upper and lower level checking.
382 If a Dict is passed to check_levels, predictive levels are used
383 automatically. The following constellations are possible:
384 Dict containing "lower" as key -> lower level checking.
385 Dict containing "upper" or "levels_upper_min" as key -> upper level checking.
386 Dict containing "lower" and "upper"/"levels_upper_min" as key ->
387 lower and upper level checking.
388 unit: unit to be displayed in the plugin output.
389 Be aware: if a (builtin) human_readable_func is stated which already
390 provides a unit info, then this unit is not necessary. An additional
391 unit info is useful if a rate is calculated, eg.
392 unit="/s",
393 human_readable_func=get_bytes_human_readable,
394 results in 'X B/s'.
395 factor: the levels are multiplied with this factor before applying
396 them to the value. This is being used for the CPU load check
397 currently. The levels here are "per CPU", so the number of
398 CPUs is used as factor.
399 scale: Scale of the levels in relation to "value" and the value in the RRDs.
400 For example if the levels are specified in GB and the RRD store KB, then
401 the scale is 1024*1024.
402 human_readable_func: Single argument function to present in a human readable fashion
403 the value. Builtin human_readable-functions already provide a unit:
404 - get_percent_human_readable
405 - get_age_human_readable
406 - get_bytes_human_readable
407 - get_filesize_human_readable
408 - get_nic_speed_human_readable
409 - get_timestamp_human_readable
410 - get_relative_date_human_readable
411 infoname: Perf value name for infotext like a title.
413 unit_info = ""
414 if unit.startswith('/'):
415 unit_info = unit
416 elif unit:
417 unit_info = " %s" % unit
419 if human_readable_func is None:
420 human_readable_func = lambda x: "%.2f" % (x / scale)
422 def scale_value(v):
423 if v is None:
424 return None
425 return v * factor * scale
427 if infoname:
428 infotext = "%s: %s%s" % (infoname, human_readable_func(value), unit_info)
429 else:
430 infotext = "%s%s" % (human_readable_func(value), unit_info)
432 # None, {} or (None, None) -> do not check any levels
433 if not params or params == (None, None):
434 if dsname:
435 return 0, infotext, [(dsname, value)]
436 return 0, infotext, []
438 # Pair of numbers -> static levels
439 elif isinstance(params, tuple):
440 levels = map(scale_value, _normalize_bounds(params))
441 ref_value = None
443 # Dictionary -> predictive levels
444 else:
445 try:
446 ref_value, levels = \
447 _prediction.get_levels(host_name(), service_description(),
448 dsname, params, "MAX", levels_factor=factor * scale)
450 if ref_value:
451 predictive_levels_msg = "predicted reference: %s" % human_readable_func(ref_value)
452 else:
453 predictive_levels_msg = "no reference for prediction yet"
455 except MKGeneralException as e:
456 ref_value = None
457 levels = [None, None, None, None]
458 predictive_levels_msg = "no reference for prediction (%s)" % e
460 except Exception as e:
461 if _debug.enabled():
462 raise
463 return 3, "%s" % e, []
465 if predictive_levels_msg:
466 infotext += " (%s)" % predictive_levels_msg
468 state, levelstext = _check_boundaries(value, levels, human_readable_func, unit_info)
469 infotext += levelstext
470 if statemarkers:
471 infotext += state_markers[state]
473 if dsname:
474 perfdata = [(dsname, value, levels[0], levels[1])]
475 if ref_value:
476 perfdata.append(('predict_' + dsname, ref_value))
477 else:
478 perfdata = []
480 return state, infotext, perfdata
483 def get_effective_service_level():
484 """Get the service level that applies to the current service.
485 This can only be used within check functions, not during discovery nor parsing."""
486 service_levels = _config.service_extra_conf(host_name(), service_description(),
487 _config.service_service_levels)
489 if service_levels:
490 return service_levels[0]
491 else:
492 service_levels = _config.host_extra_conf(host_name(), _config.host_service_levels)
493 if service_levels:
494 return service_levels[0]
495 return 0
498 def utc_mktime(time_struct):
499 """Works like time.mktime() but assumes the time_struct to be in UTC,
500 not in local time."""
501 import calendar
502 return calendar.timegm(time_struct)
505 def passwordstore_get_cmdline(fmt, pw):
506 """Use this to prepare a command line argument for using a password from the
507 Check_MK password store or an explicitly configured password."""
508 if not isinstance(pw, tuple):
509 pw = ("password", pw)
511 if pw[0] == "password":
512 return fmt % pw[1]
514 return ("store", pw[1], fmt)
517 def get_http_proxy(http_proxy):
518 """Returns proxy URL to be used for HTTP requests
520 Pass a value configured by the user using the HTTPProxyReference valuespec to this function
521 and you will get back ether a proxy URL, an empty string to enforce no proxy usage or None
522 to use the proxy configuration from the process environment.
524 return _config.get_http_proxy(http_proxy)
527 def get_agent_data_time():
528 """Use this function to get the age of the agent data cache file
529 of tcp or snmp hosts or None in case of piggyback data because
530 we do not exactly know the latest agent data. Maybe one time
531 we can handle this. For cluster hosts an exception is raised."""
532 return _agent_cache_file_age(host_name(), check_type())
535 def _agent_cache_file_age(hostname, check_plugin_name):
536 if _config.is_cluster(hostname):
537 raise MKGeneralException("get_agent_data_time() not valid for cluster")
539 import cmk_base.check_utils
540 if cmk_base.check_utils.is_snmp_check(check_plugin_name):
541 cachefile = _paths.tcp_cache_dir + "/" + hostname + "." + check_plugin_name.split(".")[0]
542 elif cmk_base.check_utils.is_tcp_check(check_plugin_name):
543 cachefile = _paths.tcp_cache_dir + "/" + hostname
544 else:
545 cachefile = None
547 if cachefile is not None and os.path.exists(cachefile):
548 return _utils.cachefile_age(cachefile)
550 return None
553 def get_parsed_item_data(check_function):
554 """Use this decorator to determine the parsed item data outside
555 of the respective check function.
557 The check function can hence be defined as follows:
559 @get_parsed_item_data
560 def check_<check_name>(item, params, data):
563 In case of parsed not being a dict the decorator returns 3
564 (UNKN state) with a wrong usage message.
565 In case of item not existing as a key in parsed or parsed[item]
566 not existing the decorator gives an empty return leading to
567 cmk_base returning 3 (UNKN state) with an item not found message
568 (see cmk_base/checking.py).
571 @functools.wraps(check_function)
572 def wrapped_check_function(item, params, parsed):
573 if not isinstance(parsed, dict):
574 return 3, "Wrong usage of decorator function 'get_parsed_item_data': parsed is not a dict"
575 if item not in parsed or not parsed[item]:
576 return
577 return check_function(item, params, parsed[item])
579 return wrapped_check_function
582 def discover_single(info):
583 # type: (Union[List, Dict]) -> Optional[List]
584 """Return a discovered item in case there is info text or parsed"""
585 if info:
586 return [(None, {})]
587 return None
590 def validate_filter(filter_function):
591 # type: (Callable) -> Callable
592 """Validate function argument is a callable and return it"""
594 if callable(filter_function):
595 return filter_function
596 elif filter_function is not None:
597 raise ValueError("Filtering function is not a callable,"
598 " a {} has been given.".format(type(filter_function)))
599 return lambda *entry: entry[0]
602 def discover(selector=None, default_params=None):
603 # type (Callable, Union[dict, str]) -> Callable
604 """Helper function to assist with service discoveries
606 The discovery function is in many cases just a boilerplate function to
607 recognize services listed in your parsed dictionary or the info
608 list. It in general looks like
610 def inventory_check(parsed):
611 for key, value in parsed.items():
612 if some_condition_based_on(key, value):
613 yield key, parameters
616 The idea of this helper is to allow you only to worry about the logic
617 function that decides if an entry is a service to be discovered or not.
620 Keyword Arguments:
621 selector -- Filtering function (default lambda entry: entry[0])
622 Default: Uses the key or first item of info variable
623 default_params -- Default parameters for discovered items (default {})
625 Possible uses:
627 If your discovery function recognizes every entry of your parsed
628 dictionary or every row of the info list as a service, then you
629 just need to call discover().
631 check_info["chk"] = {'inventory_function': discover()}
633 In case you want to have a simple filter function when dealing with
634 the info list, you can directly give a lambda function. If this
635 function returns a Boolean the first item of every entry is taken
636 as the service name, if the function returns a string that will be
637 taken as the service name. For this example we discover as services
638 entries where item3 is positive and name the service according to
639 item2.
641 check_info["chk"] = {'inventory_function': discover(selector=lambda line: line[2] if line[3]>0 else False)}
643 In case you have a more complicated selector condition and also
644 want to include default parameters you may use a decorator.
646 Please note: that this discovery function does not work with the
647 whole parsed/info data but only implements the logic for selecting
648 each individual entry as a service.
650 In the next example, we will process each entry of the parsed data
651 dictionary. Use as service name the capitalized key when the
652 corresponding value has certain keywords.
654 @discover(default_params="the_check_default_levels")
655 def inventory_thecheck(key, value):
656 required_entries = ["used", "ready", "total", "uptime"]
657 if all(data in value for data in required_entries):
658 return key.upper()
660 check_info["chk"] = {'inventory_function': inventory_thecheck}
663 def roller(parsed):
664 if isinstance(parsed, dict):
665 return parsed.iteritems()
666 elif isinstance(parsed, (list, tuple)):
667 return parsed
668 raise ValueError("Discovery function only works with dictionaries,"
669 " lists, and tuples you gave a {}".format(type(parsed)))
671 def _discovery(filter_function):
672 # type (Callable) -> Callable
673 @functools.wraps(filter_function)
674 def discoverer(parsed):
675 # type (Union[dict,list]) -> Iterable[Tuple]
677 params = default_params if isinstance(default_params,
678 six.string_types + (dict,)) else {}
679 filterer = validate_filter(filter_function)
680 from_dict = isinstance(parsed, dict)
682 for entry in roller(parsed):
683 if from_dict:
684 key, value = entry
685 name = filterer(key, value)
686 else:
687 name = filterer(entry)
689 if isinstance(name, six.string_types):
690 yield (name, params)
691 elif name is True and from_dict:
692 yield (key, params)
693 elif name is True and not from_dict:
694 yield (entry[0], params)
695 elif name and hasattr(name, '__iter__'):
696 for new_name in name:
697 yield (new_name, params)
699 return discoverer
701 if callable(selector):
702 return _discovery(selector)
704 if selector is None and default_params is None:
705 return _discovery(lambda *args: args[0])
707 return _discovery
710 # NOTE: Currently this is not really needed, it is just here to keep any start
711 # import in sync with our intended API.
712 # TODO: Do we really need this? Is there code which uses a star import for this
713 # module?
714 __all__ = get_check_api_context().keys()