]> Frank Brehm's Git Trees - pixelpark/admin-tools.git/commitdiff
Writing module status and dependency-dot-file
authorFrank Brehm <frank.brehm@pixelpark.com>
Wed, 15 Aug 2018 14:52:24 +0000 (16:52 +0200)
committerFrank Brehm <frank.brehm@pixelpark.com>
Wed, 15 Aug 2018 14:52:24 +0000 (16:52 +0200)
pp_lib/check_puppet_env_app.py

index 43966f8f737f2ca8375c0fcd4bbc8d1761c837af..351fd98110fad46636b5ee645ff5ef4067f77f7c 100644 (file)
@@ -19,9 +19,11 @@ import socket
 import pwd
 import sys
 import glob
+import datetime
 
 # Third party modules
 import six
+import yaml
 
 from six import StringIO
 from six.moves import configparser
@@ -39,7 +41,7 @@ from .merge import merge_structure
 
 from .app import PpApplication
 
-__version__ = '0.4.1'
+__version__ = '0.5.1'
 LOG = logging.getLogger(__name__)
 
 
@@ -84,6 +86,7 @@ class CheckPuppetEnvApp(PpApplication):
         self.modules_root_dir = None
         self.modules = {}
         self.dependencies = []
+        self.rev_dep = {}
 
         super(CheckPuppetEnvApp, self).__init__(
             appname=appname, verbose=verbose, version=version, base_dir=base_dir,
@@ -268,6 +271,47 @@ class CheckPuppetEnvApp(PpApplication):
         self.collect_modules()
         self.print_modules()
         self.verify_dependencies()
+        self.write_modinfo_yaml()
+        self.print_not_depended()
+        self.write_dependencies()
+
+    # -------------------------------------------------------------------------
+    def write_modinfo_yaml(self):
+
+        outfile_base = 'modules-info.{e}.{d}.yaml'.format(
+            e=self.env_name, d=datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'))
+        out_file = os.path.join(self.out_dir, outfile_base)
+
+        LOG.info("Writing information about modules in {!r}...".format(out_file))
+
+        with open(out_file, 'w', **self.open_args) as fh:
+            fh.write(yaml.dump(self.modules, width=240))
+
+    # -------------------------------------------------------------------------
+    def print_not_depended(self):
+
+        print()
+        print("Module, von denen keine anderen Module abhängen:")
+        print("================================================")
+        print()
+
+        len_base = 1
+        for b_name in self.modules.keys():
+            base_name = str(b_name)
+            if len(base_name) > len_base:
+                len_base = len(base_name)
+
+        template = '  * {{b:<{}}} -> {{n}}'.format(len_base)
+
+        for b_name in sorted(self.modules.keys(), key=str.lower):
+
+            module_info = self.modules[b_name]
+            base_name = str(b_name)
+
+            if base_name not in self.rev_dep or not self.rev_dep[base_name]:
+                print(template.format(b=base_name, n=module_info['name']))
+
+        print()
 
     # -------------------------------------------------------------------------
     def verify_dependencies(self):
@@ -276,6 +320,7 @@ class CheckPuppetEnvApp(PpApplication):
 
         mods = {}
         self.dependencies = []
+        self.rev_dep = {}
         re_name_split = re.compile(r'([^/_-]+)[/_-](.*)')
         connectors = ('-', '_', '/')
 
@@ -291,6 +336,8 @@ class CheckPuppetEnvApp(PpApplication):
         for b_name in self.modules.keys():
             module_info = self.modules[b_name]
             base_name = str(b_name)
+            if base_name not in self.rev_dep:
+                self.rev_dep[base_name] = []
             if not module_info['dependencies']:
                 continue
             if not module_info['name']:
@@ -299,13 +346,23 @@ class CheckPuppetEnvApp(PpApplication):
             if not module_info['vendor']:
                 LOG.warn("Did not found vendor of module {!r}.".format(base_name))
             mod_name = module_info['name']
+            if self.verbose > 1:
+                LOG.debug("Checking dependencies of module {!r}...".format(mod_name))
 
             for dep_key in module_info['dependencies'].keys():
                 dep_mod = str(dep_key)
                 if dep_mod in mods:
                     dep = (dep_mod, mod_name)
                     self.dependencies.append(dep)
+                    if mods[dep_mod] not in self.rev_dep:
+                        self.rev_dep[mods[dep_mod]] = []
+                    if base_name not in self.rev_dep[mods[dep_mod]]:
+                        self.rev_dep[mods[dep_mod]].append(base_name)
+                    module_info['dependencies'][dep_key]['module'] = mods[dep_mod]
                     continue
+                if self.verbose > 2:
+                    LOG.debug("Dependency to {d!r} of module {m!r} wrong formatted.".format(
+                        d=dep_mod, m=mod_name))
                 match = re_name_split.match(dep_mod)
                 found = False
                 if match:
@@ -316,15 +373,94 @@ class CheckPuppetEnvApp(PpApplication):
                     if dep_mod_name in mods:
                         dep = (dep_mod_name, mod_name)
                         self.dependencies.append(dep)
+                        if mods[dep_mod_name] not in self.rev_dep:
+                            self.rev_dep[mods[dep_mod_name]] = []
+                        if base_name not in self.rev_dep[mods[dep_mod_name]]:
+                            self.rev_dep[mods[dep_mod_name]].append(base_name)
+                        module_info['dependencies'][dep_key]['module'] = mods[dep_mod_name]
                         found = True
                         break
                 if found:
-                    break
+                    continue
                 LOG.warn("Did not found dependency to {d!r} of module {m!r}.".format(
                     d=dep_mod, m=mod_name))
 
         if self.verbose > 2:
             LOG.debug("Found dependencies:\n{}".format(pp(self.dependencies)))
+            LOG.debug("Reverse dependencies:\n{}".format(pp(self.rev_dep)))
+
+    # -------------------------------------------------------------------------
+    def write_dependencies(self):
+
+        outfile_base = 'modules-deps.{e}.{d}.dot'.format(
+            e=self.env_name, d=datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'))
+        out_file = os.path.join(self.out_dir, outfile_base)
+
+        LOG.info("Writing graphviz dot file about module dependecies in {!r}...".format(out_file))
+
+        header_lines = (
+            'digraph Dependencies {',
+            '',
+            '\t// Graph attributes',
+            '\tnodesep=0.7;',
+        )
+
+        def printout(fh, line):
+            if self.verbose:
+                print(line)
+            fh.write(line + '\n')
+
+        with open(out_file, 'w', **self.open_args) as fh:
+
+            # File header
+            for line in header_lines:
+                printout(fh, line)
+
+            # Print nodes
+            line = '\n\t// Modules as nodes'
+            printout(fh, line)
+
+            for b_name in sorted(self.modules.keys(), key=str.lower):
+
+                module_info = self.modules[b_name]
+                base_name = str(b_name)
+
+                mod_name = base_name
+                if module_info['name']:
+                    mod_name = module_info['name']
+                tgt_dot_id = module_info['dot_id']
+
+                line = '\t{};'.format(tgt_dot_id)
+                printout(fh, line)
+
+            line = '\n\t// #############################\n\t// Dependencies'
+            printout(fh, line)
+
+            # Print dependencies as edges
+            for b_name in sorted(self.modules.keys(), key=str.lower):
+
+                module_info = self.modules[b_name]
+                base_name = str(b_name)
+
+                mod_name = base_name
+                if module_info['name']:
+                    mod_name = module_info['name']
+                tgt_dot_id = module_info['dot_id']
+
+                line = '\n\t// {i} ({n})'.format(i=tgt_dot_id, n=mod_name)
+                printout(fh, line)
+
+                for dep_key in module_info['dependencies'].keys():
+                    dep_mod = str(dep_key)
+                    src_module = module_info['dependencies'][dep_key]['module']
+                    if src_module in self.modules:
+                        src_dot_id = self.modules[src_module]['dot_id']
+                        line = '\t{src} -> {tgt};'.format(
+                            src=src_dot_id, tgt=tgt_dot_id)
+                        printout(fh, line)
+
+            # File footer
+            printout(fh, '\n}\n')
 
     # -------------------------------------------------------------------------
     def print_modules(self):
@@ -427,6 +563,8 @@ class CheckPuppetEnvApp(PpApplication):
             LOG.warn("Path {!r} is not a directory".format(module_dir))
             return None
 
+        re_dot_id = re.compile(r'[/-]+')
+
         module_info = {}
         module_info['base_name'] = os.path.basename(module_dir)
         metadata_file = os.path.join(module_dir, 'metadata.json')
@@ -454,6 +592,7 @@ class CheckPuppetEnvApp(PpApplication):
             return None
 
         module_info['name'] = None
+        module_info['dot_id'] = None
         module_info['vendor'] = None
         module_info['version'] = None
         module_info['dependencies'] = {}
@@ -463,6 +602,9 @@ class CheckPuppetEnvApp(PpApplication):
             match = re.match(pat_vendor, module_info['name'])
             if match:
                 module_info['vendor'] = match.group(1)
+            module_info['dot_id'] = re_dot_id.sub('_', module_info['name'])
+        else:
+            module_info['dot_id'] = re_dot_id.sub('_',  module_info['base_name'])
 
         if 'version' in meta_info:
             module_info['version'] = meta_info['version']
@@ -472,7 +614,8 @@ class CheckPuppetEnvApp(PpApplication):
                 if 'name' in dep:
                     dep_info = {
                         'name': dep['name'],
-                        'version': None
+                        'version': None,
+                        'module': None,
                     }
                     if 'version_requirement' in dep:
                         dep_info['version'] = dep['version_requirement']