diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py index 7932dd78..ffa18a4c 100644 --- a/orchestra/contrib/domains/backends.py +++ b/orchestra/contrib/domains/backends.py @@ -25,7 +25,7 @@ class Bind9MasterDomainBackend(ServiceController): ('domains.Record', 'domain__origin'), ('domains.Domain', 'origin'), ) - ignore_fields = ['serial'] + ignore_fields = ('serial',) doc_settings = (settings, ('DOMAINS_MASTERS_PATH',) ) diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py index 92c8c615..1cd893a5 100644 --- a/orchestra/contrib/domains/models.py +++ b/orchestra/contrib/domains/models.py @@ -102,7 +102,7 @@ class Domain(models.Model): for domain in self.subdomains.exclude(pk=self.pk): # queryset.update() is not used because we want to trigger backend to delete ex-topdomains domain.top = self - domain.save(update_fields=['top']) + domain.save(update_fields=('top',)) def get_description(self): if self.is_top: @@ -151,7 +151,7 @@ class Domain(models.Model): serial = str(self.serial)[:8] + '%.2d' % num serial = int(serial) self.serial = serial - self.save(update_fields=['serial']) + self.save(update_fields=('serial',)) def get_records(self): types = {} diff --git a/orchestra/contrib/saas/backends/owncloud.py b/orchestra/contrib/saas/backends/owncloud.py new file mode 100644 index 00000000..943421fd --- /dev/null +++ b/orchestra/contrib/saas/backends/owncloud.py @@ -0,0 +1,103 @@ +import re +import sys +import textwrap +import xml.etree.ElementTree as ET +from urllib.parse import urlparse + +import requests +from django.utils.translation import ugettext_lazy as _ + +from orchestra.contrib.orchestration import ServiceController + +from . import ApacheTrafficByHost +from .. import settings + + +class OwnCloudBackend(ServiceController): + """ + Creates a wordpress site on a WordPress MultiSite installation. + + You should point it to the database server + """ + verbose_name = _("ownCloud SaaS") + model = 'saas.SaaS' + default_route_match = "saas.service == 'owncloud'" + doc_settings = (settings, + ('SAAS_OWNCLOUD_API_URL',) + ) + + def validate_response(self, response): + if response.status_code != requests.codes.ok: + request = response.request + context = (request.method, response.url, request.body, response.status_code) + 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_OWNCLOUD_API_URL.rstrip('/') + url = '/'.join((BASE_URL, url_path)) + response = action(url, *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(self, saas): + data = { + 'password': saas.password, + } + self.api_put('users/%s' % saas.name, data) + + def update_or_create(self, saas, server): + try: + self.api_get('users/%s' % saas.name) + except RuntimeError: + if getattr(saas, 'password'): + self.create(saas) + else: + raise + else: + if getattr(saas, 'password'): + self.update(saas) + + def remove(self, saas, server): + self.api_delete('users/%s' % saas.name) + + def save(self, saas): + self.append(self.update_or_create, saas) + + def delete(self, saas): + self.append(self.remove, saas) + + +class OwncloudTraffic(ApacheTrafficByHost): + __doc__ = ApacheTrafficByHost.__doc__ + verbose_name = _("ownCloud SaaS Traffic") + default_route_match = "saas.service == 'owncloud'" + doc_settings = (settings, + ('SAAS_TRAFFIC_IGNORE_HOSTS', 'SAAS_OWNCLOUD_LOG_PATH') + ) + log_path = settings.SAAS_OWNCLOUD_LOG_PATH diff --git a/orchestra/contrib/saas/services/owncloud.py b/orchestra/contrib/saas/services/owncloud.py new file mode 100644 index 00000000..2f850f07 --- /dev/null +++ b/orchestra/contrib/saas/services/owncloud.py @@ -0,0 +1,13 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from .. import settings +from .options import SoftwareService + + +class OwnCloudService(SoftwareService): + name = 'owncloud' + verbose_name = "ownCloud" + icon = 'orchestra/icons/apps/ownCloud.png' + site_domain = settings.SAAS_OWNCLOUD_DOMAIN diff --git a/orchestra/contrib/saas/settings.py b/orchestra/contrib/saas/settings.py index 2a5460d1..7552d26e 100644 --- a/orchestra/contrib/saas/settings.py +++ b/orchestra/contrib/saas/settings.py @@ -17,6 +17,7 @@ SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES', 'orchestra.contrib.saas.services.wordpress.WordPressService', 'orchestra.contrib.saas.services.dokuwiki.DokuWikiService', 'orchestra.contrib.saas.services.drupal.DrupalService', + 'orchestra.contrib.saas.services.owncloud.OwnCloudService', 'orchestra.contrib.saas.services.seafile.SeaFileService', ), # lazy loading @@ -201,6 +202,24 @@ SAAS_SEAFILE_DEFAULT_QUOTA = Setting('SAAS_SEAFILE_DEFAULT_QUOTA', ) +# ownCloud + +SAAS_OWNCLOUD_DOMAIN = Setting('SAAS_OWNCLOUD_DOMAIN', + 'owncloud.{}'.format(ORCHESTRA_BASE_DOMAIN), + help_text="Uses ORCHESTRA_BASE_DOMAIN by default.", +) + +SAAS_OWNCLOUD_API_URL = Setting('SAAS_OWNCLOUD_API_URL', + 'https://admin:secret@owncloud.{}/ocs/v1.php/cloud/'.format(ORCHESTRA_BASE_DOMAIN), +) + +SAAS_OWNCLOUD_LOG_PATH = Setting('SAAS_OWNCLOUD_LOG_PATH', + '', + help_text=_('Filesystem path for the webserver access logs.
' + 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Host}i\"" host'), +) + + # BSCW SAAS_BSCW_DOMAIN = Setting('SAAS_BSCW_DOMAIN', diff --git a/orchestra/static/orchestra/icons/apps/ownCloud.png b/orchestra/static/orchestra/icons/apps/ownCloud.png new file mode 100644 index 00000000..10f53271 Binary files /dev/null and b/orchestra/static/orchestra/icons/apps/ownCloud.png differ diff --git a/orchestra/static/orchestra/icons/apps/ownCloud.svg b/orchestra/static/orchestra/icons/apps/ownCloud.svg new file mode 100644 index 00000000..b64fe143 --- /dev/null +++ b/orchestra/static/orchestra/icons/apps/ownCloud.svg @@ -0,0 +1,151 @@ + + + + + + image/svg+xml + + Druplicon + + + + + + Druplicon + + + + + + + + + +