From c80295c27788ea85a3a877e479050729223f579a Mon Sep 17 00:00:00 2001 From: jorgepastorr Date: Tue, 19 Mar 2024 20:08:13 +0100 Subject: [PATCH] move nextcloud api and quota to file init to correct visualization --- orchestra/contrib/saas/backends/__init__.py | 146 ++++++++++++++++++ orchestra/contrib/saas/backends/nextcloud.py | 153 +------------------ 2 files changed, 147 insertions(+), 152 deletions(-) diff --git a/orchestra/contrib/saas/backends/__init__.py b/orchestra/contrib/saas/backends/__init__.py index b1c07976..bef450d3 100644 --- a/orchestra/contrib/saas/backends/__init__.py +++ b/orchestra/contrib/saas/backends/__init__.py @@ -1,7 +1,13 @@ import pkgutil import textwrap +import requests +import time +import sys from orchestra.contrib.resources import ServiceMonitor +from orchestra.contrib.orchestration import replace +from django.utils.translation import gettext_lazy as _ +import xml.etree.ElementTree as ET from .. import settings @@ -126,7 +132,147 @@ class ApacheTrafficByName(ApacheTrafficByHost): 'object_id': saas.pk, } + +class NextCloudAPIMixin(object): + def validate_response(self, response): + request = response.request + context = (request.method, response.url, request.body, response.status_code) + # sys.stderr.write("%s %s '%s' HTTP %s\n" % context) + if response.status_code != requests.codes.ok: + raise RuntimeError("%s %s '%s' HTTP %s" % context) + root = ET.fromstring(response.text) + statuscode = root.find("./meta/statuscode").text + if statuscode != '100': + message = root.find("./meta/status").text + request = response.request + context = (request.method, response.url, request.body, statuscode, message) + raise RuntimeError("%s %s '%s' ERROR %s, %s" % context) + + def api_call(self, action, url_path, *args, **kwargs): + BASE_URL = settings.SAAS_NEXTCLOUD_API_URL.rstrip('/') + url = '/'.join((BASE_URL, url_path)) + response = action(url, headers={'OCS-APIRequest':'true'}, verify=False, *args, **kwargs) + self.validate_response(response) + return response + + def api_get(self, url_path, *args, **kwargs): + return self.api_call(requests.get, url_path, *args, **kwargs) + + def api_post(self, url_path, *args, **kwargs): + return self.api_call(requests.post, url_path, *args, **kwargs) + + def api_put(self, url_path, *args, **kwargs): + return self.api_call(requests.put, url_path, *args, **kwargs) + + def api_delete(self, url_path, *args, **kwargs): + return self.api_call(requests.delete, url_path, *args, **kwargs) + + def create(self, saas): + data = { + 'userid': saas.name, + 'password': saas.password, + } + self.api_post('users', data) + def update_group(self, saas): + data = { + 'groupid': saas.account.username + } + try: + self.api_get('groups/%s' % saas.account.username) + except RuntimeError: + self.api_post('groups', data) + self.api_post(f'users/{saas.name}/groups', data) + + def update_quota(self, saas): + if hasattr(saas, 'resources') and hasattr(saas.resources, 'nextcloud-disk'): + resource = getattr(saas.resources, 'nextcloud-disk') + quotaValue = f"{resource.allocated}G" if resource.allocated > 0 else "default" + data = { + 'key': "quota", + 'value': quotaValue + } + self.api_put(f'users/{saas.name}', data) + + def update_password(self, saas): + """ + key: email|quota|display|password + value: el valor a modificar. + Si es un email, tornarà un error si la direcció no te la "@" + Si es una quota, sembla que algo per l'estil "5G", "100M", etc. funciona. Quota 0 = infinit + "display" es el display name, no crec que el fem servir, és cosmetic + """ + data = { + 'key': 'password', + 'value': saas.password, + } + self.api_put('users/%s' % saas.name, data) + + def disable_user(self, saas): + self.api_put('users/%s/disable' % saas.name) + + def enable_user(self, saas): + self.api_put('users/%s/enable' % saas.name) + + def get_user(self, saas): + """ + { + 'displayname' + 'email' + 'quota' => + { + 'free' (en Bytes) + 'relative' (en tant per cent sense signe %, e.g. 68.17) + 'total' (en Bytes) + 'used' (en Bytes) + } + } + """ + response = self.api_get('users/%s' % saas.name) + root = ET.fromstring(response.text) + ret = {} + for data in root.find('./data'): + ret[data.tag] = data.text + ret['quota'] = {} + for data in root.find('.data/quota'): + ret['quota'][data.tag] = data.text + return ret + + +class NextCloudDiskQuota(NextCloudAPIMixin, ServiceMonitor): + model = 'saas.SaaS' + verbose_name = _("nextCloud SaaS Disk Quota") + default_route_match = "saas.service == 'nextcloud'" + resource = ServiceMonitor.DISK + delete_old_equal_values = True + + def monitor(self, user): + context = self.get_context(user) + self.append("echo %(object_id)s $(monitor %(base_home)s)" % context) + + def get_context(self, user): + context = { + 'object_id': user.pk, + 'base_home': user.get_base_home(), + } + return replace(context, "'", '"') + + def get_quota(self, saas, server): + try: + user = self.get_user(saas) + except requests.exceptions.ConnectionError: + time.sleep(2) + user = self.get_user(saas) + context = { + 'object_id': saas.pk, + 'used': int(user['quota'].get('used', 0)), + } + sys.stdout.write('%(object_id)i %(used)i\n' % context) + + def monitor(self, saas): + self.append(self.get_quota, saas) + + for __, module_name, __ in pkgutil.walk_packages(__path__): # sorry for the exec(), but Import module function fails :( exec('from . import %s' % module_name) diff --git a/orchestra/contrib/saas/backends/nextcloud.py b/orchestra/contrib/saas/backends/nextcloud.py index d9eb536f..b0f3d33a 100644 --- a/orchestra/contrib/saas/backends/nextcloud.py +++ b/orchestra/contrib/saas/backends/nextcloud.py @@ -1,128 +1,11 @@ -import re -import sys -import textwrap -import time -import xml.etree.ElementTree as ET -from urllib.parse import urlparse - -import requests from django.utils.translation import gettext_lazy as _ from orchestra.contrib.orchestration import ServiceController -from orchestra.contrib.resources import ServiceMonitor -from orchestra.contrib.resources.models import ResourceData -from orchestra.contrib.saas.models import SaaS -from . import ApacheTrafficByName +from . import ApacheTrafficByName, NextCloudAPIMixin from .. import settings -class NextCloudAPIMixin(object): - def validate_response(self, response): - request = response.request - context = (request.method, response.url, request.body, response.status_code) - # sys.stderr.write("%s %s '%s' HTTP %s\n" % context) - if response.status_code != requests.codes.ok: - raise RuntimeError("%s %s '%s' HTTP %s" % context) - root = ET.fromstring(response.text) - statuscode = root.find("./meta/statuscode").text - if statuscode != '100': - message = root.find("./meta/status").text - request = response.request - context = (request.method, response.url, request.body, statuscode, message) - raise RuntimeError("%s %s '%s' ERROR %s, %s" % context) - - def api_call(self, action, url_path, *args, **kwargs): - BASE_URL = settings.SAAS_NEXTCLOUD_API_URL.rstrip('/') - url = '/'.join((BASE_URL, url_path)) - response = action(url, headers={'OCS-APIRequest':'true'}, verify=False, *args, **kwargs) - self.validate_response(response) - return response - - def api_get(self, url_path, *args, **kwargs): - return self.api_call(requests.get, url_path, *args, **kwargs) - - def api_post(self, url_path, *args, **kwargs): - return self.api_call(requests.post, url_path, *args, **kwargs) - - def api_put(self, url_path, *args, **kwargs): - return self.api_call(requests.put, url_path, *args, **kwargs) - - def api_delete(self, url_path, *args, **kwargs): - return self.api_call(requests.delete, url_path, *args, **kwargs) - - def create(self, saas): - data = { - 'userid': saas.name, - 'password': saas.password, - } - self.api_post('users', data) - - def update_group(self, saas): - data = { - 'groupid': saas.account.username - } - try: - self.api_get('groups/%s' % saas.account.username) - except RuntimeError: - self.api_post('groups', data) - self.api_post(f'users/{saas.name}/groups', data) - - def update_quota(self, saas): - if hasattr(saas, 'resources') and hasattr(saas.resources, 'nextcloud-disk'): - resource = getattr(saas.resources, 'nextcloud-disk') - quotaValue = f"{resource.allocated}G" if resource.allocated > 0 else "default" - data = { - 'key': "quota", - 'value': quotaValue - } - self.api_put(f'users/{saas.name}', data) - - def update_password(self, saas): - """ - key: email|quota|display|password - value: el valor a modificar. - Si es un email, tornarà un error si la direcció no te la "@" - Si es una quota, sembla que algo per l'estil "5G", "100M", etc. funciona. Quota 0 = infinit - "display" es el display name, no crec que el fem servir, és cosmetic - """ - data = { - 'key': 'password', - 'value': saas.password, - } - self.api_put('users/%s' % saas.name, data) - - def disable_user(self, saas): - self.api_put('users/%s/disable' % saas.name) - - def enable_user(self, saas): - self.api_put('users/%s/enable' % saas.name) - - def get_user(self, saas): - """ - { - 'displayname' - 'email' - 'quota' => - { - 'free' (en Bytes) - 'relative' (en tant per cent sense signe %, e.g. 68.17) - 'total' (en Bytes) - 'used' (en Bytes) - } - } - """ - response = self.api_get('users/%s' % saas.name) - root = ET.fromstring(response.text) - ret = {} - for data in root.find('./data'): - ret[data.tag] = data.text - ret['quota'] = {} - for data in root.find('.data/quota'): - ret['quota'][data.tag] = data.text - return ret - - class NextCloudController(NextCloudAPIMixin, ServiceController): """ Creates a wordpress site on a WordPress MultiSite installation. @@ -175,37 +58,3 @@ class NextcloudTraffic(ApacheTrafficByName): ('SAAS_TRAFFIC_IGNORE_HOSTS', 'SAAS_NEXTCLOUD_LOG_PATH') ) log_path = settings.SAAS_NEXTCLOUD_LOG_PATH - - -class NextCloudDiskQuota(NextCloudAPIMixin, ServiceMonitor): - model = 'saas.SaaS' - verbose_name = _("nextCloud SaaS Disk Quota") - default_route_match = "saas.service == 'nextcloud'" - resource = ServiceMonitor.DISK - delete_old_equal_values = True - - def monitor(self, user): - context = self.get_context(user) - self.append("echo %(object_id)s $(monitor %(base_home)s)" % context) - - def get_context(self, user): - context = { - 'object_id': user.pk, - 'base_home': user.get_base_home(), - } - return replace(context, "'", '"') - - def get_quota(self, saas, server): - try: - user = self.get_user(saas) - except requests.exceptions.ConnectionError: - time.sleep(2) - user = self.get_user(saas) - context = { - 'object_id': saas.pk, - 'used': int(user['quota'].get('used', 0)), - } - sys.stdout.write('%(object_id)i %(used)i\n' % context) - - def monitor(self, saas): - self.append(self.get_quota, saas)