]>
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') | |
9f95a23c TL |
88 | |
89 | # Grafana dashboard checks | |
90 | title = dashboard_config['title'] | |
91 | assert len(title) > 0, \ | |
92 | "Title not found in '{}'".format(json_file) | |
93 | assert len(dashboard_config.get('links', [])) == 0, \ | |
94 | "Links found in '{}'".format(json_file) | |
92f5a8d4 TL |
95 | if not uid: |
96 | continue | |
97 | if uid in dashboards: | |
98 | # duplicated uids | |
99 | error_msg = 'Duplicated UID {} found, already defined in {}'.\ | |
100 | format(uid, dashboards[uid]['file']) | |
101 | exit(error_msg) | |
9f95a23c | 102 | |
92f5a8d4 TL |
103 | dashboards[uid] = { |
104 | 'file': json_file, | |
9f95a23c | 105 | 'title': title |
92f5a8d4 TL |
106 | } |
107 | return dashboards | |
108 | ||
109 | ||
110 | def parse_args(): | |
111 | long_desc = ('Check every <cd-grafana> component in Angular template has a' | |
112 | ' mapped Grafana dashboard.') | |
113 | parser = argparse.ArgumentParser(description=long_desc) | |
114 | parser.add_argument('angular_app_dir', type=str, | |
115 | help='Angular app base directory') | |
116 | parser.add_argument('grafana_dash_dir', type=str, | |
117 | help='Directory contains Grafana dashboard JSON files') | |
118 | parser.add_argument('--verbose', action='store_true', | |
119 | help='Display verbose mapping information.') | |
120 | return parser.parse_args() | |
121 | ||
122 | ||
123 | def main(): | |
124 | args = parse_args() | |
125 | tags = get_tags(args.angular_app_dir) | |
126 | grafana_dashboards = get_grafana_dashboards(args.grafana_dash_dir) | |
127 | verbose = args.verbose | |
128 | ||
129 | if not tags: | |
130 | error_msg = 'Can not find any cd-grafana component under {}'.\ | |
131 | format(args.angular_app_dir) | |
132 | exit(error_msg) | |
133 | ||
134 | if verbose: | |
135 | stdout('Found mappings:') | |
136 | no_dashboard_tags = [] | |
137 | for tag in tags: | |
138 | uid = tag['attrs']['uid'] | |
139 | if uid not in grafana_dashboards: | |
140 | no_dashboard_tags.append(copy.copy(tag)) | |
141 | continue | |
142 | if verbose: | |
143 | msg = '{} ({}:{}) \n\t-> {} ({})'.\ | |
144 | format(uid, tag['file'], tag['line'], | |
145 | grafana_dashboards[uid]['title'], | |
146 | grafana_dashboards[uid]['file']) | |
147 | stdout(msg) | |
148 | ||
149 | if no_dashboard_tags: | |
150 | title = ('Checking Grafana dashboards UIDs: ERROR\n' | |
151 | 'Components that have no mapped Grafana dashboards:\n') | |
152 | lines = ('{} ({}:{})'.format(tag['attrs']['uid'], | |
153 | tag['file'], | |
154 | tag['line']) | |
155 | for tag in no_dashboard_tags) | |
156 | error_msg = title + '\n'.join(lines) | |
157 | exit(error_msg) | |
158 | else: | |
159 | stdout('Checking Grafana dashboards UIDs: OK') | |
160 | ||
161 | ||
162 | if __name__ == '__main__': | |
163 | main() |