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 @@
+
+