Split webapps types into separate files
This commit is contained in:
parent
f7aac57a84
commit
b36ca7a248
7
TODO.md
7
TODO.md
|
@ -203,11 +203,12 @@ POST INSTALL
|
||||||
ssh-keygen
|
ssh-keygen
|
||||||
ssh-copy-id root@<server-address>
|
ssh-copy-id root@<server-address>
|
||||||
|
|
||||||
|
Php binaries should have this format: /usr/bin/php5.2-cgi
|
||||||
|
|
||||||
* symbolicLink webapp (link stuff from other places)
|
|
||||||
|
|
||||||
* logs on panle/logs/ ? mkdir ~webapps, backend post save signal?
|
|
||||||
* transaction abortion on backend.generation, transaction fault tolerant on backend.execute()
|
* logs on panel/logs/ ? mkdir ~webapps, backend post save signal?
|
||||||
|
* transaction fault tolerant on backend.execute()
|
||||||
* <IfModule security2_module> and other IfModule on backend SecRule
|
* <IfModule security2_module> and other IfModule on backend SecRule
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,14 @@ class MailmanBackend(ServiceController):
|
||||||
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""") % context
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""") % context
|
||||||
)
|
)
|
||||||
self.append("rmlist -a %(name)s" % context)
|
self.append(textwrap.dedent("""\
|
||||||
|
# Non-existent list archives produce exit code 1
|
||||||
|
exit_code=0
|
||||||
|
rmlist -a %(name)s || exit_code=$?
|
||||||
|
if [[ $exit_code != 0 && $exit_code != 1 ]]; then
|
||||||
|
exit $exit_code
|
||||||
|
fi""") % context
|
||||||
|
)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
context = self.get_context_files()
|
context = self.get_context_files()
|
||||||
|
|
|
@ -38,24 +38,30 @@ def message_user(request, logs):
|
||||||
ids = []
|
ids = []
|
||||||
for log in logs:
|
for log in logs:
|
||||||
total += 1
|
total += 1
|
||||||
ids.append(log.pk)
|
if log.state != log.EXCEPTION:
|
||||||
|
# EXCEPTION logs are not stored on the database
|
||||||
|
ids.append(log.pk)
|
||||||
if log.state == log.SUCCESS:
|
if log.state == log.SUCCESS:
|
||||||
successes += 1
|
successes += 1
|
||||||
errors = total-successes
|
errors = total-successes
|
||||||
if total > 1:
|
if len(ids) == 1:
|
||||||
|
url = reverse('admin:orchestration_backendlog_change', args=ids)
|
||||||
|
href = '<a href="{}">backends</a>'.format(url)
|
||||||
|
elif len(ids) > 1:
|
||||||
url = reverse('admin:orchestration_backendlog_changelist')
|
url = reverse('admin:orchestration_backendlog_changelist')
|
||||||
url += '?id__in=%s' % ','.join(map(str, ids))
|
url += '?id__in=%s' % ','.join(map(str, ids))
|
||||||
|
href = '<a href="{}">backends</a>'.format(url)
|
||||||
else:
|
else:
|
||||||
url = reverse('admin:orchestration_backendlog_change', args=ids)
|
href = ''
|
||||||
if errors:
|
if errors:
|
||||||
msg = ungettext(
|
msg = ungettext(
|
||||||
_('{errors} out of {total} <a href="{url}">backends</a> has fail to execute.'),
|
_('{errors} out of {total} {href} has fail to execute.'),
|
||||||
_('{errors} out of {total} <a href="{url}">backends</a> have fail to execute.'),
|
_('{errors} out of {total} {href} have fail to execute.'),
|
||||||
errors)
|
errors)
|
||||||
messages.error(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
|
messages.error(request, mark_safe(msg.format(errors=errors, total=total, href=href)))
|
||||||
else:
|
else:
|
||||||
msg = ungettext(
|
msg = ungettext(
|
||||||
_('{total} <a href="{url}">backend</a> has been executed.'),
|
_('{total} {href} has been executed.'),
|
||||||
_('{total} <a href="{url}">backends</a> have been executed.'),
|
_('{total} {href} have been executed.'),
|
||||||
total)
|
total)
|
||||||
messages.success(request, mark_safe(msg.format(total=total, url=url)))
|
messages.success(request, mark_safe(msg.format(total=total, href=href)))
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
|
|
||||||
from django import db
|
from django import db
|
||||||
|
from django.core.mail import mail_admins
|
||||||
|
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .helpers import send_report
|
from .helpers import send_report
|
||||||
|
from .models import BackendLog
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -29,11 +32,16 @@ def close_connection(execute):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
log = execute(*args, **kwargs)
|
log = execute(*args, **kwargs)
|
||||||
except:
|
except Exception as e:
|
||||||
logger.error('EXCEPTION executing backend %s %s' % (str(args), str(kwargs)))
|
subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
|
||||||
raise
|
message = traceback.format_exc()
|
||||||
|
logger.error(subject)
|
||||||
|
logger.error(message)
|
||||||
|
mail_admins(subject, message)
|
||||||
|
# We don't propagate the exception further to avoid transaction rollback
|
||||||
else:
|
else:
|
||||||
# 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
|
||||||
wrapper.log = log
|
wrapper.log = log
|
||||||
finally:
|
finally:
|
||||||
db.connection.close()
|
db.connection.close()
|
||||||
|
@ -78,13 +86,18 @@ def execute(operations, async=False):
|
||||||
logs = []
|
logs = []
|
||||||
# collect results
|
# collect results
|
||||||
for execution, operations in executions:
|
for execution, operations in executions:
|
||||||
for operation in operations:
|
# There is no log if an exception has been rised at the very end of the execution
|
||||||
logger.info("Executed %s" % str(operation))
|
if hasattr(execution, 'log'):
|
||||||
operation.log = execution.log
|
for operation in operations:
|
||||||
operation.save()
|
logger.info("Executed %s" % str(operation))
|
||||||
stdout = execution.log.stdout.strip()
|
operation.log = execution.log
|
||||||
stdout and logger.debug('STDOUT %s', stdout)
|
operation.save()
|
||||||
stderr = execution.log.stderr.strip()
|
stdout = execution.log.stdout.strip()
|
||||||
stderr and logger.debug('STDERR %s', stderr)
|
stdout and logger.debug('STDOUT %s', stdout)
|
||||||
logs.append(execution.log)
|
stderr = execution.log.stderr.strip()
|
||||||
|
stderr and logger.debug('STDERR %s', stderr)
|
||||||
|
logs.append(execution.log)
|
||||||
|
else:
|
||||||
|
mocked_log = BackendLog(state=BackendLog.EXCEPTION)
|
||||||
|
logs.append(mocked_log)
|
||||||
return logs
|
return logs
|
||||||
|
|
|
@ -52,6 +52,8 @@ class BackendLog(models.Model):
|
||||||
FAILURE = 'FAILURE'
|
FAILURE = 'FAILURE'
|
||||||
ERROR = 'ERROR'
|
ERROR = 'ERROR'
|
||||||
REVOKED = 'REVOKED'
|
REVOKED = 'REVOKED'
|
||||||
|
# Special state for mocked backendlogs
|
||||||
|
EXCEPTION = 'EXCEPTION'
|
||||||
|
|
||||||
STATES = (
|
STATES = (
|
||||||
(RECEIVED, RECEIVED),
|
(RECEIVED, RECEIVED),
|
||||||
|
|
|
@ -26,9 +26,9 @@ class PaymentMethod(plugins.Plugin):
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clean_data(cls, data):
|
def clean_data(cls):
|
||||||
""" model clean, uses cls.serializer by default """
|
""" model clean, uses cls.serializer by default """
|
||||||
serializer = cls.serializer(data=data)
|
serializer = cls.serializer(data=self.instance.data)
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
serializer.errors.pop('non_field_errors', None)
|
serializer.errors.pop('non_field_errors', None)
|
||||||
raise ValidationError(serializer.errors)
|
raise ValidationError(serializer.errors)
|
||||||
|
@ -43,11 +43,11 @@ class PaymentMethod(plugins.Plugin):
|
||||||
self.serializer.plugin = self
|
self.serializer.plugin = self
|
||||||
return self.serializer
|
return self.serializer
|
||||||
|
|
||||||
def get_label(self, data):
|
def get_label(self):
|
||||||
return data[self.label_field]
|
return self.instance.data[self.label_field]
|
||||||
|
|
||||||
def get_number(self, data):
|
def get_number(self):
|
||||||
return data[self.number_field]
|
return self.instance.data[self.number_field]
|
||||||
|
|
||||||
def get_bill_message(self, source):
|
def get_bill_message(self):
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -45,9 +45,9 @@ class SEPADirectDebit(PaymentMethod):
|
||||||
serializer = SEPADirectDebitSerializer
|
serializer = SEPADirectDebitSerializer
|
||||||
due_delta = datetime.timedelta(days=5)
|
due_delta = datetime.timedelta(days=5)
|
||||||
|
|
||||||
def get_bill_message(self, source):
|
def get_bill_message(self):
|
||||||
return _("This bill will been automatically charged to your bank account "
|
return _("This bill will been automatically charged to your bank account "
|
||||||
" with IBAN number<br><strong>%s</strong>.") % source.number
|
" with IBAN number<br><strong>%s</strong>.") % self.instance.number
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process(cls, transactions):
|
def process(cls, transactions):
|
||||||
|
|
|
@ -36,27 +36,27 @@ class PaymentSource(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_instance(self):
|
def service_instance(self):
|
||||||
""" Per request lived method_instance """
|
""" Per request lived method_instance """
|
||||||
return self.method_class()
|
return self.method_class(self)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def label(self):
|
def label(self):
|
||||||
return self.method_instance.get_label(self.data)
|
return self.method_instance.get_label()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def number(self):
|
def number(self):
|
||||||
return self.method_instance.get_number(self.data)
|
return self.method_instance.get_number()
|
||||||
|
|
||||||
def get_bill_context(self):
|
def get_bill_context(self):
|
||||||
method = self.method_instance
|
method = self.method_instance
|
||||||
return {
|
return {
|
||||||
'message': method.get_bill_message(self),
|
'message': method.get_bill_message(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_due_delta(self):
|
def get_due_delta(self):
|
||||||
return self.method_instance.due_delta
|
return self.method_instance.due_delta
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.data = self.method_instance.clean_data(self.data)
|
self.data = self.method_instance.clean_data()
|
||||||
|
|
||||||
|
|
||||||
class TransactionQuerySet(models.QuerySet):
|
class TransactionQuerySet(models.QuerySet):
|
||||||
|
|
|
@ -38,13 +38,13 @@ class SaaS(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_instance(self):
|
def service_instance(self):
|
||||||
""" Per request lived service_instance """
|
""" Per request lived service_instance """
|
||||||
return self.service_class()
|
return self.service_class(self)
|
||||||
|
|
||||||
def get_site_name(self):
|
def get_site_name(self):
|
||||||
return self.service_instance.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)
|
self.data = self.service_instance.clean_data()
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
|
@ -84,10 +84,9 @@ class SoftwareService(plugins.Plugin):
|
||||||
plugins.append(import_class(cls))
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@classmethod
|
def clean_data(cls):
|
||||||
def clean_data(cls, saas):
|
|
||||||
""" model clean, uses cls.serizlier by default """
|
""" model clean, uses cls.serizlier by default """
|
||||||
serializer = cls.serializer(data=saas.data)
|
serializer = cls.serializer(data=self.instance.data)
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
raise ValidationError(serializer.errors)
|
raise ValidationError(serializer.errors)
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
@ -96,8 +95,10 @@ class SoftwareService(plugins.Plugin):
|
||||||
def get_change_readonly_fileds(cls):
|
def get_change_readonly_fileds(cls):
|
||||||
return cls.change_readonly_fileds + ('username',)
|
return cls.change_readonly_fileds + ('username',)
|
||||||
|
|
||||||
def get_site_name(self, saas):
|
def get_site_name(self):
|
||||||
return self.site_name or '.'.join((saas.site_name, self.site_name_base_domain))
|
return self.site_name or '.'.join(
|
||||||
|
(self.instance.site_name, self.site_name_base_domain)
|
||||||
|
)
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
self.form.plugin = self
|
self.form.plugin = self
|
||||||
|
|
|
@ -69,11 +69,12 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
||||||
url = change_url(website)
|
url = change_url(website)
|
||||||
name = "%s on %s" % (website.name, content.path)
|
name = "%s on %s" % (website.name, content.path)
|
||||||
websites.append('<a href="%s">%s</a>' % (url, name))
|
websites.append('<a href="%s">%s</a>' % (url, name))
|
||||||
add_url = reverse('admin:websites_website_add')
|
if not websites:
|
||||||
# TODO support for preselecting related web app on website
|
add_url = reverse('admin:websites_website_add')
|
||||||
add_url += '?account=%s' % webapp.account_id
|
# TODO support for preselecting related web app on website
|
||||||
plus = '<strong style="color:green; font-size:12px">+</strong>'
|
add_url += '?account=%s' % webapp.account_id
|
||||||
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
|
plus = '<strong style="color:green; font-size:12px">+</strong>'
|
||||||
|
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
|
||||||
return '<br>'.join(websites)
|
return '<br>'.join(websites)
|
||||||
display_websites.short_description = _("web sites")
|
display_websites.short_description = _("web sites")
|
||||||
display_websites.allow_tags = True
|
display_websites.allow_tags = True
|
||||||
|
|
|
@ -9,13 +9,22 @@ class WebAppServiceMixin(object):
|
||||||
directive = None
|
directive = None
|
||||||
|
|
||||||
def create_webapp_dir(self, context):
|
def create_webapp_dir(self, context):
|
||||||
self.append("[[ ! -e %(app_path)s ]] && CREATED=true" % context)
|
self.append(textwrap.dedent("""\
|
||||||
self.append("mkdir -p %(app_path)s" % context)
|
CREATED=0
|
||||||
self.append("chown %(user)s:%(group)s %(app_path)s" % context)
|
[[ ! -e %(app_path)s ]] && CREATED=1
|
||||||
|
mkdir -p %(app_path)s
|
||||||
|
chown %(user)s:%(group)s %(app_path)s
|
||||||
|
""") % context
|
||||||
|
)
|
||||||
|
|
||||||
def set_under_construction(self, context):
|
def set_under_construction(self, context):
|
||||||
if context['under_construction_path']:
|
if context['under_construction_path']:
|
||||||
self.append("[[ $CREATED ]] && cp -r %(under_construction_path)s %(app_path)s" % context)
|
self.append(textwrap.dedent("""\
|
||||||
|
if [[ $CREATED == 1 ]]; then
|
||||||
|
cp -r %(under_construction_path)s %(app_path)s
|
||||||
|
chown -R %(user)s:%(group)s %(app_path)s
|
||||||
|
fi""") % context
|
||||||
|
)
|
||||||
|
|
||||||
def delete_webapp_dir(self, context):
|
def delete_webapp_dir(self, context):
|
||||||
self.append("rm -fr %(app_path)s" % context)
|
self.append("rm -fr %(app_path)s" % context)
|
||||||
|
|
|
@ -13,7 +13,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
||||||
""" Per-webapp fcgid application """
|
""" Per-webapp fcgid application """
|
||||||
verbose_name = _("PHP-Fcgid")
|
verbose_name = _("PHP-Fcgid")
|
||||||
directive = 'fcgid'
|
directive = 'fcgid'
|
||||||
default_route_match = "webapp.type.endswith('-fcgid')"
|
default_route_match = "webapp.type_class.php_execution == 'fcgid'"
|
||||||
|
|
||||||
def save(self, webapp):
|
def save(self, webapp):
|
||||||
context = self.get_context(webapp)
|
context = self.get_context(webapp)
|
||||||
|
@ -37,6 +37,8 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
||||||
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
|
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
|
||||||
}""" ) % context
|
}""" ) % context
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.append("rm -f %(cmd_options_path)s" % context)
|
||||||
|
|
||||||
def delete(self, webapp):
|
def delete(self, webapp):
|
||||||
context = self.get_context(webapp)
|
context = self.get_context(webapp)
|
||||||
|
@ -50,14 +52,14 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
||||||
def get_fcgid_wrapper(self, webapp, context):
|
def get_fcgid_wrapper(self, webapp, context):
|
||||||
opt = webapp.type_instance
|
opt = webapp.type_instance
|
||||||
# Format PHP init vars
|
# Format PHP init vars
|
||||||
init_vars = opt.get_php_init_vars(webapp)
|
init_vars = opt.get_php_init_vars()
|
||||||
if init_vars:
|
if init_vars:
|
||||||
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
|
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
|
||||||
init_vars = ', '.join(init_vars)
|
init_vars = ', '.join(init_vars)
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
'php_binary': opt.php_binary,
|
'php_binary': opt.get_php_binary_path(),
|
||||||
'php_rc': opt.php_rc,
|
'php_rc': opt.get_php_rc_path(),
|
||||||
'php_init_vars': init_vars,
|
'php_init_vars': init_vars,
|
||||||
})
|
})
|
||||||
return textwrap.dedent("""\
|
return textwrap.dedent("""\
|
||||||
|
@ -82,7 +84,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
||||||
|
|
||||||
def get_context(self, webapp):
|
def get_context(self, webapp):
|
||||||
context = super(PHPFcgidBackend, self).get_context(webapp)
|
context = super(PHPFcgidBackend, self).get_context(webapp)
|
||||||
wrapper_path = settings.WEBAPPS_FCGID_PATH % context
|
wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context
|
||||||
context.update({
|
context.update({
|
||||||
'wrapper': self.get_fcgid_wrapper(webapp, context),
|
'wrapper': self.get_fcgid_wrapper(webapp, context),
|
||||||
'wrapper_path': wrapper_path,
|
'wrapper_path': wrapper_path,
|
||||||
|
|
|
@ -13,7 +13,7 @@ from .. import settings
|
||||||
class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
||||||
""" Per-webapp php application """
|
""" Per-webapp php application """
|
||||||
verbose_name = _("PHP-FPM")
|
verbose_name = _("PHP-FPM")
|
||||||
default_route_match = "webapp.type.endswith('-fpm')"
|
default_route_match = "webapp.type_class.php_execution == 'fpm'"
|
||||||
|
|
||||||
def save(self, webapp):
|
def save(self, webapp):
|
||||||
context = self.get_context(webapp)
|
context = self.get_context(webapp)
|
||||||
|
@ -45,7 +45,7 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
||||||
|
|
||||||
def get_fpm_config(self, webapp, context):
|
def get_fpm_config(self, webapp, context):
|
||||||
context.update({
|
context.update({
|
||||||
'init_vars': webapp.type_instance.get_php_init_vars(webapp),
|
'init_vars': webapp.type_instance.get_php_init_vars(),
|
||||||
'fpm_port': webapp.get_fpm_port(),
|
'fpm_port': webapp.get_fpm_port(),
|
||||||
'max_children': webapp.get_options().get('processes', False),
|
'max_children': webapp.get_options().get('processes', False),
|
||||||
'request_terminate_timeout': webapp.get_options().get('timeout', False),
|
'request_terminate_timeout': webapp.get_options().get('timeout', False),
|
||||||
|
@ -76,4 +76,3 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
||||||
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
|
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -44,12 +44,12 @@ class WebApp(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def type_instance(self):
|
def type_instance(self):
|
||||||
""" Per request lived type_instance """
|
""" Per request lived type_instance """
|
||||||
return self.type_class()
|
return self.type_class(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
apptype = self.type_instance
|
apptype = self.type_instance
|
||||||
apptype.validate(self)
|
apptype.validate()
|
||||||
self.data = apptype.clean_data(self)
|
self.data = apptype.clean_data()
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
|
@ -58,7 +58,7 @@ class WebApp(models.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_directive(self):
|
def get_directive(self):
|
||||||
return self.type_instance.get_directive(self)
|
return self.type_instance.get_directive()
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
context = {
|
context = {
|
||||||
|
@ -102,10 +102,10 @@ class WebAppOption(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def option_instance(self):
|
def option_instance(self):
|
||||||
""" Per request lived option instance """
|
""" Per request lived option instance """
|
||||||
return self.option_class()
|
return self.option_class(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.option_instance.validate(self)
|
self.option_instance.validate()
|
||||||
|
|
||||||
|
|
||||||
services.register(WebApp)
|
services.register(WebApp)
|
||||||
|
@ -117,9 +117,9 @@ services.register(WebApp)
|
||||||
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
||||||
def type_save(sender, *args, **kwargs):
|
def type_save(sender, *args, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
instance.type_instance.save(instance)
|
instance.type_instance.save()
|
||||||
|
|
||||||
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
||||||
def type_delete(sender, *args, **kwargs):
|
def type_delete(sender, *args, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
instance.type_instance.delete(instance)
|
instance.type_instance.delete()
|
||||||
|
|
|
@ -37,12 +37,12 @@ class AppOption(Plugin):
|
||||||
groups[opt.group] = [opt]
|
groups[opt.group] = [opt]
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def validate(self, option):
|
def validate(self):
|
||||||
if self.regex and not re.match(self.regex, option.value):
|
if self.regex and not re.match(self.regex, self.instance.value):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
||||||
params={
|
params={
|
||||||
'value': option.value,
|
'value': self.instance.value,
|
||||||
'regex': self.regex
|
'regex': self.regex
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,9 @@ from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '%(home)s/webapps/%(app_name)s/')
|
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT',
|
||||||
|
'%(home)s/webapps/%(app_name)s/')
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
|
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
|
||||||
# '127.0.0.1:9%(app_id)03d
|
# '127.0.0.1:9%(app_id)03d
|
||||||
|
@ -13,11 +15,12 @@ WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
|
||||||
'/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf')
|
'/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf')
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FCGID_PATH = getattr(settings, 'WEBAPPS_FCGID_PATH',
|
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
|
||||||
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper')
|
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper')
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PATH',
|
WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PATH',
|
||||||
|
# Loaded by Apache
|
||||||
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf')
|
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf')
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,19 +28,50 @@ WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
|
||||||
'')
|
'')
|
||||||
|
|
||||||
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
|
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
|
||||||
'orchestra.apps.webapps.types.PHP54App',
|
'orchestra.apps.webapps.types.php.PHPFPMApp',
|
||||||
'orchestra.apps.webapps.types.PHP53App',
|
'orchestra.apps.webapps.types.php.PHPFCGIDApp',
|
||||||
'orchestra.apps.webapps.types.PHP52App',
|
'orchestra.apps.webapps.types.misc.StaticApp',
|
||||||
'orchestra.apps.webapps.types.PHP4App',
|
'orchestra.apps.webapps.types.misc.WebalizerApp',
|
||||||
'orchestra.apps.webapps.types.StaticApp',
|
'orchestra.apps.webapps.types.saas.WordPressMuApp',
|
||||||
'orchestra.apps.webapps.types.WebalizerApp',
|
'orchestra.apps.webapps.types.saas.DokuWikiMuApp',
|
||||||
'orchestra.apps.webapps.types.WordPressMuApp',
|
'orchestra.apps.webapps.types.saas.DrupalMuApp',
|
||||||
'orchestra.apps.webapps.types.DokuWikiMuApp',
|
'orchestra.apps.webapps.types.misc.SymbolicLinkApp',
|
||||||
'orchestra.apps.webapps.types.DrupalMuApp',
|
'orchestra.apps.webapps.types.wordpress.WordPressFPMApp',
|
||||||
'orchestra.apps.webapps.types.SymbolicLinkApp',
|
'orchestra.apps.webapps.types.wordpress.WordPressFCGIDApp',
|
||||||
'orchestra.apps.webapps.types.WordPressApp',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_PHP_FCGID_VERSIONS = getattr(settings, 'WEBAPPS_PHP_FCGID_VERSIONS', (
|
||||||
|
('5.4', '5.4'),
|
||||||
|
('5.3', '5.3'),
|
||||||
|
('5.2', '5.2'),
|
||||||
|
('4', '4'),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_PHP_FCGID_DEFAULT_VERSION = getattr(settings, 'WEBAPPS_PHP_FCGID_DEFAULT_VERSION',
|
||||||
|
'5.4')
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH',
|
||||||
|
# Path of the cgi binary used by fcgid
|
||||||
|
'/usr/bin/php%(php_version)s-cgi')
|
||||||
|
|
||||||
|
WEBAPPS_PHP_CGI_RC_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_RC_PATH',
|
||||||
|
# Path to php.ini
|
||||||
|
'/etc/php%(php_version)s/cgi/')
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_PHP_FPM_VERSIONS = getattr(settings, 'WEBAPPS_PHP_FPM_VERSIONS', (
|
||||||
|
('5.4', '5.4'),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_PHP_FPM_DEFAULT_VERSION = getattr(settings, 'WEBAPPS_PHP_DEFAULT_VERSION',
|
||||||
|
'5.4')
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
|
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
|
||||||
# Server-side path where a under construction stock page is
|
# Server-side path where a under construction stock page is
|
||||||
# '/var/www/undercontruction/index.html',
|
# '/var/www/undercontruction/index.html',
|
||||||
|
@ -51,14 +85,6 @@ WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_
|
||||||
# WEBAPPS_TYPES[webapp_type] = value
|
# WEBAPPS_TYPES[webapp_type] = value
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_DEFAULT_TYPE = getattr(settings, 'WEBAPPS_DEFAULT_TYPE', 'php5.5')
|
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_DEFAULT_HTTPS_CERT = getattr(settings, 'WEBAPPS_DEFAULT_HTTPS_CERT',
|
|
||||||
('/etc/apache2/cert', '/etc/apache2/cert.key')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTION', [
|
WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTION', [
|
||||||
'exec',
|
'exec',
|
||||||
'passthru',
|
'passthru',
|
||||||
|
|
|
@ -1,408 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra import plugins
|
|
||||||
from orchestra.plugins.forms import PluginDataForm
|
|
||||||
from orchestra.core import validators
|
|
||||||
from orchestra.forms import widgets
|
|
||||||
from orchestra.utils.functional import cached
|
|
||||||
from orchestra.utils.python import import_class
|
|
||||||
|
|
||||||
from . import options, settings
|
|
||||||
from .options import AppOption
|
|
||||||
|
|
||||||
|
|
||||||
class AppType(plugins.Plugin):
|
|
||||||
name = None
|
|
||||||
verbose_name = ""
|
|
||||||
help_text= ""
|
|
||||||
form = PluginDataForm
|
|
||||||
change_form = None
|
|
||||||
serializer = None
|
|
||||||
icon = 'orchestra/icons/apps.png'
|
|
||||||
unique_name = False
|
|
||||||
option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS, AppOption.PHP)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@cached
|
|
||||||
def get_plugins(cls):
|
|
||||||
plugins = []
|
|
||||||
for cls in settings.WEBAPPS_TYPES:
|
|
||||||
plugins.append(import_class(cls))
|
|
||||||
return plugins
|
|
||||||
|
|
||||||
def clean_data(self, webapp):
|
|
||||||
""" model clean, uses cls.serizlier by default """
|
|
||||||
if self.serializer:
|
|
||||||
serializer = self.serializer(data=webapp.data)
|
|
||||||
if not serializer.is_valid():
|
|
||||||
raise ValidationError(serializer.errors)
|
|
||||||
return serializer.data
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_directive(self, webapp):
|
|
||||||
return ('static', webapp.get_path())
|
|
||||||
|
|
||||||
def get_form(self):
|
|
||||||
self.form.plugin = self
|
|
||||||
self.form.plugin_field = 'type'
|
|
||||||
return self.form
|
|
||||||
|
|
||||||
def get_change_form(self):
|
|
||||||
form = self.change_form or self.form
|
|
||||||
form.plugin = self
|
|
||||||
form.plugin_field = 'type'
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_serializer(self):
|
|
||||||
self.serializer.plugin = self
|
|
||||||
return self.serializer
|
|
||||||
|
|
||||||
def validate(self, instance):
|
|
||||||
""" Unique name validation """
|
|
||||||
if self.unique_name:
|
|
||||||
if not instance.pk and Webapp.objects.filter(name=instance.name, type=instance.type).exists():
|
|
||||||
raise ValidationError({
|
|
||||||
'name': _("A WordPress blog with this name already exists."),
|
|
||||||
})
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@cached
|
|
||||||
def get_php_options(cls):
|
|
||||||
php_version = getattr(cls, 'php_version', 1)
|
|
||||||
php_options = AppOption.get_option_groups()[AppOption.PHP]
|
|
||||||
return [op for op in php_options if getattr(cls, 'deprecated', 99) > php_version]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@cached
|
|
||||||
def get_options(cls):
|
|
||||||
""" Get enabled options based on cls.option_groups """
|
|
||||||
groups = AppOption.get_option_groups()
|
|
||||||
options = []
|
|
||||||
for group in cls.option_groups:
|
|
||||||
group_options = groups[group]
|
|
||||||
if group == AppOption.PHP:
|
|
||||||
group_options = cls.get_php_options()
|
|
||||||
if group is None:
|
|
||||||
options.insert(0, (group, group_options))
|
|
||||||
else:
|
|
||||||
options.append((group, group_options))
|
|
||||||
return options
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_options_choices(cls):
|
|
||||||
""" Generates grouped choices ready to use in Field.choices """
|
|
||||||
# generators can not be @cached
|
|
||||||
yield (None, '-------')
|
|
||||||
for group, options in cls.get_options():
|
|
||||||
if group is None:
|
|
||||||
for option in options:
|
|
||||||
yield (option.name, option.verbose_name)
|
|
||||||
else:
|
|
||||||
yield (group, [(op.name, op.verbose_name) for op in options])
|
|
||||||
|
|
||||||
def save(self, instance):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete(self, instance):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_related_objects(self, instance):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_directive_context(self, webapp):
|
|
||||||
return {
|
|
||||||
'app_id': webapp.id,
|
|
||||||
'app_name': webapp.name,
|
|
||||||
'user': webapp.account.username,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PHPAppType(AppType):
|
|
||||||
php_version = 5.4
|
|
||||||
fpm_listen = settings.WEBAPPS_FPM_LISTEN
|
|
||||||
|
|
||||||
def get_directive(self, webapp):
|
|
||||||
context = self.get_directive_context(webapp)
|
|
||||||
socket_type = 'unix'
|
|
||||||
if ':' in self.fpm_listen:
|
|
||||||
socket_type = 'tcp'
|
|
||||||
socket = self.fpm_listen % context
|
|
||||||
return ('fpm', socket_type, socket, webapp.get_path())
|
|
||||||
|
|
||||||
def get_context(self, webapp):
|
|
||||||
""" context used to format settings """
|
|
||||||
return {
|
|
||||||
'home': webapp.account.main_systemuser.get_home(),
|
|
||||||
'account': webapp.account.username,
|
|
||||||
'user': webapp.account.username,
|
|
||||||
'app_name': webapp.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_php_init_vars(self, webapp, per_account=False):
|
|
||||||
"""
|
|
||||||
process php options for inclusion on php.ini
|
|
||||||
per_account=True merges all (account, webapp.type) options
|
|
||||||
"""
|
|
||||||
init_vars = {}
|
|
||||||
options = webapp.options.all()
|
|
||||||
if per_account:
|
|
||||||
options = webapp.account.webapps.filter(webapp_type=webapp.type)
|
|
||||||
php_options = [option.name for option in type(self).get_php_options()]
|
|
||||||
for opt in options:
|
|
||||||
if opt.name in php_options:
|
|
||||||
init_vars[opt.name] = opt.value
|
|
||||||
enabled_functions = []
|
|
||||||
for value in options.filter(name='enabled_functions').values_list('value', flat=True):
|
|
||||||
enabled_functions += enabled_functions.get().value.split(',')
|
|
||||||
if enabled_functions:
|
|
||||||
disabled_functions = []
|
|
||||||
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
|
|
||||||
if function not in enabled_functions:
|
|
||||||
disabled_functions.append(function)
|
|
||||||
init_vars['dissabled_functions'] = ','.join(disabled_functions)
|
|
||||||
if settings.WEBAPPS_PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
|
|
||||||
context = self.get_context(webapp)
|
|
||||||
error_log_path = os.path.normpath(settings.WEBAPPS_PHP_ERROR_LOG_PATH % context)
|
|
||||||
init_vars['error_log'] = error_log_path
|
|
||||||
return init_vars
|
|
||||||
|
|
||||||
|
|
||||||
class PHP54App(PHPAppType):
|
|
||||||
name = 'php5.4-fpm'
|
|
||||||
php_version = 5.4
|
|
||||||
verbose_name = "PHP 5.4 FPM"
|
|
||||||
help_text = _("This creates a PHP5.5 application under ~/webapps/<app_name><br>"
|
|
||||||
"PHP-FPM will be used to execute PHP files.")
|
|
||||||
icon = 'orchestra/icons/apps/PHPFPM.png'
|
|
||||||
|
|
||||||
|
|
||||||
class PHP53App(PHPAppType):
|
|
||||||
name = 'php5.3-fcgid'
|
|
||||||
php_version = 5.3
|
|
||||||
php_binary = '/usr/bin/php5-cgi'
|
|
||||||
php_rc = '/etc/php5/cgi/'
|
|
||||||
verbose_name = "PHP 5.3 FCGID"
|
|
||||||
help_text = _("This creates a PHP5.3 application under ~/webapps/<app_name><br>"
|
|
||||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
|
||||||
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
|
||||||
|
|
||||||
def get_directive(self, webapp):
|
|
||||||
context = self.get_directive_context(webapp)
|
|
||||||
wrapper_path = os.path.normpath(settings.WEBAPPS_FCGID_PATH % context)
|
|
||||||
return ('fcgid', webapp.get_path(), wrapper_path)
|
|
||||||
|
|
||||||
|
|
||||||
class PHP52App(PHP53App):
|
|
||||||
name = 'php5.2-fcgid'
|
|
||||||
php_version = 5.2
|
|
||||||
php_binary = '/usr/bin/php5.2-cgi'
|
|
||||||
php_rc = '/etc/php5.2/cgi/'
|
|
||||||
verbose_name = "PHP 5.2 FCGID"
|
|
||||||
help_text = _("This creates a PHP5.2 application under ~/webapps/<app_name><br>"
|
|
||||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
|
||||||
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
|
||||||
|
|
||||||
|
|
||||||
class PHP4App(PHP53App):
|
|
||||||
name = 'php4-fcgid'
|
|
||||||
php_version = 4
|
|
||||||
php_binary = '/usr/bin/php4-cgi'
|
|
||||||
verbose_name = "PHP 4 FCGID"
|
|
||||||
help_text = _("This creates a PHP4 application under ~/webapps/<app_name><br>"
|
|
||||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
|
||||||
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
|
||||||
|
|
||||||
|
|
||||||
class StaticApp(AppType):
|
|
||||||
name = 'static'
|
|
||||||
verbose_name = "Static"
|
|
||||||
help_text = _("This creates a Static application under ~/webapps/<app_name><br>"
|
|
||||||
"Apache2 will be used to serve static content and execute CGI files.")
|
|
||||||
icon = 'orchestra/icons/apps/Static.png'
|
|
||||||
option_groups = (AppOption.FILESYSTEM,)
|
|
||||||
|
|
||||||
|
|
||||||
class WebalizerApp(AppType):
|
|
||||||
name = 'webalizer'
|
|
||||||
verbose_name = "Webalizer"
|
|
||||||
directive = ('static', '%(app_path)s%(site_name)s')
|
|
||||||
help_text = _("This creates a Webalizer application under "
|
|
||||||
"~/webapps/<app_name>-<site_name>")
|
|
||||||
icon = 'orchestra/icons/apps/Stats.png'
|
|
||||||
option_groups = ()
|
|
||||||
|
|
||||||
def get_directive(self, webapp):
|
|
||||||
webalizer_path = os.path.join(webapp.get_path(), '%(site_name)s')
|
|
||||||
webalizer_path = os.path.normpath(webalizer_path)
|
|
||||||
return ('static', webalizer_path)
|
|
||||||
|
|
||||||
|
|
||||||
class WordPressMuApp(PHPAppType):
|
|
||||||
name = 'wordpress-mu'
|
|
||||||
verbose_name = "WordPress (SaaS)"
|
|
||||||
directive = ('fpm', 'fcgi://127.0.0.1:8990/home/httpd/wordpress-mu/')
|
|
||||||
help_text = _("This creates a WordPress site on a multi-tenant WordPress server.<br>"
|
|
||||||
"By default this blog is accessible via <app_name>.blogs.orchestra.lan")
|
|
||||||
icon = 'orchestra/icons/apps/WordPressMu.png'
|
|
||||||
unique_name = True
|
|
||||||
option_groups = ()
|
|
||||||
fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
|
|
||||||
|
|
||||||
|
|
||||||
class DokuWikiMuApp(PHPAppType):
|
|
||||||
name = 'dokuwiki-mu'
|
|
||||||
verbose_name = "DokuWiki (SaaS)"
|
|
||||||
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
|
||||||
help_text = _("This create a DokuWiki wiki into a shared DokuWiki server.<br>"
|
|
||||||
"By default this wiki is accessible via <app_name>.wikis.orchestra.lan")
|
|
||||||
icon = 'orchestra/icons/apps/DokuWikiMu.png'
|
|
||||||
unique_name = True
|
|
||||||
option_groups = ()
|
|
||||||
fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
|
|
||||||
|
|
||||||
|
|
||||||
class MoodleMuApp(PHPAppType):
|
|
||||||
name = 'moodle-mu'
|
|
||||||
verbose_name = "Moodle (SaaS)"
|
|
||||||
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
|
||||||
help_text = _("This create a Moodle site into a shared Moodle server.<br>"
|
|
||||||
"By default this wiki is accessible via <app_name>.moodle.orchestra.lan")
|
|
||||||
icon = 'orchestra/icons/apps/MoodleMu.png'
|
|
||||||
unique_name = True
|
|
||||||
option_groups = ()
|
|
||||||
fpm_listen = settings.WEBAPPS_MOODLEMU_LISTEN
|
|
||||||
|
|
||||||
|
|
||||||
class DrupalMuApp(PHPAppType):
|
|
||||||
name = 'drupal-mu'
|
|
||||||
verbose_name = "Drupdal (SaaS)"
|
|
||||||
directive = ('fpm', 'fcgi://127.0.0.1:8991/home/httpd/drupal-mu/')
|
|
||||||
help_text = _("This creates a Drupal site into a multi-tenant Drupal server.<br>"
|
|
||||||
"The installation will be completed after visiting "
|
|
||||||
"http://<app_name>.drupal.orchestra.lan/install.php?profile=standard<br>"
|
|
||||||
"By default this site will be accessible via <app_name>.drupal.orchestra.lan")
|
|
||||||
icon = 'orchestra/icons/apps/DrupalMu.png'
|
|
||||||
unique_name = True
|
|
||||||
option_groups = ()
|
|
||||||
fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
|
|
||||||
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from orchestra.forms import widgets
|
|
||||||
class SymbolicLinkForm(PluginDataForm):
|
|
||||||
path = forms.CharField(label=_("Path"), widget=forms.TextInput(attrs={'size':'100'}),
|
|
||||||
help_text=_("Path for the origin of the symbolic link."))
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolicLinkSerializer(serializers.Serializer):
|
|
||||||
path = serializers.CharField(label=_("Path"))
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolicLinkApp(PHPAppType):
|
|
||||||
name = 'symbolic-link'
|
|
||||||
verbose_name = "Symbolic link"
|
|
||||||
form = SymbolicLinkForm
|
|
||||||
serializer = SymbolicLinkSerializer
|
|
||||||
icon = 'orchestra/icons/apps/SymbolicLink.png'
|
|
||||||
change_readonly_fileds = ('path',)
|
|
||||||
|
|
||||||
|
|
||||||
class WordPressForm(PluginDataForm):
|
|
||||||
db_name = forms.CharField(label=_("Database name"),
|
|
||||||
help_text=_("Database used for this webapp."))
|
|
||||||
db_user = forms.CharField(label=_("Database user"),)
|
|
||||||
db_pass = forms.CharField(label=_("Database user password"),
|
|
||||||
help_text=_("Initial database password."))
|
|
||||||
|
|
||||||
|
|
||||||
class WordPressSerializer(serializers.Serializer):
|
|
||||||
db_name = serializers.CharField(label=_("Database name"), required=False)
|
|
||||||
db_user = serializers.CharField(label=_("Database user"), required=False)
|
|
||||||
db_pass = serializers.CharField(label=_("Database user password"), required=False)
|
|
||||||
|
|
||||||
|
|
||||||
from orchestra.apps.databases.models import Database, DatabaseUser
|
|
||||||
from orchestra.utils.python import random_ascii
|
|
||||||
|
|
||||||
|
|
||||||
class WordPressApp(PHPAppType):
|
|
||||||
name = 'wordpress'
|
|
||||||
verbose_name = "WordPress"
|
|
||||||
icon = 'orchestra/icons/apps/WordPress.png'
|
|
||||||
change_form = WordPressForm
|
|
||||||
serializer = WordPressSerializer
|
|
||||||
change_readonly_fileds = ('db_name', 'db_user', 'db_pass',)
|
|
||||||
help_text = _("Visit http://<domain.lan>/wp-admin/install.php to finish the installation.")
|
|
||||||
|
|
||||||
def get_db_name(self, webapp):
|
|
||||||
db_name = 'wp_%s_%s' % (webapp.name, webapp.account)
|
|
||||||
# Limit for mysql database names
|
|
||||||
return db_name[:65]
|
|
||||||
|
|
||||||
def get_db_user(self, webapp):
|
|
||||||
db_name = self.get_db_name(webapp)
|
|
||||||
# Limit for mysql user names
|
|
||||||
return db_name[:17]
|
|
||||||
|
|
||||||
def get_db_pass(self):
|
|
||||||
return random_ascii(10)
|
|
||||||
|
|
||||||
def validate(self, webapp):
|
|
||||||
create = not webapp.pk
|
|
||||||
if create:
|
|
||||||
db = Database(name=self.get_db_name(webapp), account=webapp.account)
|
|
||||||
user = DatabaseUser(username=self.get_db_user(webapp), password=self.get_db_pass(),
|
|
||||||
account=webapp.account)
|
|
||||||
for obj in (db, user):
|
|
||||||
try:
|
|
||||||
obj.full_clean()
|
|
||||||
except ValidationError, e:
|
|
||||||
raise ValidationError({
|
|
||||||
'name': e.messages,
|
|
||||||
})
|
|
||||||
|
|
||||||
def save(self, webapp):
|
|
||||||
create = not webapp.pk
|
|
||||||
if create:
|
|
||||||
db_name = self.get_db_name(webapp)
|
|
||||||
db_user = self.get_db_user(webapp)
|
|
||||||
db_pass = self.get_db_pass()
|
|
||||||
db = Database.objects.create(name=db_name, account=webapp.account)
|
|
||||||
user = DatabaseUser(username=db_user, account=webapp.account)
|
|
||||||
user.set_password(db_pass)
|
|
||||||
user.save()
|
|
||||||
db.users.add(user)
|
|
||||||
webapp.data = {
|
|
||||||
'db_name': db_name,
|
|
||||||
'db_user': db_user,
|
|
||||||
'db_pass': db_pass,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Trigger related backends
|
|
||||||
for related in self.get_related(webapp):
|
|
||||||
related.save()
|
|
||||||
|
|
||||||
def delete(self, webapp):
|
|
||||||
for related in self.get_related(webapp):
|
|
||||||
related.delete()
|
|
||||||
|
|
||||||
def get_related(self, webapp):
|
|
||||||
related = []
|
|
||||||
try:
|
|
||||||
db_user = DatabaseUser.objects.get(username=webapp.data.get('db_user'))
|
|
||||||
except DatabaseUser.DoesNotExist:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
related.append(db_user)
|
|
||||||
try:
|
|
||||||
db = Database.objects.get(name=webapp.data.get('db_name'))
|
|
||||||
except Database.DoesNotExist:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
related.append(db)
|
|
||||||
return related
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
from orchestra import plugins
|
||||||
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
from orchestra.utils.functional import cached
|
||||||
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
from ..options import AppOption
|
||||||
|
|
||||||
|
|
||||||
|
class AppType(plugins.Plugin):
|
||||||
|
name = None
|
||||||
|
verbose_name = ""
|
||||||
|
help_text= ""
|
||||||
|
form = PluginDataForm
|
||||||
|
change_form = None
|
||||||
|
serializer = None
|
||||||
|
icon = 'orchestra/icons/apps.png'
|
||||||
|
unique_name = False
|
||||||
|
option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS, AppOption.PHP)
|
||||||
|
# TODO generic name like 'execution' ?
|
||||||
|
php_execution = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@cached
|
||||||
|
def get_plugins(cls):
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.WEBAPPS_TYPES:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
def clean_data(self):
|
||||||
|
""" model clean, uses cls.serizlier by default """
|
||||||
|
if self.serializer:
|
||||||
|
serializer = self.serializer(data=self.instance.data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
raise ValidationError(serializer.errors)
|
||||||
|
return serializer.data
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_directive(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
self.form.plugin = self
|
||||||
|
self.form.plugin_field = 'type'
|
||||||
|
return self.form
|
||||||
|
|
||||||
|
def get_change_form(self):
|
||||||
|
form = self.change_form or self.form
|
||||||
|
form.plugin = self
|
||||||
|
form.plugin_field = 'type'
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_serializer(self):
|
||||||
|
self.serializer.plugin = self
|
||||||
|
return self.serializer
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
""" Unique name validation """
|
||||||
|
if self.unique_name:
|
||||||
|
if not self.instance.pk and Webapp.objects.filter(name=self.instance.name, type=self.instance.type).exists():
|
||||||
|
raise ValidationError({
|
||||||
|
'name': _("A WordPress blog with this name already exists."),
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@cached
|
||||||
|
def get_php_options(cls):
|
||||||
|
# TODO validate php options once a php version has been selected (deprecated directives)
|
||||||
|
php_version = getattr(cls, 'php_version', 1)
|
||||||
|
php_options = AppOption.get_option_groups()[AppOption.PHP]
|
||||||
|
return [op for op in php_options if getattr(cls, 'deprecated', 99) > php_version]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@cached
|
||||||
|
def get_options(cls):
|
||||||
|
""" Get enabled options based on cls.option_groups """
|
||||||
|
groups = AppOption.get_option_groups()
|
||||||
|
options = []
|
||||||
|
for group in cls.option_groups:
|
||||||
|
group_options = groups[group]
|
||||||
|
if group == AppOption.PHP:
|
||||||
|
group_options = cls.get_php_options()
|
||||||
|
if group is None:
|
||||||
|
options.insert(0, (group, group_options))
|
||||||
|
else:
|
||||||
|
options.append((group, group_options))
|
||||||
|
return options
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_options_choices(cls):
|
||||||
|
""" Generates grouped choices ready to use in Field.choices """
|
||||||
|
# generators can not be @cached
|
||||||
|
yield (None, '-------')
|
||||||
|
for group, options in cls.get_options():
|
||||||
|
if group is None:
|
||||||
|
for option in options:
|
||||||
|
yield (option.name, option.verbose_name)
|
||||||
|
else:
|
||||||
|
yield (group, [(op.name, op.verbose_name) for op in options])
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_related_objects(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_directive_context(self):
|
||||||
|
return {
|
||||||
|
'app_id': self.instance.id,
|
||||||
|
'app_name': self.instance.name,
|
||||||
|
'user': self.instance.account.username,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
|
||||||
|
from ..options import AppOption
|
||||||
|
|
||||||
|
from . import AppType
|
||||||
|
from .php import PHPAppType
|
||||||
|
|
||||||
|
|
||||||
|
class StaticApp(AppType):
|
||||||
|
name = 'static'
|
||||||
|
verbose_name = "Static"
|
||||||
|
help_text = _("This creates a Static application under ~/webapps/<app_name><br>"
|
||||||
|
"Apache2 will be used to serve static content and execute CGI files.")
|
||||||
|
icon = 'orchestra/icons/apps/Static.png'
|
||||||
|
option_groups = (AppOption.FILESYSTEM,)
|
||||||
|
|
||||||
|
def get_directive(self):
|
||||||
|
return ('static', self.instance.get_path())
|
||||||
|
|
||||||
|
|
||||||
|
class WebalizerApp(AppType):
|
||||||
|
name = 'webalizer'
|
||||||
|
verbose_name = "Webalizer"
|
||||||
|
directive = ('static', '%(app_path)s%(site_name)s')
|
||||||
|
help_text = _("This creates a Webalizer application under "
|
||||||
|
"~/webapps/<app_name>-<site_name>")
|
||||||
|
icon = 'orchestra/icons/apps/Stats.png'
|
||||||
|
option_groups = ()
|
||||||
|
|
||||||
|
def get_directive(self, webapp):
|
||||||
|
webalizer_path = os.path.join(webapp.get_path(), '%(site_name)s')
|
||||||
|
webalizer_path = os.path.normpath(webalizer_path)
|
||||||
|
return ('static', webalizer_path)
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolicLinkForm(PluginDataForm):
|
||||||
|
path = forms.CharField(label=_("Path"), widget=forms.TextInput(attrs={'size':'100'}),
|
||||||
|
help_text=_("Path for the origin of the symbolic link."))
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolicLinkSerializer(serializers.Serializer):
|
||||||
|
path = serializers.CharField(label=_("Path"))
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolicLinkApp(PHPAppType):
|
||||||
|
name = 'symbolic-link'
|
||||||
|
verbose_name = "Symbolic link"
|
||||||
|
form = SymbolicLinkForm
|
||||||
|
serializer = SymbolicLinkSerializer
|
||||||
|
icon = 'orchestra/icons/apps/SymbolicLink.png'
|
||||||
|
change_readonly_fileds = ('path',)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from orchestra.forms import widgets
|
||||||
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
from . import AppType
|
||||||
|
|
||||||
|
|
||||||
|
class PHPAppType(AppType):
|
||||||
|
FPM = 'fpm'
|
||||||
|
FCGID = 'fcgid'
|
||||||
|
|
||||||
|
php_version = 5.4
|
||||||
|
fpm_listen = settings.WEBAPPS_FPM_LISTEN
|
||||||
|
|
||||||
|
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, per_account=False):
|
||||||
|
"""
|
||||||
|
process php options for inclusion on php.ini
|
||||||
|
per_account=True merges all (account, webapp.type) options
|
||||||
|
"""
|
||||||
|
init_vars = {}
|
||||||
|
options = self.instance.options.all()
|
||||||
|
if per_account:
|
||||||
|
options = self.instance.account.webapps.filter(webapp_type=self.instance.type)
|
||||||
|
php_options = [option.name for option in type(self).get_php_options()]
|
||||||
|
for opt in options:
|
||||||
|
if opt.name in php_options:
|
||||||
|
init_vars[opt.name] = opt.value
|
||||||
|
enabled_functions = []
|
||||||
|
for value in options.filter(name='enabled_functions').values_list('value', flat=True):
|
||||||
|
enabled_functions += enabled_functions.get().value.split(',')
|
||||||
|
if enabled_functions:
|
||||||
|
disabled_functions = []
|
||||||
|
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
|
||||||
|
if function not in enabled_functions:
|
||||||
|
disabled_functions.append(function)
|
||||||
|
init_vars['dissabled_functions'] = ','.join(disabled_functions)
|
||||||
|
if settings.WEBAPPS_PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
|
||||||
|
context = self.get_context()
|
||||||
|
error_log_path = os.path.normpath(settings.WEBAPPS_PHP_ERROR_LOG_PATH % context)
|
||||||
|
init_vars['error_log'] = error_log_path
|
||||||
|
return init_vars
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFPMAppForm(PluginDataForm):
|
||||||
|
php_version = forms.ChoiceField(label=_("PHP version"),
|
||||||
|
choices=settings.WEBAPPS_PHP_FPM_VERSIONS,
|
||||||
|
initial=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFPMAppSerializer(serializers.Serializer):
|
||||||
|
php_version = serializers.ChoiceField(label=_("PHP version"),
|
||||||
|
choices=settings.WEBAPPS_PHP_FPM_VERSIONS,
|
||||||
|
default=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFPMApp(PHPAppType):
|
||||||
|
name = 'php-fpm'
|
||||||
|
php_execution = PHPAppType.FPM
|
||||||
|
verbose_name = "PHP FPM"
|
||||||
|
help_text = _("This creates a PHP application under ~/webapps/<app_name><br>"
|
||||||
|
"PHP-FPM will be used to execute PHP files.")
|
||||||
|
icon = 'orchestra/icons/apps/PHPFPM.png'
|
||||||
|
form = PHPFPMAppForm
|
||||||
|
serializer = PHPFPMAppSerializer
|
||||||
|
|
||||||
|
def get_directive(self):
|
||||||
|
context = self.get_directive_context()
|
||||||
|
socket_type = 'unix'
|
||||||
|
if ':' in self.fpm_listen:
|
||||||
|
socket_type = 'tcp'
|
||||||
|
socket = self.fpm_listen % context
|
||||||
|
return ('fpm', socket_type, socket, self.instance.get_path())
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFCGIDAppForm(PluginDataForm):
|
||||||
|
php_version = forms.ChoiceField(label=_("PHP version"),
|
||||||
|
choices=settings.WEBAPPS_PHP_FCGID_VERSIONS,
|
||||||
|
initial=settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFCGIDAppSerializer(serializers.Serializer):
|
||||||
|
php_version = serializers.ChoiceField(label=_("PHP version"),
|
||||||
|
choices=settings.WEBAPPS_PHP_FCGID_VERSIONS,
|
||||||
|
default=settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
class PHPFCGIDApp(PHPAppType):
|
||||||
|
name = 'php-fcgid'
|
||||||
|
php_execution = PHPAppType.FCGID
|
||||||
|
verbose_name = "PHP FCGID"
|
||||||
|
help_text = _("This creates a PHP application under ~/webapps/<app_name><br>"
|
||||||
|
"Apache-mod-fcgid will be used to execute PHP files.")
|
||||||
|
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
||||||
|
form = PHPFCGIDAppForm
|
||||||
|
serializer = PHPFCGIDAppSerializer
|
||||||
|
|
||||||
|
def get_directive(self):
|
||||||
|
context = self.get_directive_context()
|
||||||
|
wrapper_path = os.path.normpath(settings.WEBAPPS_FCGID_PATH % context)
|
||||||
|
return ('fcgid', self.instance.get_path(), wrapper_path)
|
||||||
|
|
||||||
|
def get_php_binary_path(self):
|
||||||
|
default_version = settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION
|
||||||
|
context = {
|
||||||
|
'php_version': self.instance.data.get('php_version', default_version)
|
||||||
|
}
|
||||||
|
return os.path.normpath(settings.WEBAPPS_PHP_CGI_BINARY_PATH % context)
|
||||||
|
|
||||||
|
def get_php_rc_path(self):
|
||||||
|
default_version = settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION
|
||||||
|
context = {
|
||||||
|
'php_version': self.instance.data.get('php_version', default_version)
|
||||||
|
}
|
||||||
|
return os.path.normpath(settings.WEBAPPS_PHP_CGI_RC_PATH % context)
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from . import AppType
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressMuApp(AppType):
|
||||||
|
name = 'wordpress-mu'
|
||||||
|
verbose_name = "WordPress (SaaS)"
|
||||||
|
directive = ('fpm', 'fcgi://127.0.0.1:8990/home/httpd/wordpress-mu/')
|
||||||
|
help_text = _("This creates a WordPress site on a multi-tenant WordPress server.<br>"
|
||||||
|
"By default this blog is accessible via <app_name>.blogs.orchestra.lan")
|
||||||
|
icon = 'orchestra/icons/apps/WordPressMu.png'
|
||||||
|
unique_name = True
|
||||||
|
option_groups = ()
|
||||||
|
fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
|
||||||
|
|
||||||
|
|
||||||
|
class DokuWikiMuApp(AppType):
|
||||||
|
name = 'dokuwiki-mu'
|
||||||
|
verbose_name = "DokuWiki (SaaS)"
|
||||||
|
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
||||||
|
help_text = _("This create a DokuWiki wiki into a shared DokuWiki server.<br>"
|
||||||
|
"By default this wiki is accessible via <app_name>.wikis.orchestra.lan")
|
||||||
|
icon = 'orchestra/icons/apps/DokuWikiMu.png'
|
||||||
|
unique_name = True
|
||||||
|
option_groups = ()
|
||||||
|
fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
|
||||||
|
|
||||||
|
|
||||||
|
class MoodleMuApp(AppType):
|
||||||
|
name = 'moodle-mu'
|
||||||
|
verbose_name = "Moodle (SaaS)"
|
||||||
|
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
||||||
|
help_text = _("This create a Moodle site into a shared Moodle server.<br>"
|
||||||
|
"By default this wiki is accessible via <app_name>.moodle.orchestra.lan")
|
||||||
|
icon = 'orchestra/icons/apps/MoodleMu.png'
|
||||||
|
unique_name = True
|
||||||
|
option_groups = ()
|
||||||
|
fpm_listen = settings.WEBAPPS_MOODLEMU_LISTEN
|
||||||
|
|
||||||
|
|
||||||
|
class DrupalMuApp(AppType):
|
||||||
|
name = 'drupal-mu'
|
||||||
|
verbose_name = "Drupdal (SaaS)"
|
||||||
|
directive = ('fpm', 'fcgi://127.0.0.1:8991/home/httpd/drupal-mu/')
|
||||||
|
help_text = _("This creates a Drupal site into a multi-tenant Drupal server.<br>"
|
||||||
|
"The installation will be completed after visiting "
|
||||||
|
"http://<app_name>.drupal.orchestra.lan/install.php?profile=standard<br>"
|
||||||
|
"By default this site will be accessible via <app_name>.drupal.orchestra.lan")
|
||||||
|
icon = 'orchestra/icons/apps/DrupalMu.png'
|
||||||
|
unique_name = True
|
||||||
|
option_groups = ()
|
||||||
|
fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
|
|
@ -0,0 +1,123 @@
|
||||||
|
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.databases.models import Database, DatabaseUser
|
||||||
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
from orchestra.utils.python import random_ascii
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
from .php import (PHPAppType, PHPFCGIDApp, PHPFPMApp, PHPFCGIDAppForm, PHPFCGIDAppSerializer,
|
||||||
|
PHPFPMAppForm, PHPFPMAppSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressAbstractAppForm(PluginDataForm):
|
||||||
|
db_name = forms.CharField(label=_("Database name"),
|
||||||
|
help_text=_("Database used for this webapp."))
|
||||||
|
db_user = forms.CharField(label=_("Database user"),)
|
||||||
|
db_pass = forms.CharField(label=_("Database user password"),
|
||||||
|
help_text=_("Initial database password."))
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressAbstractAppSerializer(serializers.Serializer):
|
||||||
|
db_name = serializers.CharField(label=_("Database name"), required=False)
|
||||||
|
db_user = serializers.CharField(label=_("Database user"), required=False)
|
||||||
|
db_pass = serializers.CharField(label=_("Database user password"), required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressAbstractApp(object):
|
||||||
|
icon = 'orchestra/icons/apps/WordPress.png'
|
||||||
|
change_readonly_fileds = ('db_name', 'db_user', 'db_pass',)
|
||||||
|
help_text = _("Visit http://<domain.lan>/wp-admin/install.php to finish the installation.")
|
||||||
|
|
||||||
|
def get_db_name(self):
|
||||||
|
db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account)
|
||||||
|
# Limit for mysql database names
|
||||||
|
return db_name[:65]
|
||||||
|
|
||||||
|
def get_db_user(self):
|
||||||
|
db_name = self.get_db_name()
|
||||||
|
# Limit for mysql user names
|
||||||
|
return db_name[:16]
|
||||||
|
|
||||||
|
def get_db_pass(self):
|
||||||
|
return random_ascii(10)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
super(WordPressAbstractApp, self).validate()
|
||||||
|
create = not self.instance.pk
|
||||||
|
if create:
|
||||||
|
db = Database(name=self.get_db_name(), account=self.instance.account)
|
||||||
|
user = DatabaseUser(username=self.get_db_user(), password=self.get_db_pass(),
|
||||||
|
account=self.instance.account)
|
||||||
|
for obj in (db, user):
|
||||||
|
try:
|
||||||
|
obj.full_clean()
|
||||||
|
except ValidationError as e:
|
||||||
|
raise ValidationError({
|
||||||
|
'name': e.messages,
|
||||||
|
})
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
create = not self.instance.pk
|
||||||
|
if create:
|
||||||
|
db_name = self.get_db_name()
|
||||||
|
db_user = self.get_db_user()
|
||||||
|
db_pass = self.get_db_pass()
|
||||||
|
db = Database.objects.create(name=db_name, account=self.instance.account)
|
||||||
|
user = DatabaseUser(username=db_user, account=self.instance.account)
|
||||||
|
user.set_password(db_pass)
|
||||||
|
user.save()
|
||||||
|
db.users.add(user)
|
||||||
|
self.instance.data = {
|
||||||
|
'db_name': db_name,
|
||||||
|
'db_user': db_user,
|
||||||
|
'db_pass': db_pass,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Trigger related backends
|
||||||
|
for related in self.get_related():
|
||||||
|
related.save()
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
for related in self.get_related():
|
||||||
|
related.delete()
|
||||||
|
|
||||||
|
def get_related(self):
|
||||||
|
related = []
|
||||||
|
account = self.instance.account
|
||||||
|
try:
|
||||||
|
db_user = account.databaseusers.get(username=self.instance.data.get('db_user'))
|
||||||
|
except DatabaseUser.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
related.append(db_user)
|
||||||
|
try:
|
||||||
|
db = account.databases.get(name=self.instance.data.get('db_name'))
|
||||||
|
except Database.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
related.append(db)
|
||||||
|
return related
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressFPMApp(WordPressAbstractApp, PHPFPMApp):
|
||||||
|
name = 'wordpress-fpm'
|
||||||
|
php_execution = PHPAppType.FPM
|
||||||
|
verbose_name = "WordPress (FPM)"
|
||||||
|
serializer = type('WordPressFPMSerializer',
|
||||||
|
(WordPressAbstractAppSerializer, PHPFPMAppSerializer), {})
|
||||||
|
change_form = type('WordPressFPMForm',
|
||||||
|
(WordPressAbstractAppForm, PHPFPMAppForm), {})
|
||||||
|
|
||||||
|
|
||||||
|
class WordPressFCGIDApp(WordPressAbstractApp, PHPFCGIDApp):
|
||||||
|
name = 'wordpress-fcgid'
|
||||||
|
php_execution = PHPAppType.FCGID
|
||||||
|
verbose_name = "WordPress (FCGID)"
|
||||||
|
serializer = type('WordPressFCGIDSerializer',
|
||||||
|
(WordPressAbstractAppSerializer, PHPFCGIDAppSerializer), {})
|
||||||
|
change_form = type('WordPressFCGIDForm',
|
||||||
|
(WordPressAbstractAppForm, PHPFCGIDAppForm), {})
|
|
@ -1,6 +1,6 @@
|
||||||
import textwrap
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
|
@ -8,6 +8,10 @@ class Plugin(object):
|
||||||
icon = None
|
icon = None
|
||||||
change_readonly_fileds = ()
|
change_readonly_fileds = ()
|
||||||
|
|
||||||
|
def __init__(self, instance=None):
|
||||||
|
# Related model instance of this plugin
|
||||||
|
self.instance = instance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
return getattr(cls, 'name', cls.__name__)
|
return getattr(cls, 'name', cls.__name__)
|
||||||
|
|
Loading…
Reference in New Issue