From 1466c212b3917cfa7c0af719edfcdc1b8099ffbc Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke.madsen@geant.org>
Date: Wed, 27 Jan 2021 17:02:05 +0100
Subject: [PATCH] Only be dependent on grafana hostname Use datasource-specific
 config instead of environment config Add influxdb datasource config Remove
 unused config keys

---
 brian_dashboard_manager/config.py             | 46 +++++++++++++++----
 brian_dashboard_manager/grafana/dashboard.py  | 14 +++++-
 brian_dashboard_manager/grafana/datasource.py | 14 ++++--
 brian_dashboard_manager/grafana/provision.py  | 12 +++--
 .../grafana/utils/request.py                  |  8 ++--
 test/conftest.py                              | 21 ++++++---
 test/test_grafana_datasource.py               | 13 +++---
 test/test_grafana_request.py                  |  4 +-
 8 files changed, 95 insertions(+), 37 deletions(-)

diff --git a/brian_dashboard_manager/config.py b/brian_dashboard_manager/config.py
index c9c3abd..ef05a1a 100644
--- a/brian_dashboard_manager/config.py
+++ b/brian_dashboard_manager/config.py
@@ -4,24 +4,55 @@ import jsonschema
 CONFIG_SCHEMA = {
     "$schema": "http://json-schema.org/draft-07/schema#",
 
+    "definitions": {
+        "influx-datasource": {
+            "type": "object",
+            "properties": {
+                    "name": {"type": "string"},
+                    "username": {"type": "string"},
+                    "password": {"type": "string"},
+                    "type": {"type": "string"},
+                    "url": {"type": "string"},
+                    "database": {"type": "string"},
+                    "basicAuth": {"type": "boolean"},
+                    "access": {"type": "string"},
+                    "isDefault": {"type": "boolean"},
+                    "readOnly": {"type": "boolean"}
+            },
+            "required": [
+                "name",
+                "type",
+                "url",
+                "database",
+                "basicAuth",
+                "access",
+                "isDefault",
+                "readOnly"
+            ]
+        }
+    },
+
     "type": "object",
     "properties": {
         "admin_username": {"type": "string"},
         "admin_password": {"type": "string"},
         "hostname": {"type": "string"},
         "listen_port": {"type": "integer"},
-        "grafana_port": {"type": "integer"},
         "organizations": {"type": "array", "items": {"type": "string"}},
-        "BRIAN_ENVIRONMENT": {"type": "string"}
+        "datasources": {
+            "type": "object",
+            "properties": {
+                "influxdb": {"$ref": "#definitions/influx-datasource"}
+            },
+            "additionalProperties": False
+        },
     },
     "required": [
         "admin_username",
         "admin_password",
         "hostname",
-        "listen_port",
-        "grafana_port",
         "organizations",
-        "BRIAN_ENVIRONMENT"
+        "datasources"
     ],
     "additionalProperties": False
 }
@@ -31,11 +62,10 @@ def defaults():
     return {
         "admin_username": "admin",
         "admin_password": "admin",
-        "hostname": "localhost",
+        "hostname": "localhost:3000",
         "listen_port": 3001,
-        "grafana_port": 3000,
         "organizations": ["Main Org."],
-        "BRIAN_ENVIRONMENT": "test"
+        "datasources": {}
     }
 
 
diff --git a/brian_dashboard_manager/grafana/dashboard.py b/brian_dashboard_manager/grafana/dashboard.py
index ba107d5..e44502c 100644
--- a/brian_dashboard_manager/grafana/dashboard.py
+++ b/brian_dashboard_manager/grafana/dashboard.py
@@ -74,10 +74,21 @@ def _get_dashboard(request: TokenRequest, uid: int):
 # supplied dashboards are JSON blobs exported from GUI with a UID.
 def create_dashboard(request: TokenRequest, dashboard: Dict):
 
+    title = dashboard['title']
     existing_dashboard = None
