From 3d01e631a98f227b673852c35118b63d108aa3a3 Mon Sep 17 00:00:00 2001 From: ysde Date: Wed, 6 Sep 2023 13:57:39 +0800 Subject: [PATCH] contact points and notifcation policy backup --- README.md | 3 +- ...mple.json => grafanaSettings.example.json} | 0 grafana_backup/api_checks.py | 6 +- grafana_backup/archive.py | 2 +- grafana_backup/create_contact_point.py | 33 ++++++++++ grafana_backup/dashboardApi.py | 21 ++++++- grafana_backup/delete.py | 1 + grafana_backup/restore.py | 10 +++- grafana_backup/save.py | 26 +++++--- grafana_backup/save_contact_points.py | 60 +++++++++++++++++++ grafana_backup/save_notification_policies.py | 58 ++++++++++++++++++ grafana_backup/update_notification_policy.py | 32 ++++++++++ 12 files changed, 238 insertions(+), 14 deletions(-) rename examples/{grafana-backup.example.json => grafanaSettings.example.json} (100%) create mode 100644 grafana_backup/create_contact_point.py create mode 100644 grafana_backup/save_contact_points.py create mode 100644 grafana_backup/save_notification_policies.py create mode 100644 grafana_backup/update_notification_policy.py diff --git a/README.md b/README.md index 7ddb40e1..a5e5063a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ There are three ways to setup the configuration: 3. Use `~/.grafana-backup.json` to define variables in json format. ### Example Config -* Check out the [examples](examples) folder for more configuration details +* Copy [grafanaSettings.example.json](examples/grafanaSettings.example.json) and modify it for you to use, remove `azure`, `aws`, `gcp`, `influxdb` blocks (but keep the ones you used). +* Check out the [examples](examples) folder for more configuration details. **NOTE** If you use `environment variables`, you need to add the following to your `.bashrc` or execute once before using the tool (please change variables according to your setup): diff --git a/examples/grafana-backup.example.json b/examples/grafanaSettings.example.json similarity index 100% rename from examples/grafana-backup.example.json rename to examples/grafanaSettings.example.json diff --git a/grafana_backup/api_checks.py b/grafana_backup/api_checks.py index 2c1cd966..51591a54 100644 --- a/grafana_backup/api_checks.py +++ b/grafana_backup/api_checks.py @@ -1,5 +1,5 @@ from grafana_backup.commons import print_horizontal_line -from grafana_backup.dashboardApi import health_check, auth_check, uid_feature_check, paging_feature_check +from grafana_backup.dashboardApi import health_check, auth_check, uid_feature_check, paging_feature_check, contact_point_check def main(settings): @@ -30,6 +30,8 @@ def main(settings): paging_support = paging_feature_check(grafana_url, http_get_headers, verify_ssl, client_cert, debug) if isinstance(paging_support, str): raise Exception(paging_support) + + is_contact_point_available = contact_point_check(grafana_url, http_get_headers, verify_ssl, client_cert, debug) print_horizontal_line() if status == 200: @@ -38,4 +40,4 @@ def main(settings): print("[Pre-Check] Server status is NOT OK !!: {0}".format(json_resp)) print_horizontal_line() - return (status, json_resp, dashboard_uid_support, datasource_uid_support, paging_support) + return (status, json_resp, dashboard_uid_support, datasource_uid_support, paging_support, is_contact_point_available) diff --git a/grafana_backup/archive.py b/grafana_backup/archive.py index 1db698ac..2c61e63b 100755 --- a/grafana_backup/archive.py +++ b/grafana_backup/archive.py @@ -10,7 +10,7 @@ def main(args, settings): backup_files = list() for folder_name in ['folders', 'datasources', 'dashboards', 'alert_channels', 'organizations', 'users', 'snapshots', - 'dashboard_versions', 'annotations', 'library-elements', 'teams', 'team_members', 'alert_rules']: + 'dashboard_versions', 'annotations', 'library-elements', 'teams', 'team_members', 'alert_rules', 'contact_points', 'notification_policies']: backup_path = '{0}/{1}/{2}'.format(backup_dir, folder_name, timestamp) for file_path in glob(backup_path): diff --git a/grafana_backup/create_contact_point.py b/grafana_backup/create_contact_point.py new file mode 100644 index 00000000..f6ee81e9 --- /dev/null +++ b/grafana_backup/create_contact_point.py @@ -0,0 +1,33 @@ +import json +from grafana_backup.dashboardApi import create_contact_point, get_grafana_version +from packaging import version + +def main(args, settings, file_path): + grafana_url = settings.get('GRAFANA_URL') + http_post_headers = settings.get('HTTP_POST_HEADERS') + verify_ssl = settings.get('VERIFY_SSL') + client_cert = settings.get('CLIENT_CERT') + debug = settings.get('DEBUG') + + try: + grafana_version = get_grafana_version(grafana_url, verify_ssl) + except KeyError as error: + if not grafana_version: + raise Exception("Grafana version is not set.") from error + + minimum_version = version.parse('9.4.0') + + if minimum_version <= grafana_version: + with open(file_path, 'r') as f: + data = f.read() + + contact_points = json.loads(data) + for cp in contact_points: + result = create_contact_point(json.dumps(cp), grafana_url, http_post_headers, verify_ssl, client_cert, debug) + print("create contact_point: {0}, status: {1}, msg: {2}".format(cp['name'], result[0], result[1])) + else: + print("Unable to create contact points, requires Grafana version {0} or above. Current version is {1}".format(minimum_version, grafana_version)) + + + + diff --git a/grafana_backup/dashboardApi.py b/grafana_backup/dashboardApi.py index d6c31988..7124d54a 100755 --- a/grafana_backup/dashboardApi.py +++ b/grafana_backup/dashboardApi.py @@ -71,6 +71,14 @@ def get_first_dashboard_by_page(page): # No dashboards exist, disable paging feature return False, False +def contact_point_check(grafana_url, http_get_headers, verify_ssl, client_cert, debug): + print("\n[Pre-Check] grafana contact_point api check") + (status, content) = search_contact_points(grafana_url, http_get_headers, verify_ssl, client_cert, debug) + if status == 200: + return True + else: + return False + # Get first dashboard on first page (status, content) = get_first_dashboard_by_page(1) if status is False and content is False: @@ -92,7 +100,6 @@ def get_first_dashboard_by_page(page): # Compare both pages return dashboard_one_values != dashboard_two_values - def search_dashboard(page, limit, grafana_url, http_get_headers, verify_ssl, client_cert, debug): url = '{0}/api/search/?type=dash-db&limit={1}&page={2}'.format(grafana_url, limit, page) print("search dashboard in grafana: {0}".format(url)) @@ -434,6 +441,18 @@ def add_user_to_org(org_id, payload, grafana_url, http_post_headers, verify_ssl, return send_grafana_post('{0}/api/orgs/{1}/users'.format(grafana_url, org_id), payload, http_post_headers, verify_ssl, client_cert, debug) +def search_contact_points(grafana_url, http_get_headers, verify_ssl, client_cert, debug): + return send_grafana_get('{0}/api/v1/provisioning/contact-points'.format(grafana_url), http_get_headers, verify_ssl, client_cert, debug) + +def create_contact_point(json_palyload, grafana_url, http_post_headers, verify_ssl, client_cert, debug): + return send_grafana_post('{0}/api/v1/provisioning/contact-points'.format(grafana_url), json_palyload, http_post_headers, verify_ssl, client_cert, debug) + +def search_notification_policies(grafana_url, http_get_headers, verify_ssl, client_cert, debug): + return send_grafana_get('{0}/api/v1/provisioning/policies'.format(grafana_url), http_get_headers, verify_ssl, client_cert, debug) + +def update_notification_policy(json_palyload, grafana_url, http_post_headers, verify_ssl, client_cert, debug): + return send_grafana_put('{0}/api/v1/provisioning/policies'.format(grafana_url), json_palyload, http_post_headers, verify_ssl, client_cert, debug) + def get_grafana_version(grafana_url, verify_ssl): r = requests.get('{0}/api/health'.format(grafana_url), verify=verify_ssl) if r.status_code == 200: diff --git a/grafana_backup/delete.py b/grafana_backup/delete.py index f6104e3f..866aeccb 100755 --- a/grafana_backup/delete.py +++ b/grafana_backup/delete.py @@ -34,6 +34,7 @@ def main(args, settings): settings.update({'DASHBOARD_UID_SUPPORT': dashboard_uid_support}) settings.update({'DATASOURCE_UID_SUPPORT': datasource_uid_support}) settings.update({'PAGING_SUPPORT': paging_support}) + if arg_components: arg_components_list = arg_components.split(',') diff --git a/grafana_backup/restore.py b/grafana_backup/restore.py index 01c7c07c..c148c469 100755 --- a/grafana_backup/restore.py +++ b/grafana_backup/restore.py @@ -12,6 +12,8 @@ from grafana_backup.create_team import main as create_team from grafana_backup.create_team_member import main as create_team_member from grafana_backup.create_library_element import main as create_library_element +from grafana_backup.create_contact_point import main as create_contact_point +from grafana_backup.update_notification_policy import main as update_notification_policy from grafana_backup.s3_download import main as s3_download from grafana_backup.azure_storage_download import main as azure_storage_download from grafana_backup.gcs_download import main as gcs_download @@ -39,7 +41,8 @@ def open_compressed_backup(compressed_backup): azure_storage_container_name = settings.get('AZURE_STORAGE_CONTAINER_NAME') gcs_bucket_name = settings.get('GCS_BUCKET_NAME') - (status, json_resp, dashboard_uid_support, datasource_uid_support, paging_support) = api_checks(settings) + (status, json_resp, dashboard_uid_support, datasource_uid_support, paging_support, contact_point_support) = api_checks(settings) + settings.update({'CONTACT_POINT_SUPPORT': contact_point_support}) # Do not continue if API is unavailable or token is not valid if not status == 200: @@ -90,6 +93,9 @@ def open_compressed_backup(compressed_backup): restore_functions['team_member'] = create_team_member restore_functions['folder_permission'] = update_folder_permissions restore_functions['alert_rule'] = create_alert_rule + restore_functions['contact_point'] = create_contact_point + # There are some issues of notification policy restore api, it will lock the notification policy page and cannot be edited. + # restore_functions['notification_policys'] = update_notification_policy if sys.version_info >= (3,): with tempfile.TemporaryDirectory() as tmpdir: @@ -108,7 +114,7 @@ def open_compressed_backup(compressed_backup): def restore_components(args, settings, restore_functions, tmpdir): - arg_components = args.get('--components', False) + arg_components = args.get('--components', []) if arg_components: arg_components_list = arg_components.replace("-", "_").split(',') diff --git a/grafana_backup/save.py b/grafana_backup/save.py index f7e5b2a5..d95c7855 100755 --- a/grafana_backup/save.py +++ b/grafana_backup/save.py @@ -7,6 +7,8 @@ from grafana_backup.save_snapshots import main as save_snapshots from grafana_backup.save_dashboard_versions import main as save_dashboard_versions from grafana_backup.save_annotations import main as save_annotations +from grafana_backup.save_contact_points import main as save_contact_points +from grafana_backup.save_notification_policies import main as save_notification_policies from grafana_backup.archive import main as archive from grafana_backup.s3_upload import main as s3_upload from grafana_backup.influx import main as influx @@ -17,6 +19,7 @@ from grafana_backup.save_team_members import main as save_team_members from grafana_backup.azure_storage_upload import main as azure_storage_upload from grafana_backup.gcs_upload import main as gcs_upload +from grafana_backup.commons import print_horizontal_line import sys @@ -37,19 +40,28 @@ def main(args, settings): 'library-elements': save_library_elements, 'teams': save_teams, 'team-members': save_team_members, - 'alert-rules': save_alert_rules} + 'alert-rules': save_alert_rules, + 'contact-points': save_contact_points, + 'notification-policy': save_notification_policies, + } - (status, json_resp, dashboard_uid_support, datasource_uid_support, paging_support) = api_checks(settings) + (status, + json_resp, + dashboard_uid_support, + datasource_uid_support, + paging_support, + contact_point_support) = api_checks(settings) + + settings.update({'DASHBOARD_UID_SUPPORT': dashboard_uid_support}) + settings.update({'DATASOURCE_UID_SUPPORT': datasource_uid_support}) + settings.update({'PAGING_SUPPORT': paging_support}) + settings.update({'CONTACT_POINT_SUPPORT': contact_point_support}) # Do not continue if API is unavailable or token is not valid if not status == 200: - print("server status is not ok: {0}".format(json_resp)) + print("grafana server status is not ok: {0}".format(json_resp)) sys.exit(1) - settings.update({'DASHBOARD_UID_SUPPORT': dashboard_uid_support}) - settings.update({'DATASOURCE_UID_SUPPORT': datasource_uid_support}) - settings.update({'PAGING_SUPPORT': paging_support}) - if arg_components: arg_components_list = arg_components.replace("_", "-").split(',') diff --git a/grafana_backup/save_contact_points.py b/grafana_backup/save_contact_points.py new file mode 100644 index 00000000..53f70171 --- /dev/null +++ b/grafana_backup/save_contact_points.py @@ -0,0 +1,60 @@ +import os +from grafana_backup.dashboardApi import search_contact_points, get_grafana_version +from grafana_backup.commons import to_python2_and_3_compatible_string, print_horizontal_line, save_json +from packaging import version + +def main(args, settings): + backup_dir = settings.get('BACKUP_DIR') + timestamp = settings.get('TIMESTAMP') + grafana_url = settings.get('GRAFANA_URL') + http_get_headers = settings.get('HTTP_GET_HEADERS') + verify_ssl = settings.get('VERIFY_SSL') + client_cert = settings.get('CLIENT_CERT') + debug = settings.get('DEBUG') + pretty_print = settings.get('PRETTY_PRINT') + folder_path = '{0}/contact_points/{1}'.format(backup_dir, timestamp) + log_file = 'contact_points_{0}.txt'.format(timestamp) + grafana_version_string = settings.get('GRAFANA_VERSION') + + if grafana_version_string: + grafana_version = version.parse(grafana_version_string) + + try: + grafana_version = get_grafana_version(grafana_url, verify_ssl) + except KeyError as error: + if not grafana_version: + raise Exception("Grafana version is not set.") from error + + minimum_version = version.parse('9.0.0') + if minimum_version <= grafana_version: + if not os.path.exists(folder_path): + os.makedirs(folder_path) + + contact_points = get_all_contact_points_in_grafana(grafana_url, http_get_headers, verify_ssl, client_cert, debug) + save_contact_points('contact_points', contact_points, folder_path, pretty_print) + else: + print("Unable to save contact points, requires Grafana version {0} or above. Current version is {1}".format(minimum_version, grafana_version)) + + + if not os.path.exists(folder_path): + os.makedirs(folder_path) + + +def get_all_contact_points_in_grafana(grafana_url, http_get_headers, verify_ssl, client_cert, debug): + (status, content) = search_contact_points(grafana_url, http_get_headers, verify_ssl, client_cert, debug) + if status == 200: + contact_points = content + print("There are {0} contact points: ".format(len(contact_points))) + for contact_point in contact_points: + print("name: {0}, type: {1}".format(to_python2_and_3_compatible_string(contact_point['name']), to_python2_and_3_compatible_string(contact_point['type']))) + return contact_points + else: + print("query contact points failed, status: {0}, msg: {1}".format(status, content)) + return [] + + +def save_contact_points(file_name, contact_points, folder_path, pretty_print): + file_path = save_json(file_name, contact_points, folder_path, 'contact_point', pretty_print) + print_horizontal_line() + print("contact points are saved to {0}".format(file_path)) + print_horizontal_line() diff --git a/grafana_backup/save_notification_policies.py b/grafana_backup/save_notification_policies.py new file mode 100644 index 00000000..7655878c --- /dev/null +++ b/grafana_backup/save_notification_policies.py @@ -0,0 +1,58 @@ +import os +from grafana_backup.dashboardApi import search_notification_policies, get_grafana_version +from grafana_backup.commons import to_python2_and_3_compatible_string, print_horizontal_line, save_json +from packaging import version + +def main(args, settings): + backup_dir = settings.get('BACKUP_DIR') + timestamp = settings.get('TIMESTAMP') + grafana_url = settings.get('GRAFANA_URL') + http_get_headers = settings.get('HTTP_GET_HEADERS') + verify_ssl = settings.get('VERIFY_SSL') + client_cert = settings.get('CLIENT_CERT') + debug = settings.get('DEBUG') + pretty_print = settings.get('PRETTY_PRINT') + folder_path = '{0}/notification_policies/{1}'.format(backup_dir, timestamp) + log_file = 'notification_policies_{0}.txt'.format(timestamp) + grafana_version_string = settings.get('GRAFANA_VERSION') + + if grafana_version_string: + grafana_version = version.parse(grafana_version_string) + + try: + grafana_version = get_grafana_version(grafana_url, verify_ssl) + except KeyError as error: + if not grafana_version: + raise Exception("Grafana version is not set.") from error + + minimum_version = version.parse('9.0.0') + if minimum_version <= grafana_version: + if not os.path.exists(folder_path): + os.makedirs(folder_path) + + notification_policies = get_all_notification_policies_in_grafana(grafana_url, http_get_headers, verify_ssl, client_cert, debug) + save_notification_policies('notificatioin_policies', notification_policies, folder_path, pretty_print) + else: + print("Unable to save notification policies, requires Grafana version {0} or above. Current version is {1}".format(minimum_version, grafana_version)) + + + if not os.path.exists(folder_path): + os.makedirs(folder_path) + + +def get_all_notification_policies_in_grafana(grafana_url, http_get_headers, verify_ssl, client_cert, debug): + (status, content) = search_notification_policies(grafana_url, http_get_headers, verify_ssl, client_cert, debug) + if status == 200: + notification_policies = content + print("Notification policies found") + return notification_policies + else: + print("query notification policies failed, status: {0}, msg: {1}".format(status, content)) + return [] + + +def save_notification_policies(file_name, notification_policies, folder_path, pretty_print): + file_path = save_json(file_name, notification_policies, folder_path, 'notification_policys', pretty_print) + print_horizontal_line() + print("notification policies are saved to {0}".format(file_path)) + print_horizontal_line() diff --git a/grafana_backup/update_notification_policy.py b/grafana_backup/update_notification_policy.py new file mode 100644 index 00000000..4072f26a --- /dev/null +++ b/grafana_backup/update_notification_policy.py @@ -0,0 +1,32 @@ +import json +from grafana_backup.dashboardApi import update_notification_policy, get_grafana_version +from packaging import version + +def main(args, settings, file_path): + grafana_url = settings.get('GRAFANA_URL') + http_post_headers = settings.get('HTTP_POST_HEADERS') + verify_ssl = settings.get('VERIFY_SSL') + client_cert = settings.get('CLIENT_CERT') + debug = settings.get('DEBUG') + + try: + grafana_version = get_grafana_version(grafana_url, verify_ssl) + except KeyError as error: + if not grafana_version: + raise Exception("Grafana version is not set.") from error + + minimum_version = version.parse('9.4.0') + + if minimum_version <= grafana_version: + with open(file_path, 'r') as f: + data = f.read() + + notification_policies = json.loads(data) + result = update_notification_policy(json.dumps(notification_policies), grafana_url, http_post_headers, verify_ssl, client_cert, debug) + print("update notification_policy, status: {0}, msg: {1}".format(result[0], result[1])) + else: + print("Unable to update notification policy, requires Grafana version {0} or above. Current version is {1}".format(minimum_version, grafana_version)) + + + +