Repository: RedHatInsights/insights-core

Issue:
The human readable rule report includes rules with missing requirements



Discussion:

Resolving patch:
diff --git a/insights/__init__.py b/insights/__init__.py
--- a/insights/__init__.py
+++ b/insights/__init__.py
@@ -212,7 +212,7 @@ class Args(object):
                 plugins.append(path)
 
         for p in plugins:
-            dr.load_components(p)
+            dr.load_components(p, continue_on_error=False)
 
         if args.config:
             with open(args.config) as f:
diff --git a/insights/core/plugins.py b/insights/core/plugins.py
--- a/insights/core/plugins.py
+++ b/insights/core/plugins.py
@@ -2,10 +2,14 @@
 The plugins module defines the components used by the rest of Insights and
 specializes their interfaces and execution model where required.
 """
+from __future__ import print_function
 
 import logging
 import traceback
 
+from pprint import pformat
+from six import StringIO
+
 from insights.core import dr
 from insights.util.subproc import CalledProcessError
 from insights import settings
@@ -96,9 +100,7 @@ def process(self, broker):
             raise dr.SkipComponent()
         missing = self.get_missing_dependencies(broker)
         if missing:
-            details = dr.stringify_requirements(missing)
-            return _make_skip(dr.get_name(self.component),
-                    reason="MISSING_REQUIREMENTS", details=details)
+            return _make_skip(dr.get_name(self.component), missing)
         r = self.invoke(broker)
         if r is None:
             raise dr.SkipComponent()
@@ -262,6 +264,37 @@ def _log_length_error(self, key, length):
         msg = "Length of data in %s is too long." % self.__class__.__name__
         log.error(msg, extra=extra)
 
+    def __str__(self):
+        key_val = self.get_key()
+        keys = sorted(self)
+        if self.key_name in keys:
+            keys.remove(self.key_name)
+        keys.remove("type")
+
+        buf = StringIO()
+        if not keys:
+            print(key_val, file=buf)
+            buf.seek(0)
+            return buf.read()
+
+        print("%s:" % key_val, file=buf)
+        indent = len(max(keys, key=len)) + 6
+        hang_indent = "\n" + " " * indent
+        for k in keys:
+            key = ("    %s" % k) + " " * (indent - len(k) - 6) + ": "
+            buf.write(key)
+            lines = pformat(self[k]).splitlines()
+            num_lines = len(lines)
+            if num_lines > 10:
+                lines = lines[:10]
+                lines.append("<...%s more lines...>" % (num_lines - 10))
+            out = hang_indent.join(lines)
+            buf.write(out)
+            buf.write("\n")
+
+        buf.seek(0)
+        return buf.read()
+
 
 class make_response(Response):
     """
