From 18a8172c0abb1fc19109910f9fe064a1fd3a541c Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Fri, 20 Oct 2023 19:42:49 +0200
Subject: [PATCH] update project

---
 README.md                                    |   2 +-
 bin/topology_data/create_topology_classes.py |  54 ++++++++++++++---
 bin/topology_data/create_topology_data.py    |  60 ++++++++++++-------
 bin/topology_data/create_topology_utils.py   |  30 ++++++++--
 create_topology_data-0.0.8-20231020.mkp      | Bin 0 -> 10489 bytes
 packages/create_topology_data                |  19 +++---
 6 files changed, 121 insertions(+), 44 deletions(-)
 create mode 100644 create_topology_data-0.0.8-20231020.mkp

diff --git a/README.md b/README.md
index e38ee2f..fb19868 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/create_topology_data-0.0.7-20231019.mkp "create_topology_data-0.0.7-20231019.mkp"
+[PACKAGE]: ../../raw/master/create_topology_data-0.0.8-20231020.mkp "create_topology_data-0.0.8-20231020.mkp"
 # PoC for Network Visualization data creation from inventory data
 
 This script creates the topology data file needed for the [Checkmk Exchange Network visualization](https://forum.checkmk.com/u/schnetz) 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 4e3ad73..145a84d 100755
--- a/bin/topology_data/create_topology_classes.py
+++ b/bin/topology_data/create_topology_classes.py
@@ -12,7 +12,28 @@
 from os import environ
 from time import strftime
 from typing import Dict, List, Any, NamedTuple
-from create_topology_utils import lldp_path, lldp_columns, lldp_label, user_data_file
+from enum import Enum, unique
+from create_topology_utils import (
+    cdp_path,
+    cdp_columns,
+    cdp_label,
+    lldp_path,
+    lldp_columns,
+    lldp_label,
+    user_data_file,
+)
+
+
+class TopologyParams(NamedTuple):
+    path: str
+    columns: str
+    label: str
+
+
+@unique
+class Topologies(Enum):
+    CDP = TopologyParams(path=cdp_path, columns=cdp_columns, label=cdp_label)
+    LLDP = TopologyParams(path=lldp_path, columns=lldp_columns, label=lldp_label)
 
 
 class InventoryColumns(NamedTuple):
@@ -51,13 +72,14 @@ class Settings:
 
         self.__settings = {
             'seed_devices': None,
-            'path_in_inventory': 'networking,cdp_cache',
-            'inventory_columns': 'device_id,local_port,device_port',
-            'data_source': 'inv_CDP',
+            'path_in_inventory': cdp_path,
+            'inventory_columns': cdp_columns,
+            'data_source': cdp_label,
             'time_format': '%Y-%m-%dT%H:%M:%S.%m',
             'user_data_file': f'{self.__omd_root}/local/bin/topology_data/{user_data_file}',
+            'merge': None,
             'output_directory': None,
-            'make_default': False,
+            'default': False,
             'keep_domain': False,
             'lowercase': False,
             'uppercase': False,
@@ -79,11 +101,29 @@ class Settings:
             self.__settings['inventory_columns'] = lldp_columns
         # Then update values with cli values
         self.__settings.update(self.__args)
+        if not self.merge:
+            if not self.lldp:
+                self.merge = ['CDP']
+            else:
+                self.merge = ['LLDP']
+
+    def set_topology_param(self, topology: TopologyParams):
+        self.__settings['path_in_inventory'] = topology.path
+        self.__settings['inventory_columns'] = topology.columns
+        self.__settings['data_source'] = topology.label
 
     @property
     def version(self) -> bool:
         return self.__settings['version']
 
+    @property
+    def merge(self) -> List[str] | None:
+        return self.__settings['merge']
+
+    @merge.setter
+    def merge(self, topologies: List[str]):
+        self.__settings['merge'] = topologies
+
     @property
     def keep(self) -> int | None:
         if self.__settings['keep']:
@@ -113,8 +153,8 @@ class Settings:
         return self.__settings['dont_compare']
 
     @property
-    def make_default(self) -> bool:
-        return self.__settings['make_default']
+    def default(self) -> bool:
+        return self.__settings['default']
 
     @property
     def keep_domain(self) -> bool:
diff --git a/bin/topology_data/create_topology_data.py b/bin/topology_data/create_topology_data.py
index 563e8c1..0ac0a99 100755
--- a/bin/topology_data/create_topology_data.py
+++ b/bin/topology_data/create_topology_data.py
@@ -21,6 +21,8 @@
 #             get interfaces from autochecks changed to live status query
 # 2023-10-19: cleanup removed "all" unused functions
 #             added option -u, --user-data-file to provide a user specific data file
+# 2023-10-20: changed option -m/--make-default to -d/--default (-m is needed for -m/--merge
+#             added -m/--merge option
 
 #
 # PoC for creating topology_data.json from inventory data
@@ -63,15 +65,17 @@ from create_topology_utils import (
     merge_topologies,
     save_topology,
     get_data_form_live_status,
+    ExitCodes,
 )
 from create_topology_classes import (
     InventoryColumns,
     Settings,
     StaticConnection,
+    Topologies,
 )
 
 
-CREATE_TOPOLOGY_VERSION = '0.0.7-202310119'
+CREATE_TOPOLOGY_VERSION = '0.0.8-202310120'
 
 ITEMS = {}
 HOST_MAP: Dict[str, str] = {}
@@ -293,14 +297,21 @@ def create_topology(
 
 if __name__ == '__main__':
     SETTINGS = Settings(vars(parse_arguments(CREATE_TOPOLOGY_VERSION)))
+
     if SETTINGS.version:
         print(f'{Path(__file__).name} version: {CREATE_TOPOLOGY_VERSION}')
-        exit(0)
+        exit(code=ExitCodes.ok)
 
     if SETTINGS.check_user_data_only:
         user_data = get_data_from_toml(file=SETTINGS.user_data_file)
         print(f'Could read/parse the user data from {SETTINGS.user_data_file}')
-        exit(0)
+        exit(code=ExitCodes.OK)
+
+    if SETTINGS.merge:
+        _merge = list(set(SETTINGS.merge.copy()))
+        if len(_merge) != len(SETTINGS.merge):
+            print(f'-m/--merge options must be unique. Use "-m cdp lldp" oder "-m lldp cdp"')
+            exit(code=ExitCodes.BAD_OPTION_LIST)
 
     print()
     print(f'Start time: {strftime(SETTINGS.time_format)}')
@@ -309,28 +320,33 @@ if __name__ == '__main__':
     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', [])
+
+    final_topology = {}
+    while SETTINGS.merge:
+        SETTINGS.set_topology_param(Topologies[SETTINGS.merge[-1]].value)
+        topology = create_topology(
+            seed_devices=list(set(SETTINGS.seed_devices + user_data.get('SEED_DEVICES', []))),
+            path_in_inventory=SETTINGS.path_in_inventory,
+            inv_columns=SETTINGS.inventory_columns,
+            data_source=SETTINGS.data_source,
+            debug=SETTINGS.debug,
+        )
+        if topology:
+            final_topology = merge_topologies(topo_pri=topology, topo_sec=final_topology)
+        SETTINGS.merge.remove(SETTINGS.merge[-1])
+
     STATIC_CONNECTIONS = create_static_connections(connections=user_data.get('STATIC_CONNECTIONS', []))
 
-    topology = create_topology(
-        seed_devices=list(set(SETTINGS.seed_devices + user_data.get('SEED_DEVICES', []))),
-        path_in_inventory=SETTINGS.path_in_inventory,
-        inv_columns=SETTINGS.inventory_columns,
-        data_source=SETTINGS.data_source,
-        debug=SETTINGS.debug,
-    )
+    final_topology = merge_topologies(topo_pri=STATIC_CONNECTIONS, topo_sec=final_topology)
 
-    if topology:
-        # merge topology with static connections
-        topology = merge_topologies(topo_pri=STATIC_CONNECTIONS, topo_sec=topology)
-
-        save_topology(
-            data=topology,
-            base_directory=f'{SETTINGS.omd_root}/{SETTINGS.topology_save_path}',
-            output_directory=SETTINGS.output_directory,
-            topology_file_name=SETTINGS.topology_file_name,
-            dont_compare=SETTINGS.dont_compare,
-            make_default=SETTINGS.make_default,
-        )
+    save_topology(
+        data=final_topology,
+        base_directory=f'{SETTINGS.omd_root}/{SETTINGS.topology_save_path}',
+        output_directory=SETTINGS.output_directory,
+        topology_file_name=SETTINGS.topology_file_name,
+        dont_compare=SETTINGS.dont_compare,
+        make_default=SETTINGS.default,
+    )
 
     if SETTINGS.keep:
         remove_old_data(
diff --git a/bin/topology_data/create_topology_utils.py b/bin/topology_data/create_topology_utils.py
index 727dbe8..bf6776f 100755
--- a/bin/topology_data/create_topology_utils.py
+++ b/bin/topology_data/create_topology_utils.py
@@ -9,7 +9,7 @@
 
 #
 # options used
-# -m --make-default
+# -d --default
 # -o --output-directory
 # -s --seed-devices
 # -u --user-data-file
@@ -43,6 +43,15 @@ from argparse import (
     ArgumentParser,
     RawTextHelpFormatter,
 )
+from enum import Enum, unique
+
+
+@unique
+class ExitCodes(Enum):
+    OK = 0
+    BAD_OPTION_LIST = 1
+    BAD_TOML_FORMAT = 3
+
 
 script = '~/local/bin/network-topology/create_topology_data.py'
 sample_seeds = 'Core01 Core02'
@@ -82,7 +91,7 @@ def get_data_from_toml(file: str, debug: bool = False) -> Dict:
         except TOMLDecodeError as e:
             print(
                 f'ERROR: data file {toml_file} is not in valid TOML format! ({e}), (see https://toml.io/en/)')
-            exit(2)
+            exit(code=ExitCodes.BAD_TOML_FORMAT)
     else:
         print(f'WARNING: User data {file} not found.')
     if debug:
@@ -325,16 +334,27 @@ def parse_arguments(create_topology_version: str) -> arg_Namespace:
         formatter_class=RawTextHelpFormatter,
         epilog='Usage:\n'
                'for CDP (the default):\n'
-               f'{script} -s {sample_seeds} -m\n'
+               f'{script} -s {sample_seeds} -d\n'
                'for LLDP:\n'
-               f'{script} -s {sample_seeds} -m --lldp\n',
+               f'{script} -s {sample_seeds} -d --lldp\n',
     )
     command_group = parser.add_mutually_exclusive_group()
 
     parser.add_argument(
-        '-m', '--make-default', default=False, action='store_const', const=True,
+        '-d', '--default', default=False, action='store_const', const=True,
         help='Set the created topology data as default',
     )
+    parser.add_argument(
+        '-m', '--merge',
+        nargs=2,
+        choices=['CDP', 'LLDP'],
+        help=f'Merge topologies. This runs the topology creation for CDP and LLDP.\n'
+             f'The topologies are then merged in the specified order.\n'
+             f'I.e. -m CDP LLDP merges the CDP topology into the LLDP topology, overwriting\n'
+             f'the LLDP data in case there are conflicts.\n'
+             f'NOTE: static connection data from the user file will always merged with the\n'
+             f' highest priority',
+    )
     parser.add_argument(
         '-o', '--output-directory', type=str,
         help='Directory name where to save the topology data.\n'
diff --git a/create_topology_data-0.0.8-20231020.mkp b/create_topology_data-0.0.8-20231020.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..5d22cfd2f9bca0ad9256fe5ac3185ffabf3039e1
GIT binary patch
literal 10489
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

literal 0
HcmV?d00001

diff --git a/packages/create_topology_data b/packages/create_topology_data
index b7662ba..85ce824 100644
--- a/packages/create_topology_data
+++ b/packages/create_topology_data
@@ -10,14 +10,15 @@
                 'tester ;-)\n'
                 '\n'
                 'Features:\n'
-                '- Reading connection data from the Checkmk inventory\n'
-                '- Adding user defined connections for connections not '
-                'included in the inventory\n'
-                '- handling differences between inventory neighbor names and '
-                'checkmk hostnames\n'
-                '- optimized for my CDP (default) and LLDP cache plugins\n'
-                '- can also be used with custom inventory plugins\n'
-                '- clean up old topologies\n'
+                '- Reading connection data from the checkmk HW/SW inventory.\n'
+                '- Handling differences between inventory neighbor names and '
+                'checkmk host names.\n'
+                '- Add custom connections for connections that are not in the '
+                'inventory\n'
+                '- Optimized for my CDP (default) and LLDP cache plugins\n'
+                '- Read and merge CDP and LLDP topologies\n'
+                '- Can also be used with custom inventory plugins\n'
+                '- Clean up old topologies\n'
                 '\n'
                 'For more information about the network visualization plugin '
                 'see: \n'
@@ -55,7 +56,7 @@
                    'topology_data/create_topology_data.toml']},
  'name': 'create_topology_data',
  'title': 'Network Visualization data creation',
- 'version': '0.0.7-20231019',
+ 'version': '0.0.8-20231020',
  'version.min_required': '2.2.0p1',
  'version.packaged': '2.2.0p11',
  'version.usable_until': '2.3.0p1'}
-- 
GitLab