Lots of improvements on webapps and saas
This commit is contained in:
parent
40930a480e
commit
dd84217320
19
TODO.md
19
TODO.md
|
@ -241,12 +241,25 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
|
|
||||||
* WPMU blog traffic
|
* WPMU blog traffic
|
||||||
|
|
||||||
* normurlpath '' returns '/'
|
* normurlpath '' return '/'
|
||||||
|
|
||||||
* rename webapps.type to something more generic
|
* rename webapps.type to something more generic
|
||||||
|
|
||||||
* initial configuration of multisite sas apps with password stored in DATA
|
* initial configuration of multisite sas apps with password stored in DATA
|
||||||
|
|
||||||
* websites links on webpaps ans saas
|
* webapps installation complete, passowrd protected
|
||||||
|
* saas.initial_password autogenerated (ok because its random and not user provided) vs saas.password /change_Form provided + send email with initial_password
|
||||||
|
|
||||||
|
* disable saas apps
|
||||||
|
|
||||||
|
* more robust backend error handling, continue executing but exit code > 0 if failure, replace exit_code=0; do_sometging || exit_code=1
|
||||||
|
|
||||||
|
* saas require unique emails? connect to backend server to find out because they change
|
||||||
|
|
||||||
|
* automaitcally set passwords and email users?
|
||||||
|
|
||||||
|
* website directives uniquenes validation on serializers
|
||||||
|
|
||||||
|
* gitlab store id, username changes
|
||||||
|
|
||||||
|
|
||||||
* /var/lib/fcgid/wrappers/ rm write permissions
|
|
||||||
|
|
|
@ -38,10 +38,11 @@ class MySQLBackend(ServiceController):
|
||||||
if database.type != database.MYSQL:
|
if database.type != database.MYSQL:
|
||||||
return
|
return
|
||||||
context = self.get_context(database)
|
context = self.get_context(database)
|
||||||
self.append("mysql -e 'DROP DATABASE `%(database)s`;'" % context)
|
self.append("mysql -e 'DROP DATABASE `%(database)s`;' || exit_code=1" % context)
|
||||||
self.append("mysql mysql -e 'DELETE FROM db WHERE db = \"%(database)s\";'" % context)
|
self.append("mysql mysql -e 'DELETE FROM db WHERE db = \"%(database)s\";'" % context)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
super(MySQLBackend, self).commit()
|
||||||
self.append("mysql -e 'FLUSH PRIVILEGES;'")
|
self.append("mysql -e 'FLUSH PRIVILEGES;'")
|
||||||
|
|
||||||
def get_context(self, database):
|
def get_context(self, database):
|
||||||
|
|
|
@ -36,8 +36,6 @@ DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.
|
||||||
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
|
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
|
||||||
'/usr/sbin/named-checkzone -i local -k fail -n fail')
|
'/usr/sbin/named-checkzone -i local -k fail -n fail')
|
||||||
|
|
||||||
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
|
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
|
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
|
||||||
|
|
||||||
|
|
|
@ -108,11 +108,10 @@ def validate_soa_record(value):
|
||||||
def validate_zone(zone):
|
def validate_zone(zone):
|
||||||
""" Ultimate zone file validation using named-checkzone """
|
""" Ultimate zone file validation using named-checkzone """
|
||||||
zone_name = zone.split()[0][:-1]
|
zone_name = zone.split()[0][:-1]
|
||||||
path = os.path.join(settings.DOMAINS_CHECKZONE_PATH, zone_name)
|
|
||||||
with open(path, 'wb') as f:
|
|
||||||
f.write(zone)
|
|
||||||
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
|
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
|
||||||
check = run(' '.join([checkzone, zone_name, path]), error_codes=[0,1], display=False)
|
cmd = ' '.join(["echo -e '%s'" % zone, '|', checkzone, zone_name, '/dev/stdin'])
|
||||||
|
print cmd
|
||||||
|
check = run(cmd, error_codes=[0, 1], display=False)
|
||||||
if check.return_code == 1:
|
if check.return_code == 1:
|
||||||
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
|
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
|
||||||
raise ValidationError(', '.join(errors))
|
raise ValidationError(', '.join(errors))
|
||||||
|
|
|
@ -177,7 +177,8 @@ class ServiceBackend(plugins.Plugin):
|
||||||
"""
|
"""
|
||||||
self.append(
|
self.append(
|
||||||
'set -e\n'
|
'set -e\n'
|
||||||
'set -o pipefail'
|
'set -o pipefail\n'
|
||||||
|
'exit_code=0;'
|
||||||
)
|
)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
@ -187,7 +188,7 @@ class ServiceBackend(plugins.Plugin):
|
||||||
reloading a service is done in a separated method in order to reload
|
reloading a service is done in a separated method in order to reload
|
||||||
the service once in bulk operations
|
the service once in bulk operations
|
||||||
"""
|
"""
|
||||||
self.append('exit 0')
|
self.append('exit $exit_code')
|
||||||
|
|
||||||
|
|
||||||
class ServiceController(ServiceBackend):
|
class ServiceController(ServiceBackend):
|
||||||
|
|
|
@ -22,18 +22,10 @@ def as_task(execute):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
""" send report """
|
""" send report """
|
||||||
# Tasks run on a separate transaction pool (thread), no need to temper with the transaction
|
# Tasks run on a separate transaction pool (thread), no need to temper with the transaction
|
||||||
log = execute(*args, **kwargs)
|
|
||||||
if log.state != log.SUCCESS:
|
|
||||||
send_report(execute, args, log)
|
|
||||||
return log
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def close_connection(execute):
|
|
||||||
""" Threads have their own connection pool, closing it when finishing """
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
try:
|
||||||
log = execute(*args, **kwargs)
|
log = execute(*args, **kwargs)
|
||||||
|
if log.state != log.SUCCESS:
|
||||||
|
send_report(execute, args, log)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
|
subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
|
||||||
message = traceback.format_exc()
|
message = traceback.format_exc()
|
||||||
|
@ -45,6 +37,19 @@ def close_connection(execute):
|
||||||
# Using the wrapper function as threader messenger for the execute output
|
# Using the wrapper function as threader messenger for the execute output
|
||||||
# Absense of it will indicate a failure at this stage
|
# Absense of it will indicate a failure at this stage
|
||||||
wrapper.log = log
|
wrapper.log = log
|
||||||
|
return log
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def close_connection(execute):
|
||||||
|
""" Threads have their own connection pool, closing it when finishing """
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
log = execute(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
wrapper.log = log
|
||||||
finally:
|
finally:
|
||||||
db.connection.close()
|
db.connection.close()
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -89,15 +94,15 @@ def execute(operations, async=False):
|
||||||
backend, operations = value
|
backend, operations = value
|
||||||
backend.commit()
|
backend.commit()
|
||||||
execute = as_task(backend.execute)
|
execute = as_task(backend.execute)
|
||||||
execute = close_connection(execute)
|
|
||||||
# DEBUG: substitute all thread related stuff for this function
|
|
||||||
#execute(server, async=async)
|
|
||||||
logger.debug('%s is going to be executed on %s' % (backend, server))
|
logger.debug('%s is going to be executed on %s' % (backend, server))
|
||||||
thread = threading.Thread(target=execute, args=(server,), kwargs={'async': async})
|
|
||||||
thread.start()
|
|
||||||
if block:
|
if block:
|
||||||
thread.join()
|
# Execute one bakend at a time, no need for threads
|
||||||
threads.append(thread)
|
execute(server, async=async)
|
||||||
|
else:
|
||||||
|
execute = close_connection(execute)
|
||||||
|
thread = threading.Thread(target=execute, args=(server,), kwargs={'async': async})
|
||||||
|
thread.start()
|
||||||
|
threads.append(thread)
|
||||||
executions.append((execute, operations))
|
executions.append((execute, operations))
|
||||||
[ thread.join() for thread in threads ]
|
[ thread.join() for thread in threads ]
|
||||||
logs = []
|
logs = []
|
||||||
|
@ -108,7 +113,9 @@ def execute(operations, async=False):
|
||||||
for operation in operations:
|
for operation in operations:
|
||||||
logger.info("Executed %s" % str(operation))
|
logger.info("Executed %s" % str(operation))
|
||||||
operation.log = execution.log
|
operation.log = execution.log
|
||||||
operation.save()
|
if operation.object_id:
|
||||||
|
# Not all backends are call with objects saved on the database
|
||||||
|
operation.save()
|
||||||
stdout = execution.log.stdout.strip()
|
stdout = execution.log.stdout.strip()
|
||||||
stdout and logger.debug('STDOUT %s', stdout)
|
stdout and logger.debug('STDOUT %s', stdout)
|
||||||
stderr = execution.log.stderr.strip()
|
stderr = execution.log.stderr.strip()
|
||||||
|
|
|
@ -6,6 +6,7 @@ def compute_resource_usage(data):
|
||||||
resource = data.resource
|
resource = data.resource
|
||||||
result = 0
|
result = 0
|
||||||
has_result = False
|
has_result = False
|
||||||
|
today = datetime.date.today()
|
||||||
for dataset in data.get_monitor_datasets():
|
for dataset in data.get_monitor_datasets():
|
||||||
if resource.period == resource.MONTHLY_AVG:
|
if resource.period == resource.MONTHLY_AVG:
|
||||||
last = dataset.latest()
|
last = dataset.latest()
|
||||||
|
|
|
@ -11,18 +11,19 @@ from .services import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
class SaaSAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
|
class SaaSAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'service', 'display_site_name', 'account_link')
|
list_display = ('name', 'service', 'display_site_domain', 'account_link')
|
||||||
list_filter = ('service',)
|
list_filter = ('service',)
|
||||||
|
change_readonly_fields = ('service',)
|
||||||
plugin = SoftwareService
|
plugin = SoftwareService
|
||||||
plugin_field = 'service'
|
plugin_field = 'service'
|
||||||
plugin_title = 'Software as a Service'
|
plugin_title = 'Software as a Service'
|
||||||
|
|
||||||
def display_site_name(self, saas):
|
def display_site_domain(self, saas):
|
||||||
site_name = saas.get_site_name()
|
site_domain = saas.get_site_domain()
|
||||||
return '<a href="http://%s">%s</a>' % (site_name, site_name)
|
return '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
||||||
display_site_name.short_description = _("Site name")
|
display_site_domain.short_description = _("Site domain")
|
||||||
display_site_name.allow_tags = True
|
display_site_domain.allow_tags = True
|
||||||
display_site_name.admin_order_field = 'site_name'
|
display_site_domain.admin_order_field = 'name'
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(SaaS, SaaSAdmin)
|
admin.site.register(SaaS, SaaSAdmin)
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.apps.orchestration import ServiceController
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
|
class GitLabSaaSBackend(ServiceController):
|
||||||
|
verbose_name = _("GitLab SaaS")
|
||||||
|
model = 'saas.SaaS'
|
||||||
|
default_route_match = "saas.service == 'gitlab'"
|
||||||
|
block = True
|
||||||
|
actions = ('save', 'delete', 'validate_creation')
|
||||||
|
|
||||||
|
def get_base_url(self):
|
||||||
|
return 'https://%s/api/v3' % settings.SAAS_GITLAB_DOMAIN
|
||||||
|
|
||||||
|
def get_user_url(self, saas):
|
||||||
|
user_id = saas.data['user_id']
|
||||||
|
return self.get_base_url() + '/users/%i' % user_id
|
||||||
|
|
||||||
|
def validate_response(self, response, status_codes):
|
||||||
|
if response.status_code not in status_codes:
|
||||||
|
raise RuntimeError("[%i] %s" % (response.status_code, response.content))
|
||||||
|
|
||||||
|
def authenticate(self):
|
||||||
|
login_url = self.get_base_url() + '/session'
|
||||||
|
data = {
|
||||||
|
'login': 'root',
|
||||||
|
'password': settings.SAAS_GITLAB_ROOT_PASSWORD,
|
||||||
|
}
|
||||||
|
response = requests.post(login_url, data=data)
|
||||||
|
self.validate_response(response, [201])
|
||||||
|
token = json.loads(response.content)['private_token']
|
||||||
|
self.headers = {
|
||||||
|
'PRIVATE-TOKEN': token,
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_user(self, saas, server):
|
||||||
|
self.authenticate()
|
||||||
|
user_url = self.get_base_url() + '/users'
|
||||||
|
data = {
|
||||||
|
'email': saas.data['email'],
|
||||||
|
'password': saas.password,
|
||||||
|
'username': saas.name,
|
||||||
|
'name': saas.account.get_full_name(),
|
||||||
|
}
|
||||||
|
response = requests.post(user_url, data=data, headers=self.headers)
|
||||||
|
self.validate_response(response, [201])
|
||||||
|
print response.content
|
||||||
|
user = json.loads(response.content)
|
||||||
|
saas.data['user_id'] = user['id']
|
||||||
|
# Using queryset update to avoid triggering backends with the post_save signal
|
||||||
|
type(saas).objects.filter(pk=saas.pk).update(data=saas.data)
|
||||||
|
print json.dumps(user, indent=4)
|
||||||
|
|
||||||
|
def change_password(self, saas, server):
|
||||||
|
self.authenticate()
|
||||||
|
user_url = self.get_user_url(saas)
|
||||||
|
data = {
|
||||||
|
'password': saas.password,
|
||||||
|
}
|
||||||
|
response = requests.patch(user_url, data=data, headers=self.headers)
|
||||||
|
self.validate_response(response, [200])
|
||||||
|
print json.dumps(json.loads(response.content), indent=4)
|
||||||
|
|
||||||
|
def delete_user(self, saas, server):
|
||||||
|
self.authenticate()
|
||||||
|
user_url = self.get_user_url(saas)
|
||||||
|
response = requests.delete(user_url, headers=self.headers)
|
||||||
|
self.validate_response(response, [200, 404])
|
||||||
|
print json.dumps(json.loads(response.content), indent=4)
|
||||||
|
|
||||||
|
def _validate_creation(self, saas, server):
|
||||||
|
""" checks if a saas object is valid for creation on the server side """
|
||||||
|
self.authenticate()
|
||||||
|
username = saas.name
|
||||||
|
email = saas.data['email']
|
||||||
|
users_url = self.get_base_url() + '/users/'
|
||||||
|
users = json.loads(requests.get(users_url, headers=self.headers).content)
|
||||||
|
for user in users:
|
||||||
|
if user['username'] == username:
|
||||||
|
print 'user-exists'
|
||||||
|
if user['email'] == email:
|
||||||
|
print 'email-exists'
|
||||||
|
|
||||||
|
def validate_creation(self, saas):
|
||||||
|
self.append(self._validate_creation, saas)
|
||||||
|
|
||||||
|
def save(self, saas):
|
||||||
|
if hasattr(saas, 'password'):
|
||||||
|
if saas.data.get('user_id', None):
|
||||||
|
self.append(self.change_password, saas)
|
||||||
|
else:
|
||||||
|
self.append(self.create_user, saas)
|
||||||
|
|
||||||
|
def delete(self, saas):
|
||||||
|
self.append(self.delete_user, saas)
|
|
@ -17,7 +17,7 @@ class PhpListSaaSBackend(ServiceController):
|
||||||
|
|
||||||
def initialize_database(self, saas, server):
|
def initialize_database(self, saas, server):
|
||||||
base_domain = settings.SAAS_PHPLIST_BASE_DOMAIN
|
base_domain = settings.SAAS_PHPLIST_BASE_DOMAIN
|
||||||
admin_link = 'http://%s.%s/admin/' % (saas.get_site_name(), base_domain)
|
admin_link = 'http://%s/admin/' % saas.get_site_domain()
|
||||||
admin_content = requests.get(admin_link).content
|
admin_content = requests.get(admin_link).content
|
||||||
if admin_content.startswith('Cannot connect to Database'):
|
if admin_content.startswith('Cannot connect to Database'):
|
||||||
raise RuntimeError("Database is not yet configured")
|
raise RuntimeError("Database is not yet configured")
|
||||||
|
@ -28,7 +28,7 @@ class PhpListSaaSBackend(ServiceController):
|
||||||
install = install.groups()[0]
|
install = install.groups()[0]
|
||||||
install_link = admin_link + install[1:]
|
install_link = admin_link + install[1:]
|
||||||
post = {
|
post = {
|
||||||
'adminname': saas.username,
|
'adminname': saas.name,
|
||||||
'orgname': saas.account.username,
|
'orgname': saas.account.username,
|
||||||
'adminemail': saas.account.username,
|
'adminemail': saas.account.username,
|
||||||
'adminpassword': saas.password,
|
'adminpassword': saas.password,
|
||||||
|
|
|
@ -14,10 +14,9 @@ from .services import SoftwareService
|
||||||
class SaaS(models.Model):
|
class SaaS(models.Model):
|
||||||
service = models.CharField(_("service"), max_length=32,
|
service = models.CharField(_("service"), max_length=32,
|
||||||
choices=SoftwareService.get_plugin_choices())
|
choices=SoftwareService.get_plugin_choices())
|
||||||
username = models.CharField(_("name"), max_length=64,
|
name = models.CharField(_("Name"), max_length=64,
|
||||||
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
||||||
validators=[validators.validate_username])
|
validators=[validators.validate_username])
|
||||||
# site_name = NullableCharField(_("site name"), max_length=32, null=True)
|
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='saas')
|
related_name='saas')
|
||||||
data = JSONField(_("data"), default={},
|
data = JSONField(_("data"), default={},
|
||||||
|
@ -27,12 +26,11 @@ class SaaS(models.Model):
|
||||||
verbose_name = "SaaS"
|
verbose_name = "SaaS"
|
||||||
verbose_name_plural = "SaaS"
|
verbose_name_plural = "SaaS"
|
||||||
unique_together = (
|
unique_together = (
|
||||||
('username', 'service'),
|
('name', 'service'),
|
||||||
# ('site_name', 'service'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s@%s" % (self.username, self.service)
|
return "%s@%s" % (self.name, self.service)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_class(self):
|
def service_class(self):
|
||||||
|
@ -43,12 +41,12 @@ class SaaS(models.Model):
|
||||||
""" Per request lived service_instance """
|
""" Per request lived service_instance """
|
||||||
return self.service_class(self)
|
return self.service_class(self)
|
||||||
|
|
||||||
def get_site_name(self):
|
|
||||||
return self.service_instance.get_site_name()
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.data = self.service_instance.clean_data()
|
self.data = self.service_instance.clean_data()
|
||||||
|
|
||||||
|
def get_site_domain(self):
|
||||||
|
return self.service_instance.get_site_domain()
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,14 @@ from .options import SoftwareService, SoftwareServiceForm
|
||||||
|
|
||||||
class BSCWForm(SoftwareServiceForm):
|
class BSCWForm(SoftwareServiceForm):
|
||||||
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
|
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
|
||||||
quota = forms.IntegerField(label=_("Quota"), help_text=_("Disk quota in MB."))
|
quota = forms.IntegerField(label=_("Quota"), initial=settings.SAAS_BSCW_DEFAULT_QUOTA,
|
||||||
|
help_text=_("Disk quota in MB."))
|
||||||
|
|
||||||
|
|
||||||
class BSCWDataSerializer(serializers.Serializer):
|
class BSCWDataSerializer(serializers.Serializer):
|
||||||
email = serializers.EmailField(label=_("Email"))
|
email = serializers.EmailField(label=_("Email"))
|
||||||
quota = serializers.IntegerField(label=_("Quota"), help_text=_("Disk quota in MB."))
|
quota = serializers.IntegerField(label=_("Quota"), default=settings.SAAS_BSCW_DEFAULT_QUOTA,
|
||||||
|
help_text=_("Disk quota in MB."))
|
||||||
|
|
||||||
|
|
||||||
class BSCWService(SoftwareService):
|
class BSCWService(SoftwareService):
|
||||||
|
@ -26,5 +28,5 @@ class BSCWService(SoftwareService):
|
||||||
serializer = BSCWDataSerializer
|
serializer = BSCWDataSerializer
|
||||||
icon = 'orchestra/icons/apps/BSCW.png'
|
icon = 'orchestra/icons/apps/BSCW.png'
|
||||||
# TODO override from settings
|
# TODO override from settings
|
||||||
site_name = settings.SAAS_BSCW_DOMAIN
|
site_domain = settings.SAAS_BSCW_DOMAIN
|
||||||
change_readonly_fileds = ('email',)
|
change_readonly_fileds = ('email',)
|
||||||
|
|
|
@ -1,6 +1,50 @@
|
||||||
from .options import SoftwareService
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from orchestra.apps.orchestration.models import BackendOperation as Operation
|
||||||
|
from orchestra.forms import widgets
|
||||||
|
|
||||||
|
from .options import SoftwareService, SoftwareServiceForm
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
|
class GitLabForm(SoftwareServiceForm):
|
||||||
|
email = forms.EmailField(label=_("Email"),
|
||||||
|
help_text=_("Initial email address, changes on the GitLab server are not reflected here."))
|
||||||
|
|
||||||
|
|
||||||
|
class GitLaChangebForm(GitLabForm):
|
||||||
|
user_id = forms.IntegerField(label=("User ID"), widget=widgets.ShowTextWidget,
|
||||||
|
help_text=_("ID of this user on the GitLab server, the only attribute that not changes."))
|
||||||
|
|
||||||
|
|
||||||
|
class GitLabSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField(label=_("Email"))
|
||||||
|
user_id = serializers.IntegerField(label=_("User ID"), required=False)
|
||||||
|
|
||||||
|
|
||||||
class GitLabService(SoftwareService):
|
class GitLabService(SoftwareService):
|
||||||
|
name = 'gitlab'
|
||||||
|
form = GitLabForm
|
||||||
|
change_form = GitLaChangebForm
|
||||||
|
serializer = GitLabSerializer
|
||||||
|
site_domain = settings.SAAS_GITLAB_DOMAIN
|
||||||
|
change_readonly_fileds = ('email', 'user_id',)
|
||||||
verbose_name = "GitLab"
|
verbose_name = "GitLab"
|
||||||
icon = 'orchestra/icons/apps/gitlab.png'
|
icon = 'orchestra/icons/apps/gitlab.png'
|
||||||
|
|
||||||
|
def clean_data(self):
|
||||||
|
data = super(GitLabService, self).clean_data()
|
||||||
|
if not self.instance.pk:
|
||||||
|
log = Operation.execute_action(self.instance, 'validate_creation')[0]
|
||||||
|
errors = {}
|
||||||
|
if 'user-exists' in log.stdout:
|
||||||
|
errors['name'] = _("User with this username already exists.")
|
||||||
|
elif 'email-exists' in log.stdout:
|
||||||
|
errors['email'] = _("User with this email address already exists.")
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
return data
|
||||||
|
|
|
@ -8,13 +8,13 @@ from orchestra.plugins.forms import PluginDataForm
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms import widgets
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class, random_ascii
|
||||||
|
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
class SoftwareServiceForm(PluginDataForm):
|
class SoftwareServiceForm(PluginDataForm):
|
||||||
site_name = forms.CharField(widget=widgets.ShowTextWidget, required=False)
|
site_url = forms.CharField(label=_("Site URL"), widget=widgets.ShowTextWidget, required=False)
|
||||||
password = forms.CharField(label=_("Password"), required=False,
|
password = forms.CharField(label=_("Password"), required=False,
|
||||||
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
||||||
help_text=_("Passwords are not stored, so there is no way to see this "
|
help_text=_("Passwords are not stored, so there is no way to see this "
|
||||||
|
@ -30,25 +30,21 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
super(SoftwareServiceForm, self).__init__(*args, **kwargs)
|
super(SoftwareServiceForm, self).__init__(*args, **kwargs)
|
||||||
self.is_change = bool(self.instance and self.instance.pk)
|
self.is_change = bool(self.instance and self.instance.pk)
|
||||||
if self.is_change:
|
if self.is_change:
|
||||||
site_name = self.instance.get_site_name()
|
site_domain = self.instance.get_site_domain()
|
||||||
self.fields['password1'].required = False
|
self.fields['password1'].required = False
|
||||||
self.fields['password1'].widget = forms.HiddenInput()
|
self.fields['password1'].widget = forms.HiddenInput()
|
||||||
self.fields['password2'].required = False
|
self.fields['password2'].required = False
|
||||||
self.fields['password2'].widget = forms.HiddenInput()
|
self.fields['password2'].widget = forms.HiddenInput()
|
||||||
else:
|
else:
|
||||||
self.fields['password'].widget = forms.HiddenInput()
|
self.fields['password'].widget = forms.HiddenInput()
|
||||||
site_name = self.plugin.site_name
|
self.fields['password1'].help_text = _("Suggestion: %s") % random_ascii(10)
|
||||||
if site_name:
|
site_domain = self.plugin.site_domain
|
||||||
site_name_link = '<a href="http://%s">%s</a>' % (site_name, site_name)
|
if site_domain:
|
||||||
|
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
||||||
else:
|
else:
|
||||||
site_name_link = '<name>.%s' % self.plugin.site_name_base_domain
|
site_link = '<site_name>.%s' % self.plugin.site_base_domain
|
||||||
self.fields['site_name'].initial = site_name_link
|
self.fields['site_url'].initial = site_link
|
||||||
## self.fields['site_name'].widget = widgets.ReadOnlyWidget(site_name, mark_safe(link))
|
self.fields['name'].label = _("Username")
|
||||||
## self.fields['site_name'].required = False
|
|
||||||
# else:
|
|
||||||
# base_name = self.plugin.site_name_base_domain
|
|
||||||
# help_text = _("The final URL would be <site_name>.%s") % base_name
|
|
||||||
# self.fields['site_name'].help_text = help_text
|
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
if not self.is_change:
|
if not self.is_change:
|
||||||
|
@ -59,11 +55,6 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
raise forms.ValidationError(msg)
|
raise forms.ValidationError(msg)
|
||||||
return password2
|
return password2
|
||||||
|
|
||||||
def clean_site_name(self):
|
|
||||||
if self.plugin.site_name:
|
|
||||||
return None
|
|
||||||
return self.cleaned_data['site_name']
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
obj = super(SoftwareServiceForm, self).save(commit=commit)
|
obj = super(SoftwareServiceForm, self).save(commit=commit)
|
||||||
if not self.is_change:
|
if not self.is_change:
|
||||||
|
@ -73,11 +64,10 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
|
|
||||||
class SoftwareService(plugins.Plugin):
|
class SoftwareService(plugins.Plugin):
|
||||||
form = SoftwareServiceForm
|
form = SoftwareServiceForm
|
||||||
site_name = None
|
site_domain = None
|
||||||
site_name_base_domain = 'orchestra.lan'
|
site_base_domain = None
|
||||||
has_custom_domain = False
|
has_custom_domain = False
|
||||||
icon = 'orchestra/icons/apps.png'
|
icon = 'orchestra/icons/apps.png'
|
||||||
change_readonly_fileds = ('site_name',)
|
|
||||||
class_verbose_name = _("Software as a Service")
|
class_verbose_name = _("Software as a Service")
|
||||||
plugin_field = 'service'
|
plugin_field = 'service'
|
||||||
|
|
||||||
|
@ -89,14 +79,13 @@ class SoftwareService(plugins.Plugin):
|
||||||
plugins.append(import_class(cls))
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_change_readonly_fileds(cls):
|
def get_change_readonly_fileds(cls):
|
||||||
fields = super(SoftwareService, cls).get_change_readonly_fileds()
|
fields = super(SoftwareService, cls).get_change_readonly_fileds()
|
||||||
return fields + ('username',)
|
return fields + ('name',)
|
||||||
|
|
||||||
def get_site_name(self):
|
def get_site_domain(self):
|
||||||
return self.site_name or '.'.join(
|
return self.site_domain or '.'.join(
|
||||||
(self.instance.username, self.site_name_base_domain)
|
(self.instance.name, self.site_base_domain)
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
|
|
@ -17,23 +17,22 @@ class PHPListForm(SoftwareServiceForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PHPListForm, self).__init__(*args, **kwargs)
|
super(PHPListForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['username'].label = _("Name")
|
self.fields['name'].label = _("Site name")
|
||||||
base_domain = self.plugin.site_name_base_domain
|
base_domain = self.plugin.site_base_domain
|
||||||
help_text = _("Admin URL http://<name>.{}/admin/").format(base_domain)
|
help_text = _("Admin URL http://<site_name>.{}/admin/").format(base_domain)
|
||||||
self.fields['site_name'].help_text = help_text
|
self.fields['site_url'].help_text = help_text
|
||||||
|
|
||||||
|
|
||||||
class PHPListChangeForm(PHPListForm):
|
class PHPListChangeForm(PHPListForm):
|
||||||
# site_name = forms.CharField(widget=widgets.ShowTextWidget, required=False)
|
|
||||||
db_name = forms.CharField(label=_("Database name"),
|
db_name = forms.CharField(label=_("Database name"),
|
||||||
help_text=_("Database used for this webapp."))
|
help_text=_("Database used for this webapp."))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PHPListChangeForm, self).__init__(*args, **kwargs)
|
super(PHPListChangeForm, self).__init__(*args, **kwargs)
|
||||||
site_name = self.instance.get_site_name()
|
site_domain = self.instance.get_site_domain()
|
||||||
admin_url = "http://%s/admin/" % site_name
|
admin_url = "http://%s/admin/" % site_domain
|
||||||
help_text = _("Admin URL <a href={0}>{0}</a>").format(admin_url)
|
help_text = _("Admin URL <a href={0}>{0}</a>").format(admin_url)
|
||||||
self.fields['site_name'].help_text = help_text
|
self.fields['site_url'].help_text = help_text
|
||||||
|
|
||||||
|
|
||||||
class PHPListSerializer(serializers.Serializer):
|
class PHPListSerializer(serializers.Serializer):
|
||||||
|
@ -48,21 +47,25 @@ class PHPListService(SoftwareService):
|
||||||
change_readonly_fileds = ('db_name',)
|
change_readonly_fileds = ('db_name',)
|
||||||
serializer = PHPListSerializer
|
serializer = PHPListSerializer
|
||||||
icon = 'orchestra/icons/apps/Phplist.png'
|
icon = 'orchestra/icons/apps/Phplist.png'
|
||||||
site_name_base_domain = settings.SAAS_PHPLIST_BASE_DOMAIN
|
site_base_domain = settings.SAAS_PHPLIST_BASE_DOMAIN
|
||||||
|
|
||||||
def get_db_name(self):
|
def get_db_name(self):
|
||||||
db_name = 'phplist_mu_%s' % self.instance.username
|
db_name = 'phplist_mu_%s' % self.instance.name
|
||||||
# Limit for mysql database names
|
# Limit for mysql database names
|
||||||
return db_name[:65]
|
return db_name[:65]
|
||||||
|
|
||||||
def get_db_user(self):
|
def get_db_user(self):
|
||||||
return settings.SAAS_PHPLIST_DB_NAME
|
return settings.SAAS_PHPLIST_DB_NAME
|
||||||
|
|
||||||
|
def get_account(self):
|
||||||
|
return type(self.instance.account).get_main()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PHPListService, self).validate()
|
super(PHPListService, self).validate()
|
||||||
create = not self.instance.pk
|
create = not self.instance.pk
|
||||||
if create:
|
if create:
|
||||||
db = Database(name=self.get_db_name(), account=self.instance.account)
|
account = self.get_account()
|
||||||
|
db = Database(name=self.get_db_name(), account=account)
|
||||||
try:
|
try:
|
||||||
db.full_clean()
|
db.full_clean()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
@ -73,7 +76,8 @@ class PHPListService(SoftwareService):
|
||||||
def save(self):
|
def save(self):
|
||||||
db_name = self.get_db_name()
|
db_name = self.get_db_name()
|
||||||
db_user = self.get_db_user()
|
db_user = self.get_db_user()
|
||||||
db, db_created = Database.objects.get_or_create(name=db_name, account=self.instance.account)
|
account = self.get_account()
|
||||||
|
db, db_created = account.databases.get_or_create(name=db_name)
|
||||||
user = DatabaseUser.objects.get(username=db_user)
|
user = DatabaseUser.objects.get(username=db_user)
|
||||||
db.users.add(user)
|
db.users.add(user)
|
||||||
self.instance.data = {
|
self.instance.data = {
|
||||||
|
@ -90,9 +94,10 @@ class PHPListService(SoftwareService):
|
||||||
|
|
||||||
def get_related(self):
|
def get_related(self):
|
||||||
related = []
|
related = []
|
||||||
account = self.instance.account
|
account = self.get_account()
|
||||||
|
db_name = self.instance.data.get('db_name')
|
||||||
try:
|
try:
|
||||||
db = account.databases.get(name=self.instance.data.get('db_name'))
|
db = account.databases.get(name=db_name)
|
||||||
except Database.DoesNotExist:
|
except Database.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -47,3 +47,16 @@ SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
|
||||||
|
50
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
|
||||||
|
'secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
SAAS_GITLAB_DOMAIN = getattr(settings, 'SAAS_GITLAB_DOMAIN',
|
||||||
|
'gitlab.orchestra.lan'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,11 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
|
||||||
self.create_webapp_dir(context)
|
self.create_webapp_dir(context)
|
||||||
self.set_under_construction(context)
|
self.set_under_construction(context)
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
|
fpm_config='%(fpm_config)s'
|
||||||
{
|
{
|
||||||
echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s -
|
echo -e "${fpm_config}" | diff -N -I'^\s*;;' %(fpm_path)s -
|
||||||
} || {
|
} || {
|
||||||
echo -e '%(fpm_config)s' > %(fpm_path)s
|
echo -e "${fpm_config}" > %(fpm_path)s
|
||||||
UPDATEDFPM=1
|
UPDATEDFPM=1
|
||||||
}""") % context
|
}""") % context
|
||||||
)
|
)
|
||||||
|
@ -41,20 +42,23 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
|
||||||
self.set_under_construction(context)
|
self.set_under_construction(context)
|
||||||
self.append("mkdir -p %(wrapper_dir)s" % context)
|
self.append("mkdir -p %(wrapper_dir)s" % context)
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
|
wrapper='%(wrapper)s'
|
||||||
{
|
{
|
||||||
echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s -
|
echo -e "${wrapper}" | diff -N -I'^\s*#' %(wrapper_path)s -
|
||||||
} || {
|
} || {
|
||||||
echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1
|
echo -e "${wrapper}" > %(wrapper_path)s; UPDATED_APACHE=1
|
||||||
}""") % context
|
}""") % context
|
||||||
)
|
)
|
||||||
self.append("chmod +x %(wrapper_path)s" % context)
|
self.append("chmod 550 %(wrapper_dir)s" % context)
|
||||||
|
self.append("chmod 550 %(wrapper_path)s" % context)
|
||||||
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
|
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
|
||||||
if context['cmd_options']:
|
if context['cmd_options']:
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
|
cmd_options='%(cmd_options)s'
|
||||||
{
|
{
|
||||||
echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s -
|
echo -e "${cmd_options}" | diff -N -I'^\s*#' %(cmd_options_path)s -
|
||||||
} || {
|
} || {
|
||||||
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
|
echo -e "${cmd_options}" > %(cmd_options_path)s; UPDATED_APACHE=1
|
||||||
}""" ) % context
|
}""" ) % context
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,6 +16,7 @@ WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
|
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
|
||||||
|
# Inside SuExec Document root
|
||||||
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper')
|
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -89,5 +89,6 @@ class AppType(plugins.Plugin):
|
||||||
'app_id': self.instance.id,
|
'app_id': self.instance.id,
|
||||||
'app_name': self.instance.name,
|
'app_name': self.instance.name,
|
||||||
'user': self.instance.account.username,
|
'user': self.instance.account.username,
|
||||||
|
'home': self.instance.account.main_systemuser.get_home(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ class WebalizerApp(AppType):
|
||||||
icon = 'orchestra/icons/apps/Stats.png'
|
icon = 'orchestra/icons/apps/Stats.png'
|
||||||
option_groups = ()
|
option_groups = ()
|
||||||
|
|
||||||
def get_directive(self, webapp):
|
def get_directive(self):
|
||||||
webalizer_path = os.path.join(webapp.get_path(), '%(site_name)s')
|
webalizer_path = os.path.join(self.instance.get_path(), '%(site_name)s')
|
||||||
webalizer_path = os.path.normpath(webalizer_path)
|
webalizer_path = os.path.normpath(webalizer_path)
|
||||||
return ('static', webalizer_path)
|
return ('static', webalizer_path)
|
||||||
|
|
||||||
|
|
|
@ -57,15 +57,6 @@ class PHPApp(AppType):
|
||||||
def get_detail(self):
|
def get_detail(self):
|
||||||
return self.instance.data.get('php_version', '')
|
return self.instance.data.get('php_version', '')
|
||||||
|
|
||||||
def get_context(self):
|
|
||||||
""" context used to format settings """
|
|
||||||
return {
|
|
||||||
'home': self.instance.account.main_systemuser.get_home(),
|
|
||||||
'account': self.instance.account.username,
|
|
||||||
'user': self.instance.account.username,
|
|
||||||
'app_name': self.instance.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_php_init_vars(self, merge=False):
|
def get_php_init_vars(self, merge=False):
|
||||||
"""
|
"""
|
||||||
process php options for inclusion on php.ini
|
process php options for inclusion on php.ini
|
||||||
|
@ -77,17 +68,17 @@ class PHPApp(AppType):
|
||||||
# Get options from the same account and php_version webapps
|
# Get options from the same account and php_version webapps
|
||||||
options = []
|
options = []
|
||||||
php_version = self.get_php_version()
|
php_version = self.get_php_version()
|
||||||
webapps = self.instance.account.webapps.filter(webapp_type=self.instance.type)
|
webapps = self.instance.account.webapps.filter(type=self.instance.type)
|
||||||
for webapp in webapps:
|
for webapp in webapps:
|
||||||
if webapp.type_instance.get_php_version == php_version:
|
if webapp.type_instance.get_php_version == php_version:
|
||||||
options += list(webapp.options.all())
|
options += list(webapp.options.all())
|
||||||
php_options = [option.name for option in type(self).get_php_options()]
|
php_options = [option.name for option in type(self).get_php_options()]
|
||||||
|
enabled_functions = set()
|
||||||
for opt in options:
|
for opt in options:
|
||||||
if opt.name in php_options:
|
if opt.name in php_options:
|
||||||
init_vars[opt.name] = opt.value
|
init_vars[opt.name] = opt.value
|
||||||
enabled_functions = []
|
elif opt.name == 'enabled_functions':
|
||||||
for value in options.filter(name='enabled_functions').values_list('value', flat=True):
|
enabled_functions.union(set(opt.value.split(',')))
|
||||||
enabled_functions += enabled_functions.get().value.split(',')
|
|
||||||
if enabled_functions:
|
if enabled_functions:
|
||||||
disabled_functions = []
|
disabled_functions = []
|
||||||
for function in self.PHP_DISABLED_FUNCTIONS:
|
for function in self.PHP_DISABLED_FUNCTIONS:
|
||||||
|
@ -95,11 +86,18 @@ class PHPApp(AppType):
|
||||||
disabled_functions.append(function)
|
disabled_functions.append(function)
|
||||||
init_vars['dissabled_functions'] = ','.join(disabled_functions)
|
init_vars['dissabled_functions'] = ','.join(disabled_functions)
|
||||||
if self.PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
|
if self.PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
|
||||||
context = self.get_context()
|
context = self.get_directive_context()
|
||||||
error_log_path = os.path.normpath(self.PHP_ERROR_LOG_PATH % context)
|
error_log_path = os.path.normpath(self.PHP_ERROR_LOG_PATH % context)
|
||||||
init_vars['error_log'] = error_log_path
|
init_vars['error_log'] = error_log_path
|
||||||
return init_vars
|
return init_vars
|
||||||
|
|
||||||
|
def get_directive_context(self):
|
||||||
|
context = super(PHPApp, self).get_directive_context()
|
||||||
|
context.update({
|
||||||
|
'php_version': self.get_php_version(),
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
def get_directive(self):
|
def get_directive(self):
|
||||||
context = self.get_directive_context()
|
context = self.get_directive_context()
|
||||||
if self.is_fpm:
|
if self.is_fpm:
|
||||||
|
|
|
@ -12,12 +12,13 @@ from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .directives import SiteDirective
|
from .directives import SiteDirective
|
||||||
from .forms import WebsiteAdminForm
|
from .forms import WebsiteAdminForm, WebsiteDirectiveInlineFormSet
|
||||||
from .models import Content, Website, WebsiteDirective
|
from .models import Content, Website, WebsiteDirective
|
||||||
|
|
||||||
|
|
||||||
class WebsiteDirectiveInline(admin.TabularInline):
|
class WebsiteDirectiveInline(admin.TabularInline):
|
||||||
model = WebsiteDirective
|
model = WebsiteDirective
|
||||||
|
formset = WebsiteDirectiveInlineFormSet
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
DIRECTIVES_HELP_TEXT = {
|
DIRECTIVES_HELP_TEXT = {
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Apache2Backend(ServiceController):
|
||||||
extra_conf += self.get_security(directives)
|
extra_conf += self.get_security(directives)
|
||||||
extra_conf += self.get_redirects(directives)
|
extra_conf += self.get_redirects(directives)
|
||||||
extra_conf += self.get_proxies(directives)
|
extra_conf += self.get_proxies(directives)
|
||||||
|
extra_conf += self.get_saas(directives)
|
||||||
# Order extra conf directives based on directives (longer first)
|
# Order extra conf directives based on directives (longer first)
|
||||||
extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True)
|
extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True)
|
||||||
context['extra_conf'] = '\n'.join([conf for location, conf in extra_conf])
|
context['extra_conf'] = '\n'.join([conf for location, conf in extra_conf])
|
||||||
|
@ -46,7 +47,7 @@ class Apache2Backend(ServiceController):
|
||||||
SuexecUserGroup {{ user }} {{ group }}\
|
SuexecUserGroup {{ user }} {{ group }}\
|
||||||
{% for line in extra_conf.splitlines %}
|
{% for line in extra_conf.splitlines %}
|
||||||
{{ line | safe }}{% endfor %}
|
{{ line | safe }}{% endfor %}
|
||||||
#IncludeOptional /etc/apache2/extra-vhos[t]/{{ site_unique_name }}.con[f]
|
IncludeOptional /etc/apache2/extra-vhos[t]/{{ site_unique_name }}.con[f]
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
""")
|
""")
|
||||||
).render(Context(context))
|
).render(Context(context))
|
||||||
|
@ -80,10 +81,11 @@ class Apache2Backend(ServiceController):
|
||||||
apache_conf += self.render_redirect_https(context)
|
apache_conf += self.render_redirect_https(context)
|
||||||
context['apache_conf'] = apache_conf
|
context['apache_conf'] = apache_conf
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
|
apache_conf='%(apache_conf)s'
|
||||||
{
|
{
|
||||||
echo -e '%(apache_conf)s' | diff -N -I'^\s*#' %(sites_available)s -
|
echo -e "${apache_conf}" | diff -N -I'^\s*#' %(sites_available)s -
|
||||||
} || {
|
} || {
|
||||||
echo -e '%(apache_conf)s' > %(sites_available)s
|
echo -e "${apache_conf}" > %(sites_available)s
|
||||||
UPDATED=1
|
UPDATED=1
|
||||||
}""") % context
|
}""") % context
|
||||||
)
|
)
|
||||||
|
@ -116,7 +118,7 @@ class Apache2Backend(ServiceController):
|
||||||
return directives
|
return directives
|
||||||
|
|
||||||
def get_static_directives(self, context, app_path):
|
def get_static_directives(self, context, app_path):
|
||||||
context['app_path'] = app_path % context
|
context['app_path'] = os.path.normpath(app_path % context)
|
||||||
location = "%(location)s/" % context
|
location = "%(location)s/" % context
|
||||||
directive = "Alias %(location)s/ %(app_path)s/" % context
|
directive = "Alias %(location)s/ %(app_path)s/" % context
|
||||||
return [(location, directive)]
|
return [(location, directive)]
|
||||||
|
@ -128,10 +130,10 @@ class Apache2Backend(ServiceController):
|
||||||
else:
|
else:
|
||||||
# UNIX socket
|
# UNIX socket
|
||||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
|
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
|
||||||
if context['location'] != '/':
|
if context['location']:
|
||||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
|
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
|
||||||
context.update({
|
context.update({
|
||||||
'app_path': app_path,
|
'app_path': os.path.normpath(app_path),
|
||||||
'socket': socket,
|
'socket': socket,
|
||||||
})
|
})
|
||||||
location = "%(location)s/" % context
|
location = "%(location)s/" % context
|
||||||
|
@ -143,7 +145,7 @@ class Apache2Backend(ServiceController):
|
||||||
|
|
||||||
def get_fcgid_directives(self, context, app_path, wrapper_path):
|
def get_fcgid_directives(self, context, app_path, wrapper_path):
|
||||||
context.update({
|
context.update({
|
||||||
'app_path': app_path,
|
'app_path': os.path.normpath(app_path),
|
||||||
'wrapper_path': wrapper_path,
|
'wrapper_path': wrapper_path,
|
||||||
})
|
})
|
||||||
location = "%(location)s/" % context
|
location = "%(location)s/" % context
|
||||||
|
@ -158,16 +160,20 @@ class Apache2Backend(ServiceController):
|
||||||
return [(location, directives)]
|
return [(location, directives)]
|
||||||
|
|
||||||
def get_ssl(self, directives):
|
def get_ssl(self, directives):
|
||||||
config = ''
|
|
||||||
ca = directives.get('ssl_ca')
|
|
||||||
if ca:
|
|
||||||
config += "SSLCACertificateFile %s\n" % ca[0]
|
|
||||||
cert = directives.get('ssl_cert')
|
cert = directives.get('ssl_cert')
|
||||||
if cert:
|
|
||||||
config += "SSLCertificateFile %\n" % cert[0]
|
|
||||||
key = directives.get('ssl_key')
|
key = directives.get('ssl_key')
|
||||||
if key:
|
ca = directives.get('ssl_ca')
|
||||||
config += "SSLCertificateKeyFile %s\n" % key[0]
|
if not (cert and key):
|
||||||
|
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
||||||
|
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
|
||||||
|
ca = [settings.WEBSITES_DEFAULT_SSL_CA]
|
||||||
|
if not (cert and key):
|
||||||
|
return []
|
||||||
|
config = 'SSLEngine on\n'
|
||||||
|
config += "SSLCertificateFile %s\n" % cert[0]
|
||||||
|
config += "SSLCertificateKeyFile %s\n" % key[0]
|
||||||
|
if ca:
|
||||||
|
config += "SSLCACertificateFile %s\n" % ca[0]
|
||||||
return [('', config)]
|
return [('', config)]
|
||||||
|
|
||||||
def get_security(self, directives):
|
def get_security(self, directives):
|
||||||
|
@ -210,13 +216,14 @@ class Apache2Backend(ServiceController):
|
||||||
|
|
||||||
def get_saas(self, directives):
|
def get_saas(self, directives):
|
||||||
saas = []
|
saas = []
|
||||||
for name, value in directives.iteritems():
|
for name, values in directives.iteritems():
|
||||||
if name.endswith('-saas'):
|
if name.endswith('-saas'):
|
||||||
context = {
|
for value in values:
|
||||||
'location': normurlpath(value),
|
context = {
|
||||||
}
|
'location': normurlpath(value),
|
||||||
directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
|
}
|
||||||
saas += self.get_directive(context, directive)
|
directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
|
||||||
|
saas += self.get_directives(directive, context)
|
||||||
return saas
|
return saas
|
||||||
# def get_protections(self, site):
|
# def get_protections(self, site):
|
||||||
# protections = ''
|
# protections = ''
|
||||||
|
@ -280,7 +287,8 @@ class Apache2Backend(ServiceController):
|
||||||
'site_unique_name': site.unique_name,
|
'site_unique_name': site.unique_name,
|
||||||
'user': self.get_username(site),
|
'user': self.get_username(site),
|
||||||
'group': self.get_groupname(site),
|
'group': self.get_groupname(site),
|
||||||
'sites_enabled': "%s.conf" % os.path.join(sites_enabled, site.unique_name),
|
# TODO remove '0-'
|
||||||
|
'sites_enabled': "%s.conf" % os.path.join(sites_enabled, '0-'+site.unique_name),
|
||||||
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
|
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
|
||||||
'access_log': site.get_www_access_log_path(),
|
'access_log': site.get_www_access_log_path(),
|
||||||
'error_log': site.get_www_error_log_path(),
|
'error_log': site.get_www_error_log_path(),
|
||||||
|
|
|
@ -18,7 +18,8 @@ class SiteDirective(Plugin):
|
||||||
SAAS = 'SaaS'
|
SAAS = 'SaaS'
|
||||||
|
|
||||||
help_text = ""
|
help_text = ""
|
||||||
unique = True
|
unique_name = False
|
||||||
|
unique_value = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@cached
|
@cached
|
||||||
|
@ -67,6 +68,7 @@ class Redirect(SiteDirective):
|
||||||
help_text = _("<tt><website path> <destination URL></tt>")
|
help_text = _("<tt><website path> <destination URL></tt>")
|
||||||
regex = r'^[^ ]+\s[^ ]+$'
|
regex = r'^[^ ]+\s[^ ]+$'
|
||||||
group = SiteDirective.HTTPD
|
group = SiteDirective.HTTPD
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class Proxy(SiteDirective):
|
class Proxy(SiteDirective):
|
||||||
|
@ -75,6 +77,7 @@ class Proxy(SiteDirective):
|
||||||
help_text = _("<tt><website path> <target URL></tt>")
|
help_text = _("<tt><website path> <target URL></tt>")
|
||||||
regex = r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$'
|
regex = r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$'
|
||||||
group = SiteDirective.HTTPD
|
group = SiteDirective.HTTPD
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class ErrorDocument(SiteDirective):
|
class ErrorDocument(SiteDirective):
|
||||||
|
@ -87,6 +90,7 @@ class ErrorDocument(SiteDirective):
|
||||||
"<tt> 403 \"Sorry can't allow you access today\"</tt>")
|
"<tt> 403 \"Sorry can't allow you access today\"</tt>")
|
||||||
regex = r'[45]0[0-9]\s.*'
|
regex = r'[45]0[0-9]\s.*'
|
||||||
group = SiteDirective.HTTPD
|
group = SiteDirective.HTTPD
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class SSLCA(SiteDirective):
|
class SSLCA(SiteDirective):
|
||||||
|
@ -95,6 +99,7 @@ class SSLCA(SiteDirective):
|
||||||
help_text = _("Filesystem path of the CA certificate file.")
|
help_text = _("Filesystem path of the CA certificate file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
group = SiteDirective.SSL
|
group = SiteDirective.SSL
|
||||||
|
unique_name = True
|
||||||
|
|
||||||
|
|
||||||
class SSLCert(SiteDirective):
|
class SSLCert(SiteDirective):
|
||||||
|
@ -103,6 +108,7 @@ class SSLCert(SiteDirective):
|
||||||
help_text = _("Filesystem path of the certificate file.")
|
help_text = _("Filesystem path of the certificate file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
group = SiteDirective.SSL
|
group = SiteDirective.SSL
|
||||||
|
unique_name = True
|
||||||
|
|
||||||
|
|
||||||
class SSLKey(SiteDirective):
|
class SSLKey(SiteDirective):
|
||||||
|
@ -111,6 +117,7 @@ class SSLKey(SiteDirective):
|
||||||
help_text = _("Filesystem path of the key file.")
|
help_text = _("Filesystem path of the key file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
group = SiteDirective.SSL
|
group = SiteDirective.SSL
|
||||||
|
unique_name = True
|
||||||
|
|
||||||
|
|
||||||
class SecRuleRemove(SiteDirective):
|
class SecRuleRemove(SiteDirective):
|
||||||
|
@ -123,34 +130,38 @@ class SecRuleRemove(SiteDirective):
|
||||||
|
|
||||||
class SecEngine(SiteDirective):
|
class SecEngine(SiteDirective):
|
||||||
name = 'sec_engine'
|
name = 'sec_engine'
|
||||||
verbose_name = _("Modsecurity engine")
|
verbose_name = _("SecRuleEngine Off")
|
||||||
help_text = _("URL location for disabling modsecurity engine.")
|
help_text = _("URL path with disabled modsecurity engine.")
|
||||||
regex = r'^/[^ ]*$'
|
regex = r'^/[^ ]*$'
|
||||||
group = SiteDirective.SEC
|
group = SiteDirective.SEC
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class WordPressSaaS(SiteDirective):
|
class WordPressSaaS(SiteDirective):
|
||||||
name = 'wordpress-saas'
|
name = 'wordpress-saas'
|
||||||
verbose_name = "WordPress"
|
verbose_name = "WordPress SaaS"
|
||||||
help_text = _("URL location for mounting wordpress multisite.")
|
help_text = _("URL path for mounting wordpress multisite.")
|
||||||
# fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
|
# fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
|
||||||
group = SiteDirective.SAAS
|
group = SiteDirective.SAAS
|
||||||
regex = r'^/[^ ]*$'
|
regex = r'^/[^ ]*$'
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class DokuWikiSaaS(SiteDirective):
|
class DokuWikiSaaS(SiteDirective):
|
||||||
name = 'dokuwiki-saas'
|
name = 'dokuwiki-saas'
|
||||||
verbose_name = "DokuWiki"
|
verbose_name = "DokuWiki SaaS"
|
||||||
help_text = _("URL location for mounting wordpress multisite.")
|
help_text = _("URL path for mounting wordpress multisite.")
|
||||||
# fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
|
# fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
|
||||||
group = SiteDirective.SAAS
|
group = SiteDirective.SAAS
|
||||||
regex = r'^/[^ ]*$'
|
regex = r'^/[^ ]*$'
|
||||||
|
unique_value = True
|
||||||
|
|
||||||
|
|
||||||
class DrupalSaaS(SiteDirective):
|
class DrupalSaaS(SiteDirective):
|
||||||
name = 'drupal-saas'
|
name = 'drupal-saas'
|
||||||
verbose_name = "Drupdal"
|
verbose_name = "Drupdal SaaS"
|
||||||
help_text = _("URL location for mounting wordpress multisite.")
|
help_text = _("URL path for mounting wordpress multisite.")
|
||||||
# fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
|
# fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
|
||||||
group = SiteDirective.SAAS
|
group = SiteDirective.SAAS
|
||||||
regex = r'^/[^ ]*$'
|
regex = r'^/[^ ]*$'
|
||||||
|
unique_value = True
|
||||||
|
|
|
@ -19,3 +19,26 @@ class WebsiteAdminForm(forms.ModelForm):
|
||||||
self.add_error(None, e)
|
self.add_error(None, e)
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteDirectiveInlineFormSet(forms.models.BaseInlineFormSet):
|
||||||
|
""" Validate uniqueness """
|
||||||
|
def clean(self):
|
||||||
|
values = {}
|
||||||
|
for form in self.forms:
|
||||||
|
name = form.cleaned_data.get('name', None)
|
||||||
|
if name is not None:
|
||||||
|
directive = form.instance.directive_class
|
||||||
|
if directive.unique_name and name in values:
|
||||||
|
form.add_error(None, ValidationError(
|
||||||
|
_("Only one %s can be defined.") % directive.get_verbose_name()
|
||||||
|
))
|
||||||
|
value = form.cleaned_data.get('value', None)
|
||||||
|
if value is not None:
|
||||||
|
if directive.unique_value and value in values.get(name, []):
|
||||||
|
form.add_error('value', ValidationError(
|
||||||
|
_("This value is already used by other %s.") % unicode(directive.get_verbose_name())
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
values[name].append(value)
|
||||||
|
except KeyError:
|
||||||
|
values[name] = [value]
|
||||||
|
|
|
@ -76,13 +76,21 @@ WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS
|
||||||
# '')
|
# '')
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_SAAS_DIRECTIVES = getattr(settings, 'WEBAPPS_SAAS_DIRECTIVES', {
|
WEBSITES_SAAS_DIRECTIVES = getattr(settings, 'WEBSITES_SAAS_DIRECTIVES', {
|
||||||
'wordpress-saas': ('fpm', '/home/httpd/wordpress-mu/', '/opt/php/5.4/socks/wordpress-mu.sock'),
|
'wordpress-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock', '/home/httpd/wordpress-mu/'),
|
||||||
'drupal-saas': ('fpm', '/home/httpd/drupal-mu/', '/opt/php/5.4/socks/drupal-mu.sock'),
|
'drupal-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/drupal-mu/'),
|
||||||
'dokuwiki-saas': ('fpm', '/home/httpd/moodle-mu/', '/opt/php/5.4/socks/moodle-mu.sock'),
|
'dokuwiki-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/moodle-mu/'),
|
||||||
# 'moodle-saas': ('fpm', '/home/httpd/moodle-mu/', '/opt/php/5.4/socks/moodle-mu.sock'),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
WEBSITES_DEFAULT_SSL_CERT = getattr(settings, 'WEBSITES_DEFAULT_SSL_CERT',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
WEBSITES_DEFAULT_SSL_KEY = getattr(settings, 'WEBSITES_DEFAULT_SSL_KEY',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
WEBSITES_DEFAULT_SSL_CA = getattr(settings, 'WEBSITES_DEFAULT_SSL_CA',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
|
@ -2,6 +2,8 @@ from django import forms
|
||||||
from django.contrib.auth import forms as auth_forms
|
from django.contrib.auth import forms as auth_forms
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.utils.python import random_ascii
|
||||||
|
|
||||||
from ..core.validators import validate_password
|
from ..core.validators import validate_password
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +22,10 @@ class UserCreationForm(forms.ModelForm):
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput,
|
||||||
help_text=_("Enter the same password as above, for verification."))
|
help_text=_("Enter the same password as above, for verification."))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(UserCreationForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['password1'].help_text = _("Suggestion: %s") % random_ascii(10)
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
password1 = self.cleaned_data.get('password1')
|
password1 = self.cleaned_data.get('password1')
|
||||||
password2 = self.cleaned_data.get('password2')
|
password2 = self.cleaned_data.get('password2')
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ class Plugin(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_change_readonly_fileds(cls):
|
def get_change_readonly_fileds(cls):
|
||||||
return (cls.plugin_field,) + cls.change_readonly_fileds
|
return cls.change_readonly_fileds
|
||||||
|
|
||||||
def clean_data(self):
|
def clean_data(self):
|
||||||
""" model clean, uses cls.serizlier by default """
|
""" model clean, uses cls.serizlier by default """
|
||||||
|
|
|
@ -13,7 +13,7 @@ def import_class(cls):
|
||||||
|
|
||||||
|
|
||||||
def random_ascii(length):
|
def random_ascii(length):
|
||||||
return ''.join([random.choice(string.hexdigits) for i in range(0, length)]).lower()
|
return ''.join([random.SystemRandom().choice(string.hexdigits) for i in range(0, length)]).lower()
|
||||||
|
|
||||||
|
|
||||||
class OrderedSet(collections.MutableSet):
|
class OrderedSet(collections.MutableSet):
|
||||||
|
|
Loading…
Reference in New Issue