@@ -322,8 +355,30 @@ class _make_skip(Response):
     """
     response_type = "skip"
 
-    def __init__(self, rule_fqdn, reason, details=None):
+    def __str__(self):
+        required = self.missing[0]
+        at_least_one = self.missing[1]
+
+        buf = StringIO()
+
+        print("Missing Dependencies:", file=buf)
+
+        if required:
+            print("    Requires:", file=buf)
+            for d in required:
+                print("        %s" % dr.get_name(d), file=buf)
+        if at_least_one:
+            for alo in at_least_one:
+                print("    At Least One Of:", file=buf)
+                for d in alo:
+                    print("        %s" % dr.get_name(d), file=buf)
+        buf.seek(0)
+        return buf.read()
+
+    def __init__(self, rule_fqdn, missing):
+        self.missing = missing
+        details = dr.stringify_requirements(missing)
         super(_make_skip, self).__init__(None,
                                         rule_fqdn=rule_fqdn,
-                                        reason=reason,
+                                        reason="MISSING_REQUIREMENTS",
                                         details=details)
diff --git a/insights/formats/text.py b/insights/formats/text.py
--- a/insights/formats/text.py
+++ b/insights/formats/text.py
@@ -59,23 +59,24 @@ def __init__(self, broker,
         self.dropped = dropped
         self.stream = stream
 
+    def print_header(self, header, color):
+        ln = len(header)
+        print(color + '-' * ln, file=self.stream)
+        print(header, file=self.stream)
+        print('-' * ln + Style.RESET_ALL, file=self.stream)
+
     def preprocess(self):
         self.counts = {'skip': 0, 'pass': 0, 'rule': 0, 'metadata': 0, 'metadata_key': 0, 'exception': 0}
         response = namedtuple('response', 'color label intl title')
-        self.responses = {'skip': response(color=Fore.BLUE, label="SKIP", intl='S', title="Total Skipped Due To Rule Dependencies "
-                                                                            "Not Met - "),
-                          'pass': response(color=Fore.GREEN, label="PASS", intl='P', title="Total Return Type 'make_pass' - "),
-                          'rule': response(color=Fore.RED, label="FAIL", intl='R', title="Total Return Type "
-                                                                           "'make_fail/make_response' - "),
-                          'metadata': response(color=Fore.YELLOW, label="META", intl='M', title="Total Return Type 'make_metadata' - "),
-                          'metadata_key': response(color=Fore.MAGENTA, label="META", intl='K', title="Total Return Type "
-                                                                                       "'make_metadata_key' - "),
-                          'exception': response(color=Fore.RED, label="EXCEPT", intl='E', title="Total Exceptions Reported to Broker - ")
+        self.responses = {'skip': response(color=Fore.BLUE, label="SKIP", intl='S', title="Missing Deps: "),
+                          'pass': response(color=Fore.GREEN, label="PASS", intl='P', title="Passed      : "),
+                          'rule': response(color=Fore.RED, label="FAIL", intl='F', title="Failed      : "),
+                          'metadata': response(color=Fore.YELLOW, label="META", intl='M', title="Metadata    : "),
+                          'metadata_key': response(color=Fore.MAGENTA, label="META", intl='K', title="Metadata Key: "),
+                          'exception': response(color=Fore.RED, label="EXCEPT", intl='E', title="Exceptions  : ")
                           }
 
-        print(Fore.CYAN + '-' * 9, file=self.stream)
-        print("Progress:", file=self.stream)
-        print('-' * 9 + Style.RESET_ALL, file=self.stream)
+        self.print_header("Progress:", Fore.CYAN)
         self.broker.add_observer(self.progress_bar, rule)
         self.broker.add_observer(self.progress_bar, condition)
         self.broker.add_observer(self.progress_bar, incident)
@@ -97,13 +98,6 @@ def progress_bar(self, c, broker):
             print(Fore.RED + "E" + Style.RESET_ALL, end="", file=self.stream)
         return self
 
-    def show_missing(self):
-        """ Show missing requirements """
-        if self.broker.missing_requirements:
-            print(file=self.stream)
-            print("Missing Requirements:", file=self.stream)
-            print(self.broker.missing_requirements, file=self.stream)
-
     def show_tracebacks(self):
         """ Show tracebacks """
         if self.broker.tracebacks:
@@ -129,33 +123,31 @@ def show_dropped(self):
 
     def show_description(self):
         """ Prints the formatted response for the matching return type """
-        def printit(c, v):
-            name = None
 
-            if v["type"] in self.responses:
-                self.counts[v["type"]] += 1
-                name = self.responses[v["type"]].color + dr.get_name(c) + " - [" + self.responses[v["type"]].label + "]" + Style.RESET_ALL
-            if name:
-                print(name, file=self.stream)
-                print('-' * len(name), file=self.stream)
-                print(render(c, v), file=self.stream)
-                print(file=self.stream)
+        def printit(c, v):
+            _type = v.get("type")
+            if _type:
+                underline = "-" * len(dr.get_name(c))
+                resp = self.responses[v["type"]]
+                name = "%s[%s] %s%s" % (resp.color, resp.label, dr.get_name(c), Style.RESET_ALL)
+                if _type != "skip" or (_type == "skip" and self.missing):
+                    print(name, file=self.stream)
+                    print(underline, file=self.stream)
+                    print(render(c, v), file=self.stream)
+                    print(file=self.stream)
 
         for c in sorted(self.broker.get_by_type(rule), key=dr.get_name):
             v = self.broker[c]
+            if v["type"] in self.responses:
+                self.counts[v["type"]] += 1
             printit(c, v)
         print(file=self.stream)
 
-        print(Fore.CYAN + '*' * 31 + Style.RESET_ALL, file=self.stream)
-        print(Fore.CYAN + "**** Counts By Return Type ****" + Style.RESET_ALL, file=self.stream)
-        print(Fore.CYAN + '*' * 31 + Style.RESET_ALL, file=self.stream)
-
+        self.print_header("Rule Execution Summary", Fore.CYAN)
         for c in self.counts:
             print(self.responses[c].color + self.responses[c].title + str(self.counts[c]) + Style.RESET_ALL, file=self.stream)
 
     def postprocess(self):
-        if self.missing:
-            self.show_missing()
         if self.tracebacks:
             self.show_tracebacks()
         if self.dropped:
@@ -163,10 +155,7 @@ def postprocess(self):
 
         print(file=self.stream)
         print(file=self.stream)
-        print(Fore.CYAN + "-" * 15, file=self.stream)
-        print("Rules Executed", file=self.stream)
-        print('-' * 15 + Style.RESET_ALL, file=self.stream)
-
+        self.print_header("Rules Executed", Fore.CYAN)
         self.show_description()
 
 


Test patch: