| import abc |
| import os |
| import time |
| |
| from cfg_checker.common import const |
| from cfg_checker.common import logger_cli |
| |
| import jinja2 |
| |
| import six |
| |
| pkg_dir = os.path.dirname(__file__) |
| pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir) |
| pkg_dir = os.path.normpath(pkg_dir) |
| |
| |
| def line_breaks(text): |
| # replace python linebreaks with html breaks |
| return text.replace("\n", "<br />") |
| |
| |
| def get_sorted_keys(td): |
| # detect if we can sort by desc |
| # Yes, this is slow, but bullet-proof from empty desc |
| _desc = all([bool(td[k]['desc']) for k in td.keys()]) |
| # Get sorted list |
| if not _desc: |
| return sorted(td.keys()) |
| else: |
| return sorted( |
| td.keys(), |
| key=lambda k: ( |
| td[k]['desc']['section'], |
| td[k]['desc']['app'], |
| k |
| ) |
| ) |
| |
| |
| def get_max(_list): |
| return sorted(_list)[-1] |
| |
| |
| def make_action_label(act): |
| _act_labels = { |
| const.ACT_UPGRADE: "Upgrade possible", |
| const.ACT_NEED_UP: "Needs upgrade", |
| const.ACT_NEED_DOWN: "Needs downgrade", |
| const.ACT_REPO: "Needs repo update", |
| const.ACT_NA: "" |
| } |
| return _act_labels[act] |
| |
| |
| def make_action_class(act): |
| _act_classes = { |
| const.ACT_UPGRADE: "possible", |
| const.ACT_NEED_UP: "needs_up", |
| const.ACT_NEED_DOWN: "needs_down", |
| const.ACT_REPO: "needs_repo", |
| const.ACT_NA: "" |
| } |
| return _act_classes[act] |
| |
| |
| def make_status_label(sts): |
| _status_labels = { |
| const.VERSION_OK: "OK", |
| const.VERSION_UP: "Upgraded", |
| const.VERSION_DOWN: "Downgraded", |
| const.VERSION_ERR: "ERROR", |
| const.VERSION_NA: "N/A" |
| } |
| return _status_labels[sts] |
| |
| |
| def make_status_class(sts): |
| return const.all_statuses[sts] |
| |
| |
| def make_repo_info(repos): |
| _text = "" |
| for r in repos: |
| # tag |
| _text += r['tag'] + ": " |
| # repo header |
| _text += " ".join([ |
| r['subset'], |
| r['release'], |
| r['ubuntu-release'], |
| r['type'], |
| r['arch'] |
| ]) + ", " |
| # maintainer w/o email |
| _m = r['maintainer'][:r['maintainer'].find('<')-1] |
| _m_ascii = _m.encode('ascii', errors="xmlcharrefreplace") |
| _text += _m_ascii |
| # newline |
| _text += "<br />" |
| return _text |
| |
| |
| @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, payload): |
| # init data structures |
| data = self.common_data() |
| # payload should have pre-sorted structure according to report called |
| # nodes, openstack_release, mcp_release, etc... |
| data.update(payload) |
| |
| # add template specific data |
| self._extend_data(data) |
| |
| # do counts global |
| self._count_totals(data) |
| |
| # specific filters |
| self.jinja2_env.filters['linebreaks'] = line_breaks |
| self.jinja2_env.filters['get_max'] = get_max |
| |
| self.jinja2_env.filters['get_sorted_keys'] = get_sorted_keys |
| self.jinja2_env.filters['make_status_label'] = make_status_label |
| self.jinja2_env.filters['make_status_class'] = make_status_class |
| self.jinja2_env.filters['make_action_label'] = make_action_label |
| self.jinja2_env.filters['make_action_class'] = make_action_class |
| self.jinja2_env.filters['make_repo_info'] = make_repo_info |
| |
| # render! |
| logger_cli.info("-> Using template: {}".format(self.tmpl)) |
| tmpl = self.jinja2_env.get_template(self.tmpl) |
| logger_cli.info("-> Rendering") |
| return tmpl.render(data) |
| |
| def common_data(self): |
| return { |
| 'counters': {}, |
| 'salt_info': {}, |
| 'gen_date': time.strftime("%m/%d/%Y %H:%M:%S") |
| } |
| |
| def _extend_data(self, data): |
| pass |
| |
| |
| # HTML Package versions report |
| class CSVAllPackages(_TMPLBase): |
| tmpl = "pkg_versions_csv.j2" |
| |
| |
| # HTML Package versions report |
| class HTMLPackageCandidates(_TMPLBase): |
| tmpl = "pkg_versions_html.j2" |
| |
| |
| # Package versions report |
| class HTMLModelCompare(_TMPLBase): |
| tmpl = "model_tree_cmp_tmpl.j2" |
| |
| def _extend_data(self, data): |
| # move names into separate place |
| data["names"] = data["diffs"].pop("diff_names") |
| data["tabs"] = data.pop("diffs") |
| |
| # counters - mdl_diff |
| for _tab in data["tabs"].keys(): |
| data['counters'][_tab] = len(data["tabs"][_tab]["diffs"].keys()) |
| |
| |
| class HTMLNetworkReport(_TMPLBase): |
| tmpl = "network_check_tmpl.j2" |
| |
| |
| 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) |