Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk / gui / config.py
blobc17b6bd9eac9f230bcce1134bee39a09b0f2e204
1 #!/usr/bin/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 import sys
28 import errno
29 import os
30 import copy
31 from typing import Callable, Union, Tuple, Dict # pylint: disable=unused-import
32 import six
34 import cmk.gui.utils as utils
35 import cmk.gui.i18n
36 from cmk.gui.i18n import _
37 import cmk.gui.log as log
38 import cmk.utils.paths
39 import cmk.utils.store as store
40 from cmk.gui.exceptions import MKConfigError, MKAuthException
41 import cmk.gui.permissions as permissions
43 import cmk.gui.plugins.config
45 # This import is added for static analysis tools like pylint to make them
46 # know about all shipped config options. The default config options are
47 # later handled with the default_config dict and _load_default_config()
48 from cmk.gui.plugins.config.base import * # pylint: disable=wildcard-import,unused-wildcard-import
50 if not cmk.is_raw_edition():
51 from cmk.gui.cee.plugins.config.cee import * # pylint: disable=wildcard-import,unused-wildcard-import
53 if cmk.is_managed_edition():
54 from cmk.gui.cme.plugins.config.cme import * # pylint: disable=wildcard-import,unused-wildcard-import
56 # .--Declarations--------------------------------------------------------.
57 # | ____ _ _ _ |
58 # | | _ \ ___ ___| | __ _ _ __ __ _| |_(_) ___ _ __ ___ |
59 # | | | | |/ _ \/ __| |/ _` | '__/ _` | __| |/ _ \| '_ \/ __| |
60 # | | |_| | __/ (__| | (_| | | | (_| | |_| | (_) | | | \__ \ |
61 # | |____/ \___|\___|_|\__,_|_| \__,_|\__|_|\___/|_| |_|___/ |
62 # | |
63 # +----------------------------------------------------------------------+
64 # | Declarations of global variables and constants |
65 # '----------------------------------------------------------------------'
67 sites = {}
68 multisite_users = {}
69 admin_users = []
71 # hard coded in various permissions
72 builtin_role_ids = ["user", "admin", "guest"]
74 # Base directory of dynamic configuration
75 config_dir = cmk.utils.paths.var_dir + "/web"
77 # Stores the initial configuration values
78 default_config = {}
80 # TODO: Clean this up
81 permission_declaration_functions = []
83 # Constants for BI
84 ALL_HOSTS = '(.*)'
85 HOST_STATE = ('__HOST_STATE__',)
86 HIDDEN = ('__HIDDEN__',)
89 class FOREACH_HOST(object):
90 pass
93 class FOREACH_CHILD(object):
94 pass
97 class FOREACH_CHILD_WITH(object):
98 pass
101 class FOREACH_PARENT(object):
102 pass
105 class FOREACH_SERVICE(object):
106 pass
109 class REMAINING(object):
110 pass
113 class DISABLED(object):
114 pass
117 class HARD_STATES(object):
118 pass
121 class DT_AGGR_WARN(object):
122 pass
125 # Has to be declared here once since the functions can be assigned in
126 # bi.py and also in multisite.mk. "Double" declarations are no problem
127 # here since this is a dict (List objects have problems with duplicate
128 # definitions).
129 aggregation_functions = {}
132 # .--Functions-----------------------------------------------------------.
133 # | _____ _ _ |
134 # | | ___| _ _ __ ___| |_(_) ___ _ __ ___ |
135 # | | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __| |
136 # | | _|| |_| | | | | (__| |_| | (_) | | | \__ \ |
137 # | |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ |
138 # | |
139 # +----------------------------------------------------------------------+
140 # | Helper functions for config parsing, login, etc. |
141 # '----------------------------------------------------------------------'
144 def initialize():
145 clear_user_login()
146 load_config()
147 log.set_log_levels(log_levels)
148 cmk.gui.i18n.set_user_localizations(user_localizations)
151 # Read in a multisite.d/*.mk file
152 def include(filename):
153 if not filename.startswith("/"):
154 filename = cmk.utils.paths.default_config_dir + "/" + filename
156 # Config file is obligatory. An empty example is installed
157 # during setup.sh. Better signal an error then simply ignore
158 # Absence.
159 try:
160 execfile(filename, globals(), globals())
161 except Exception as e:
162 raise MKConfigError(_("Cannot read configuration file %s: %s:") % (filename, e))
165 # Load multisite.mk and all files in multisite.d/. This will happen
166 # for *each* HTTP request.
167 # FIXME: Optimize this to cache the config etc. until either the config files or plugins
168 # have changed. We could make this being cached for multiple requests just like the
169 # plugins of other modules. This may save significant time in case of small requests like
170 # the graph ajax page or similar.
171 def load_config():
172 global sites
174 # Set default values for all user-changable configuration settings
175 _initialize_with_default_config()
177 # Initialze sites with default site configuration. Need to do it here to
178 # override possibly deleted sites
179 sites = default_single_site_configuration()
181 # First load main file
182 include("multisite.mk")
184 # Load also recursively all files below multisite.d
185 conf_dir = cmk.utils.paths.default_config_dir + "/multisite.d"
186 filelist = []
187 if os.path.isdir(conf_dir):
188 for root, _directories, files in os.walk(conf_dir):
189 for filename in files:
190 if filename.endswith(".mk"):
191 filelist.append(root + "/" + filename)
193 filelist.sort()
194 for p in filelist:
195 include(p)
197 if sites:
198 sites = migrate_old_site_config(sites)
199 else:
200 sites = default_single_site_configuration()
202 migrate_old_sample_config_tag_groups(wato_host_tags, wato_aux_tags)
204 execute_post_config_load_hooks()
207 def execute_post_config_load_hooks():
208 for func in _post_config_load_hooks:
209 func()
212 _post_config_load_hooks = []
215 def register_post_config_load_hook(func):
216 # type: (Callable) -> None
217 _post_config_load_hooks.append(func)
220 def _initialize_with_default_config():
221 vars_before_plugins = all_nonfunction_vars(globals())
222 load_plugins(True)
223 vars_after_plugins = all_nonfunction_vars(globals())
224 _load_default_config(vars_before_plugins, vars_after_plugins)
226 _apply_default_config()
229 def _apply_default_config():
230 for k, v in default_config.items():
231 if isinstance(v, (dict, list)):
232 v = copy.deepcopy(v)
233 globals()[k] = v
236 def _load_default_config(vars_before_plugins, vars_after_plugins):
237 default_config.clear()
238 _load_default_config_from_module_plugins()
239 _load_default_config_from_legacy_plugins(vars_before_plugins, vars_after_plugins)
242 def _load_default_config_from_module_plugins():
243 # TODO: Find a better solution for this. Probably refactor declaration of default
244 # config option.
245 config_plugin_vars = {}
246 for module in _config_plugin_modules():
247 config_plugin_vars.update(module.__dict__)
249 for k, v in config_plugin_vars.items():
250 if k[0] == "_":
251 continue
253 if isinstance(v, (dict, list)):
254 v = copy.deepcopy(v)
256 default_config[k] = v
259 def _load_default_config_from_legacy_plugins(vars_before_plugins, vars_after_plugins):
260 new_vars = vars_after_plugins.difference(vars_before_plugins)
261 default_config.update(dict([(k, copy.deepcopy(globals()[k])) for k in new_vars]))
264 def _config_plugin_modules():
265 return [
266 module for name, module in sys.modules.items()
267 if (name.startswith("cmk.gui.plugins.config.") or name.startswith(
268 "cmk.gui.cee.plugins.config.") or name.startswith("cmk.gui.cme.plugins.config.")) and
269 module is not None
273 def reporting_available():
274 try:
275 # Check the existance of one arbitrary config variable from the
276 # reporting module
277 _dummy = reporting_filename
278 return True
279 except:
280 return False
283 def combined_graphs_available():
284 try:
285 _dummy = have_combined_graphs
286 return True
287 except:
288 return False
291 def hide_language(lang):
292 return lang in hide_languages
295 def all_nonfunction_vars(var_dict):
296 return set([name for name, value in var_dict.items() if name[0] != '_' and not callable(value)])
299 def get_language(default=None):
300 if default is None:
301 return default_language
302 return default
305 def tag_alias(tag):
306 for entry in host_tag_groups():
307 tag_id, _title, tags = entry[:3]
308 for t in tags:
309 if t[0] == tag:
310 return t[1]
312 for tag_id, alias in aux_tags():
313 if tag_id == tag:
314 return alias
317 def tag_group_title(tag):
318 for entry in host_tag_groups():
319 _tag_id, title, tags = entry[:3]
320 for t in tags:
321 if t[0] == tag:
322 return title
326 # .--Permissions---------------------------------------------------------.
327 # | ____ _ _ |
328 # | | _ \ ___ _ __ _ __ ___ (_)___ ___(_) ___ _ __ ___ |
329 # | | |_) / _ \ '__| '_ ` _ \| / __/ __| |/ _ \| '_ \/ __| |
330 # | | __/ __/ | | | | | | | \__ \__ \ | (_) | | | \__ \ |
331 # | |_| \___|_| |_| |_| |_|_|___/___/_|\___/|_| |_|___/ |
332 # | |
333 # +----------------------------------------------------------------------+
334 # | Declarations of permissions and roles |
335 # '----------------------------------------------------------------------'
337 # Kept for compatibility with pre 1.6 GUI plugins
338 declare_permission = permissions.declare_permission
339 declare_permission_section = permissions.declare_permission_section
342 # Some module have a non-fixed list of permissions. For example for
343 # each user defined view there is also a permission. This list is
344 # not known at the time of the loading of the module - though. For
345 # that purpose module can register functions. These functions should
346 # just call declare_permission(). They are being called in the correct
347 # situations.
348 # TODO: Clean this up
349 def declare_dynamic_permissions(func):
350 permission_declaration_functions.append(func)
353 # This function needs to be called by all code that needs access
354 # to possible dynamic permissions
355 # TODO: Clean this up
356 def load_dynamic_permissions():
357 for func in permission_declaration_functions:
358 func()
361 def get_role_permissions():
362 """Returns the set of permissions for all roles"""
363 role_permissions = {}
364 roleids = roles.keys()
365 for perm_class in permissions.permission_registry.values():
366 perm = perm_class()
367 for role_id in roleids:
368 if not role_id in role_permissions:
369 role_permissions[role_id] = []
371 if _may_with_roles([role_id], perm.name):
372 role_permissions[role_id].append(perm.name)
373 return role_permissions
376 def _may_with_roles(some_role_ids, pname):
377 # If at least one of the given roles has this permission, it's fine
378 for role_id in some_role_ids:
379 role = roles[role_id]
381 he_may = role.get("permissions", {}).get(pname)
382 # Handle compatibility with permissions without "general." that
383 # users might have saved in their own custom roles.
384 if he_may is None and pname.startswith("general."):
385 he_may = role.get("permissions", {}).get(pname[8:])
387 if he_may is None: # not explicitely listed -> take defaults
388 if "basedon" in role:
389 base_role_id = role["basedon"]
390 else:
391 base_role_id = role_id
392 if pname not in permissions.permission_registry:
393 return False # Permission unknown. Assume False. Functionality might be missing
394 perm = permissions.permission_registry[pname]()
395 he_may = base_role_id in perm.defaults
396 if he_may:
397 return True
398 return False
402 # .--User Login----------------------------------------------------------.
403 # | _ _ _ _ |
404 # | | | | |___ ___ _ __ | | ___ __ _(_)_ __ |
405 # | | | | / __|/ _ \ '__| | | / _ \ / _` | | '_ \ |
406 # | | |_| \__ \ __/ | | |__| (_) | (_| | | | | | |
407 # | \___/|___/\___|_| |_____\___/ \__, |_|_| |_| |
408 # | |___/ |
409 # +----------------------------------------------------------------------+
410 # | Managing the currently logged in user |
411 # '----------------------------------------------------------------------'
412 # TODO: Shouldn't this be moved to e.g. login.py or userdb.py?
415 # This objects intention is currently only to handle the currently logged in user after authentication.
416 # But maybe this can be used for managing all user objects in future.
417 # TODO: Cleanup accesses to module global vars and functions
418 class LoggedInUser(object):
419 def __init__(self, user_id):
420 self.id = user_id
422 self._load_confdir()
423 self._load_roles()
424 self._load_attributes()
425 self._load_permissions()
426 self._load_site_config()
427 self._button_counts = None
429 # TODO: Clean up that baserole_* stuff?
430 def _load_roles(self):
431 # Determine the roles of the user. If the user is listed in
432 # users, admin_users or guest_users in multisite.mk then we
433 # give him the according roles. If the user has an explicit
434 # profile in multisite_users (e.g. due to WATO), we rather
435 # use that profile. Remaining (unknown) users get the default_user_role.
436 # That can be set to None -> User has no permissions at all.
437 self.role_ids = self._gather_roles()
439 # Get base roles (admin/user/guest)
440 self._load_base_roles()
442 # Get best base roles and use as "the" role of the user
443 if "admin" in self.baserole_ids:
444 self.baserole_id = "admin"
445 elif "user" in self.baserole_ids:
446 self.baserole_id = "user"
447 else:
448 self.baserole_id = "guest"
450 def _gather_roles(self):
451 return roles_of_user(self.id)
453 def _load_base_roles(self):
454 base_roles = set([])
455 for r in self.role_ids:
456 if r in builtin_role_ids:
457 base_roles.add(r)
458 else:
459 base_roles.add(roles[r]["basedon"])
461 self.baserole_ids = list(base_roles)
463 def _load_attributes(self):
464 self.attributes = self.load_file("cached_profile", None)
465 if self.attributes is None:
466 if self.id in multisite_users:
467 self.attributes = multisite_users[self.id]
468 else:
469 self.attributes = {
470 "roles": self.role_ids,
473 self.alias = self.attributes.get("alias", self.id)
474 self.email = self.attributes.get("email", self.id)
476 def _load_permissions(self):
477 # Prepare cache of already computed permissions
478 # Make sure, admin can restore permissions in any case!
479 if self.id in admin_users:
480 self.permissions = {
481 "general.use": True, # use Multisite
482 "wato.use": True, # enter WATO
483 "wato.edit": True, # make changes in WATO...
484 "wato.users": True, # ... with access to user management
486 else:
487 self.permissions = {}
489 def _load_confdir(self):
490 self.confdir = config_dir + "/" + self.id.encode("utf-8")
491 store.mkdir(self.confdir)
493 def _load_site_config(self):
494 self.siteconf = self.load_file("siteconfig", {})
496 def get_button_counts(self):
497 if not self._button_counts:
498 self._button_counts = self.load_file("buttoncounts", {})
499 return self._button_counts
501 def save_site_config(self):
502 self.save_file("siteconfig", self.siteconf)
504 def get_attribute(self, key, deflt=None):
505 return self.attributes.get(key, deflt)
507 def set_attribute(self, key, value):
508 self.attributes[key] = value
510 def unset_attribute(self, key):
511 try:
512 del self.attributes[key]
513 except KeyError:
514 pass
516 def language(self, default=None):
517 return self.get_attribute("language", get_language(default))
519 def contact_groups(self):
520 return self.get_attribute("contactgroups", [])
522 def load_stars(self):
523 return set(self.load_file("favorites", []))
525 def save_stars(self, stars):
526 self.save_file("favorites", list(stars))
528 def is_site_disabled(self, site_id):
529 siteconf = self.siteconf.get(site_id, {})
530 return siteconf.get("disabled", False)
532 def authorized_sites(self, unfiltered_sites=None):
533 if unfiltered_sites is None:
534 unfiltered_sites = allsites().items()
536 authorized_sites = self.get_attribute("authorized_sites")
537 if authorized_sites is None:
538 return unfiltered_sites
540 return [(site_id, s) for site_id, s in unfiltered_sites if site_id in authorized_sites]
542 def authorized_login_sites(self):
543 login_site_ids = get_login_slave_sites()
544 login_sites = [
545 (site_id, s) for site_id, s in allsites().items() if site_id in login_site_ids
547 return self.authorized_sites(login_sites)
549 def may(self, pname):
550 if pname in self.permissions:
551 return self.permissions[pname]
552 he_may = _may_with_roles(user.role_ids, pname)
553 self.permissions[pname] = he_may
554 return he_may
556 def need_permission(self, pname):
557 if not self.may(pname):
558 perm = permissions.permission_registry[pname]()
559 raise MKAuthException(
560 _("We are sorry, but you lack the permission "
561 "for this operation. If you do not like this "
562 "then please ask you administrator to provide you with "
563 "the following permission: '<b>%s</b>'.") % perm.title)
565 def load_file(self, name, deflt, lock=False):
566 # In some early error during login phase there are cases where it might
567 # happen that a user file is requested but the user is not yet
568 # set. We have all information to set it, then do it.
569 if not user:
570 return deflt # No user known at this point of time
572 path = self.confdir + "/" + name + ".mk"
573 return store.load_data_from_file(path, deflt, lock)
575 def save_file(self, name, content, unlock=False):
576 save_user_file(name, content, self.id, unlock)
578 def file_modified(self, name):
579 if self.confdir is None:
580 return 0
582 try:
583 return os.stat(self.confdir + "/" + name + ".mk").st_mtime
584 except OSError as e:
585 if e.errno == errno.ENOENT:
586 return 0
587 else:
588 raise
591 # Login a user that has all permissions. This is needed for making
592 # Livestatus queries from unauthentiated page handlers
593 # TODO: Can we somehow get rid of this?
594 class LoggedInSuperUser(LoggedInUser):
595 def __init__(self):
596 super(LoggedInSuperUser, self).__init__(None)
597 self.alias = "Superuser for unauthenticated pages"
598 self.email = "admin"
600 def _gather_roles(self):
601 return ["admin"]
603 def _load_confdir(self):
604 self.confdir = None
606 def _load_site_config(self):
607 self.siteconf = {}
609 def load_file(self, name, deflt, lock=False):
610 return deflt
613 class LoggedInNobody(LoggedInUser):
614 def __init__(self):
615 super(LoggedInNobody, self).__init__(None)
616 self.alias = "Unauthenticated user"
617 self.email = "nobody"
619 def _gather_roles(self):
620 return []
622 def _load_confdir(self):
623 self.confdir = None
625 def _load_site_config(self):
626 self.siteconf = {}
628 def load_file(self, name, deflt, lock=False):
629 return deflt
632 def clear_user_login():
633 _set_user(LoggedInNobody())
636 def set_user_by_id(user_id):
637 _set_user(LoggedInUser(user_id))
640 def set_super_user():
641 _set_user(LoggedInSuperUser())
644 def _set_user(_user):
645 global user
646 user = _user
649 # This holds the currently logged in user object
650 user = LoggedInNobody()
653 # .--User Handling-------------------------------------------------------.
654 # | _ _ _ _ _ _ _ |
655 # | | | | |___ ___ _ __ | | | | __ _ _ __ __| | (_)_ __ __ _ |
656 # | | | | / __|/ _ \ '__| | |_| |/ _` | '_ \ / _` | | | '_ \ / _` | |
657 # | | |_| \__ \ __/ | | _ | (_| | | | | (_| | | | | | | (_| | |
658 # | \___/|___/\___|_| |_| |_|\__,_|_| |_|\__,_|_|_|_| |_|\__, | |
659 # | |___/ |
660 # +----------------------------------------------------------------------+
661 # | General user handling of all users, not only the currently logged |
662 # | in user. These functions are mostly working with the loaded multisite|
663 # | configuration data (multisite_users, admin_users, ...), so they are |
664 # | more related to this module than to the userdb module. |
665 # '----------------------------------------------------------------------'
668 def roles_of_user(user_id):
669 def existing_role_ids(role_ids):
670 return [role_id for role_id in role_ids if role_id in roles]
672 if user_id in multisite_users:
673 return existing_role_ids(multisite_users[user_id]["roles"])
674 elif user_id in admin_users:
675 return ["admin"]
676 elif user_id in guest_users:
677 return ["guest"]
678 elif users is not None and user_id in users:
679 return ["user"]
680 elif os.path.exists(config_dir + "/" + user_id.encode("utf-8") + "/automation.secret"):
681 return ["guest"] # unknown user with automation account
682 elif 'roles' in default_user_profile:
683 return existing_role_ids(default_user_profile['roles'])
684 elif default_user_role:
685 return existing_role_ids([default_user_role])
686 return []
689 def alias_of_user(user_id):
690 if user_id in multisite_users:
691 return multisite_users[user_id].get("alias", user_id)
692 return user_id
695 def user_may(user_id, pname):
696 return _may_with_roles(roles_of_user(user_id), pname)
699 # TODO: Check all calls for arguments (changed optional user to 3rd positional)
700 def save_user_file(name, data, user_id, unlock=False):
701 path = config_dir + "/" + user_id.encode("utf-8") + "/" + name + ".mk"
702 store.mkdir(os.path.dirname(path))
703 store.save_data_to_file(path, data)
707 # .--Host tags-----------------------------------------------------------.
708 # | _ _ _ _ |
709 # | | | | | ___ ___| |_ | |_ __ _ __ _ ___ |
710 # | | |_| |/ _ \/ __| __| | __/ _` |/ _` / __| |
711 # | | _ | (_) \__ \ |_ | || (_| | (_| \__ \ |
712 # | |_| |_|\___/|___/\__| \__\__,_|\__, |___/ |
713 # | |___/ |
714 # +----------------------------------------------------------------------+
715 # | Helper functions for dealing with host tags |
716 # '----------------------------------------------------------------------'
719 class BuiltinTags(object):
720 def host_tags(self):
721 return [
722 ("agent", "%s/%s" % (_("Data sources"), _("Check_MK Agent")), [
723 ("cmk-agent", _("Contact either Check_MK Agent or use datasource program"),
724 ["tcp"]),
725 ("all-agents", _("Contact Check_MK agent and all enabled datasource programs"),
726 ["tcp"]),
727 ("special-agents", _("Use all enabled datasource programs"), ["tcp"]),
728 ("no-agent", _("No agent"), []),
729 ], ["!ping"]),
730 ("snmp", "%s/%s" % (_("Data sources"), _("SNMP")), [
731 ("no-snmp", _("No SNMP"), []),
732 ("snmp-v2", _("SNMP v2 or v3"), ["snmp"]),
733 ("snmp-v1", _("SNMP v1"), ["snmp"]),
734 ], ["!ping"]),
735 ("address_family", "%s/%s " % (_("Address"), _("IP Address Family")), [
736 ("ip-v4-only", _("IPv4 only"), ["ip-v4"]),
737 ("ip-v6-only", _("IPv6 only"), ["ip-v6"]),
738 ("ip-v4v6", _("IPv4/IPv6 dual-stack"), ["ip-v4", "ip-v6"]),
739 ("no-ip", _("No IP"), []),
743 def aux_tags(self):
744 return [
745 ("ip-v4", "%s/%s" % (_("Address"), _("IPv4"))),
746 ("ip-v6", "%s/%s" % (_("Address"), _("IPv6"))),
747 ("snmp", "%s/%s" % (_("Data sources"), _("Monitor via SNMP"))),
748 ("tcp", "%s/%s" % (_("Data sources"), _("Monitor via Check_MK Agent"))),
749 ("ping", "%s/%s" % (_("Data sources"), _("Only ping this device"))),
752 def get_effective_tag_groups(self, tag_groups):
753 """Extend the given tag group definitions with the builtin tag groups
754 and return the extended list"""
755 tag_groups = tag_groups[:]
756 tag_group_ids = set([tg[0] for tg in tag_groups])
758 for tag_group in self.host_tags():
759 if tag_group[0] not in tag_group_ids:
760 tag_groups.append(tag_group)
762 return tag_groups
764 def get_effective_aux_tags(self, aux_tag_list):
765 aux_tags_ = aux_tag_list[:]
766 aux_tag_ids = set([at[0] for at in aux_tag_list])
768 for aux_tag in self.aux_tags():
769 if aux_tag[0] not in aux_tag_ids:
770 aux_tags_.append(aux_tag)
772 return aux_tags_
775 # During development of the 1.6 version the site configuration has been cleaned up in several ways:
776 # 1. The "socket" attribute could be "disabled" to disable a site connection. This has already been
777 # deprecated long time ago and was not configurable in WATO. This has now been superceeded by
778 # the dedicated "disabled" attribute.
779 # 2. The "socket" attribute was optional. A not present socket meant "connect to local unix" socket.
780 # This is now replaced with a value like this ("local", None) to reflect the generic
781 # CascadingDropdown() data structure of "(type, attributes)".
782 # 3. The "socket" attribute was stored in the livestatus.py socketurl encoded format, at least when
783 # livestatus proxy was not used. This is now stored in the CascadingDropdown() native format and
784 # converted here to the correct format.
785 def migrate_old_site_config(site_config):
786 if not site_config:
787 # Prevent problem when user has deleted all sites from his
788 # configuration and sites is {}. We assume a default single site
789 # configuration in that case.
790 return default_single_site_configuration()
792 for site_cfg in site_config.itervalues():
793 if site_cfg.get("socket") is None:
794 site_cfg["socket"] = ("local", None)
795 continue
797 socket = site_cfg["socket"]
798 if socket == 'disabled':
799 site_cfg['disabled'] = True
800 site_cfg['socket'] = ("local", None)
801 continue
803 if isinstance(socket, six.string_types):
804 site_cfg["socket"] = _migrate_string_encoded_socket(socket)
806 return site_config
809 def _migrate_string_encoded_socket(value):
810 # type: (str) -> Tuple[str, Union[Dict]]
811 family_txt, address = value.split(":", 1) # pylint: disable=no-member
813 if family_txt == "unix":
814 return "unix", {
815 "path": value.split(":", 1)[1],
818 if family_txt in ["tcp", "tcp6"]:
819 host, port = address.rsplit(":", 1)
820 return family_txt, {
821 "address": (host, int(port)),
824 raise NotImplementedError()
827 # Previous to 1.5 the "Agent type" tag group was created as sample config and was not
828 # a builtin tag group (which can not be modified by the user). With werk #5535 we changed
829 # the tag scheme and need to deal with the user config (which might extend the original tag group).
830 # Use two strategies:
832 # a) Check whether or not the tag group has been modified. If not, simply remove it from the user
833 # config and use the builtin tag group in the future.
834 # b) Extend the tag group in the user configuration with the tag configuration we need for 1.5.
835 # TODO: Move to wato/watolib and register using register_post_config_load_hook()
836 def migrate_old_sample_config_tag_groups(host_tags, aux_tags_):
837 remove_old_sample_config_tag_groups(host_tags, aux_tags_)
838 extend_user_modified_tag_groups(host_tags)
841 def remove_old_sample_config_tag_groups(host_tags, aux_tags_):
842 legacy_tag_group_default = (
843 'agent',
844 u'Agent type',
846 ('cmk-agent', u'Check_MK Agent (Server)', ['tcp']),
847 ('snmp-only', u'SNMP (Networking device, Appliance)', ['snmp']),
848 ('snmp-v1', u'Legacy SNMP device (using V1)', ['snmp']),
849 ('snmp-tcp', u'Dual: Check_MK Agent + SNMP', ['snmp', 'tcp']),
850 ('ping', u'No Agent', []),
854 try:
855 host_tags.remove(legacy_tag_group_default)
857 # Former tag choices (see above) are added as aux tags to allow the user to migrate
858 # these tags and the objects that use them
859 aux_tags_.insert(0,
860 ("snmp-only", "Data sources/Legacy: SNMP (Networking device, Appliance)"))
861 aux_tags_.insert(0, ("snmp-tcp", "Data sources/Legacy: Dual: Check_MK Agent + SNMP"))
862 except ValueError:
863 pass # Not there or modified
865 legacy_aux_tag_ids = [
866 'snmp',
867 'tcp',
870 for aux_tag in aux_tags_[:]:
871 if aux_tag[0] in legacy_aux_tag_ids:
872 aux_tags_.remove(aux_tag)
875 def extend_user_modified_tag_groups(host_tags):
876 """This method supports migration from <1.5 to 1.5 in case the user has a customized "Agent type" tag group
877 See help of migrate_old_sample_config_tag_groups() and werk #5535 and #6446 for further information.
879 Disclaimer: The host_tags data structure is a mess which will hopefully be cleaned up during 1.6 development.
880 Basically host_tags is a list of configured tag groups. Each tag group is represented by a tuple like this:
882 # tag_group_id, tag_group_title, tag_choices
883 ('agent', u'Agent type',
885 # tag_id, tag_title, aux_tag_ids
886 ('cmk-agent', u'Check_MK Agent (Server)', ['tcp']),
887 ('snmp-only', u'SNMP (Networking device, Appliance)', ['snmp']),
888 ('snmp-v1', u'Legacy SNMP device (using V1)', ['snmp']),
889 ('snmp-tcp', u'Dual: Check_MK Agent + SNMP', ['snmp', 'tcp']),
890 ('ping', u'No Agent', []),
894 tag_group = None
895 for this_tag_group in host_tags:
896 if this_tag_group[0] == "agent":
897 tag_group = this_tag_group
899 if tag_group is None:
900 return # Tag group does not exist
902 # Mark all existing tag choices as legacy to help the user that this should be cleaned up
903 for index, tag_choice in enumerate(tag_group[2][:]):
904 if tag_choice[0] in ["no-agent", "special-agents", "all-agents", "cmk-agent"]:
905 continue # Don't prefix the standard choices
907 if tag_choice[1].startswith("Legacy: "):
908 continue # Don't prefix already prefixed choices
910 tag_choice_list = list(tag_choice)
911 tag_choice_list[1] = "Legacy: %s" % tag_choice_list[1]
912 tag_group[2][index] = tuple(tag_choice_list)
914 tag_choices = [c[0] for c in tag_group[2]]
916 if "no-agent" not in tag_choices:
917 tag_group[2].insert(0, ("no-agent", _("No agent"), []))
919 if "special-agents" not in tag_choices:
920 tag_group[2].insert(0,
921 ("special-agents", _("Use all enabled datasource programs"), ["tcp"]))
923 if "all-agents" not in tag_choices:
924 tag_group[2].insert(
925 0, ("all-agents", _("Contact Check_MK agent and all enabled datasource programs"),
926 ["tcp"]))
928 if "cmk-agent" not in tag_choices:
929 tag_group[2].insert(
930 0, ("cmk-agent", _("Contact either Check_MK Agent or use datasource program"), ["tcp"]))
931 else:
932 # Change title of cmk-agent tag choice and move to top
933 for index, tag_choice in enumerate(tag_group[2]):
934 if tag_choice[0] == "cmk-agent":
935 tag_choice_list = list(tag_group[2].pop(index))
936 tag_choice_list[1] = _("Contact either Check_MK Agent or use datasource program")
937 tag_group[2].insert(0, tuple(tag_choice_list))
938 break
941 def host_tag_groups():
942 """Returns the effective set of tag groups defined. This includes
943 the implicitly declared builtin host tags. This function must be used by
944 the GUI code to get the tag group definitions."""
945 return BuiltinTags().get_effective_tag_groups(wato_host_tags)
948 def aux_tags():
949 """Returns the effective set of auxiliary tags defined. This includes
950 the implicitly declared builtin host tags. This function must be used by
951 the GUI code to get the auxiliay tag definitions."""
952 return BuiltinTags().get_effective_aux_tags(wato_aux_tags)
956 # .--Sites---------------------------------------------------------------.
957 # | ____ _ _ |
958 # | / ___|(_) |_ ___ ___ |
959 # | \___ \| | __/ _ \/ __| |
960 # | ___) | | || __/\__ \ |
961 # | |____/|_|\__\___||___/ |
962 # | |
963 # +----------------------------------------------------------------------+
964 # | The config module provides some helper functions for sites. |
965 # '----------------------------------------------------------------------'
968 def omd_site():
969 return os.environ["OMD_SITE"]
972 def url_prefix():
973 return "/%s/" % omd_site()
976 use_siteicons = False
979 def default_single_site_configuration():
980 return {
981 omd_site(): {
982 'alias': _("Local site %s") % omd_site(),
983 'socket': ("local", None),
984 'disable_wato': True,
985 'disabled': False,
986 'insecure': False,
987 'multisiteurl': '',
988 'persist': False,
989 'replicate_ec': False,
990 'replication': '',
991 'timeout': 10,
992 'user_login': True,
997 sites = {}
1000 def sitenames():
1001 return sites.keys()
1004 # TODO: Cleanup: Make clear that this function is used by the status GUI (and not WATO)
1005 # and only returns the currently enabled sites. Or should we redeclare the "disabled" state
1006 # to disable the sites at all?
1007 # TODO: Rename this!
1008 # TODO: All site listing functions should return the same data structure, e.g. a list of
1009 # pairs (site_id, site)
1010 def allsites():
1011 return dict(
1012 [(name, site(name)) for name in sitenames() if not site(name).get("disabled", False)])
1015 def configured_sites():
1016 return [(site_id, site(site_id)) for site_id in sitenames()]
1019 def has_wato_slave_sites():
1020 return bool(wato_slave_sites())
1023 def is_wato_slave_site():
1024 return _has_distributed_wato_file() and not has_wato_slave_sites()
1027 def _has_distributed_wato_file():
1028 return os.path.exists(cmk.utils.paths.check_mk_config_dir + "/distributed_wato.mk") \
1029 and os.stat(cmk.utils.paths.check_mk_config_dir + "/distributed_wato.mk").st_size != 0
1032 def get_login_sites():
1033 """Returns the WATO slave sites a user may login and the local site"""
1034 return get_login_slave_sites() + [omd_site()]
1037 # TODO: All site listing functions should return the same data structure, e.g. a list of
1038 # pairs (site_id, site)
1039 def get_login_slave_sites():
1040 """Returns a list of site ids which are WATO slave sites and users can login"""
1041 login_sites = []
1042 for site_id, site_spec in wato_slave_sites():
1043 if site_spec.get('user_login', True) and not site_is_local(site_id):
1044 login_sites.append(site_id)
1045 return login_sites
1048 def wato_slave_sites():
1049 return [(site_id, s) for site_id, s in sites.items() if s.get("replication")]
1052 def sorted_sites():
1053 sorted_choices = []
1054 for site_id, s in user.authorized_sites():
1055 sorted_choices.append((site_id, s['alias']))
1056 return sorted(sorted_choices, key=lambda k: k[1], cmp=lambda a, b: cmp(a.lower(), b.lower()))
1059 def site(site_id):
1060 s = dict(sites.get(site_id, {}))
1061 # Now make sure that all important keys are available.
1062 # Add missing entries by supplying default values.
1063 s.setdefault("alias", site_id)
1064 s.setdefault("socket", ("local", None))
1065 s.setdefault("url_prefix", "../") # relative URL from /check_mk/
1066 s["id"] = site_id
1067 return s
1070 def site_is_local(site_id):
1071 family_spec, address_spec = site(site_id)["socket"]
1073 if _is_local_socket_spec(family_spec, address_spec):
1074 return True
1076 if family_spec == "proxy" and _is_local_socket_spec(*address_spec["socket"]):
1077 return True
1079 return False
1082 def _is_local_socket_spec(family_spec, address_spec):
1083 if family_spec == "local":
1084 return True
1086 if family_spec == "unix" and address_spec["path"] == cmk.utils.paths.livestatus_unix_socket:
1087 return True
1089 return False
1092 def default_site():
1093 for site_name, _site in sites.items():
1094 if site_is_local(site_name):
1095 return site_name
1096 return None
1099 def is_single_local_site():
1100 if len(sites) > 1:
1101 return False
1102 elif len(sites) == 0:
1103 return True
1105 # Also use Multisite mode if the one and only site is not local
1106 sitename = sites.keys()[0]
1107 return site_is_local(sitename)
1110 def site_attribute_default_value():
1111 def_site = default_site()
1112 authorized_site_ids = [x[0] for x in user.authorized_sites(unfiltered_sites=configured_sites())]
1113 if def_site and def_site in authorized_site_ids:
1114 return def_site
1117 def site_attribute_choices():
1118 authorized_site_ids = [x[0] for x in user.authorized_sites(unfiltered_sites=configured_sites())]
1119 return site_choices(filter_func=lambda site_id, site: site_id in authorized_site_ids)
1122 def site_choices(filter_func=None):
1123 choices = []
1124 for site_id, site_spec in sites.items():
1125 if filter_func and not filter_func(site_id, site_spec):
1126 continue
1128 title = site_id
1129 if site_spec.get("alias"):
1130 title += " - " + site_spec["alias"]
1132 choices.append((site_id, title))
1134 return sorted(choices, key=lambda s: s[1])
1137 def get_event_console_site_choices():
1138 return site_choices(
1139 filter_func=lambda site_id, site: site_is_local(site_id) or site.get("replicate_ec"))
1143 # .--Plugins-------------------------------------------------------------.
1144 # | ____ _ _ |
1145 # | | _ \| |_ _ __ _(_)_ __ ___ |
1146 # | | |_) | | | | |/ _` | | '_ \/ __| |
1147 # | | __/| | |_| | (_| | | | | \__ \ |
1148 # | |_| |_|\__,_|\__, |_|_| |_|___/ |
1149 # | |___/ |
1150 # +----------------------------------------------------------------------+
1151 # | Handling of our own plugins. In plugins other software pieces can |
1152 # | declare defaults for configuration variables. |
1153 # '----------------------------------------------------------------------'
1156 def load_plugins(force):
1157 utils.load_web_plugins("config", globals())
1159 # Make sure, builtin roles are present, even if not modified and saved with WATO.
1160 for br in builtin_role_ids:
1161 roles.setdefault(br, {})
1164 def theme_choices():
1165 return [
1166 ("classic", _("Classic")),
1167 ("facelift", _("Modern")),
1171 def get_page_heading():
1172 if "%s" in page_heading:
1173 return page_heading % (site(omd_site()).get('alias', _("GUI")))
1174 return page_heading