From 1994ef4e2397d43c0ad3e90f0224c553f9ec6a23 Mon Sep 17 00:00:00 2001 From: peekjef72 Date: Wed, 2 Feb 2022 08:06:29 +0100 Subject: [PATCH] changes v 0.1.0 --- .gitignore | 7 +- HISTORY.md | 10 ++ HISTORY.rst | 17 +- README.md | 41 +++-- grafana_import/cli.py | 310 +++++++++++++------------------- grafana_import/constants.py | 2 +- grafana_import/grafana.py | 344 ++++++++++++++++++++++++++++++++++++ rebuild.sh | 2 +- 8 files changed, 529 insertions(+), 204 deletions(-) create mode 100644 grafana_import/grafana.py diff --git a/.gitignore b/.gitignore index a8b459d..2eb309a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +rebuild.sh */__pycache__/* -.eggs/* -*.egg-info/* +.vscode/* build/* -INFO.txt +dist/* +*.egg-*/ \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md index cbbe570..748c2d4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,16 @@ # History +## 0.1.0 (2022-02-01) +* fixe behavior for dashboard moved from one folder to another + +## 0.0.3 (2022-02-31) +* add remove dashboard feature + +## 0.0.2 (2022-01-07) +* change config file format from json to yml +* add labels in config to define multi grafana servers. + ## 0.0.1 (2021-03-15) * First release on github. diff --git a/HISTORY.rst b/HISTORY.rst index 2db6c8b..4f7c194 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,9 +3,24 @@ History ======= +# 0.1.0 (2022-02-01) +==================== + +* fixe behavior for dashboard moved from one folder to another + +# 0.0.3 (2022-02-31) +==================== + +* add remove dashboard feature + +# 0.0.2 (2022-01-07) +==================== + +* change config file format from json to yml +* add labels in config to define multi grafana servers. + # 0.0.1 (2021-03-15) ==================== * First release on github. - diff --git a/README.md b/README.md index d2aaf08..84ee6cd 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Grafana import Tool -A python3 bases application to play with grafana dashboards using [Grafana API](https://grafana.com/docs/grafana/latest/http_api/) and a python interface [grafana_api](https://github.com/m0nhawk/grafana_api) +A python3 based application to play with grafana dashboards using [Grafana API](https://grafana.com/docs/grafana/latest/http_api/) and a python interface [grafana_api](https://github.com/m0nhawk/grafana_api) The aim of this tool is to: -1. Export easilly an existing Grafana dashboard. -3. Import a dashboard in JSON format into a Grafana. +1. Export easilly an existing Grafana dashboard from a folder. +2. Import a dashboard in JSON format into a Grafana. +3. Remove a dashboard ## Install using this repo @@ -87,21 +88,30 @@ then enter into your directory and type in you commands. **usage**: ```shell -usage: grafana-import [-h] [-b BASE_PATH] [-c CONFIG_FILE] [-d DASHBOARD_NAME] - [-g GRAFANA_LABEL] [-f GRAFANA_FOLDER] - [-i DASHBOARD_FILE] [-o] [-p] [-v] [-V] +usage: grafana-import [-h] [-a] [-b BASE_PATH] [-c CONFIG_FILE] + [-d DASHBOARD_NAME] [-g GRAFANA_LABEL] + [-f GRAFANA_FOLDER] [-i DASHBOARD_FILE] [-o] [-p] [-v] + [-V] [ACTION] play with grafana dashboards json files. positional arguments: - ACTION action to perform. Is one of 'export' or 'import' - (default). export: lookup for dashboard name in - Grafana and dump it to local file. import: import a - local dashboard file (previously exported) to Grafana. + ACTION action to perform. Is one of 'export', 'import' + (default), or 'remove'. + export: lookup for dashboard name in Grafana and dump + it to local file. + import: import a local dashboard file (previously + exported) to Grafana. + remove: lookup for dashboard name in Grafana and remove + it from Grafana server. + optional arguments: -h, --help show this help message and exit + -a, --allow_new if a dashboard with same name exists in an another + folder, allow to create a new dashboard with same name + it that folder. -b BASE_PATH, --base_path BASE_PATH set base directory to find default files. -c CONFIG_FILE, --config_file CONFIG_FILE @@ -109,7 +119,7 @@ optional arguments: -d DASHBOARD_NAME, --dashboard_name DASHBOARD_NAME name of dashboard to export. -g GRAFANA_LABEL, --grafana_label GRAFANA_LABEL - label in the config file that represent the grafana to + label in the config file that represents the grafana to connect to. -f GRAFANA_FOLDER, --grafana_folder GRAFANA_FOLDER the folder name where to import into Grafana. @@ -125,9 +135,8 @@ optional arguments: ``` import action preserves the version history. - ***Example:*** -* **import** the dashboard located in default directory imports to grafana folder "Application" +* **import** the dashboard located in default directory imports to grafana folder "Applications" ```bash $ grafana-import -i my-first-dashboard_202104011548.json -f Applications -o @@ -141,6 +150,12 @@ then you can go into Grafana Gui and find the folder Applications $ /usr/local/bin/grafana-import -d "my-first-dashboard" -p export OK: dashboard exported to './exports/my-first-dashboard_20210401165925.json'. ``` +When the dashboard is not required anymore, you can remove it: +* **remove** the dashboard "my-first-dashboard" from folder "Applications" +```bash +$ grafana-import -f Applications -d "my-first-dashboard" remove +OK: dashboard my-first-dashboard removed from 'Applications'. +``` diff --git a/grafana_import/cli.py b/grafana_import/cli.py index 5c46ac7..d3dd994 100644 --- a/grafana_import/cli.py +++ b/grafana_import/cli.py @@ -18,13 +18,12 @@ from grafana_import.constants import (PKG_NAME, PKG_VERSION, CONFIG_NAME) -import argparse, json, sys, os, re, socket, logging -import unicodedata, traceback +import argparse, json, sys, os, re, traceback from datetime import datetime -from grafana_api.grafana_face import GrafanaFace import grafana_api.grafana_api as GrafanaApi +import grafana_import.grafana as Grafana import yaml @@ -32,103 +31,43 @@ config = None #****************************************************************************************** -def get_dashboard_content(config, args, grafana_api, dashboard_name): +def save_dashboard(config, args, base_path, dashboard_name, dashboard, action): - try: - res = grafana_api.search.search_dashboards( - type_='dash-db', - limit=config['grafana']['search_api_limit'], - ) - except Exception as e: - print("error: {}".format(traceback.format_exc()) ) -# print("error: {} - message: {}".format(e.__doc__, e.message) ) - return None - dashboards = res - board = None - b_found = False - if args.verbose: - print("There are {0} dashboards:".format(len(dashboards))) - for board in dashboards: - if board['title'] == dashboard_name: - b_found = True - if args.verbose: - print("dashboard found") - break - if b_found and board: - try: - board = grafana_api.dashboard.get_dashboard(board['uid']) - except Exception as e: - print("error: {}".format(traceback.format_exc()) ) - else: - board = None - - return board - -#****************************************************************************************** -def get_folder(config, args, grafana_api, folder_name): - - try: - res = grafana_api.folder.get_all_folders() - except Exception as e: - print("error: {}".format(traceback.format_exc()) ) -# print("error: {} - message: {}".format(e.__doc__, e.message) ) - return None - - folders = res - folder = None + output_file = base_path - if args.verbose: - print("There are {0} folderss:".format(len(folders))) - for tmp_folder in folders: - if tmp_folder['title'] == folder_name: - if args.verbose: - print("Folder found") - folder = tmp_folder - break + if 'exports_path' in config['general'] and \ + not re.search(r'^(\.|\/)?/', config['general']['exports_path']): + output_file = os.path.join(output_file, config['general']['exports_path'] ) - return folder + if 'export_suffix' in config['general']: + dashboard_name += datetime.today().strftime(config['general']['export_suffix']) -#****************************************************************************************** -def remove_accents_and_space(input_str): - nfkd_form = unicodedata.normalize('NFKD', input_str) - res = u"".join([c for c in nfkd_form if not unicodedata.combining(c)]) - res = re.sub('\s+', '_', res) - return res + if 'meta' in dashboard and 'folderId' in dashboard['meta'] and dashboard['meta']['folderId'] != 0: + dashboard_name = dashboard['meta']['folderTitle'] + '_' + dashboard_name -#****************************************************************************************** -def save_dashboard(config, args, base_path, dashboard_name, params, action): - - output_file = base_path + '/' - - if 'exports_path' in config['general']: - output_file += config['general']['exports_path'] + '/' - - if 'export_suffix' in config['general']: - dashboard_name += datetime.today().strftime(config['general']['export_suffix']) - - file_name = remove_accents_and_space( dashboard_name ) - output_file += file_name + '.json' - try: - output = open(output_file, 'w') - except OSError as e: - print('File {0} error: {1}.'.format(output_file, e.strerror)) - sys.exit(2) - - content = None - if args.pretty: - content = json.dumps( params, sort_keys=True, indent=2 ) - else: - content = json.dumps( params ) - output.write( content ) - output.close() - print("OK: dashboard {1} to '{0}'.".format(output_file, action)) + file_name = Grafana.remove_accents_and_space( dashboard_name ) + output_file = os.path.join(output_file, file_name + '.json') + try: + output = open(output_file, 'w') + except OSError as e: + print('File {0} error: {1}.'.format(output_file, e.strerror)) + sys.exit(2) + + content = None + if args.pretty: + content = json.dumps( dashboard['dashboard'], sort_keys=True, indent=2 ) + else: + content = json.dumps( dashboard['dashboard'] ) + output.write( content ) + output.close() + print("OK: dashboard {1} to '{0}'.".format(output_file, action)) #****************************************************************************************** class myArgs: attrs = [ 'pattern' , 'base_path', 'config_file' , 'grafana', 'dashboard_name' - , 'pretty', 'overwrite', 'verbose' + , 'pretty', 'overwrite', 'allow_new', 'verbose' ] def __init__(self): @@ -150,6 +89,12 @@ def main(): # get command line arguments parser = argparse.ArgumentParser(description='play with grafana dashboards json files.') + + parser.add_argument('-a', '--allow_new' + , action='store_true' + , default=False + , help='if a dashboard with same name exists in an another folder, allow to create a new dashboard with same name it that folder.') + parser.add_argument('-b', '--base_path' , help='set base directory to find default files.') parser.add_argument('-c', '--config_file' @@ -159,7 +104,7 @@ def main(): , help='name of dashboard to export.') parser.add_argument('-g', '--grafana_label' - , help='label in the config file that represent the grafana to connect to.' + , help='label in the config file that represents the grafana to connect to.' , default='default') parser.add_argument('-f', '--grafana_folder' @@ -170,7 +115,8 @@ def main(): parser.add_argument('-o', '--overwrite' , action='store_true' - , help='if a dashboard with same name exists in folder, overwrite it with this new one.') + , default=False + , help='if a dashboard with same name exists in same folder, overwrite it with this new one.') parser.add_argument('-p', '--pretty' , action='store_true' @@ -186,9 +132,13 @@ def main(): parser.add_argument('action', metavar='ACTION' , nargs='?' - , choices=['import', 'export'] + , choices=['import', 'export', 'remove'] , default='import' - , help='action to perform. Is one of \'export\' or \'import\' (default).\nexport: lookup for dashboard name in Grafana and dump it to local file.\nimport: import a local dashboard file (previously exported) to Grafana.') + , help='action to perform. Is one of \'export\', \'import\' (default), or \'remove\'.\n' \ + 'export: lookup for dashboard name in Grafana and dump it to local file.\n' \ + 'import: import a local dashboard file (previously exported) to Grafana.\n' \ + 'remove: lookup for dashboard name in Grafana and remove it from Grafana server.' + ) inArgs = myArgs() args = parser.parse_args(namespace=inArgs) @@ -223,7 +173,16 @@ def main(): args.verbose = config['general']['debug'] else: args.verbose = False + + if args.allow_new is None: + args.allow_new = False + + if args.overwrite is None: + args.overwrite = False + if args.pretty is None: + args.pretty = False + #print( json.dumps(config, sort_keys=True, indent=4) ) #************ @@ -247,26 +206,34 @@ def main(): if not args.grafana_label in config['grafana']: print("ERROR: invalid grafana config label has been specified (-g {0}).".format(args.grafana_label)) sys.exit(1) - + + #** init default conf from grafana with set label. + config['grafana'] = config['grafana'][args.grafana_label] + #************ - grafana_api = GrafanaFace( - auth=config['grafana'][args.grafana_label]['token'], - host=config['grafana'][args.grafana_label]['host'], - protocol=config['grafana'][args.grafana_label]['protocol'], - port=config['grafana'][args.grafana_label]['port'], - verify=config['grafana'][args.grafana_label]['verify_ssl'], - ) + if not 'token' in config['grafana']: + print("ERROR: no token has been specified in grafana config label '{0}'.".format(args.grafana_label)) + sys.exit(1) + + params = { + 'host': config['grafana'].get('host', 'localhost'), + 'protocol': config['grafana'].get('protocol', 'http'), + 'port': config['grafana'].get('port', '3000'), + 'token': config['grafana'].get('token'), + 'verify_ssl': config['grafana'].get('verify_ssl', True), + 'search_api_limit': config['grafana'].get('search_api_limit', 5000), + 'folder': config['general'].get('grafana_folder', 'General'), + 'overwrite': args.overwrite, + 'allow_new': args.allow_new, + } + try: - res = grafana_api.health.check() - if res['database'] != 'ok': - print("grafana health_check is not KO.") - sys.exit(1) - elif args.verbose: - print("grafana health_check is OK.") + grafana_api = Grafana.Grafana( **params ) except Exception as e: - print("ERROR: {} - message: {}".format(res, e.message) ) + print("ERROR: {} - message: {}".format(e) ) sys.exit(1) + #******************************************************************************* if args.action == 'import': if args.dashboard_file is None: print('ERROR: no file to import provided!') @@ -274,10 +241,11 @@ def main(): import_path = '' import_file = args.dashboard_file if not re.search(r'^(?:(?:/)|(?:\.?\./))', import_file): - import_path = base_path + '/' + import_path = base_path if 'imports_path' in config['general']: - import_path += config['general']['imports_path'] + '/' - import_path += import_file + import_path = os.path.join(import_path, config['general']['imports_path'] ) + import_path = os.path.join(import_path, import_file) + try: input = open(import_path, 'r') except OSError as e: @@ -293,86 +261,58 @@ def main(): print("ERROR: reading '{0}': {1}".format(import_path, e)) sys.exit(1) - #** check dashboard existence - #** dash from file has no meta data (folder infos) - new_dash = { 'dashboard': dash } + try: + res = grafana_api.import_dashboard( dash ) + except GrafanaApi.GrafanaClientError as exp: + print('ERROR: {0}.'.format(exp)) + print("maybe you want to set --overwrite option.") + sys.exit(1) - if 'uid' in dash: - try: - old_dash = grafana_api.dashboard.get_dashboard(dash['uid']) - except GrafanaApi.GrafanaClientError: - old_dash = None - - if old_dash is not None: - new_dash['overwrite'] = True - if not config['check_folder']: - if 'meta' in old_dash and 'folderUrl' in old_dash['meta']: - config['general']['grafana_folder'] = old_dash['meta']['folderTitle'] - new_dash['folderId'] = old_dash['meta']['folderId'] - config['check_folder'] = False - else: - config['general']['grafana_folder'] = 'General' - if config['general']['grafana_folder'] == 'General': - config['check_folder'] = False - else: - new_dash['overwrite'] = args.overwrite - dash['version'] = 1 - if not config['check_folder']: - config['general']['grafana_folder'] = 'General' - config['check_folder'] = False - - #** check folder existence - if config['check_folder']: - res = get_folder(config, args, grafana_api, config['general']['grafana_folder']) - if res is None: - try: - res = grafana_api.folder.create_folder(config['general']['grafana_folder']) - except Exception as e: - print("error: {}".format(traceback.format_exc()) ) - sys.exit(1) - - if res: - if args.verbose: - print("folder created") - #** update folder - new_dash['folderId'] = res['id'] - else: - print("KO: folder creation failed.") - sys.exit(1) - else: - new_dash['folderId'] = res['id'] - elif 'folderId' not in dash: - new_dash['folderId'] = 0 # 0 for default 'General' pseudo folder - new_dash['message'] = 'imported from grafana-import.' - try: - res = grafana_api.dashboard.update_dashboard(new_dash) - except GrafanaApi.GrafanaClientError as e: - print("ERROR({1}): {0} ".format(e.message,e.status_code)) - print("maybe you want to set --overwrite option.") - sys.exit(1) - - except Exception as e: - print("error: {}".format(traceback.format_exc()) ) - sys.exit(1) - - if res['status']: - print("OK: dashboard {0} imported into '{1}'.".format(dash['title'], config['general']['grafana_folder'])) - sys.exit(0) - else: - print("KO: dashboard {0} not imported into '{1}'.".format(dash['title'], config['general']['grafana_folder'])) - sys.exit(1) + if res: + print("OK: dashboard '{0}' imported into '{1}'.".format(dash['title'], grafana_api.grafana_folder)) + sys.exit(0) else: - print("error invalid dashboard file '{0}': can't find dashboard uid".format(import_path)) + print("KO: dashboard '{0}' not imported into '{1}'.".format(dash['title'], grafana_api.grafana_folder)) sys.exit(1) - else: # export - dash = get_dashboard_content(config, args, grafana_api, config['general']['dashboard_name']) - if dash is not None: - save_dashboard(config, args, base_path, config['general']['dashboard_name'], dash['dashboard'], 'exported') - sys.exit(0) + + #******************************************************************************* + elif args.action == 'remove': + try: + res = grafana_api.remove_dashboard(config['general']['dashboard_name']) + print("OK: dashboard '{0}' removed.".format(config['general']['dashboard_name'])) + except Grafana.GrafanaDashboardNotFoundError as exp: + print("KO: dashboard '{0}' not found in '{1}".format(exp.dashboard, exp.folder)) + sys.exit(0) + except Grafana.GrafanaFolderNotFoundError as exp: + print("KO: folder '{0}' not found".format(exp.folder)) + sys.exit(0) + except GrafanaApi.GrafanaBadInputError as exp: + print("KO: dashboard '{0}' not removed: {1}".format(config['general']['dashboard_name'], exp)) + sys.exit(1) + except Exception as exp: + print("error: dashboard '{0}' remove exception '{1}'".format(config['general']['dashboard_name'], traceback.format_exc())) + sys.exit(1) + + #******************************************************************************* + else: # export or + try: + dash = grafana_api.export_dashboard(config['general']['dashboard_name']) + except Grafana.GrafanaNotFoundError: + print("KO: dashboard name not found '{0}'".format(config['general']['dashboard_name'])) + sys.exit(1) + except Exception as exp: + print("error: dashboard '{0}' export exception '{1}'".format(config['general']['dashboard_name'], traceback.format_exc())) + sys.exit(1) + + if dash is not None: + save_dashboard(config, args, base_path, config['general']['dashboard_name'], dash, 'exported') + sys.exit(0) # end main... #*********************************************************************************************** -#*********************************************************************************************** if __name__ == '__main__': main() + +#*********************************************************************************************** +# over diff --git a/grafana_import/constants.py b/grafana_import/constants.py index 3cc1697..e87bca5 100644 --- a/grafana_import/constants.py +++ b/grafana_import/constants.py @@ -1,3 +1,3 @@ PKG_NAME = 'grafana-import' -PKG_VERSION = '0.0.2' +PKG_VERSION = '0.1.0' CONFIG_NAME = 'conf/grafana-import.yml' diff --git a/grafana_import/grafana.py b/grafana_import/grafana.py new file mode 100644 index 0000000..e28b4a1 --- /dev/null +++ b/grafana_import/grafana.py @@ -0,0 +1,344 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +#****************************************************************************************** +from tkinter.messagebox import NO +import grafana_api.grafana_face as GrafanaFace +import grafana_api.grafana_api as GrafanaApi +import re, traceback, unicodedata + +from grafana_import.constants import (PKG_NAME) + +#****************************************************************************************** +class GrafanaDashboardNotFoundError(Exception): + """ + input: + dashboard_name + folder + message + + """ + + def __init__(self, dashboard_name, folder, message): + self.dashboard = dashboard_name + self.folder = folder + self.message = message + # Backwards compatible with implementations that rely on just the message. + super(GrafanaDashboardNotFoundError, self).__init__(message) + +#****************************************************************************************** +class GrafanaFolderNotFoundError(Exception): + """ + input: + folder + message + + """ + + def __init__(self, folder, message): + self.folder = folder + self.message = message + # Backwards compatible with implementations that rely on just the message. + super(GrafanaFolderNotFoundError, self).__init__(message) + +#****************************************************************************************** +def remove_accents_and_space(input_str): + """ + build a valid file name from dashboard name. + + as mentioned in the function name remove .... + + input: a dashboard name + + :result: converted string + """ + nfkd_form = unicodedata.normalize('NFKD', input_str) + res = u"".join([c for c in nfkd_form if not unicodedata.combining(c)]) + res = re.sub('\s+', '_', res) + + return res + +#****************************************************************************************** +class Grafana(object): + #* to store the folders list, dashboards list (kind of cache) + folders = [] + dashboards =[] + + #*********************************************** + def __init__( *args, **kwargs ): + self = args[0] + + config = { } + config['protocol'] = kwargs.get('protocol', 'http') + config['host'] = kwargs.get('host', 'localhost') + config['port'] = kwargs.get('port', 3000) + config['token'] = kwargs.get('token', None) + if config['token'] is None: + raise GrafanaApi.GrafanaBadInputError('grafana token is not defined') + + config['verify_ssl'] = kwargs.get('verify_ssl', True) + + self.search_api_limit = kwargs.get('search_api_limit', 5000) + #* set the default destination folder for dash + self.grafana_folder = kwargs.get('folder', 'General') + + #* when importing dash, allow to overwrite dash if it already exists + self.overwrite = kwargs.get('overwrite', True) + + #* when importing dash, if dashboard name exists, but destination folder mismatch + #* allow to create new dashboard with same name in specified folder. + self.allow_new = kwargs.get('allow_new', False) + + #* build an aapi object + self.grafana_api = GrafanaFace.GrafanaFace( + auth=config['token'], + host=config['host'], + protocol=config['protocol'], + port=config['port'], + verify=config['verify_ssl'], + ) + #* try to connect to the API + try: + res = self.grafana_api.health.check() + if res['database'] != 'ok': + raise Exception('grafana is not UP') + except: + raise + + #*********************************************** + def find_dashboard(self, dashboard_name): + + #* use to retrive dashboards which name are matching the lookup named + #* some api version didn't return forlderTitle... require to lookup in two phases + found_dashs = [] + + #* init cache for dashboards. + if len(Grafana.dashboards) == 0: + #** collect all dashboard names. + try: + res = self.grafana_api.search.search_dashboards( + type_='dash-db', + limit=self.search_api_limit + ) + except Exception as e: + raise Exception("error: {}".format(traceback.format_exc()) ) + Grafana.dashboards = res + + dashboards = Grafana.dashboards + + folder = { + 'id': 0, + 'title': 'General', + } + if not re.match('general', self.grafana_folder, re.IGNORECASE): + found_folder = self.get_folder(folder_name=self.grafana_folder) + if found_folder is not None: + folder = found_folder + + board = None + #* find the board uid in the list + for cur_dash in dashboards: + if cur_dash['title'] == dashboard_name: + # set current dashbard as found candidate + board = cur_dash + # check the folder part + if ('folderTitle' in cur_dash and cur_dash['folderTitle'] == folder['title']) or \ + ('folderTitle' not in cur_dash and folder['id'] == 0 ): + # this is a requested folder or no folder ! + break + + return board + + #*********************************************** + def export_dashboard(self, dashboard_name): + """ + retrive the dashboard object from Grafana server. + params: + dashboard_name (str): name of the dashboard to retrieve + result: + dashboard (dict [json]) + """ + + board = self.find_dashboard(dashboard_name) + + if board is not None: + + #* collect the board object itself from it uid. + try: + board = self.grafana_api.dashboard.get_dashboard(board['uid']) + except Exception as e: + raise + else: + raise GrafanaDashboardNotFoundError(dashboard_name, self.grafana_folder, 'dashboard not found') + + return board + + + #*********************************************** + def remove_dashboard(self, dashboard_name): + """ + retrive the dashboard object from Grafana server and remove it. + params: + dashboard_name (str): name of the dashboard to retrieve + result: + True or Exception + """ + + res = False + folder = { + 'id' : 0, + 'title': self.grafana_folder, + } + #* check the destination folder is General + + if not re.match('general', self.grafana_folder, re.IGNORECASE): + #** check 'custom' folder existence (custom != General) + folder = self.get_folder( self.grafana_folder ) + if folder is None: + raise GrafanaFolderNotFoundError(self.grafana_folder, 'folder not found') + + #* collect the board object itself from it uid. + try: + board = self.find_dashboard(dashboard_name) + except Exception as e: + raise + + if board is None: + raise GrafanaDashboardNotFoundError(dashboard_name, folder['title'], 'dashboard not found') + + if (folder['id'] == 0 and 'folderId' in board and board['folderId'] != folder['id'] ) \ + or (folder['id'] != 0 and not 'folderId' in board ): + raise GrafanaApi.GrafanaBadInputError("Dashboard name found but in folder '{0}'!".format(board['folderTitle'])) + + if 'uid' in board: + try: + board = self.grafana_api.dashboard.delete_dashboard(board['uid']) + res = True + except Exception as exp: + raise + + return res + + #****************************************************************************************** + def get_folder(self, folder_name=None, folder_uid=None): + """ + try to find folder meta data (uid...) from folder name + params: + folder_name (str): name of the folder (case sensitive) into Grafana folders tree + return: + folder object (dict) + """ + if folder_name is None and folder_uid is None: + return None + + #* init cache for folders. + if len(Grafana.folders) == 0: + try: + res = self.grafana_api.folder.get_all_folders() + except Exception as e: + raise + + Grafana.folders = res + + folders = Grafana.folders + folder = None + + for tmp_folder in folders: + + if (folder_name is not None and folder_name == tmp_folder['title'] ) \ + or (folder_uid is not None and folder_uid == tmp_folder['folderId'] ): + folder = tmp_folder + break + + return folder + + #*********************************************** + def import_dashboard(self, dashboard): + + #** build a temporary meta dashboard struct to store info + #** by default dashboard will be overwritten + new_dash = { + 'dashboard': dashboard, + 'overwrite': True, + } + + old_dash = self.find_dashboard(dashboard['title']) + + #** check a previous dashboard existence (same folder, same uid) + if old_dash is None: + new_dash['overwrite'] = self.overwrite + dashboard['version'] = 1 + + #* check the destination folder is General + if re.match('general', self.grafana_folder, re.IGNORECASE): + new_dash['folderId'] = 0 + else: + #** check 'custom' folder existence (custom != General) + folder = self.get_folder( self.grafana_folder ) + if folder is None: + try: + folder = self.grafana_api.folder.create_folder( self.grafana_folder ) + except Exception as e: + raise + + if folder: + new_dash['folderId'] = folder['id'] + else: + raise Exception("KO: grafana folder '{0}' creation failed.".format(self.grafana_folder)) + else: + new_dash['folderId'] = folder['id'] + + #** several case + # read new folder1/dash1(uid1) => old folder1/dash1(uid1): classic update + # b) read new folder_new/dash1(uid1) => old folder1/dash1(uid1): create new dash in folder_new + # => new folder_new/dash1(uid_new) if allow_new + # c) read new folder_new/dash1(uid_new) => old folder1/dash1(uid1): create new in new folder folder_new + # => classic create (update) + # d) read new folder1/dash1(uid_new) => old folder1/dash1(uid1) + # => new folder1/dash1(uid1) if overwrite + if old_dash is not None: + if 'meta' in old_dash and 'folderUrl' in old_dash['meta']: + old_dash['folderId'] = old_dash['meta']['folderId'] + elif not 'folderId' in old_dash: + old_dash['folderId'] = 0 + + # case b) get a copy of an existing dash to a folder where dash is not present + if new_dash['folderId'] != old_dash['folderId']: + # if new_dash['dashboard']['uid'] == old_dash['uid']: + if self.allow_new: + new_dash['overwrite'] = False + #force the creation of a new dashboard + new_dash['dashboard']['uid'] = None + new_dash['dashboard']['id'] = None + else: + raise GrafanaApi.GrafanaBadInputError("dashboard already exists in an another folder and allow_new is False.") + #** case d) send a copy to existing dash : update existing + elif new_dash['folderId'] == old_dash['folderId']: + if new_dash['dashboard']['uid'] != old_dash['uid']: + if self.overwrite: + new_dash['dashboard']['uid'] = old_dash['uid'] + new_dash['dashboard']['id'] = old_dash['id'] + else: + raise GrafanaApi.GrafanaBadInputError("dashboard already exists in this folder with an another id and overwrite is False.") + else: + #force the creation of a new dashboard + new_dash['dashboard']['uid'] = None + new_dash['dashboard']['id'] = None + new_dash['overwrite'] = False + + new_dash['message'] = 'imported from {0}.'.format(PKG_NAME) + + try: + res = self.grafana_api.dashboard.update_dashboard(new_dash) + except Exception as e: + raise + + if res['status']: + res = True + else: + res = False + + return res + +#****************************************************************************************** +# over \ No newline at end of file diff --git a/rebuild.sh b/rebuild.sh index bff9fe1..aa32640 100755 --- a/rebuild.sh +++ b/rebuild.sh @@ -1,5 +1,5 @@ #!/bin/bash -rm -rf /usr/local/lib/python3.6/site-packages/grafana_imprt /usr/local/lib/python3.6/site-packages/grafana_import-0.0.1-py3.6.egg-info/ +rm -rf /usr/local/lib/python3.6/site-packages/grafana_import /usr/local/lib/python3.6/site-packages/grafana_import-0.0.1-py3.6.egg-info/ pip3 install .