Skip to content
Snippets Groups Projects
Commit 1a1849a5 authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

flake8 fixup

parent 2f114fd8
No related branches found
No related tags found
No related merge requests found
...@@ -14,7 +14,15 @@ CONFIG_SCHEMA = { ...@@ -14,7 +14,15 @@ CONFIG_SCHEMA = {
"organizations": {"type": "array", "items": {"type": "string"}}, "organizations": {"type": "array", "items": {"type": "string"}},
"BRIAN_ENVIRONMENT": {"type": "string"} "BRIAN_ENVIRONMENT": {"type": "string"}
}, },
"required": ["admin_username", "admin_password", "hostname", "listen_port", "grafana_port", "organizations", "BRIAN_ENVIRONMENT"], "required": [
"admin_username",
"admin_password",
"hostname",
"listen_port",
"grafana_port",
"organizations",
"BRIAN_ENVIRONMENT"
],
"additionalProperties": False "additionalProperties": False
} }
......
...@@ -21,19 +21,20 @@ def get_dashboard_definitions(dir=None): # pragma: no cover ...@@ -21,19 +21,20 @@ def get_dashboard_definitions(dir=None): # pragma: no cover
yield dashboard yield dashboard
# Deletes a single dashboard for the organization the API token is registered to. # Deletes a single dashboard for the organization
# the API token is registered to.
def _delete_dashboard(request: TokenRequest, uid: int): def _delete_dashboard(request: TokenRequest, uid: int):
try: try:
r = request.delete(f'api/dashboards/uid/{uid}') r = request.delete(f'api/dashboards/uid/{uid}')
if r and 'deleted' in r.get('message', ''): if r and 'deleted' in r.get('message', ''):
return r return r
except HTTPError as e: except HTTPError:
logger.error( logger.exception(f'Error when deleting dashboard with UID #{uid}')
f'Error when deleting dashboard with UID #{uid}: {e.response.text}')
return None return None
# Deletes all dashboards for the organization the API token is registered to. # Deletes all dashboards for the organization
# the API token is registered to.
def delete_dashboards(request: TokenRequest): def delete_dashboards(request: TokenRequest):
r = request.get('api/search') r = request.get('api/search')
if r and len(r) > 0: if r and len(r) > 0:
...@@ -42,7 +43,8 @@ def delete_dashboards(request: TokenRequest): ...@@ -42,7 +43,8 @@ def delete_dashboards(request: TokenRequest):
return True return True
# Searches Grafana for a dashboard matching the title of the provided dashboard. # Searches Grafana for a dashboard
# matching the title of the provided dashboard.
def _search_dashboard(request: TokenRequest, dashboard: Dict): def _search_dashboard(request: TokenRequest, dashboard: Dict):
try: try:
r = request.get('api/search', params={ r = request.get('api/search', params={
...@@ -90,12 +92,12 @@ def create_dashboard(request: TokenRequest, dashboard: Dict): ...@@ -90,12 +92,12 @@ def create_dashboard(request: TokenRequest, dashboard: Dict):
'dashboard': dashboard, 'dashboard': dashboard,
'overwrite': False 'overwrite': False
} }
title = dashboard["title"]
try: try:
logger.info( action = "Updating" if existing_dashboard else "Provisioning"
f'{"Updating" if existing_dashboard else "Provisioning"} dashboard: {dashboard["title"]}') logger.info(f'{action} dashboard: {title}')
r = request.post('api/dashboards/db', json=payload) r = request.post('api/dashboards/db', json=payload)
return r return r
except HTTPError as e: except HTTPError:
logger.error( logger.exception(f'Error when provisioning dashboard {title}')
f'Error when provisioning dashboard {dashboard["title"]}: {e.response.text}')
return None return None
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
import re import re
import os import os
import json import json
from typing import Dict, List from typing import Dict
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from brian_dashboard_manager.grafana.utils.request import Request, TokenRequest from brian_dashboard_manager.grafana.utils.request import Request, TokenRequest
...@@ -11,7 +11,7 @@ from brian_dashboard_manager.grafana.utils.request import Request, TokenRequest ...@@ -11,7 +11,7 @@ from brian_dashboard_manager.grafana.utils.request import Request, TokenRequest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _datasource_provisioned(datasource_to_check: Dict, provisioned_datasources: List[Dict]): def _datasource_provisioned(datasource_to_check, provisioned_datasources):
if len(datasource_to_check.keys()) == 0: if len(datasource_to_check.keys()) == 0:
return True return True
for datasource in provisioned_datasources: for datasource in provisioned_datasources:
...@@ -25,15 +25,19 @@ def _datasource_provisioned(datasource_to_check: Dict, provisioned_datasources: ...@@ -25,15 +25,19 @@ def _datasource_provisioned(datasource_to_check: Dict, provisioned_datasources:
def get_missing_datasource_definitions(request: Request, dir=None): def get_missing_datasource_definitions(request: Request, dir=None):
datasource_dir = dir or os.path.join( datasource_dir = dir or os.path.join(
os.path.dirname(__file__), '../../datasources/') os.path.dirname(__file__), '../../datasources/')
existing_datasources = get_datasources(request)
def check_ds_not_provisioned(filename):
datasource = json.load(open(filename, 'r'))
if not _datasource_provisioned(datasource, existing_datasources):
return datasource
provisioned_datasources = get_datasources(request)
for (dirpath, _, filenames) in os.walk(datasource_dir): # pragma: no cover for (dirpath, _, filenames) in os.walk(datasource_dir): # pragma: no cover
for file in filenames: for file in filenames:
if file.endswith('.json'): if not file.endswith('.json'):
filename = os.path.join(dirpath, file) continue
datasource = json.load(open(filename, 'r')) filename = os.path.join(dirpath, file)
if not _datasource_provisioned(datasource, provisioned_datasources): yield check_ds_not_provisioned(filename)
yield datasource
def get_datasources(request: Request): def get_datasources(request: Request):
...@@ -45,8 +49,8 @@ def create_datasource(request: TokenRequest, datasource: Dict, environment): ...@@ -45,8 +49,8 @@ def create_datasource(request: TokenRequest, datasource: Dict, environment):
datasource["url"] = re.sub( datasource["url"] = re.sub(
'test|uat|prod', environment, datasource["url"]) 'test|uat|prod', environment, datasource["url"])
r = request.post('api/datasources', json=datasource) r = request.post('api/datasources', json=datasource)
except HTTPError as e: except HTTPError:
logger.error('Error when provisioning datasource: ' + e.response.text) logger.exception('Error when provisioning datasource')
return None return None
return r return r
......
...@@ -43,8 +43,10 @@ def delete_organization(request: AdminRequest, id: int) -> bool: ...@@ -43,8 +43,10 @@ def delete_organization(request: AdminRequest, id: int) -> bool:
def create_api_token(request: AdminRequest, org_id: int, key_data=None): def create_api_token(request: AdminRequest, org_id: int, key_data=None):
characters = string.ascii_uppercase + string.digits
name = ''.join(random.choices(characters, k=16))
data = { data = {
'name': ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)), 'name': name,
'role': 'Admin', 'role': 'Admin',
'secondsToLive': 3600 # 60 minutes 'secondsToLive': 3600 # 60 minutes
} }
...@@ -75,8 +77,12 @@ def delete_expired_api_tokens(request: AdminRequest, org_id: int) -> bool: ...@@ -75,8 +77,12 @@ def delete_expired_api_tokens(request: AdminRequest, org_id: int) -> bool:
tokens = request.get('api/auth/keys', params={'includeExpired': True}) tokens = request.get('api/auth/keys', params={'includeExpired': True})
now = datetime.now() now = datetime.now()
expired_tokens = [t for t in tokens if 'expiration' in t
and datetime.strptime(t['expiration'], '%Y-%m-%dT%H:%M:%SZ') < now] def is_expired(token):
date = datetime.strptime(token['expiration'], '%Y-%m-%dT%H:%M:%SZ')
return date < now
expired_tokens = [t for t in tokens if 'expiration' in t and is_expired(t)]
for token in expired_tokens: for token in expired_tokens:
delete_api_token(request, org_id, token['id']) delete_api_token(request, org_id, token['id'])
......
import logging import logging
from brian_dashboard_manager.grafana.utils.request import AdminRequest, TokenRequest from brian_dashboard_manager.grafana.utils.request import \
from brian_dashboard_manager.grafana.organization import get_organizations, create_organization, create_api_token, delete_api_token, delete_expired_api_tokens AdminRequest, \
from brian_dashboard_manager.grafana.dashboard import get_dashboard_definitions, create_dashboard TokenRequest
from brian_dashboard_manager.grafana.datasource import get_missing_datasource_definitions, create_datasource from brian_dashboard_manager.grafana.organization import \
get_organizations, \
create_organization, \
create_api_token, \
delete_api_token, \
delete_expired_api_tokens
from brian_dashboard_manager.grafana.dashboard import \
get_dashboard_definitions, \
create_dashboard
from brian_dashboard_manager.grafana.datasource import \
get_missing_datasource_definitions,\
create_datasource
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -31,8 +43,9 @@ def provision(config): ...@@ -31,8 +43,9 @@ def provision(config):
# Provision missing data sources # Provision missing data sources
for datasource in get_missing_datasource_definitions(token_request): for datasource in get_missing_datasource_definitions(token_request):
ds = create_datasource( ds = create_datasource(token_request,
token_request, datasource, config.get('BRIAN_ENVIRONMENT', 'test')) datasource,
config.get('BRIAN_ENVIRONMENT', 'test'))
if ds: if ds:
logger.info(f'Provisioned datasource: {datasource["name"]}') logger.info(f'Provisioned datasource: {datasource["name"]}')
......
...@@ -53,10 +53,11 @@ class Request(object): ...@@ -53,10 +53,11 @@ class Request(object):
class AdminRequest(Request): class AdminRequest(Request):
def __init__(self, hostname, grafana_port, admin_username, admin_password, **kwargs): def __init__(self, hostname, grafana_port, admin_username, admin_password,
**kwargs):
self.username = admin_username self.username = admin_username
super().__init__( url = f'{admin_username}:{admin_password}@{hostname}:{grafana_port}/'
f'http://{admin_username}:{admin_password}@{hostname}:{grafana_port}/') super().__init__('http://' + url)
def __str__(self): def __str__(self):
return f'admin user: {self.username}' return f'admin user: {self.username}'
......
...@@ -13,7 +13,12 @@ def data_config(): ...@@ -13,7 +13,12 @@ def data_config():
"hostname": "localhost", "hostname": "localhost",
"listen_port": 65001, "listen_port": 65001,
"grafana_port": 65005, "grafana_port": 65005,
"organizations": ['Testorg1', 'GÉANT Testorg2', 'NRENsTestorg3', 'General Public'], "organizations": [
'Testorg1',
'GÉANT Testorg2',
'NRENsTestorg3',
'General Public'
],
"BRIAN_ENVIRONMENT": "test" "BRIAN_ENVIRONMENT": "test"
} }
......
...@@ -144,11 +144,12 @@ def test_create_dashboard(data_config): ...@@ -144,11 +144,12 @@ def test_create_dashboard(data_config):
dashboard = {'id': ID, 'uid': UID, 'title': TITLE, 'version': VERSION} dashboard = {'id': ID, 'uid': UID, 'title': TITLE, 'version': VERSION}
request = TokenRequest(**data_config, token='test') request = TokenRequest(**data_config, token='test')
def get_callback(request):
return 200, {}, json.dumps({'dashboard': dashboard})
responses.add_callback(method=responses.GET, responses.add_callback(method=responses.GET,
url=request.BASE_URL + f'api/dashboards/uid/{UID}', url=request.BASE_URL + f'api/dashboards/uid/{UID}',
callback=lambda f: (200, callback=get_callback)
{},
json.dumps({'dashboard': dashboard})))
responses.add_callback( responses.add_callback(
method=responses.GET, method=responses.GET,
......
...@@ -22,41 +22,42 @@ def test_get_datasources(data_config): ...@@ -22,41 +22,42 @@ def test_get_datasources(data_config):
@responses.activate @responses.activate
def test_get_missing_datasource_definitions(data_config): def test_get_missing_datasource_definitions(data_config):
# this only retrieves data from the filesystem and checks against # this only retrieves data from the filesystem and checks against
# what's configured in grafana.. just make sure it fetches datasources # what's configured in grafana.. just make sure
# we cover the part for fetching datasources
request = AdminRequest(**data_config) request = AdminRequest(**data_config)
responses.add(method=responses.GET, url=request.BASE_URL + responses.add(method=responses.GET, url=request.BASE_URL +
'api/datasources', json=[]) 'api/datasources')
dir = '/tmp/dirthatreallyshouldnotexistsousealonganduniquestring'
# it returns a generator, so iterate :) # it returns a generator, so iterate :)
for data in provision.get_missing_datasource_definitions( for data in provision.get_missing_datasource_definitions(request, dir):
request, '/tmp/dirthatreallyshouldnotexistsousealonganduniquestring'):
pass pass
def test_datasource_provisioned(): def test_datasource_provisioned():
provisioned = datasource._datasource_provisioned({}, []) val = datasource._datasource_provisioned({}, [])
assert provisioned assert val
provisioned = datasource._datasource_provisioned({'id': 1}, []) val = datasource._datasource_provisioned({'id': 1}, [])
assert provisioned is False assert val is False
provisioned = datasource._datasource_provisioned({'id': 1, "name": 'testcasetwo'}, val = datasource._datasource_provisioned({'id': 1, "name": 'testcase2'},
[{'id': -1, 'name': 'testcaseone'}, [{'id': -1, 'name': 'testcase1'},
{'id': 1, 'name': 'testcaseone'}]) {'id': 1, 'name': 'testcase1'}])
assert provisioned is False assert val is False
provisioned = datasource._datasource_provisioned({'id': 1}, val = datasource._datasource_provisioned({'id': 1},
[{'id': -1, 'name': 'testcaseone'}, [{'id': -1, 'name': 'testcase1'},
{'id': 1, 'name': 'testcasetwo'}]) {'id': 1, 'name': 'testcase2'}])
assert provisioned assert val
provisioned = datasource._datasource_provisioned({'id': 2, "name": 'testcasetwo'}, val = datasource._datasource_provisioned({'id': 2, "name": 'testcase2'},
[{'id': -1, 'name': 'testcaseone'}, [{'id': -1, 'name': 'testcase1'},
{'id': 1, 'name': 'testcaseone'}, {'id': 1, 'name': 'testcase1'},
{'id': 2, 'name': 'testcasetwo'}]) {'id': 2, 'name': 'testcase2'}])
assert provisioned assert val
@responses.activate @responses.activate
......
...@@ -2,15 +2,18 @@ import pytest ...@@ -2,15 +2,18 @@ import pytest
import responses import responses
import requests import requests
import json import json
from brian_dashboard_manager.grafana.utils.request import AdminRequest, TokenRequest from brian_dashboard_manager.grafana.utils.request import \
AdminRequest, \
TokenRequest
@responses.activate @responses.activate
def test_admin_request(data_config): def test_admin_request(data_config):
ENDPOINT = 'test/url/endpoint' ENDPOINT = 'test/url/endpoint'
request = AdminRequest(**data_config) request = AdminRequest(**data_config)
assert request.BASE_URL == 'http://{admin_username}:{admin_password}@{hostname}:{grafana_port}/'.format( url = '{admin_username}:{admin_password}@{hostname}:{grafana_port}/'. \
**data_config) format(**data_config)
assert request.BASE_URL == 'http://' + url
assert request.username == data_config['admin_username'] assert request.username == data_config['admin_username']
def get_callback(request): def get_callback(request):
......
...@@ -50,9 +50,10 @@ def test_provision(data_config, mocker, client): ...@@ -50,9 +50,10 @@ def test_provision(data_config, mocker, client):
_mocked_create_api_token.return_value = { _mocked_create_api_token.return_value = {
'key': 'testtoken', 'id': 0} # api token 'key': 'testtoken', 'id': 0} # api token
_mocked_get_missing_datasource_definitions = mocker.patch( _mocked_get_missing_ds_defs = mocker.patch(
'brian_dashboard_manager.grafana.provision.get_missing_datasource_definitions') 'brian_dashboard_manager.grafana.provision.' +
_mocked_get_missing_datasource_definitions.return_value = TEST_DATASOURCE # test datasource 'get_missing_datasource_definitions')
_mocked_get_missing_ds_defs.return_value = TEST_DATASOURCE
_mocked_create_datasource = mocker.patch( _mocked_create_datasource = mocker.patch(
'brian_dashboard_manager.grafana.provision.create_datasource') 'brian_dashboard_manager.grafana.provision.create_datasource')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment