From 95fef179b409cb7d297c44caee35d5f54052463c Mon Sep 17 00:00:00 2001 From: peekjef72 Date: Thu, 27 Jan 2022 19:25:03 +0100 Subject: [PATCH] changes --- .gitignore | 2 + README.md | 76 +++++++++++++++++++--- grafana_import/cli.py | 84 +++++++++++++++---------- grafana_import/conf/grafana-import.json | 15 ----- grafana_import/conf/grafana-import.yml | 15 +++++ grafana_import/constants.py | 4 +- grafana_import/jsonConfig.py | 69 -------------------- 7 files changed, 136 insertions(+), 129 deletions(-) delete mode 100644 grafana_import/conf/grafana-import.json create mode 100644 grafana_import/conf/grafana-import.yml delete mode 100644 grafana_import/jsonConfig.py diff --git a/.gitignore b/.gitignore index c5e8c91..a8b459d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ */__pycache__/* .eggs/* +*.egg-info/* +build/* INFO.txt diff --git a/README.md b/README.md index 0dab327..d2aaf08 100644 --- a/README.md +++ b/README.md @@ -35,18 +35,43 @@ install with bitbuckt repo or github repo. $ pip install git+https://github.com/peekjef72/grafana_api.git ``` ## Configuration -The configuration is stored in a JSON file, with extended syntax that authorize comments in C++ style with // or /*... */. +The configuration is stored in a YAML file. + It contains 2 parts: * **general**: for script env. * **debug**: enable verbose (debug) trace (for dev only...) * **export_suffix**: when exporting a dashboard, append that suffix to the file name. The suffix can contain plain text and pattern that is translated with strftime command. * **export_path**: where to store the exported dashboards. * **import_path**: where to load the dashboards before to import then into grafana server. -- **grafana**: for grafana access settings - * **protocal**, **host**, **port**: use to build the access url - * **verify_ssl**: to check ssl certificate or not - * **token**: APIKEY with admin right from Grafana to access the REST API. - * **search_api_limit**: the maximum element to retrieve on search over API. +- **grafana**: for grafana access settings; you can define several grafana acces with different api_key or grafana server url + * **label**: a label to refer this grafana server default at least + * **protocal**, **host**, **port**: use to build the access url + * **verify_ssl**: to check ssl certificate or not + * **token**: APIKEY with admin right from Grafana to access the REST API. + * **search_api_limit**: the maximum element to retrieve on search over API. + +
+example: + +```yaml +--- + + general: + debug: false + import_folder: test_import + + grafana: + default: + protocol: http + host: localhost + port: 3000 + token: "____APIKEY____" + search_api_limit: 5000 + verify_ssl: true +... +``` + +
## Usages build a directory structure: @@ -60,11 +85,44 @@ build a directory structure: then enter into your directory and type in you commands. -**usage**: grafana-import [-h] [-b BASE_PATH] [-c CONFIG_FILE] [-d DASHBOARD_NAME] - [-f GRAFANA_FOLDER] [-i DASHBOARD_FILE] [-o] [-p] [-v] - [-V] +**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] [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. + +optional arguments: + -h, --help show this help message and exit + -b BASE_PATH, --base_path BASE_PATH + set base directory to find default files. + -c CONFIG_FILE, --config_file CONFIG_FILE + path to config files. + -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 + connect to. + -f GRAFANA_FOLDER, --grafana_folder GRAFANA_FOLDER + the folder name where to import into Grafana. + -i DASHBOARD_FILE, --dashboard_file DASHBOARD_FILE + path to the dashboard file to import into Grafana. + -o, --overwrite if a dashboard with same name exists in folder, + overwrite it with this new one. + -p, --pretty use JSON indentation when exporting or extraction of + dashboards. + -v, --verbose verbose mode; display log message to stdout. + -V, --version display program version and exit.. + +``` import action preserves the version history. diff --git a/grafana_import/cli.py b/grafana_import/cli.py index c4c96c6..5c46ac7 100644 --- a/grafana_import/cli.py +++ b/grafana_import/cli.py @@ -16,7 +16,7 @@ #*********************************************************************************************** -from grafana_import.constants import (PKG_NAME, PKG_VERSION, JSON_CONFIG_NAME) +from grafana_import.constants import (PKG_NAME, PKG_VERSION, CONFIG_NAME) import argparse, json, sys, os, re, socket, logging import unicodedata, traceback @@ -26,7 +26,7 @@ from grafana_api.grafana_face import GrafanaFace import grafana_api.grafana_api as GrafanaApi -from grafana_import.jsonConfig import jsonConfig +import yaml #****************************************************************************************** config = None @@ -36,9 +36,9 @@ def get_dashboard_content(config, args, grafana_api, dashboard_name): try: res = grafana_api.search.search_dashboards( - type_='dash-db' - , limit=config['grafana']['search_api_limit'] - ) + 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) ) @@ -127,7 +127,7 @@ def save_dashboard(config, args, base_path, dashboard_name, params, action): class myArgs: attrs = [ 'pattern' , 'base_path', 'config_file' - , 'dashboard_name' + , 'grafana', 'dashboard_name' , 'pretty', 'overwrite', 'verbose' ] def __init__(self): @@ -158,6 +158,10 @@ def main(): parser.add_argument('-d', '--dashboard_name' , 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.' + , default='default') + parser.add_argument('-f', '--grafana_folder' , help='the folder name where to import into Grafana.') @@ -172,7 +176,7 @@ def main(): , action='store_true' , help='use JSON indentation when exporting or extraction of dashboards.') - parser.add_argument('-v ', '--verbose' + parser.add_argument('-v', '--verbose' , action='store_true' , help='verbose mode; display log message to stdout.') @@ -193,18 +197,26 @@ def main(): if args.base_path is not None: base_path = inArgs.base_path - config_file = base_path + '/' + JSON_CONFIG_NAME + config_file = os.path.join(base_path, CONFIG_NAME) if args.config_file is not None: - config_file = inArgs.config_file + if not re.search(r'^(\.|\/)?/', config_file): + config_file = os.path.join(base_path,args.config_file) + else: + config_file = args.config_file + + config = None + try: + with open(config_file, 'r') as cfg_fh: + try: + config = yaml.safe_load(cfg_fh) + except yaml.scanner.ScannerError as exc: + mark = exc.problem_mark + print("Yaml file parsing unsuccessul : %s - line: %s column: %s => %s" % (config_file, mark.line+1, mark.column+1, exc.problem) ) + except Exception as exp: + print('ERROR: config file not read: %s' % str(exp)) - confObj = jsonConfig(config_file) - if confObj is None: - print( 'init config failure !') - sys.exit(2) - config = confObj.load() if config is None: - print( confObj.err_msg ) - sys.exit(2) + sys.exit(1) if args.verbose is None: if 'debug' in config['general']: @@ -220,7 +232,7 @@ def main(): if args.action == 'exporter' and ( not 'dashboard_name' in config['general'] or config['general']['dashboard_name'] is None) : print("ERROR: no dashboard has been specified.") - sys.exit(2) + sys.exit(1) #************ config['check_folder'] = False @@ -232,29 +244,33 @@ def main(): if 'export_suffix' not in config['general'] or config['general']['export_suffix'] is None: config['general']['export_suffix'] = "_%Y%m%d%H%M%S" + 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) + #************ grafana_api = GrafanaFace( - auth=config['grafana']['token'] - , host=config['grafana']['host'] - , protocol=config['grafana']['protocol'] - , port=config['grafana']['port'] - , verify=config['grafana']['verify_ssl'] + 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'], ) try: res = grafana_api.health.check() if res['database'] != 'ok': print("grafana health_check is not KO.") - sys.exit(2) + sys.exit(1) elif args.verbose: print("grafana health_check is OK.") - except e: - print("error: {} - message: {}".format(status_code, e.message) ) - sys.exit(2) + except Exception as e: + print("ERROR: {} - message: {}".format(res, e.message) ) + sys.exit(1) if args.action == 'import': if args.dashboard_file is None: - print('no file to import provided!') - sys.exit(2) + print('ERROR: no file to import provided!') + sys.exit(1) import_path = '' import_file = args.dashboard_file if not re.search(r'^(?:(?:/)|(?:\.?\./))', import_file): @@ -265,8 +281,8 @@ def main(): try: input = open(import_path, 'r') except OSError as e: - print('File {0} error: {1}.'.format(import_path, e.strerror)) - sys.exit(2) + print('ERROR: File {0} error: {1}.'.format(import_path, e.strerror)) + sys.exit(1) data = input.read() input.close() @@ -274,8 +290,8 @@ def main(): try: dash = json.loads(data) except json.JSONDecodeError as e: - print("error reading '{0}': {1}".format(import_path, e)) - sys.exit(2) + print("ERROR: reading '{0}': {1}".format(import_path, e)) + sys.exit(1) #** check dashboard existence #** dash from file has no meta data (folder infos) @@ -344,10 +360,10 @@ def main(): sys.exit(0) else: print("KO: dashboard {0} not imported into '{1}'.".format(dash['title'], config['general']['grafana_folder'])) - sys.exit(2) + sys.exit(1) else: print("error invalid dashboard file '{0}': can't find dashboard uid".format(import_path)) - sys.exit(2) + sys.exit(1) else: # export dash = get_dashboard_content(config, args, grafana_api, config['general']['dashboard_name']) if dash is not None: diff --git a/grafana_import/conf/grafana-import.json b/grafana_import/conf/grafana-import.json deleted file mode 100644 index e33c981..0000000 --- a/grafana_import/conf/grafana-import.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "general": { - "debug": false, - "import_folder": "test_import", - }, - "grafana": { - "protocol": "http", - "host": "localhost", - "port": "3000", - "token": "____APIKEY____", - "search_api_limit": 5000 - "verify_ssl": true, - } -} - diff --git a/grafana_import/conf/grafana-import.yml b/grafana_import/conf/grafana-import.yml new file mode 100644 index 0000000..4df30a6 --- /dev/null +++ b/grafana_import/conf/grafana-import.yml @@ -0,0 +1,15 @@ +--- + + general: + debug: false + import_folder: test_import + + grafana: + default: + protocol: http + host: localhost + port: 3000 + token: "____APIKEY____" + search_api_limit: 5000 + verify_ssl: true + diff --git a/grafana_import/constants.py b/grafana_import/constants.py index e0e9cc5..3cc1697 100644 --- a/grafana_import/constants.py +++ b/grafana_import/constants.py @@ -1,3 +1,3 @@ PKG_NAME = 'grafana-import' -PKG_VERSION = '0.0.1' -JSON_CONFIG_NAME = 'conf/grafana-import.json' +PKG_VERSION = '0.0.2' +CONFIG_NAME = 'conf/grafana-import.yml' diff --git a/grafana_import/jsonConfig.py b/grafana_import/jsonConfig.py deleted file mode 100644 index 4137a37..0000000 --- a/grafana_import/jsonConfig.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -__version__ = '0.0.1' - -#****************************************************************************************** - -import json, sys, os, re - -#****************************************************************************************** -class jsonConfig(object): - """ - Class for call Centreon Web Rest webservices - """ - - #************************************************* - def __init__(self, filepath): - """ - Constructor with singleton for webservices - """ - self.filepath = filepath - - #************************************************* - def load(self, **kwargs): - - filepath = kwargs.get('filepath', None) - if filepath is not None: - self.filepath = filepath - - try: - fh = open(self.filepath,'r') - except IOError as e: - self.err_msg = 'File {0} not present.'.format(self.filepath) - return None - - buf = '' - for line in fh: - #remove line starting with // : one line comment - if re.search(r'^\s*//', line): - #print('find comment: {}'.format(line)) - continue - buf += line.strip() + '\n' - #print( buf ) - - fh.close() - #** remove multi-line C-style comments /* ... */ sequence -# regex = re.compile(r"/\*.*\*/", re.MULTILINE + re.DOTALL) - regex = re.compile(r"/[*][^*]*[*]+(?:[^/*][^*]*[*]+)*/", re.MULTILINE + re.DOTALL) - buf = regex.sub('', buf) - - try: - self.config = json.loads(buf) - except json.JSONDecodeError as e: - pos = e.pos - start = 0 - if pos > 20: - start = '...' + buf[pos - 20: pos-1] - else: - start = buf[0: pos-1] - - start += ' --> ' - if pos + 20 < len(buf): - start += buf[pos:pos+20] + '...' - else: - start += buf[pos:len(buf)-1] - - self.err_msg = "error reading '{}': {}".format(self.filepath, e) \ - + 'error near -->: ' + start - self.config = None - return self.config -