-    if dashboard.get('uid') is not None:
+    has_uid = dashboard.get('uid') is not None
+    if has_uid:
         existing_dashboard = _get_dashboard(request, uid=dashboard['uid'])
+
+    # The title might not match the one that's provisioned with that UID.
+    # Try to find it by searching for the title instead.
+    if existing_dashboard is not None:
+        grafana_title = existing_dashboard['dashboard']['title']
+        different = grafana_title != title
     else:
+        different = False
+
+    if existing_dashboard is None or different:
         existing_dashboard = _search_dashboard(request, dashboard)
 
     if existing_dashboard:
@@ -92,7 +103,6 @@ def create_dashboard(request: TokenRequest, dashboard: Dict):
         'dashboard': dashboard,
         'overwrite': False
     }
-    title = dashboard["title"]
     try:
         action = "Updating" if existing_dashboard else "Provisioning"
         logger.info(f'{action} dashboard: {title}')
diff --git a/brian_dashboard_manager/grafana/datasource.py b/brian_dashboard_manager/grafana/datasource.py
index 4313fbd..50ab365 100644
--- a/brian_dashboard_manager/grafana/datasource.py
+++ b/brian_dashboard_manager/grafana/datasource.py
@@ -1,5 +1,4 @@
 import logging
-import re
 import os
 import json
 from typing import Dict
@@ -44,10 +43,17 @@ def get_datasources(request: Request):
     return request.get('api/datasources')
 
 
-def create_datasource(request: TokenRequest, datasource: Dict, environment):
+def create_datasource(request: TokenRequest, datasource: Dict, datasources):
     try:
-        datasource["url"] = re.sub(
-            'test|uat|prod', environment, datasource["url"])
+        ds_type = datasource["type"]
+        # find out which params
+        # we need to configure for this datasource type
+        config = datasources.get(ds_type, None)
+        if config is None:
+            logger.exception(
+                f'No datasource config could be found for {ds_type}')
+            return None
+        datasource.update(config)
         r = request.post('api/datasources', json=datasource)
     except HTTPError:
         logger.exception('Error when provisioning datasource')
diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py
index f9aea13..b258101 100644
--- a/brian_dashboard_manager/grafana/provision.py
+++ b/brian_dashboard_manager/grafana/provision.py
@@ -43,11 +43,13 @@ def provision(config):
 
         # Provision missing data sources
         for datasource in get_missing_datasource_definitions(token_request):
-            ds = create_datasource(token_request,
-                                   datasource,
-                                   config.get('BRIAN_ENVIRONMENT', 'test'))
-            if ds:
-                logger.info(f'Provisioned datasource: {datasource["name"]}')
+            if datasource:
+                ds = create_datasource(token_request,
+                                       datasource,
+                                       config.get('datasources'))
+                if ds:
+                    logger.info(
+                        f'Provisioned datasource: {datasource["name"]}')
 
         # Provision dashboards, overwriting existing ones.
         for dashboard in get_dashboard_definitions():
diff --git a/brian_dashboard_manager/grafana/utils/request.py b/brian_dashboard_manager/grafana/utils/request.py
index cbb8556..c5f6987 100644
--- a/brian_dashboard_manager/grafana/utils/request.py
+++ b/brian_dashboard_manager/grafana/utils/request.py
@@ -53,10 +53,10 @@ class Request(object):
 
 
 class AdminRequest(Request):
