From 0d8ac6281bc1daa021b06b7c30d089a23738bb9e Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Tue, 28 Nov 2023 20:08:19 +0100
Subject: [PATCH] update project

---
 README.md                                    |   2 +-
 bin/topology_data/create_topology_classes.py |  85 ++++++++++++++++++-
 bin/topology_data/create_topology_data.py    |  15 ++--
 mkp/create_topology_data-0.3.0-20231128.mkp  | Bin 0 -> 12443 bytes
 packages/create_topology_data                |   2 +-
 5 files changed, 95 insertions(+), 9 deletions(-)
 create mode 100644 mkp/create_topology_data-0.3.0-20231128.mkp

diff --git a/README.md b/README.md
index d771a66..3f1306f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/create_topology_data-0.2.0-20231116.mkp "create_topology_data-0.2.0-20231116.mkp"
+[PACKAGE]: ../../raw/master/mkp/create_topology_data-0.3.0-20231128.mkp "create_topology_data-0.3.0-20231128.mkp"
 Network Visualization data creation tool from inventory data
 
 This script creates the topology data file needed for the [Checkmk Exchange Network visualization](https://exchange.checkmk.com/p/network-visualization) plugin by Andreas Boesl and [schnetz](https://exchange.checkmk.com/u/schnetz).\
diff --git a/bin/topology_data/create_topology_classes.py b/bin/topology_data/create_topology_classes.py
index 2610ead..6b3a176 100755
--- a/bin/topology_data/create_topology_classes.py
+++ b/bin/topology_data/create_topology_classes.py
@@ -16,6 +16,8 @@ from typing import Dict, List, Any, NamedTuple
 from enum import Enum, unique
 from abc import abstractmethod
 from ast import literal_eval
+import livestatus
+
 
 from create_topology_utils import (
     CREATE_TOPOLOGY_VERSION,
@@ -186,7 +188,7 @@ class Settings:
 
 
 class HostCache:
-    def __init__(self):
+    def __init__(self, debug: bool = False):
         self.__cache = {}
         self.__inventory_pre_fetch_list: List[str] = [
             PATH_INTERFACES,
@@ -202,6 +204,7 @@ class HostCache:
         Returns:
             the inventory data as dictionary
         """
+        raise NotImplementedError()
 
     @abstractmethod
     def get_interface_items(self, host: str, debug: bool = False) -> List:
@@ -214,6 +217,7 @@ class HostCache:
         Returns:
             list of the interface items
         """
+        raise NotImplementedError()
 
     def __fill_cache(self, host: str):
         # pre fill inventory data
@@ -337,3 +341,82 @@ class HostCacheFileSystem(HostCache):
             return []
 
         return __data
+
+
+class HostCacheMultiSite(HostCache):
+    def __init__(self, debug: bool = False):
+        super().__init__(debug=debug)
+        self.__sites = {}
+        self.get_sites(debug=debug)
+        self.__c = livestatus.MultiSiteConnection(self.__sites)
+        # self.__c.set_prepend_site(False)  # is default
+        # self.__c.parallelize = True   # is default
+        self.__dead_sites = [site['site']['alias'] for site in self.__c.dead_sites().values()]
+        if self.__dead_sites:
+            self.__dead_sites = ', '.join( self.__dead_sites)
+            print(f'WARNING: use of dead site(s) {self.__dead_sites} is disabled')
+            self.__c.set_only_sites(self.__c.alive_sites())
+
+    def get_sites(self, debug: bool = False):
+        sites_mk = Path(f'{OMD_ROOT}/etc/check_mk/multisite.d/sites.mk')
+        socket_path = f'unix:{OMD_ROOT}/tmp/run/live'
+        if sites_mk.exists():
+            # make eval() "secure"
+            # https://realpython.com/python-eval-function/#minimizing-the-security-issues-of-eval
+            _code = compile(sites_mk.read_text(), "<string>", "eval")
+            allowed_names = ['sites', 'update']
+            for name in _code.co_names:
+                if name not in allowed_names:
+                    raise NameError(f'Use of {name} in {sites_mk.name} not allowed.')
+
+            sites_raw = {}
+            eval(sites_mk.read_text(), {"__builtins__": {}}, {"sites": sites_raw})
+
+            for site, data in sites_raw.items():
+                self.__sites[site] = {
+                    'alias': data['alias'],
+                    'timeout': data['timeout'],
+                    'nagios_url': '/nagios/',
+                }
+                if data['socket'] == ('local', None):
+                    self.__sites[site]['socket'] = socket_path
+                else:
+                    protocol, socket = data['socket']
+                    address, port = socket['address']
+                    self.__sites[site]['socket'] = f'{protocol}:{address}:{port}'
+                    self.__sites[site]['tls'] = socket['tls']
+                if debug:
+                    print(f'Multisite: Site {site} found, '
+                          f'Socket: {self.__sites[site]["socket"]}, '
+                          f'TLS: {self.__sites[site].get("tls", "N/A")}.')
+
+    def get_inventory_data(self, host: str, debug: bool = False) -> Dict[str, str] | None:
+        query = f'GET hosts\nColumns: mk_inventory\nOutputFormat: python3\nFilter: host_name = {host}\n'
+        data = self.__c.query(query=query)
+        if data:
+            try:
+                data = literal_eval(data[0][0].decode('utf-8'))
+            except SyntaxError as e:
+                if debug:
+                    print(f'data: |{data}|')
+                    print(f'type: {type(data)}')
+                    print(f'exception: {e}')
+                return
+            return data
+
+    def get_interface_items(self, host: str, debug: bool = False) -> List:
+        query = (
+            'GET services\n'
+            'Columns: host_name description check_command\n'
+            'Filter: description ~ Interface\n'
+            f'Filter: host_name = {host}\n'
+            'OutputFormat: python3\n'
+        )
+        data = self.__c.query(query=query)
+        items = []
+        for host, description, check_command in data:
+            items.append(description[10:])  # remove 'Interface ' from description
+
+        if debug:
+            print(f'Interfaces items found: {len(items)} an host {host}')
+        return items
diff --git a/bin/topology_data/create_topology_data.py b/bin/topology_data/create_topology_data.py
index e3c6627..d3caccd 100755
--- a/bin/topology_data/create_topology_data.py
+++ b/bin/topology_data/create_topology_data.py
@@ -38,12 +38,12 @@
 # 2023-11-16: added option -b/--backend [LIVESTATUS, FILESYSTEM],
 #             LIVESTATUS is the default for performance reasons -> for now only local site
 #             FILESYSTEM fethches the data directly form the inventory files -> use in distributed environments
-
+# 2023-11-28: added MULTISITE as backend, via livestatus
 #
-# PoC for creating topology_data.json from inventory data
+# creating topology_data.json from inventory data
 #
 # This script creates the topology data file needed for the Checkmk "network_visualization" plugin by
-# Andreas Boesl and schnetz. For more information see
+# Andreas Boesl/schnetz. For more information see
 # https://forum.checkmk.com/t/network-visualization/41680
 # https://exchange.checkmk.com/p/network-visualization
 #
@@ -76,6 +76,7 @@ from create_topology_classes import (
     StaticConnection,
     HostCacheLiveStatus,
     HostCacheFileSystem,
+    HostCacheMultiSite,
     CacheItems,
 )
 
@@ -275,18 +276,20 @@ def create_topology(
 if __name__ == '__main__':
     start_time = time_ns()
     SETTINGS = Settings(vars(parse_arguments()))
+    print()
+    print(f'Start time: {strftime(SETTINGS.time_format)}')
+
     match SETTINGS.backend:
         case 'LIVESTATUS':
             HOST_CACHE = HostCacheLiveStatus()
         case 'FILESYSTEM':
             HOST_CACHE = HostCacheFileSystem()
+        case 'MULTISITE':
+            HOST_CACHE = HostCacheMultiSite(debug=SETTINGS.debug)
         case _:
             print(f'Backend {SETTINGS.backend} not (yet) implemented')
             exit()
 
-    print()
-    print(f'Start time: {strftime(SETTINGS.time_format)}')
-
     user_data = get_data_from_toml(file=SETTINGS.user_data_file)
     HOST_MAP = user_data.get('HOST_MAP', {})
     DROP_HOSTS = user_data.get('DROP_HOSTS', [])
diff --git a/mkp/create_topology_data-0.3.0-20231128.mkp b/mkp/create_topology_data-0.3.0-20231128.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..c56f1b9d2510820ca71ec418fcf8ed95361ddc67
GIT binary patch
literal 12443
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
Z!{_1i@Ok(=eE$7D{|7X0Dl-7^006%BZ2<rP

literal 0
HcmV?d00001

diff --git a/packages/create_topology_data b/packages/create_topology_data
index 3675ee3..9c199bf 100644
--- a/packages/create_topology_data
+++ b/packages/create_topology_data
@@ -59,7 +59,7 @@
                    'topology_data/create_topology_args.py']},
  'name': 'create_topology_data',
  'title': 'Network Visualization data creation',
- 'version': '0.2.0-20231116',
+ 'version': '0.3.0-20231128',
  'version.min_required': '2.2.0p1',
  'version.packaged': '2.2.0p14',
  'version.usable_until': '2.3.0p1'}
-- 
GitLab