diff --git a/TODO.md b/TODO.md index 25804c9a..ab595ef6 100644 --- a/TODO.md +++ b/TODO.md @@ -199,4 +199,11 @@ Php binaries should have this format: /usr/bin/php5.2-cgi * Orchestra global search box on the header, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields -* contain error on plugin missing key (plugin dissabled) +* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently + +* contact.alternative_phone on a phone.tooltip, email:to + + +* better validate options and directives (url locations, filesystem paths, etc..) +* filter php deprecated options out based on version +* Todo get php_version for fcgid wrapper diff --git a/orchestra/apps/mailboxes/backends.py b/orchestra/apps/mailboxes/backends.py index 483c4c93..fb752539 100644 --- a/orchestra/apps/mailboxes/backends.py +++ b/orchestra/apps/mailboxes/backends.py @@ -56,13 +56,13 @@ class MailSystemUserBackend(ServiceController): def delete(self, mailbox): context = self.get_context(mailbox) + self.append('mv %(home)s %(home)s.deleted' % context) self.append(textwrap.dedent(""" { sleep 2 && killall -u %(user)s -s KILL; } & killall -u %(user)s || true userdel %(user)s || true groupdel %(user)s || true""") % context ) - self.append('mv %(home)s %(home)s.deleted' % context) def get_context(self, mailbox): context = { diff --git a/orchestra/apps/orchestration/admin.py b/orchestra/apps/orchestration/admin.py index a74277f5..8c83a0b4 100644 --- a/orchestra/apps/orchestration/admin.py +++ b/orchestra/apps/orchestration/admin.py @@ -25,8 +25,7 @@ STATE_COLORS = { class RouteAdmin(admin.ModelAdmin): list_display = [ - 'id', 'backend', 'host', 'match', 'display_model', 'display_actions', - 'is_active' + 'backend', 'host', 'match', 'display_model', 'display_actions', 'is_active' ] list_editable = ['host', 'match', 'is_active'] list_filter = ['host', 'is_active', 'backend'] @@ -65,7 +64,7 @@ class RouteAdmin(admin.ModelAdmin): """ Include dynamic help text for existing objects """ form = super(RouteAdmin, self).get_form(request, obj=obj, **kwargs) if obj: - form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT[obj.backend] + form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '') return form diff --git a/orchestra/apps/orchestration/manager.py b/orchestra/apps/orchestration/manager.py index d0b220dd..69ca0f4e 100644 --- a/orchestra/apps/orchestration/manager.py +++ b/orchestra/apps/orchestration/manager.py @@ -10,6 +10,7 @@ from orchestra.utils.python import import_class from . import settings from .helpers import send_report from .models import BackendLog +from .signals import pre_action, post_action logger = logging.getLogger(__name__) @@ -65,8 +66,17 @@ def execute(operations, async=False): else: scripts[key][1].append(operation) # Get and call backend action method - method = getattr(scripts[key][0], operation.action) + backend = scripts[key][0] + method = getattr(backend, operation.action) + kwargs = { + 'sender': backend.__class__, + 'backend': backend, + 'instance': operation.instance, + 'action': operation.action, + } + pre_action.send(**kwargs) method(operation.instance) + post_action.send(**kwargs) # Execute scripts on each server threads = [] executions = [] diff --git a/orchestra/apps/orchestration/middlewares.py b/orchestra/apps/orchestration/middlewares.py index 16ee19ca..c65561ea 100644 --- a/orchestra/apps/orchestration/middlewares.py +++ b/orchestra/apps/orchestration/middlewares.py @@ -7,9 +7,9 @@ from django.http.response import HttpResponseServerError from orchestra.utils.python import OrderedSet -from .manager import router from .backends import ServiceBackend from .helpers import message_user +from .manager import router from .models import BackendLog from .models import BackendOperation as Operation @@ -103,7 +103,7 @@ class OperationsMiddleware(object): pass else: update_fields = kwargs.get('update_fields', None) - if update_fields: + if update_fields is not None: # "update_fileds=[]" is a convention for explicitly executing backend # i.e. account.disable() if update_fields != []: diff --git a/orchestra/apps/orchestration/signals.py b/orchestra/apps/orchestration/signals.py new file mode 100644 index 00000000..6f8dc6e3 --- /dev/null +++ b/orchestra/apps/orchestration/signals.py @@ -0,0 +1,6 @@ +import django.dispatch + + +pre_action = django.dispatch.Signal(providing_args=['backend', 'instance', 'action']) + +post_action = django.dispatch.Signal(providing_args=['backend', 'instance', 'action']) diff --git a/orchestra/apps/webapps/admin.py b/orchestra/apps/webapps/admin.py index a472427d..4b8aa003 100644 --- a/orchestra/apps/webapps/admin.py +++ b/orchestra/apps/webapps/admin.py @@ -53,6 +53,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin) inlines = [WebAppOptionInline] readonly_fields = ('account_link',) change_readonly_fields = ('name', 'type') + search_fuelds = ('name', 'account__username') list_prefetch_related = ('content_set__website',) plugin = AppType plugin_field = 'type' diff --git a/orchestra/apps/webapps/backends/php.py b/orchestra/apps/webapps/backends/php.py new file mode 100644 index 00000000..6388cba1 --- /dev/null +++ b/orchestra/apps/webapps/backends/php.py @@ -0,0 +1,177 @@ +import os +import textwrap + +from django.template import Template, Context +from django.utils.translation import ugettext_lazy as _ + +from orchestra.apps.orchestration import ServiceController + +from . import WebAppServiceMixin +from .. import settings + + +class PHPBackend(WebAppServiceMixin, ServiceController): + verbose_name = _("PHP FPM/FCGID") + default_route_match = "webapp.type == 'php'" + + def save(self, webapp): + context = self.get_context(webapp) + if webapp.type_instance.is_fpm: + self.save_fpm(webapp, context) + self.delete_fcgid(webapp, context) + elif webapp.type_instance.is_fcgid: + self.save_fcgid(webapp, context) + self.delete_fpm(webapp, context) + + def save_fpm(self, webapp, context): + self.create_webapp_dir(context) + self.set_under_construction(context) + self.append(textwrap.dedent("""\ + { + echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s - + } || { + echo -e '%(fpm_config)s' > %(fpm_path)s + UPDATEDFPM=1 + }""") % context + ) + + def save_fcgid(self, webapp, context): + self.create_webapp_dir(context) + self.set_under_construction(context) + self.append("mkdir -p %(wrapper_dir)s" % context) + self.append(textwrap.dedent("""\ + { + echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s - + } || { + echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1 + }""") % context + ) + self.append("chmod +x %(wrapper_path)s" % context) + self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context) + if context['cmd_options']: + self.append(textwrap.dedent(""" + { + echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s - + } || { + 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) + if webapp.type_instance.is_fpm: + self.delete_fpm(webapp, context) + elif webapp.type_instance.is_fcgid: + self.delete_fcgid(webapp, context) + self.delete_webapp_dir(context) + + def delete_fpm(self, webapp, context): + self.append("rm -f %(fpm_path)s" % context) + + def delete_fcgid(self, webapp, context): + self.append("rm -f %(wrapper_path)s" % context) + self.append("rm -f %(cmd_options_path)s" % context) + + def commit(self): + if self.content: + self.append(textwrap.dedent(""" + if [[ $UPDATEDFPM == 1 ]]; then + service php5-fpm reload + service php5-fpm start + fi""") + ) + self.append(textwrap.dedent("""\ + if [[ $UPDATED_APACHE == 1 ]]; then + service apache2 reload + fi""") + ) + + def get_fpm_config(self, webapp, context): + context.update({ + 'init_vars': webapp.type_instance.get_php_init_vars(), + 'max_children': webapp.get_options().get('processes', False), + 'request_terminate_timeout': webapp.get_options().get('timeout', False), + }) + context['fpm_listen'] = webapp.type_instance.FPM_LISTEN % context + fpm_config = Template(textwrap.dedent("""\ + ;; {{ banner }} + [{{ user }}] + user = {{ user }} + group = {{ group }} + + listen = {{ fpm_listen | safe }} + listen.owner = {{ user }} + listen.group = {{ group }} + pm = ondemand + {% if max_children %}pm.max_children = {{ max_children }}{% endif %} + {% if request_terminate_timeout %}request_terminate_timeout = {{ request_terminate_timeout }}{% endif %} + {% for name, value in init_vars.iteritems %} + php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %} + """ + )) + return fpm_config.render(Context(context)) + + def get_fcgid_wrapper(self, webapp, context): + opt = webapp.type_instance + # Format PHP init vars + 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': os.path.normpath(settings.WEBAPPS_PHP_CGI_BINARY_PATH % context), + 'php_rc': os.path.normpath(settings.WEBAPPS_PHP_CGI_RC_DIR % context), + 'php_ini_scan': os.path.normpath(settings.WEBAPPS_PHP_CGI_INI_SCAN_DIR % context), + 'php_init_vars': init_vars, + }) + return textwrap.dedent("""\ + #!/bin/sh + # %(banner)s + export PHPRC=%(php_rc)s + export PHP_INI_SCAN_DIR=%(php_ini_scan)s + exec %(php_binary)s %(php_init_vars)s""") % context + + def get_fcgid_cmd_options(self, webapp, context): + maps = { + 'MaxProcesses': webapp.get_options().get('processes', None), + 'IOTimeout': webapp.get_options().get('timeout', None), + } + cmd_options = [] + for directive, value in maps.iteritems(): + if value: + cmd_options.append("%s %s" % (directive, value)) + if cmd_options: + head = '# %(banner)s\nFcgidCmdOptions %(wrapper_path)s' % context + cmd_options.insert(0, head) + return ' \\\n '.join(cmd_options) + + def update_fcgid_context(self, webapp, context): + wrapper_path = webapp.type_instance.FCGID_WRAPPER_PATH % context + context.update({ + 'wrapper': self.get_fcgid_wrapper(webapp, context), + 'wrapper_path': wrapper_path, + 'wrapper_dir': os.path.dirname(wrapper_path), + }) + context.update({ + 'cmd_options': self.get_fcgid_cmd_options(webapp, context), + 'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context, + }) + + def update_fpm_context(self, webapp, context): + context.update({ + 'fpm_config': self.get_fpm_config(webapp, context), + 'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context, + }) + return context + + def get_context(self, webapp): + context = super(PHPBackend, self).get_context(webapp) + context.update({ + 'php_version': webapp.type_instance.get_php_version(), + 'php_version_number': webapp.type_instance.get_php_version_number(), + }) + self.update_fcgid_context(webapp, context) + self.update_fpm_context(webapp, context) + return context diff --git a/orchestra/apps/webapps/backends/phpfcgid.py b/orchestra/apps/webapps/backends/phpfcgid.py deleted file mode 100644 index d8103b84..00000000 --- a/orchestra/apps/webapps/backends/phpfcgid.py +++ /dev/null @@ -1,97 +0,0 @@ -import os -import textwrap - -from django.utils.translation import ugettext_lazy as _ - -from orchestra.apps.orchestration import ServiceController - -from . import WebAppServiceMixin -from .. import settings - - -class PHPFcgidBackend(WebAppServiceMixin, ServiceController): - """ Per-webapp fcgid application """ - verbose_name = _("PHP-Fcgid") - directive = 'fcgid' - default_route_match = "webapp.type_class.php_execution == 'fcgid'" - - def save(self, webapp): - context = self.get_context(webapp) - self.create_webapp_dir(context) - self.set_under_construction(context) - self.append("mkdir -p %(wrapper_dir)s" % context) - self.append(textwrap.dedent("""\ - { - echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s - - } || { - echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1 - }""") % context - ) - self.append("chmod +x %(wrapper_path)s" % context) - self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context) - if context['cmd_options']: - self.append(textwrap.dedent(""" - { - echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s - - } || { - 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) - self.append("rm -f '%(wrapper_path)s'" % context) - self.append("rm -f '%(cmd_options_path)s'" % context) - self.delete_webapp_dir(context) - - def commit(self): - self.append('if [[ $UPDATED_APACHE == 1 ]]; then service apache2 reload; fi') - - def get_fcgid_wrapper(self, webapp, context): - opt = webapp.type_instance - # Format PHP init vars - 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.get_php_binary_path(), - 'php_rc': opt.get_php_rc_path(), - 'php_init_vars': init_vars, - }) - return textwrap.dedent("""\ - #!/bin/sh - # %(banner)s - export PHPRC=%(php_rc)s - exec %(php_binary)s %(php_init_vars)s""") % context - - def get_fcgid_cmd_options(self, webapp, context): - maps = { - 'MaxProcesses': webapp.get_options().get('processes', None), - 'IOTimeout': webapp.get_options().get('timeout', None), - } - cmd_options = [] - for directive, value in maps.iteritems(): - if value: - cmd_options.append("%s %s" % (directive, value)) - if cmd_options: - head = '# %(banner)s\nFcgidCmdOptions %(wrapper_path)s' % context - cmd_options.insert(0, head) - return ' \\\n '.join(cmd_options) - - def get_context(self, webapp): - context = super(PHPFcgidBackend, self).get_context(webapp) - wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context - context.update({ - 'wrapper': self.get_fcgid_wrapper(webapp, context), - 'wrapper_path': wrapper_path, - 'wrapper_dir': os.path.dirname(wrapper_path), - }) - context.update({ - 'cmd_options': self.get_fcgid_cmd_options(webapp, context), - 'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context, - }) - return context diff --git a/orchestra/apps/webapps/backends/phpfpm.py b/orchestra/apps/webapps/backends/phpfpm.py deleted file mode 100644 index 77384459..00000000 --- a/orchestra/apps/webapps/backends/phpfpm.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -import textwrap - -from django.template import Template, Context -from django.utils.translation import ugettext_lazy as _ - -from orchestra.apps.orchestration import ServiceController - -from . import WebAppServiceMixin -from .. import settings - - -class PHPFPMBackend(WebAppServiceMixin, ServiceController): - """ Per-webapp php application """ - verbose_name = _("PHP-FPM") - default_route_match = "webapp.type_class.php_execution == 'fpm'" - - def save(self, webapp): - context = self.get_context(webapp) - self.create_webapp_dir(context) - self.set_under_construction(context) - self.append(textwrap.dedent("""\ - { - echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s - - } || { - echo -e '%(fpm_config)s' > %(fpm_path)s - UPDATEDFPM=1 - }""") % context - ) - - def delete(self, webapp): - context = self.get_context(webapp) - self.append("rm '%(fpm_path)s'" % context) - self.delete_webapp_dir(context) - - def commit(self): - if not self.cmds: - return - super(PHPFPMBackend, self).commit() - self.append(textwrap.dedent(""" - if [[ $UPDATEDFPM == 1 ]]; then - service php5-fpm reload - service php5-fpm start - fi""")) - - def get_fpm_config(self, webapp, context): - context.update({ - '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), - }) - context['fpm_listen'] = settings.WEBAPPS_FPM_LISTEN % context - fpm_config = Template(textwrap.dedent("""\ - ;; {{ banner }} - [{{ user }}] - user = {{ user }} - group = {{ group }} - - listen = {{ fpm_listen | safe }} - listen.owner = {{ user }} - listen.group = {{ group }} - pm = ondemand - {% if max_children %}pm.max_children = {{ max_children }}{% endif %} - {% if request_terminate_timeout %}request_terminate_timeout = {{ request_terminate_timeout }}{% endif %} - {% for name, value in init_vars.iteritems %} - php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %} - """ - )) - return fpm_config.render(Context(context)) - - def get_context(self, webapp): - context = super(PHPFPMBackend, self).get_context(webapp) - context.update({ - 'fpm_config': self.get_fpm_config(webapp, context), - 'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context, - }) - return context diff --git a/orchestra/apps/webapps/settings.py b/orchestra/apps/webapps/settings.py index 022d0753..ae9b844f 100644 --- a/orchestra/apps/webapps/settings.py +++ b/orchestra/apps/webapps/settings.py @@ -27,50 +27,48 @@ WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PA WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH', '') + WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', ( - 'orchestra.apps.webapps.types.php.PHPFPMApp', - 'orchestra.apps.webapps.types.php.PHPFCGIDApp', + 'orchestra.apps.webapps.types.php.PHPApp', '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', + 'orchestra.apps.webapps.types.wordpress.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_VERSIONS = getattr(settings, 'WEBAPPS_PHP_VERSIONS', ( + # Execution modle choose by ending with -fpm or -cgi + ('php-5.4-fpm', 'PHP 5.4 FPM'), + ('php-5.4-cgi', 'PHP 5.4 FCGID'), + ('php-5.3-cgi', 'PHP 5.3 FCGID'), + ('php-5.2-cgi', 'PHP 5.2 FCGID'), + ('php-4-cgi', 'PHP 4 FCGID'), )) -WEBAPPS_PHP_FCGID_DEFAULT_VERSION = getattr(settings, 'WEBAPPS_PHP_FCGID_DEFAULT_VERSION', - '5.4') +WEBAPPS_DEFAULT_PHP_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PHP_VERSION', + '5.4-cgi') 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') + '/usr/bin/php%(php_version_number)s-cgi') -WEBAPPS_PHP_CGI_RC_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_RC_PATH', + +WEBAPPS_PHP_CGI_RC_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_RC_DIR', # Path to php.ini - '/etc/php%(php_version)s/cgi/') + '/etc/php%(php_version_number)s/cgi/') -WEBAPPS_PHP_FPM_VERSIONS = getattr(settings, 'WEBAPPS_PHP_FPM_VERSIONS', ( - ('5.4', '5.4'), -)) +WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR', + # Path to php.ini + '/etc/php%(php_version_number)s/cgi/conf.d') -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 diff --git a/orchestra/apps/webapps/types/__init__.py b/orchestra/apps/webapps/types/__init__.py index 4fa2a773..17fe6aa9 100644 --- a/orchestra/apps/webapps/types/__init__.py +++ b/orchestra/apps/webapps/types/__init__.py @@ -20,7 +20,6 @@ class AppType(plugins.Plugin): unique_name = False option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS, AppOption.PHP) # TODO generic name like 'execution' ? - php_execution = None @classmethod @cached diff --git a/orchestra/apps/webapps/types/misc.py b/orchestra/apps/webapps/types/misc.py index 7a6d3d1a..82829e3f 100644 --- a/orchestra/apps/webapps/types/misc.py +++ b/orchestra/apps/webapps/types/misc.py @@ -9,7 +9,7 @@ from orchestra.plugins.forms import PluginDataForm from ..options import AppOption from . import AppType -from .php import PHPAppType +from .php import PHPApp class StaticApp(AppType): @@ -48,7 +48,7 @@ class SymbolicLinkSerializer(serializers.Serializer): path = serializers.CharField(label=_("Path")) -class SymbolicLinkApp(PHPAppType): +class SymbolicLinkApp(PHPApp): name = 'symbolic-link' verbose_name = "Symbolic link" form = SymbolicLinkForm diff --git a/orchestra/apps/webapps/types/php.py b/orchestra/apps/webapps/types/php.py index b8098544..dc0cdc12 100644 --- a/orchestra/apps/webapps/types/php.py +++ b/orchestra/apps/webapps/types/php.py @@ -1,4 +1,5 @@ import os +import re from django import forms from django.utils.translation import ugettext_lazy as _ @@ -12,12 +13,46 @@ from .. import settings from . import AppType -class PHPAppType(AppType): - FPM = 'fpm' - FCGID = 'fcgid' +help_message = _("Version of PHP used to execute this webapp.
" + "Changing the PHP version may result in application malfunction, " + "make sure that everything continue to work as expected.") + + +class PHPAppForm(PluginDataForm): + php_version = forms.ChoiceField(label=_("PHP version"), + choices=settings.WEBAPPS_PHP_VERSIONS, + initial=settings.WEBAPPS_DEFAULT_PHP_VERSION, + help_text=help_message) + + +class PHPAppSerializer(serializers.Serializer): + php_version = serializers.ChoiceField(label=_("PHP version"), + choices=settings.WEBAPPS_PHP_VERSIONS, + default=settings.WEBAPPS_DEFAULT_PHP_VERSION, + help_text=help_message) + + +class PHPApp(AppType): + name = 'php' + verbose_name = "PHP" + help_text = _("This creates a PHP application under ~/webapps/<app_name>
") + form = PHPAppForm + serializer = PHPAppSerializer + icon = 'orchestra/icons/apps/PHP.png' - php_version = 5.4 - fpm_listen = settings.WEBAPPS_FPM_LISTEN + DEFAULT_PHP_VERSION = settings.WEBAPPS_DEFAULT_PHP_VERSION + PHP_DISABLED_FUNCTIONS = settings.WEBAPPS_PHP_DISABLED_FUNCTIONS + PHP_ERROR_LOG_PATH = settings.WEBAPPS_PHP_ERROR_LOG_PATH + FPM_LISTEN = settings.WEBAPPS_FPM_LISTEN + FCGID_WRAPPER_PATH = settings.WEBAPPS_FCGID_WRAPPER_PATH + + @property + def is_fpm(self): + return self.get_php_version().endswith('-fpm') + + @property + def is_fcgid(self): + return self.get_php_version().endswith('-cgi') def get_context(self): """ context used to format settings """ @@ -46,95 +81,37 @@ class PHPAppType(AppType): enabled_functions += enabled_functions.get().value.split(',') if enabled_functions: disabled_functions = [] - for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS: + for function in self.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: + if self.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) + error_log_path = os.path.normpath(self.PHP_ERROR_LOG_PATH % context) init_vars['error_log'] = error_log_path return init_vars - - -help_message = _("Version of PHP used to execute this webapp.
" - "Changing the PHP version may result in application malfunction, " - "make sure that everything continue to work as expected.") - - -class PHPFPMAppForm(PluginDataForm): - php_version = forms.ChoiceField(label=_("PHP version"), - choices=settings.WEBAPPS_PHP_FPM_VERSIONS, - initial=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION, - help_text=help_message) - - -class PHPFPMAppSerializer(serializers.Serializer): - php_version = serializers.ChoiceField(label=_("PHP version"), - choices=settings.WEBAPPS_PHP_FPM_VERSIONS, - default=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION, - help_text=help_message) - - -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>
" - "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, - help_text=help_message) - - -class PHPFCGIDAppSerializer(serializers.Serializer): - php_version = serializers.ChoiceField(label=_("PHP version"), - choices=settings.WEBAPPS_PHP_FCGID_VERSIONS, - default=settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION, - help_text=help_message) - - -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>
" - "Apache-mod-fcgid will be used to execute PHP files.") - icon = 'orchestra/icons/apps/PHPFCGI.png' - form = PHPFCGIDAppForm - serializer = PHPFCGIDAppSerializer + if self.is_fpm: + 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()) + elif self.is_fcgid: + wrapper_path = os.path.normpath(self.FCGID_WRAPPER_PATH % context) + return ('fcgid', self.instance.get_path(), wrapper_path) + else: + raise ValueError("Unknown directive for php version '%s'" % php_version) - 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_version(self): + default_version = self.DEFAULT_PHP_VERSION + return self.instance.data.get('php_version', default_version) - 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) - + def get_php_version_number(self): + php_version = self.get_php_version() + number = re.findall(r'[0-9]+\.?[0-9]+', php_version) + if len(number) > 1: + raise ValueError("Multiple version number matches for '%'" % php_version) + return number[0] diff --git a/orchestra/apps/webapps/types/wordpress.py b/orchestra/apps/webapps/types/wordpress.py index b835a3a3..5278a5bf 100644 --- a/orchestra/apps/webapps/types/wordpress.py +++ b/orchestra/apps/webapps/types/wordpress.py @@ -9,11 +9,10 @@ from orchestra.utils.python import random_ascii from .. import settings -from .php import (PHPAppType, PHPFCGIDApp, PHPFPMApp, PHPFCGIDAppForm, PHPFCGIDAppSerializer, - PHPFPMAppForm, PHPFPMAppSerializer) +from .php import PHPApp, PHPAppForm, PHPAppSerializer -class WordPressAbstractAppForm(PluginDataForm): +class WordPressAppForm(PHPAppForm): db_name = forms.CharField(label=_("Database name"), help_text=_("Database used for this webapp.")) db_user = forms.CharField(label=_("Database user"),) @@ -21,16 +20,20 @@ class WordPressAbstractAppForm(PluginDataForm): help_text=_("Initial database password.")) -class WordPressAbstractAppSerializer(serializers.Serializer): +class WordPressAppSerializer(PHPAppSerializer): 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' +class WordPressApp(PHPApp): + name = 'wordpress' + verbose_name = "WordPress" + serializer = WordPressAppSerializer + change_form = WordPressAppForm change_readonly_fileds = ('db_name', 'db_user', 'db_pass',) help_text = _("Visit http://<domain.lan>/wp-admin/install.php to finish the installation.") + icon = 'orchestra/icons/apps/WordPress.png' def get_db_name(self): db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account) @@ -46,7 +49,7 @@ class WordPressAbstractApp(object): return random_ascii(10) def validate(self): - super(WordPressAbstractApp, self).validate() + super(WordPressApp, self).validate() create = not self.instance.pk if create: db = Database(name=self.get_db_name(), account=self.instance.account) @@ -79,7 +82,7 @@ class WordPressAbstractApp(object): else: # Trigger related backends for related in self.get_related(): - related.save() + related.save(updated_fields=[]) def delete(self): for related in self.get_related(): @@ -101,23 +104,3 @@ class WordPressAbstractApp(object): 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), {}) diff --git a/orchestra/apps/websites/backends/apache.py b/orchestra/apps/websites/backends/apache.py index f117e02f..dea37cda 100644 --- a/orchestra/apps/websites/backends/apache.py +++ b/orchestra/apps/websites/backends/apache.py @@ -164,11 +164,11 @@ class Apache2Backend(ServiceController): for rules in directives.get('sec_rule_remove', []): for rule in rules.value.split(): config += "SecRuleRemoveById %i\n" % int(rule) - for modsecurity in directives.get('sec_rule_off', []): + for modsecurity in directives.get('sec_engine', []): config += textwrap.dedent("""\ SecRuleEngine off - + """) % modsecurity return config diff --git a/orchestra/apps/websites/backends/webalizer.py b/orchestra/apps/websites/backends/webalizer.py index 440fe898..335fb790 100644 --- a/orchestra/apps/websites/backends/webalizer.py +++ b/orchestra/apps/websites/backends/webalizer.py @@ -11,6 +11,7 @@ from .. import settings class WebalizerBackend(ServiceController): verbose_name = _("Webalizer Content") model = 'websites.Content' + default_route_match = "content.webapp.type == 'webalizer'" def save(self, content): context = self.get_context(content) diff --git a/orchestra/apps/websites/directives.py b/orchestra/apps/websites/directives.py index 7187b1b0..2a80d1c5 100644 --- a/orchestra/apps/websites/directives.py +++ b/orchestra/apps/websites/directives.py @@ -149,5 +149,5 @@ class SecEngine(SiteDirective): name = 'sec_engine' verbose_name = _("Modsecurity engine") help_text = _("URL location for disabling modsecurity engine.") - regex = r'^[^ ]+$' + regex = r'^/[^ ]*$' group = SiteDirective.SEC diff --git a/orchestra/static/orchestra/icons/apps/PHP.png b/orchestra/static/orchestra/icons/apps/PHP.png new file mode 100644 index 00000000..d3bcfb71 Binary files /dev/null and b/orchestra/static/orchestra/icons/apps/PHP.png differ diff --git a/orchestra/static/orchestra/icons/apps/PHP.svg b/orchestra/static/orchestra/icons/apps/PHP.svg index ffa40a38..c4b54a8f 100644 --- a/orchestra/static/orchestra/icons/apps/PHP.svg +++ b/orchestra/static/orchestra/icons/apps/PHP.svg @@ -1,63 +1,203 @@ + + + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="PHP.svg" + inkscape:export-filename="/home/glic3rinu/orchestra/django-orchestra/orchestra/static/orchestra/icons/apps/PHP.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + id="defs3111"> + inkscape:collect="always" + xlink:href="#linearGradient4350" + id="linearGradient2623" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.972701,0,0,0.925949,-23.47387,-13.25574)" + x1="97.728783" + y1="54.517036" + x2="97.728783" + y2="42.400635" /> + + id="stop4352" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + id="stop4354" + offset="1" + style="stop-color:#ffffff;stop-opacity:0;" /> + + + + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop4288" /> + + + + + + + + id="radialGradient3107" + xlink:href="#linearGradient4114" + inkscape:collect="always" /> - - - - - \ No newline at end of file + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/orchestra/utils/humanize.py b/orchestra/utils/humanize.py index 4fa44170..da34d075 100644 --- a/orchestra/utils/humanize.py +++ b/orchestra/utils/humanize.py @@ -64,7 +64,7 @@ def naturaldatetime(date, include_seconds=False): if days == 0: if hours == 0: if minutes > 0: - minutes += float(seconds)/60 + minutes = float(seconds)/60 return ungettext( _('{minutes:.1f} minute{ago}'), _('{minutes:.1f} minutes{ago}'), minutes @@ -77,7 +77,7 @@ def naturaldatetime(date, include_seconds=False): ).format(seconds=seconds, ago=ago) return _('just now') else: - hours += float(minutes)/60 + hours = float(minutes)/60 return ungettext( _('{hours:.1f} hour{ago}'), _('{hours:.1f} hours{ago}'), hours