2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
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.
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
36 from typing
import Any
, Dict
, Iterable
, List
, Optional
, Tuple
# pylint: disable=unused-import
39 from pathlib2
import Path
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"""
53 class MkpRulePackProxy(UserDict
.DictMixin
):
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.
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
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
]
81 return '%s("%s")' % (self
.__class
__.__name
__, self
.id_
)
83 # __iter__ and __len__ are only defined as a workaround for a buggy entry
90 return len(self
.keys())
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
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
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
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'
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
), [''])
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():
169 Returns the default directory for rule pack exports of the
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_
)
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
:
192 if isinstance(rule_pack
, MkpRulePackProxy
):
193 rule_pack
.bind_to(mkp_rule_packs
[rule_pack
.id_
])
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"])]
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"],
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
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
256 def load_rule_packs():
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
268 output
= "# Written by WATO\n# encoding: utf-8\n\n"
271 rule_packs_text
= pprint
.pformat(rule_packs
)
273 rule_packs_text
= repr(rule_packs
)
275 output
+= "rule_packs += \\\n%s\n" % rule_packs_text
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
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
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"
306 "mkp_rule_packs['%s'] = \\\n"
307 "%s\n") % (rule_pack
['id'], repr_
)
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
]
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
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.
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
]
361 for id_
in affected_ids
:
362 index
= rule_pack_ids
.index(id_
)
363 if not isinstance(rule_packs
[index
], MkpRulePackProxy
):
365 export_rule_pack(rule_packs
[index
])
366 rule_packs
[index
] = MkpRulePackProxy(id_
)
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.
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
]
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
):
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', []):
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
}