diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py b/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py new file mode 100644 index 000000000..d37337b40 --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# pylint: disable=F0401 +""" +This script does: +* Scan through Angular html templates and extract <cd-grafana> tags +* Check if every tag has a corresponding Grafana dashboard by `uid` + +Usage: + python <script> <angular_app_dir> <grafana_dashboard_dir> + +e.g. + cd /ceph/src/pybind/mgr/dashboard + python ci/<script> frontend/src/app /ceph/monitoring/ceph-mixin/dashboards_out +""" +import argparse +import codecs +import copy +import json +import os +from html.parser import HTMLParser + + +class TemplateParser(HTMLParser): + + def __init__(self, _file, search_tag): + super().__init__() + self.search_tag = search_tag + self.file = _file + self.parsed_data = [] + + def parse(self): + with codecs.open(self.file, encoding='UTF-8') as f: + self.feed(f.read()) + + def handle_starttag(self, tag, attrs): + if tag != self.search_tag: + return + tag_data = { + 'file': self.file, + 'attrs': dict(attrs), + 'line': self.getpos()[0] + } + self.parsed_data.append(tag_data) + + def error(self, message): + error_msg = 'fail to parse file {} (@{}): {}'.\ + format(self.file, self.getpos(), message) + exit(error_msg) + + +def get_files(base_dir, file_ext): + result = [] + for root, _, files in os.walk(base_dir): + for _file in files: + if _file.endswith('.{}'.format(file_ext)): + result.append(os.path.join(root, _file)) + return result + + +def get_tags(base_dir, tag='cd-grafana'): + templates = get_files(base_dir, 'html') + tags = [] + for templ in templates: + parser = TemplateParser(templ, tag) + parser.parse() + if parser.parsed_data: + tags.extend(parser.parsed_data) + return tags + + +def get_grafana_dashboards(base_dir): + json_files = get_files(base_dir, 'json') + dashboards = {} + for json_file in json_files: + try: + with open(json_file) as f: + dashboard_config = json.load(f) + uid = dashboard_config.get('uid') + # if it's not a grafana dashboard, skip checks + # Fields in a dasbhoard: + # https://grafana.com/docs/grafana/latest/dashboards/json-model/#json-fields + expected_fields = [ + 'id', 'uid', 'title', 'tags', 'style', 'timezone', 'editable', + 'hideControls', 'graphTooltip', 'panels', 'time', 'timepicker', + 'templating', 'annotations', 'refresh', 'schemaVersion', 'version', 'links', + ] + not_a_dashboard = False + for field in expected_fields: + if field not in dashboard_config: + not_a_dashboard = True + break + if not_a_dashboard: + continue + + assert dashboard_config['id'] is None, \ + "'id' not null: '{}'".format(dashboard_config['id']) + + assert 'timezone' not in dashboard_config or dashboard_config['timezone'] == '', \ + ("'timezone' field must not be set to anything but an empty string or be " + "omitted completely") + + # Grafana dashboard checks + title = dashboard_config['title'] + assert len(title) > 0, \ + "Title not found in '{}'".format(json_file) + assert len(dashboard_config.get('links', [])) == 0, \ + "Links found in '{}'".format(json_file) + if not uid: + continue + if uid in dashboards: + # duplicated uids + error_msg = 'Duplicated UID {} found, already defined in {}'.\ + format(uid, dashboards[uid]['file']) + exit(error_msg) + + dashboards[uid] = { + 'file': json_file, + 'title': title + } + except Exception as e: + print(f"Error in file {json_file}") + raise e + return dashboards + + +def parse_args(): + long_desc = ('Check every <cd-grafana> component in Angular template has a' + ' mapped Grafana dashboard.') + parser = argparse.ArgumentParser(description=long_desc) + parser.add_argument('angular_app_dir', type=str, + help='Angular app base directory') + parser.add_argument('grafana_dash_dir', type=str, + help='Directory contains Grafana dashboard JSON files') + parser.add_argument('--verbose', action='store_true', + help='Display verbose mapping information.') + return parser.parse_args() + + +def main(): + args = parse_args() + tags = get_tags(args.angular_app_dir) + grafana_dashboards = get_grafana_dashboards(args.grafana_dash_dir) + verbose = args.verbose + + if not tags: + error_msg = 'Can not find any cd-grafana component under {}'.\ + format(args.angular_app_dir) + exit(error_msg) + + if verbose: + print('Found mappings:') + no_dashboard_tags = [] + for tag in tags: + uid = tag['attrs']['uid'] + if uid not in grafana_dashboards: + no_dashboard_tags.append(copy.copy(tag)) + continue + if verbose: + msg = '{} ({}:{}) \n\t-> {} ({})'.\ + format(uid, tag['file'], tag['line'], + grafana_dashboards[uid]['title'], + grafana_dashboards[uid]['file']) + print(msg) + + if no_dashboard_tags: + title = ('Checking Grafana dashboards UIDs: ERROR\n' + 'Components that have no mapped Grafana dashboards:\n') + lines = ('{} ({}:{})'.format(tag['attrs']['uid'], + tag['file'], + tag['line']) + for tag in no_dashboard_tags) + error_msg = title + '\n'.join(lines) + exit(error_msg) + else: + print('Checking Grafana dashboards UIDs: OK') + + +if __name__ == '__main__': + main() |