]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | # -*- coding: utf-8 -*- |
2 | # pylint: disable=F0401 | |
3 | """ | |
4 | This script does: | |
5 | * Scan through Angular html templates and extract <cd-grafana> tags | |
6 | * Check if every tag has a corresponding Grafana dashboard by `uid` | |
7 | ||
8 | Usage: | |
9 | python <script> <angular_app_dir> <grafana_dashboard_dir> | |
10 | ||
11 | e.g. | |
12 | cd /ceph/src/pybind/mgr/dashboard | |
13 | python ci/<script> frontend/src/app /ceph/monitoring/grafana/dashboards | |
14 | """ | |
15 | import argparse | |
16 | import codecs | |
17 | import copy | |
18 | import json | |
19 | import os | |
20 | ||
21 | import six | |
22 | from six.moves.html_parser import HTMLParser | |
23 | ||
24 | ||
25 | class TemplateParser(HTMLParser): | |
26 | ||
27 | def __init__(self, _file, search_tag): | |
28 | if six.PY3: | |
29 | super(TemplateParser, self).__init__() | |
30 | else: | |
31 | # HTMLParser is not a new-style class in py2 | |
32 | HTMLParser.__init__(self) | |
33 | self.search_tag = search_tag | |
34 | self.file = _file | |
35 | self.parsed_data = [] | |
36 | ||
37 | def parse(self): | |
38 | with codecs.open(self.file, encoding='UTF-8') as f: | |
39 | self.feed(f.read()) | |
40 | ||
41 | def handle_starttag(self, tag, attrs): | |
42 | if tag != self.search_tag: | |
43 | return | |
44 | tag_data = { | |
45 | 'file': self.file, | |
46 | 'attrs': dict(attrs), | |
47 | 'line': self.getpos()[0] | |
48 | } | |
49 | self.parsed_data.append(tag_data) | |
50 | ||
51 | def error(self, message): | |
52 | error_msg = 'fail to parse file {} (@{}): {}'.\ | |
53 | format(self.file, self.getpos(), message) | |
54 | exit(error_msg) | |
55 | ||
56 | ||
57 | def stdout(msg): | |
58 | six.print_(msg) | |
59 | ||
60 | ||
61 | def get_files(base_dir, file_ext): | |
62 | result = [] | |
63 | for root, _, files in os.walk(base_dir): | |
64 | for _file in files: | |
65 | if _file.endswith('.{}'.format(file_ext)): | |
66 | result.append(os.path.join(root, _file)) | |
67 | return result | |
68 | ||
69 | ||
70 | def get_tags(base_dir, tag='cd-grafana'): | |
71 | templates = get_files(base_dir, 'html') | |
72 | tags = [] | |
73 | for templ in templates: | |
74 | parser = TemplateParser(templ, tag) | |
75 | parser.parse() | |
76 | if parser.parsed_data: | |
77 | tags.extend(parser.parsed_data) | |
78 | return tags | |
79 | ||
80 | ||
81 | def get_grafana_dashboards(base_dir): | |
82 | json_files = get_files(base_dir, 'json') | |
83 | dashboards = {} | |
84 | for json_file in json_files: | |
85 | with open(json_file) as f: | |
86 | dashboard_config = json.load(f) | |
87 | uid = dashboard_config.get('uid') | |
88 | if not uid: | |
89 | continue | |
90 | if uid in dashboards: | |
91 | # duplicated uids | |
92 | error_msg = 'Duplicated UID {} found, already defined in {}'.\ | |
93 | format(uid, dashboards[uid]['file']) | |
94 | exit(error_msg) | |
95 | dashboards[uid] = { | |
96 | 'file': json_file, | |
97 | 'title': dashboard_config['title'] | |
98 | } | |
99 | return dashboards | |
100 | ||
101 | ||
102 | def parse_args(): | |
103 | long_desc = ('Check every <cd-grafana> component in Angular template has a' | |
104 | ' mapped Grafana dashboard.') | |
105 | parser = argparse.ArgumentParser(description=long_desc) | |
106 | parser.add_argument('angular_app_dir', type=str, | |
107 | help='Angular app base directory') | |
108 | parser.add_argument('grafana_dash_dir', type=str, | |
109 | help='Directory contains Grafana dashboard JSON files') | |
110 | parser.add_argument('--verbose', action='store_true', | |
111 | help='Display verbose mapping information.') | |
112 | return parser.parse_args() | |
113 | ||
114 | ||
115 | def main(): | |
116 | args = parse_args() | |
117 | tags = get_tags(args.angular_app_dir) | |
118 | grafana_dashboards = get_grafana_dashboards(args.grafana_dash_dir) | |
119 | verbose = args.verbose | |
120 | ||
121 | if not tags: | |
122 | error_msg = 'Can not find any cd-grafana component under {}'.\ | |
123 | format(args.angular_app_dir) | |
124 | exit(error_msg) | |
125 | ||
126 | if verbose: | |
127 | stdout('Found mappings:') | |
128 | no_dashboard_tags = [] | |
129 | for tag in tags: | |
130 | uid = tag['attrs']['uid'] | |
131 | if uid not in grafana_dashboards: | |
132 | no_dashboard_tags.append(copy.copy(tag)) | |
133 | continue | |
134 | if verbose: | |
135 | msg = '{} ({}:{}) \n\t-> {} ({})'.\ | |
136 | format(uid, tag['file'], tag['line'], | |
137 | grafana_dashboards[uid]['title'], | |
138 | grafana_dashboards[uid]['file']) | |
139 | stdout(msg) | |
140 | ||
141 | if no_dashboard_tags: | |
142 | title = ('Checking Grafana dashboards UIDs: ERROR\n' | |
143 | 'Components that have no mapped Grafana dashboards:\n') | |
144 | lines = ('{} ({}:{})'.format(tag['attrs']['uid'], | |
145 | tag['file'], | |
146 | tag['line']) | |
147 | for tag in no_dashboard_tags) | |
148 | error_msg = title + '\n'.join(lines) | |
149 | exit(error_msg) | |
150 | else: | |
151 | stdout('Checking Grafana dashboards UIDs: OK') | |
152 | ||
153 | ||
154 | if __name__ == '__main__': | |
155 | main() |