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-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
|
||||
|
||||
|
||||
|
|
|
@ -107,7 +107,14 @@ class MailmanBackend(ServiceController):
|
|||
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
||||
-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):
|
||||
context = self.get_context_files()
|
||||
|
|
|
@ -38,24 +38,30 @@ def message_user(request, logs):
|
|||
ids = []
|
||||
for log in logs:
|
||||
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:
|
||||
successes += 1
|
||||
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 += '?id__in=%s' % ','.join(map(str, ids))
|
||||
href = '<a href="{}">backends</a>'.format(url)
|
||||
else:
|
||||
url = reverse('admin:orchestration_backendlog_change', args=ids)
|
||||
href = ''
|
||||
if errors:
|
||||
msg = ungettext(
|
||||
_('{errors} out of {total} <a href="{url}">backends</a> has fail to execute.'),
|
||||
_('{errors} out of {total} <a href="{url}">backends</a> have fail to execute.'),
|
||||
_('{errors} out of {total} {href} has fail to execute.'),
|
||||
_('{errors} out of {total} {href} have fail to execute.'),
|
||||
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:
|
||||
msg = ungettext(
|
||||
_('{total} <a href="{url}">backend</a> has been executed.'),
|
||||
_('{total} <a href="{url}">backends</a> have been executed.'),
|
||||
_('{total} {href} has been executed.'),
|
||||
_('{total} {href} have been executed.'),
|
||||
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 threading
|
||||
import traceback
|
||||
|
||||
from django import db
|
||||
from django.core.mail import mail_admins
|
||||
|
||||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import settings
|
||||
from .helpers import send_report
|
||||
from .models import BackendLog
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -29,11 +32,16 @@ def close_connection(execute):
|
|||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
log = execute(*args, **kwargs)
|
||||
except:
|
||||
logger.error('EXCEPTION executing backend %s %s' % (str(args), str(kwargs)))
|
||||
raise
|
||||
except Exception as e:
|
||||
subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
|
||||
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:
|
||||
# Using the wrapper function as threader messenger for the execute output
|
||||
# Absense of it will indicate a failure at this stage
|
||||
wrapper.log = log
|
||||
finally:
|
||||
db.connection.close()
|
||||
|
@ -78,13 +86,18 @@ def execute(operations, async=False):
|
|||
logs = []
|
||||
# collect results
|
||||
for execution, operations in executions:
|
||||
for operation in operations:
|
||||
logger.info("Executed %s" % str(operation))
|
||||
operation.log = execution.log
|
||||
operation.save()
|
||||
stdout = execution.log.stdout.strip()
|
||||
stdout and logger.debug('STDOUT %s', stdout)
|
||||
stderr = execution.log.stderr.strip()
|
||||
stderr and logger.debug('STDERR %s', stderr)
|
||||
logs.append(execution.log)
|
||||
# There is no log if an exception has been rised at the very end of the execution
|
||||
if hasattr(execution, 'log'):
|
||||
for operation in operations:
|
||||
logger.info("Executed %s" % str(operation))
|
||||
operation.log = execution.log
|
||||
operation.save()
|
||||
stdout = execution.log.stdout.strip()
|
||||
stdout and logger.debug('STDOUT %s', stdout)
|
||||
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
|
||||
|
|
|
@ -52,6 +52,8 @@ class BackendLog(models.Model):
|
|||
FAILURE = 'FAILURE'
|
||||
ERROR = 'ERROR'
|
||||
REVOKED = 'REVOKED'
|
||||
# Special state for mocked backendlogs
|
||||
EXCEPTION = 'EXCEPTION'
|
||||
|
||||
STATES = (
|
||||
(RECEIVED, RECEIVED),
|
||||
|
|
|
@ -26,9 +26,9 @@ class PaymentMethod(plugins.Plugin):
|
|||
return plugins
|
||||
|
||||
@classmethod
|
||||
def clean_data(cls, data):
|
||||
def clean_data(cls):
|
||||
""" model clean, uses cls.serializer by default """
|
||||
serializer = cls.serializer(data=data)
|
||||
serializer = cls.serializer(data=self.instance.data)
|
||||
if not serializer.is_valid():
|
||||
serializer.errors.pop('non_field_errors', None)
|
||||
raise ValidationError(serializer.errors)
|
||||
|
@ -43,11 +43,11 @@ class PaymentMethod(plugins.Plugin):
|
|||
self.serializer.plugin = self
|
||||
return self.serializer
|
||||
|
||||
def get_label(self, data):
|
||||
return data[self.label_field]
|
||||
def get_label(self):
|
||||
return self.instance.data[self.label_field]
|
||||
|
||||
def get_number(self, data):
|
||||
return data[self.number_field]
|
||||
def get_number(self):
|
||||
return self.instance.data[self.number_field]
|
||||
|
||||
def get_bill_message(self, source):
|
||||
def get_bill_message(self):
|
||||
return ''
|
||||
|
|
|
@ -45,9 +45,9 @@ class SEPADirectDebit(PaymentMethod):
|
|||
serializer = SEPADirectDebitSerializer
|
||||
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 "
|
||||
" with IBAN number<br><strong>%s</strong>.") % source.number
|
||||
" with IBAN number<br><strong>%s</strong>.") % self.instance.number
|
||||
|
||||
@classmethod
|
||||
def process(cls, transactions):
|
||||
|
|
|
@ -36,27 +36,27 @@ class PaymentSource(models.Model):
|
|||
@cached_property
|
||||
def service_instance(self):
|
||||
""" Per request lived method_instance """
|
||||
return self.method_class()
|
||||
return self.method_class(self)
|
||||
|
||||
@cached_property
|
||||
def label(self):
|
||||
return self.method_instance.get_label(self.data)
|
||||
return self.method_instance.get_label()
|
||||
|
||||
@cached_property
|
||||
def number(self):
|
||||
return self.method_instance.get_number(self.data)
|
||||
return self.method_instance.get_number()
|
||||
|
||||
def get_bill_context(self):
|
||||
method = self.method_instance
|
||||
return {
|
||||
'message': method.get_bill_message(self),
|
||||
'message': method.get_bill_message(),
|
||||
}
|
||||
|
||||
def get_due_delta(self):
|
||||
return self.method_instance.due_delta
|
||||
|
||||
def clean(self):
|
||||
self.data = self.method_instance.clean_data(self.data)
|
||||
self.data = self.method_instance.clean_data()
|
||||
|
||||
|
||||
class TransactionQuerySet(models.QuerySet):
|
||||
|
|
|
@ -38,13 +38,13 @@ class SaaS(models.Model):
|
|||
@cached_property
|
||||
def service_instance(self):
|
||||
""" Per request lived service_instance """
|
||||
return self.service_class()
|
||||
return self.service_class(self)
|
||||
|
||||
def get_site_name(self):
|
||||
return self.service_instance.get_site_name(self)
|
||||
return self.service_instance.get_site_name()
|
||||
|
||||
def clean(self):
|
||||
self.data = self.service_instance.clean_data(self)
|
||||
self.data = self.service_instance.clean_data()
|
||||
|
||||
def set_password(self, password):
|
||||
self.password = password
|
||||
|
|
|
@ -84,10 +84,9 @@ class SoftwareService(plugins.Plugin):
|
|||
plugins.append(import_class(cls))
|
||||
return plugins
|
||||
|
||||
@classmethod
|
||||
def clean_data(cls, saas):
|
||||
def clean_data(cls):
|
||||
""" 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():
|
||||
raise ValidationError(serializer.errors)
|
||||
return serializer.data
|
||||
|
@ -96,8 +95,10 @@ class SoftwareService(plugins.Plugin):
|
|||
def get_change_readonly_fileds(cls):
|
||||
return cls.change_readonly_fileds + ('username',)
|
||||
|
||||
def get_site_name(self, saas):
|
||||
return self.site_name or '.'.join((saas.site_name, self.site_name_base_domain))
|
||||
def get_site_name(self):
|
||||
return self.site_name or '.'.join(
|
||||
(self.instance.site_name, self.site_name_base_domain)
|
||||
)
|
||||
|
||||
def get_form(self):
|
||||
self.form.plugin = self
|
||||
|
|
|
@ -69,11 +69,12 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
|||
url = change_url(website)
|
||||
name = "%s on %s" % (website.name, content.path)
|
||||
websites.append('<a href="%s">%s</a>' % (url, name))
|
||||
add_url = reverse('admin:websites_website_add')
|
||||
# TODO support for preselecting related web app on website
|
||||
add_url += '?account=%s' % webapp.account_id
|
||||
plus = '<strong style="color:green; font-size:12px">+</strong>'
|
||||
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
|
||||
if not websites:
|
||||
add_url = reverse('admin:websites_website_add')
|
||||
# TODO support for preselecting related web app on website
|
||||
add_url += '?account=%s' % webapp.account_id
|
||||
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)
|
||||
display_websites.short_description = _("web sites")
|
||||
display_websites.allow_tags = True
|
||||
|
|
|
@ -9,13 +9,22 @@ class WebAppServiceMixin(object):
|
|||
directive = None
|
||||
|
||||
def create_webapp_dir(self, context):
|
||||
self.append("[[ ! -e %(app_path)s ]] && CREATED=true" % context)
|
||||
self.append("mkdir -p %(app_path)s" % context)
|
||||
self.append("chown %(user)s:%(group)s %(app_path)s" % context)
|
||||
self.append(textwrap.dedent("""\
|
||||
CREATED=0
|
||||
[[ ! -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):
|
||||
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):
|
||||
self.append("rm -fr %(app_path)s" % context)
|
||||
|
|
|
@ -13,7 +13,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
|||
""" Per-webapp fcgid application """
|
||||
verbose_name = _("PHP-Fcgid")
|
||||
directive = 'fcgid'
|
||||
default_route_match = "webapp.type.endswith('-fcgid')"
|
||||
default_route_match = "webapp.type_class.php_execution == 'fcgid'"
|
||||
|
||||
def save(self, 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
|
||||
}""" ) % context
|
||||
)
|
||||
else:
|
||||
self.append("rm -f %(cmd_options_path)s" % context)
|
||||
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
|
@ -50,14 +52,14 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
|||
def get_fcgid_wrapper(self, webapp, context):
|
||||
opt = webapp.type_instance
|
||||
# Format PHP init vars
|
||||
init_vars = opt.get_php_init_vars(webapp)
|
||||
init_vars = opt.get_php_init_vars()
|
||||
if init_vars:
|
||||
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
|
||||
init_vars = ', '.join(init_vars)
|
||||
|
||||
context.update({
|
||||
'php_binary': opt.php_binary,
|
||||
'php_rc': opt.php_rc,
|
||||
'php_binary': opt.get_php_binary_path(),
|
||||
'php_rc': opt.get_php_rc_path(),
|
||||
'php_init_vars': init_vars,
|
||||
})
|
||||
return textwrap.dedent("""\
|
||||
|
@ -82,7 +84,7 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
|||
|
||||
def get_context(self, 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({
|
||||
'wrapper': self.get_fcgid_wrapper(webapp, context),
|
||||
'wrapper_path': wrapper_path,
|
||||
|
|
|
@ -13,7 +13,7 @@ from .. import settings
|
|||
class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
||||
""" Per-webapp php application """
|
||||
verbose_name = _("PHP-FPM")
|
||||
default_route_match = "webapp.type.endswith('-fpm')"
|
||||
default_route_match = "webapp.type_class.php_execution == 'fpm'"
|
||||
|
||||
def save(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
|
@ -45,7 +45,7 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
|||
|
||||
def get_fpm_config(self, webapp, context):
|
||||
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(),
|
||||
'max_children': webapp.get_options().get('processes', 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,
|
||||
})
|
||||
return context
|
||||
|
||||
|
|
|
@ -44,12 +44,12 @@ class WebApp(models.Model):
|
|||
@cached_property
|
||||
def type_instance(self):
|
||||
""" Per request lived type_instance """
|
||||
return self.type_class()
|
||||
return self.type_class(self)
|
||||
|
||||
def clean(self):
|
||||
apptype = self.type_instance
|
||||
apptype.validate(self)
|
||||
self.data = apptype.clean_data(self)
|
||||
apptype.validate()
|
||||
self.data = apptype.clean_data()
|
||||
|
||||
@cached
|
||||
def get_options(self):
|
||||
|
@ -58,7 +58,7 @@ class WebApp(models.Model):
|
|||
}
|
||||
|
||||
def get_directive(self):
|
||||
return self.type_instance.get_directive(self)
|
||||
return self.type_instance.get_directive()
|
||||
|
||||
def get_path(self):
|
||||
context = {
|
||||
|
@ -102,10 +102,10 @@ class WebAppOption(models.Model):
|
|||
@cached_property
|
||||
def option_instance(self):
|
||||
""" Per request lived option instance """
|
||||
return self.option_class()
|
||||
return self.option_class(self)
|
||||
|
||||
def clean(self):
|
||||
self.option_instance.validate(self)
|
||||
self.option_instance.validate()
|
||||
|
||||
|
||||
services.register(WebApp)
|
||||
|
@ -117,9 +117,9 @@ services.register(WebApp)
|
|||
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
||||
def type_save(sender, *args, **kwargs):
|
||||
instance = kwargs['instance']
|
||||
instance.type_instance.save(instance)
|
||||
instance.type_instance.save()
|
||||
|
||||
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
||||
def type_delete(sender, *args, **kwargs):
|
||||
instance = kwargs['instance']
|
||||
instance.type_instance.delete(instance)
|
||||
instance.type_instance.delete()
|
||||
|
|
|
@ -37,12 +37,12 @@ class AppOption(Plugin):
|
|||
groups[opt.group] = [opt]
|
||||
return groups
|
||||
|
||||
def validate(self, option):
|
||||
if self.regex and not re.match(self.regex, option.value):
|
||||
def validate(self):
|
||||
if self.regex and not re.match(self.regex, self.instance.value):
|
||||
raise ValidationError({
|
||||
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
||||
params={
|
||||
'value': option.value,
|
||||
'value': self.instance.value,
|
||||
'regex': self.regex
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -2,7 +2,9 @@ from django.conf import settings
|
|||
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',
|
||||
# '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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
|
@ -25,19 +28,50 @@ WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
|
|||
'')
|
||||
|
||||
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
|
||||
'orchestra.apps.webapps.types.PHP54App',
|
||||
'orchestra.apps.webapps.types.PHP53App',
|
||||
'orchestra.apps.webapps.types.PHP52App',
|
||||
'orchestra.apps.webapps.types.PHP4App',
|
||||
'orchestra.apps.webapps.types.StaticApp',
|
||||
'orchestra.apps.webapps.types.WebalizerApp',
|
||||
'orchestra.apps.webapps.types.WordPressMuApp',
|
||||
'orchestra.apps.webapps.types.DokuWikiMuApp',
|
||||
'orchestra.apps.webapps.types.DrupalMuApp',
|
||||
'orchestra.apps.webapps.types.SymbolicLinkApp',
|
||||
'orchestra.apps.webapps.types.WordPressApp',
|
||||
'orchestra.apps.webapps.types.php.PHPFPMApp',
|
||||
'orchestra.apps.webapps.types.php.PHPFCGIDApp',
|
||||
'orchestra.apps.webapps.types.misc.StaticApp',
|
||||
'orchestra.apps.webapps.types.misc.WebalizerApp',
|
||||
'orchestra.apps.webapps.types.saas.WordPressMuApp',
|
||||
'orchestra.apps.webapps.types.saas.DokuWikiMuApp',
|
||||
'orchestra.apps.webapps.types.saas.DrupalMuApp',
|
||||
'orchestra.apps.webapps.types.misc.SymbolicLinkApp',
|
||||
'orchestra.apps.webapps.types.wordpress.WordPressFPMApp',
|
||||
'orchestra.apps.webapps.types.wordpress.WordPressFCGIDApp',
|
||||
))
|
||||
|
||||
|
||||
|
||||
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',
|
||||
# Server-side path where a under construction stock page is
|
||||
# '/var/www/undercontruction/index.html',
|
||||
|
@ -51,14 +85,6 @@ WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_
|
|||
# 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', [
|
||||
'exec',
|
||||
'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
|
119
orchestra/apps/webapps/types/__init__.py
Normal file
119
orchestra/apps/webapps/types/__init__.py
Normal file
|
@ -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,
|
||||
}
|
||||
|
59
orchestra/apps/webapps/types/misc.py
Normal file
59
orchestra/apps/webapps/types/misc.py
Normal file
|
@ -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',)
|
||||
|
||||
|
131
orchestra/apps/webapps/types/php.py
Normal file
131
orchestra/apps/webapps/types/php.py
Normal file
|
@ -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)
|
||||
|
54
orchestra/apps/webapps/types/saas.py
Normal file
54
orchestra/apps/webapps/types/saas.py
Normal file
|
@ -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
|
123
orchestra/apps/webapps/types/wordpress.py
Normal file
123
orchestra/apps/webapps/types/wordpress.py
Normal file
|
@ -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 re
|
||||
import textwrap
|
||||
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
|
|
@ -8,6 +8,10 @@ class Plugin(object):
|
|||
icon = None
|
||||
change_readonly_fileds = ()
|
||||
|
||||
def __init__(self, instance=None):
|
||||
# Related model instance of this plugin
|
||||
self.instance = instance
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return getattr(cls, 'name', cls.__name__)
|
||||
|
|
Loading…
Reference in a new issue