-    def __init__(self, hostname, grafana_port, admin_username, admin_password,
+    def __init__(self, hostname, admin_username, admin_password,
                  **kwargs):
         self.username = admin_username
-        url = f'{admin_username}:{admin_password}@{hostname}:{grafana_port}/'
+        url = f'{admin_username}:{admin_password}@{hostname}/'
         super().__init__('http://' + url)
 
     def __str__(self):
@@ -64,10 +64,10 @@ class AdminRequest(Request):
 
 
 class TokenRequest(Request):
-    def __init__(self, hostname, grafana_port, token: str, **kwargs):
+    def __init__(self, hostname, token: str, **kwargs):
         self.token = token
 
-        super().__init__(f'http://{hostname}:{grafana_port}/', {
+        super().__init__(f'http://{hostname}/', {
             'Authorization': 'Bearer ' + token
         })
 
diff --git a/test/conftest.py b/test/conftest.py
index d1cf7ae..39e0477 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -8,18 +8,27 @@ import brian_dashboard_manager
 @pytest.fixture
 def data_config():
     return {
-        "admin_username": "admin",
-        "admin_password": "admin",
-        "hostname": "localhost",
-        "listen_port": 65001,
-        "grafana_port": 65005,
+        "admin_username": "fakeadmin",
+        "admin_password": "fakeadmin",
+        "hostname": "myfakehostname.org",
         "organizations": [
             'Testorg1',
             'GÉANT Testorg2',
             'NRENsTestorg3',
             'General Public'
         ],
-        "BRIAN_ENVIRONMENT": "test"
+        "datasources": {
+            "influxdb": {
+                "name": "PollerInfluxDB",
+                "type": "influxdb",
+                "access": "proxy",
+                "url": "http://prod-poller-ui01.geant.org:8086",
+                "database": "poller",
+                "basicAuth": False,
+                "isDefault": True,
+                "readOnly": False
+            }
+        }
     }
 
 
diff --git a/test/test_grafana_datasource.py b/test/test_grafana_datasource.py
index 59c2606..af30deb 100644
--- a/test/test_grafana_datasource.py
+++ b/test/test_grafana_datasource.py
@@ -79,9 +79,6 @@ def test_create_prod_datasource(data_config):
 
     def post_callback(request):
         body = json.loads(request.body)
-        # we are testing provisioning logic to prod, so the URL needs test ->
-        # prod translation.
-        assert body['url'] == 'http://prod-brian-datasource.geant.org:8086'
         result = {
             'datasource': {
                 'id': 1,
@@ -109,9 +106,12 @@ def test_create_prod_datasource(data_config):
         url=request.BASE_URL + 'api/datasources',
         callback=post_callback)
 
-    data = provision.create_datasource(request, BODY, environment='prod')
+    data = provision.create_datasource(
+        request, BODY, datasources=data_config['datasources'])
 
-    assert data is not None
+    datasource_type = data['datasource']['type']
+    datasource_config_url = data_config['datasources'][datasource_type]['url']
+    assert data['datasource']['url'] == datasource_config_url
 
 
 @responses.activate
@@ -134,7 +134,8 @@ def test_create_prod_datasource_fails(data_config):
         url=request.BASE_URL + 'api/datasources',
         callback=lambda f: (400, {}, ''))
 
-    data = provision.create_datasource(request, BODY, environment='prod')
+    data = provision.create_datasource(
+        request, BODY, datasources=data_config['datasources'])
 
     # if an error occured when provisioning a datasource, we log the response
     # but return None
diff --git a/test/test_grafana_request.py b/test/test_grafana_request.py
index c4f1139..d90b191 100644
--- a/test/test_grafana_request.py
+++ b/test/test_grafana_request.py
@@ -11,7 +11,7 @@ from brian_dashboard_manager.grafana.utils.request import \
 def test_admin_request(data_config):
     ENDPOINT = 'test/url/endpoint'
     request = AdminRequest(**data_config)
-    url = '{admin_username}:{admin_password}@{hostname}:{grafana_port}/'. \
+    url = '{admin_username}:{admin_password}@{hostname}/'. \
         format(**data_config)
     assert request.BASE_URL == 'http://' + url
     assert request.username == data_config['admin_username']
@@ -33,7 +33,7 @@ def test_token_request(data_config):
     TOKEN = '123'
     ENDPOINT = 'test/url/endpoint'
     request = TokenRequest(**data_config, token=TOKEN)
-    assert request.BASE_URL == 'http://{hostname}:{grafana_port}/'.format(
+    assert request.BASE_URL == 'http://{hostname}/'.format(
         **data_config)
     assert request.token == TOKEN
 
-- 
GitLab