summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/ci/check_grafana_uids.py
blob: 134bb6e7fbd3242de754324f3192ff18eca32114 (plain)
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
# -*- 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/grafana/dashboards
"""
import argparse
import codecs
import copy
import json
import os

import six
from six.moves.html_parser import HTMLParser


class TemplateParser(HTMLParser):

    def __init__(self, _file, search_tag):
        if six.PY3:
            super(TemplateParser, self).__init__()
        else:
            # HTMLParser is not a new-style class in py2
            HTMLParser.__init__(self)
        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 stdout(msg):
    six.print_(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:
        with open(json_file) as f:
            dashboard_config = json.load(f)
            uid = dashboard_config.get('uid')
            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': dashboard_config['title']
            }
    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:
        stdout('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'])
            stdout(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:
        stdout('Checking Grafana dashboards UIDs: OK')


if __name__ == '__main__':
    main()