Refactored to include varios reports and checks
diff --git a/ci_checker/reporter.py b/ci_checker/reporter.py
new file mode 100644
index 0000000..326969e
--- /dev/null
+++ b/ci_checker/reporter.py
@@ -0,0 +1,179 @@
+import jinja2
+import six
+import abc
+import os
+
+from ci_checker.common import const
+
+pkg_dir = os.path.dirname(__file__)
+pkg_dir = os.path.join(pkg_dir, os.pardir)
+pkg_dir = os.path.normpath(pkg_dir)
+
+
+def shortname(node_fqdn):
+ # form shortname out of node fqdn
+ return node_fqdn.split(".", 1)[0]
+
+
+def is_equal(pkg_dict):
+ # compare versions of given package
+ return pkg_dict['installed'] == pkg_dict['candidate']
+
+
+def is_active(node_dict):
+ # check node status in node dict
+ return node_dict['status'] == const.NODE_UP
+
+
+def line_breaks(text):
+ # replace python linebreaks with html breaks
+ return text.replace("\n", "<br />")
+
+
+@six.add_metaclass(abc.ABCMeta)
+class _Base(object):
+ def __init__(self):
+ self.jinja2_env = self.init_jinja2_env()
+
+ @abc.abstractmethod
+ def __call__(self, payload):
+ pass
+
+ @staticmethod
+ def init_jinja2_env():
+ return jinja2.Environment(
+ loader=jinja2.FileSystemLoader(os.path.join(pkg_dir, 'templates')),
+ trim_blocks=True,
+ lstrip_blocks=True)
+
+
+class _TMPLBase(_Base):
+ @abc.abstractproperty
+ def tmpl(self):
+ pass
+
+ @staticmethod
+ def _count_totals(data):
+ data['counters']['total_nodes'] = len(data['nodes'])
+
+ def __call__(self, nodes={}, mdl_diff={}):
+ # init data structures
+ data = self.common_data()
+ data.update({
+ "nodes": nodes,
+ "compare": data
+ })
+
+ # add template specific data
+ self._extend_data(data)
+
+ # do counts global
+ self._count_totals(data)
+
+ # specific filters
+ self.jinja2_env.filters['shortname'] = shortname
+ self.jinja2_env.filters['is_equal'] = is_equal
+ self.jinja2_env.filters['is_active'] = is_active
+ self.jinja2_env.filters['linebreaks'] = line_breaks
+
+ # render!
+ tmpl = self.jinja2_env.get_template(self.tmpl)
+ return tmpl.render(data)
+
+ def common_data(self):
+ return {
+ 'counters': {},
+ 'salt_info': {}
+ }
+
+ def _extend_data(self, data):
+ pass
+
+
+# Package versions report
+class HTMLPackageCandidates(_TMPLBase):
+ tmpl = "pkg_versions_tmpl.j2"
+
+ @staticmethod
+ def is_fail_uniq(p_dict, p_name, nodes, node_name):
+ # look up package fail for nodes with similar role
+ _tgroup = nodes[node_name]['node_group']
+ # filter all nodes with the same role
+ _nodes_list = filter(
+ lambda nd: nodes[nd]['node_group'] == _tgroup and nd != node_name,
+ nodes
+ )
+ # lookup same package
+ _fail_uniq = False
+ for _node_name in _nodes_list:
+ # check if there is a package present on node
+ _nd = nodes[_node_name]['packages']
+ if p_name not in _nd:
+ continue
+ # if both backages has same version and differ from candidate
+ if p_dict['candidate'] == _nd[p_name]['candidate'] \
+ and _nd[p_name]['candidate'] == _nd[p_name]['installed']:
+ # it is not uniq, mark and break
+ _fail_uniq = True
+ return _fail_uniq
+
+ def _extend_data(self, data):
+ _all_pkg = 0
+ for key, value in data['nodes'].iteritems():
+ # add count of packages for this node to total
+ _all_pkg += len(value.keys())
+
+ # count differences
+ data['counters'][key] = {}
+ data['counters'][key]['packages'] = len(value['packages'].keys())
+ data['counters'][key]['package_diff'] = 0
+ for pkg_name, pkg_value in value['packages'].iteritems():
+ if pkg_value['installed'] != pkg_value['candidate']:
+ pkg_value['is_equal'] = False
+ pkg_value['fail_uniq'] = self.is_fail_uniq(
+ pkg_value,
+ pkg_name,
+ data['nodes'],
+ key
+ )
+ data['counters'][key]['package_diff'] += 1
+ else:
+ pkg_value['is_equal'] = True
+ pkg_value['fail_uniq'] = False
+
+ data['counters']['total_packages'] = _all_pkg
+
+
+# Package versions report
+class HTMLModelCompare(_TMPLBase):
+ tmpl = "model_tree_cmp_tmpl.j2"
+
+ def _extend_data(self, data):
+ # extend data with the compare dict
+
+ # counters - mdl_diff
+ data['counters']['mdl_diff'] = 51
+
+ pass
+
+
+class ReportToFile(object):
+ def __init__(self, report, target):
+ self.report = report
+ self.target = target
+
+ def __call__(self, payload):
+ payload = self.report(payload)
+
+ if isinstance(self.target, six.string_types):
+ self._wrapped_dump(payload)
+ else:
+ self._dump(payload, self.target)
+
+ def _wrapped_dump(self, payload):
+ with open(self.target, 'wt') as target:
+ self._dump(payload, target)
+
+ @staticmethod
+ def _dump(payload, target):
+ target.write(payload)