Refactoring: Changed remaining check parameters starting with an 's' to the new rules...
[check_mk.git] / cmk / ec / export.py
blobf3b1f35b1a953e0b0b45036d3067496844acce77
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2016 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.
26 """
27 Utility module for common code between the Event Console and other parts
28 of Check_MK. The GUI is e.g. accessing this module for gathering the default
29 configuration.
30 """
32 import copy
33 from enum import Enum
34 import os
35 import pprint
36 from typing import Any, Dict, Iterable, List, Optional, Tuple # pylint: disable=unused-import
37 import UserDict
39 from pathlib2 import Path
41 import cmk.utils.log
42 import cmk.utils.paths
43 import cmk.utils.store
44 import cmk.ec.defaults
45 import cmk.ec.settings
48 class MkpRulePackBindingError(Exception):
49 """Base class for exceptions related to rule pack binding"""
50 pass
53 class MkpRulePackProxy(UserDict.DictMixin):
54 """
55 An object of this class represents an entry (i.e. a rule pack) in
56 mkp_rule_packs. It is used as a reference to an EC rule pack
57 that either can be exported or is already exported in a MKP.
59 A newly created instance is not yet connected to a specific rule pack.
60 This is achieved via the method bind_to.
61 """
63 def __init__(self, rule_pack_id):
64 # Ideally the 'id_' would not be necessary and the proxy object would
65 # be bound to it's referenced object upon initialization. Unfortunately,
66 # this is not possible because the mknotifyd.mk could specify referenced
67 # objects as well.
68 self.id_ = rule_pack_id # type: str
69 self.rule_pack = None # type: Dict[str, Any]
71 def __getitem__(self, key):
72 return self.rule_pack[key]
74 def __setitem__(self, key, value):
75 self.rule_pack[key] = value
77 def __delattr__(self, key):
78 del self.rule_pack[key]
80 def __repr__(self):
81 return '%s("%s")' % (self.__class__.__name__, self.id_)
83 # __iter__ and __len__ are only defined as a workaround for a buggy entry
84 # in the typeshed
85 def __iter__(self):
86 for k in self.keys():
87 yield k
89 def __len__(self):
90 return len(self.keys())
92 def keys(self):
93 # type: () -> List[str]
94 """List of keys of this rule pack"""
95 return self.rule_pack.keys()
97 def bind_to(self, mkp_rule_pack):
98 # type: (Dict[str, Any]) -> None
99 """Binds this rule pack to the given MKP rule pack"""
100 if self.id_ != mkp_rule_pack['id']:
101 raise MkpRulePackBindingError(
102 'The IDs of %s and %s cannot be different.' % (self, mkp_rule_pack))
104 self.rule_pack = mkp_rule_pack
106 @property
107 def is_bound(self):
108 # type: () -> bool
109 """Has this rule pack been bound via bind_to()?"""
110 return self.rule_pack is not None
113 class RulePackType(Enum): # pylint: disable=too-few-public-methods
115 A class to distinguishes the four kinds of rule pack types:
117 1. internal: A rule pack that is not available in the Extension Packages module.
118 2. exported: A rule pack that is available in the Extension Packages, but not
119 yet part of a MKP.
120 3. unmodified MKP: A rule pack that is packaged/provided in a MKP.
121 4. modified MKP: A rule pack that was orignially packaged/provided in a MKP but
122 was modified by a User and therefore replaced by a modified copy
123 of the rule pack.
125 To get the type of a rule pack for an existing rule pack ID to
126 MKP mapping the static method type_of can be used.
128 internal = 'internal'
129 exported = 'exported'
130 unmodified_mkp = 'unmodified, packaged'
131 modified_mkp = 'modified, packaged'
133 @staticmethod
134 def type_of(rule_pack, id_to_mkp):
135 # type: (Dict[str, Any], Dict[Any, Any]) -> RulePackType
137 Returns the type of rule pack for a given rule pack ID to MKP mapping.
139 is_proxy = isinstance(rule_pack, MkpRulePackProxy)
140 is_packaged = id_to_mkp.get(rule_pack.get('id')) is not None
142 if not is_proxy and not is_packaged:
143 return RulePackType.internal
144 if is_proxy and not is_packaged:
145 return RulePackType.exported
146 if is_proxy and is_packaged:
147 return RulePackType.unmodified_mkp
148 return RulePackType.modified_mkp
151 def _default_settings():
152 # type: () -> cmk.ec.settings.Settings
153 """Returns default EC settings. This function should vanish in the long run!"""
154 return cmk.ec.settings.settings('', Path(cmk.utils.paths.omd_root),
155 Path(cmk.utils.paths.default_config_dir), [''])
158 def rule_pack_dir():
159 # type: () -> Path
161 Returns the default WATO directory of the Event Console.
163 return _default_settings().paths.rule_pack_dir.value
166 def mkp_rule_pack_dir():
167 # type: () -> Path
169 Returns the default directory for rule pack exports of the
170 Event Console.
172 return _default_settings().paths.mkp_rule_pack_dir.value
175 def remove_exported_rule_pack(id_):
176 # type: (str) -> None
178 Removes the .mk file representing the exported rule pack.
180 export_file = mkp_rule_pack_dir() / ("%s.mk" % id_)
181 export_file.unlink()
184 def _bind_to_rule_pack_proxies(rule_packs, mkp_rule_packs):
185 # type: (Any, Any) -> None
187 Binds all proxy rule packs of the variable rule_packs to
188 the corresponding mkp_rule_packs.
190 for rule_pack in rule_packs:
191 try:
192 if isinstance(rule_pack, MkpRulePackProxy):
193 rule_pack.bind_to(mkp_rule_packs[rule_pack.id_])
194 except KeyError:
195 raise MkpRulePackBindingError(
196 'Exported rule pack with ID "%s" not found.' % rule_pack.id_)
199 def load_config(settings):
200 # type: (cmk.ec.settings.Settings) -> Dict[str, Any]
201 """Load event console configuration."""
202 config = cmk.ec.defaults.default_config()
203 config["MkpRulePackProxy"] = MkpRulePackProxy
204 for path in [settings.paths.main_config_file.value] + \
205 sorted(settings.paths.config_dir.value.glob('**/*.mk')):
206 with open(str(path)) as file_object:
207 exec (file_object, config) # pylint: disable=exec-used
208 config.pop("MkpRulePackProxy", None)
209 _bind_to_rule_pack_proxies(config['rule_packs'], config['mkp_rule_packs'])
211 # Convert livetime fields in rules into new format
212 for rule in config["rules"]:
213 if "livetime" in rule:
214 livetime = rule["livetime"]
215 if not isinstance(livetime, tuple):
216 rule["livetime"] = (livetime, ["open"])
218 # Convert legacy rules into a default rule pack. Note that we completely
219 # ignore legacy rules if there are rule packs alreday. It's a bit unclear
220 # if we really want that, but at least that's how it worked in the past...
221 if config["rules"] and not config["rule_packs"]:
222 config["rule_packs"] = [cmk.ec.defaults.default_rule_pack(config["rules"])]
223 config["rules"] = []
225 for rule_pack in config["rule_packs"]:
226 for rule in rule_pack["rules"]:
227 # Convert old contact_groups config
228 if isinstance(rule.get("contact_groups"), list):
229 rule["contact_groups"] = {
230 "groups": rule["contact_groups"],
231 "notify": False,
232 "precedence": "host",
234 # Old configs only have a naked service level without a precedence.
235 if isinstance(rule["sl"], int):
236 rule["sl"] = {"value": rule["sl"], "precedence": "message"}
238 # Convert old logging configurations
239 levels = config["log_level"]
240 if isinstance(levels, int):
241 level = cmk.utils.log.INFO if levels == 0 else cmk.utils.log.VERBOSE
242 levels = {
243 "cmk.mkeventd": level,
244 "cmk.mkeventd.EventServer": level,
245 "cmk.mkeventd.EventStatus": level,
246 "cmk.mkeventd.StatusServer": level,
247 "cmk.mkeventd.lock": level
249 if "cmk.mkeventd.lock" not in levels:
250 levels["cmk.mkeventd.lock"] = levels["cmk.mkeventd"]
251 config["log_level"] = levels
253 return config
256 def load_rule_packs():
257 # type: () -> Any
258 """Returns all rule packs (including MKP rule packs) of a site. Proxy objects
259 in the rule packs are already bound to the referenced object."""
260 return load_config(_default_settings())["rule_packs"]
263 def save_rule_packs(rule_packs, pretty_print=False, dir_=None):
264 # type: (List[Dict[str, Any]], bool, Optional[Path]) -> None
265 """Saves the given rule packs to rules.mk. By default they are saved to the
266 default directory for rule packs. If dir_ is given it is used instead of
267 the default."""
268 output = "# Written by WATO\n# encoding: utf-8\n\n"
270 if pretty_print:
271 rule_packs_text = pprint.pformat(rule_packs)
272 else:
273 rule_packs_text = repr(rule_packs)
275 output += "rule_packs += \\\n%s\n" % rule_packs_text
277 if not dir_:
278 dir_ = rule_pack_dir()
279 dir_.mkdir(parents=True, exist_ok=True)
280 cmk.utils.store.save_file(str(dir_ / "rules.mk"), output)
283 # NOTE: It is essential that export_rule_pack() is called *before*
284 # save_rule_packs(), otherwise there is a race condition when the EC
285 # recursively reads all *.mk files!
286 def export_rule_pack(rule_pack, pretty_print=False, dir_=None):
287 # type: (Dict[str, Any], bool, Optional[Path]) -> None
289 Export the representation of a rule pack (i.e. a dict) to a .mk
290 file accessible by the WATO module Extension Packages. In case
291 of a MkpRulePackProxy the representation of the underlying rule
292 pack is used.
293 The name of the .mk file is determined by the ID of the rule pack,
294 i.e. the rule pack 'test' will be saved as 'test.mk'
295 By default the rule pack is saved to the default directory for
296 mkp rule packs. If dir_ is given the default is replaced by the
297 directory dir_.
299 if isinstance(rule_pack, MkpRulePackProxy):
300 rule_pack = rule_pack.rule_pack
302 repr_ = (pprint.pformat(rule_pack) if pretty_print else repr(rule_pack))
303 output = ("# Written by WATO\n"
304 "# encoding: utf-8\n"
305 "\n"
306 "mkp_rule_packs['%s'] = \\\n"
307 "%s\n") % (rule_pack['id'], repr_)
309 if not dir_:
310 dir_ = mkp_rule_pack_dir()
311 dir_.mkdir(parents=True, exist_ok=True)
312 cmk.utils.store.save_file(str(dir_ / ("%s.mk" % rule_pack['id'])), output)
315 def add_rule_pack_proxies(file_names):
316 # type: (Iterable[str]) -> None
318 Adds rule pack proxy objects to the list of rule packs given a list
319 of file names. The file names without the file extension are used as
320 the ID of the rule pack.
322 rule_packs = load_rule_packs()
323 ids = [os.path.splitext(fn)[0] for fn in file_names]
324 for id_ in ids:
325 rule_packs.append(MkpRulePackProxy(id_))
326 save_rule_packs(rule_packs)
329 def override_rule_pack_proxy(rule_pack_nr, rule_packs):
330 # type: (str, Dict[str, Any]) -> None
332 Replaces a MkpRulePackProxy by a working copy of the underlying rule pack.
334 proxy = rule_packs[rule_pack_nr]
335 if not isinstance(proxy, MkpRulePackProxy):
336 raise TypeError('Expected an instance of %s got %s' % (MkpRulePackProxy.__name__,
337 proxy.__class__.__name__))
338 rule_packs[rule_pack_nr] = copy.deepcopy(proxy.rule_pack)
341 def release_packaged_rule_packs(file_names):
342 # type: (Iterable[str]) -> None
344 This function synchronizes the rule packs in rules.mk and the rule packs
345 packaged in a MKP upon release of that MKP. The following cases have
346 to be distinguished:
348 1. Upon release of an unmodified MKP package the proxy in rules.mk
349 and the exported rule pack are unchanged.
350 2. Upon release of a MKP package with locally modified rule packs the
351 modified rule pack updates the exported version.
353 if not file_names:
354 return
356 rule_packs = load_rule_packs()
357 rule_pack_ids = [rp['id'] for rp in rule_packs]
358 affected_ids = [os.path.splitext(fn)[0] for fn in file_names]
360 save = False
361 for id_ in affected_ids:
362 index = rule_pack_ids.index(id_)
363 if not isinstance(rule_packs[index], MkpRulePackProxy):
364 save = True
365 export_rule_pack(rule_packs[index])
366 rule_packs[index] = MkpRulePackProxy(id_)
368 if save:
369 save_rule_packs(rule_packs)
372 def remove_packaged_rule_packs(file_names, delete_export=True):
373 # type: (Iterable[str], bool) -> None
375 This function synchronizes the rule packs in rules.mk and the packaged rule packs
376 of a MKP upon deletion of that MKP. When a modified or an unmodified MKP is
377 deleted the exported rule pack and the rule pack in rules.mk are both deleted.
379 if not file_names:
380 return
382 rule_packs = load_rule_packs()
383 rule_pack_ids = [rp['id'] for rp in rule_packs]
384 affected_ids = [os.path.splitext(fn)[0] for fn in file_names]
386 for id_ in affected_ids:
387 index = rule_pack_ids.index(id_)
388 del rule_packs[index]
389 if delete_export:
390 remove_exported_rule_pack(id_)
392 save_rule_packs(rule_packs)
395 def rule_pack_id_to_mkp(package_info):
396 # type: (Any) -> Dict[str, Any]
398 Returns a dictionary of rule pack ID to MKP package for a given package_info.
399 The package info has to be in the format defined by cmk_base/packaging.py.
400 Every rule pack is contained exactly once in this mapping. If no corresponding
401 MKP exists, the value of that mapping is None.
404 def mkp_of(rule_pack_file):
405 # type: (str) -> Any
406 """Find the MKP for the given file"""
407 for mkp, content in package_info.get('installed', {}).iteritems():
408 if rule_pack_file in content.get('files', {}).get('ec_rule_packs', []):
409 return mkp
410 return None
412 exported_rule_packs = package_info['parts']['ec_rule_packs']['files']
414 return {os.path.splitext(file_)[0]: mkp_of(file_) for file_ in exported_rule_packs}