1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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()
|