diff --git a/TODO.md b/TODO.md index 0815b9bd..0fd3659e 100644 --- a/TODO.md +++ b/TODO.md @@ -285,15 +285,12 @@ https://code.djangoproject.com/ticket/24576 # Apache restart fails: detect if appache running, and execute start # Change crons, create cron for deleted webapps and users * UNIFY PHP FPM settings name -# virtualhost name name-account? +# virtualhost name: name-account? * add a delay to changes on the webserver apache to no overwelm it with backend executions? * replace unique_name by natural_key? * do not require contact or create default * send signals for backend triggers -* monitor resources with multiple backends not possible to identify the correct last execution, allow to specify backends only once or change backend.model = 'resources.MonitorData' * force ignore slack billing period overridig when billing * fpm reload starts new pools? -* more processes for webmail, or use fpm pool * rename resource.monitors to resource.backends ? - * abstract model classes enabling overriding? diff --git a/orchestra/contrib/webapps/models.py b/orchestra/contrib/webapps/models.py index e9b8b7b8..90fcf216 100644 --- a/orchestra/contrib/webapps/models.py +++ b/orchestra/contrib/webapps/models.py @@ -57,7 +57,7 @@ class WebApp(models.Model): @cached def get_options(self): - return OrderedDict((opt.name, opt.value) for opt in self.options.all()) + return OrderedDict((opt.name, opt.value) for opt in self.options.all().order_by('name')) def get_directive(self): return self.type_instance.get_directive() diff --git a/orchestra/contrib/webapps/options.py b/orchestra/contrib/webapps/options.py index a6f52b1f..ee19a337 100644 --- a/orchestra/contrib/webapps/options.py +++ b/orchestra/contrib/webapps/options.py @@ -92,9 +92,9 @@ class Processes(AppOption): group = AppOption.PROCESS -class PHPEnabledFunctions(PHPAppOption): - name = 'enabled_functions' - verbose_name = _("Enabled functions") +class PHPEnableFunctions(PHPAppOption): + name = 'enable_functions' + verbose_name = _("Enable functions") help_text = ','.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS) regex = r'^[\w\.,-]+$' diff --git a/orchestra/contrib/webapps/settings.py b/orchestra/contrib/webapps/settings.py index 399a1439..c4cd4f2d 100644 --- a/orchestra/contrib/webapps/settings.py +++ b/orchestra/contrib/webapps/settings.py @@ -139,7 +139,7 @@ WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', ( 'orchestra.contrib.webapps.options.PublicRoot', 'orchestra.contrib.webapps.options.Timeout', 'orchestra.contrib.webapps.options.Processes', - 'orchestra.contrib.webapps.options.PHPEnabledFunctions', + 'orchestra.contrib.webapps.options.PHPEnableFunctions', 'orchestra.contrib.webapps.options.PHPAllowURLInclude', 'orchestra.contrib.webapps.options.PHPAllowURLFopen', 'orchestra.contrib.webapps.options.PHPAutoAppendFile', diff --git a/orchestra/contrib/webapps/types/php.py b/orchestra/contrib/webapps/types/php.py index 5d05c7bc..69374370 100644 --- a/orchestra/contrib/webapps/types/php.py +++ b/orchestra/contrib/webapps/types/php.py @@ -71,7 +71,7 @@ class PHPApp(AppType): per_account=True merges all (account, webapp.type) options """ init_vars = OrderedDict() - options = self.instance.options.all() + options = self.instance.options.all().order_by('name') if merge: # Get options from the same account and php_version webapps options = [] @@ -84,7 +84,7 @@ class PHPApp(AppType): enabled_functions = set() for opt in options: if opt.name in php_options: - if opt.name == 'enabled_functions': + if opt.name == 'enable_functions': enabled_functions = enabled_functions.union(set(opt.value.split(','))) else: init_vars[opt.name] = opt.value @@ -93,7 +93,7 @@ class PHPApp(AppType): for function in self.PHP_DISABLED_FUNCTIONS: if function not in enabled_functions: disabled_functions.append(function) - init_vars['dissabled_functions'] = ','.join(disabled_functions) + init_vars['disable_functions'] = ','.join(disabled_functions) timeout = self.instance.options.filter(name='timeout').first() if timeout: # Give a little slack here diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py index e5a8545e..6519cd82 100644 --- a/orchestra/contrib/websites/backends/apache.py +++ b/orchestra/contrib/websites/backends/apache.py @@ -25,7 +25,7 @@ class Apache2Backend(ServiceController): def render_virtual_host(self, site, context, ssl=False): context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT - extra_conf = self.get_content_directives(site) + extra_conf = self.get_content_directives(site, context) directives = site.get_directives() if ssl: extra_conf += self.get_ssl(directives) @@ -147,11 +147,11 @@ class Apache2Backend(ServiceController): (self.__class__.__name__, method)) return method(context, *args) - def get_content_directives(self, site): + def get_content_directives(self, site, context): directives = [] for content in site.content_set.all(): directive = content.webapp.get_directive() - context = self.get_content_context(content) + self.set_content_context(content, context) directives += self.get_directives(directive, context) return directives @@ -189,15 +189,27 @@ class Apache2Backend(ServiceController): def get_fcgid_directives(self, context, app_path, wrapper_path): context.update({ 'app_path': os.path.normpath(app_path), - 'wrapper_path': wrapper_path, + 'wrapper_name': os.path.basename(wrapper_path), }) - directives = self.get_location_filesystem_map(context) + directives = '' + # This Alias trick is used instead of FcgidWrapper because we don't want to define + # a new fcgid process class each time an app is mounted (num proc limits enforcement). + if 'wrapper_dir' not in context: + # fcgi-bin only needs to be defined once per vhots + context['wrapper_dir'] = os.path.dirname(wrapper_path) + directives = textwrap.dedent("""\ + Alias /fcgi-bin/ %(wrapper_dir)s/ + + SetHandler fcgid-script + Options +ExecCGI + + """) % context + directives += self.get_location_filesystem_map(context) directives += textwrap.dedent(""" ProxyPass %(location)s/ ! - Options +ExecCGI - AddHandler fcgid-script .php - FcgidWrapper %(wrapper_path)s + AddHandler php-fcgi .php + Action php-fcgi /fcgi-bin/%(wrapper_name)s """) % context return [ (context['location'], directives), @@ -295,7 +307,7 @@ class Apache2Backend(ServiceController): def get_server_names(self, site): server_name = None server_alias = [] - for domain in site.domains.all(): + for domain in site.domains.all().order_by('name'): if not server_name and not domain.name.startswith('*'): server_name = domain.name else: @@ -311,29 +323,28 @@ class Apache2Backend(ServiceController): 'site': site, 'site_name': site.name, 'ip': settings.WEBSITES_DEFAULT_IP, - 'site_unique_name': '0-'+site.unique_name, + 'site_unique_name': site.unique_name, 'user': self.get_username(site), 'group': self.get_groupname(site), 'server_name': server_name, 'server_alias': server_alias, - # TODO remove '0-' - 'sites_enabled': "%s.conf" % os.path.join(sites_enabled, '0-'+site.unique_name), - 'sites_available': "%s.conf" % os.path.join(sites_available, '0-'+site.unique_name), + 'sites_enabled': "%s.conf" % os.path.join(sites_enabled, site.unique_name), + 'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name), 'access_log': site.get_www_access_log_path(), 'error_log': site.get_www_error_log_path(), 'banner': self.get_banner(), } return replace(context, "'", '"') - def get_content_context(self, content): - context = self.get_context(content.website) - context.update({ + def set_content_context(self, content, context): + content_context = { 'type': content.webapp.type, 'location': normurlpath(content.path), 'app_name': content.webapp.name, 'app_path': content.webapp.get_path(), - }) - return replace(context, "'", '"') + } + content_context = replace(content_context, "'", '"') + context.update(content_context) class Apache2Traffic(ServiceMonitor): diff --git a/orchestra/contrib/websites/models.py b/orchestra/contrib/websites/models.py index d1985b90..6ffa6bd7 100644 --- a/orchestra/contrib/websites/models.py +++ b/orchestra/contrib/websites/models.py @@ -1,4 +1,5 @@ import os +from collections import OrderedDict from django.db import models from django.utils.functional import cached_property @@ -69,8 +70,8 @@ class Website(models.Model): @cached def get_directives(self): - directives = {} - for opt in self.directives.all(): + directives = OrderedDict() + for opt in self.directives.all().order_by('name', 'value'): try: directives[opt.name].append(opt.value) except KeyError: @@ -104,7 +105,7 @@ class Website(models.Model): class WebsiteDirective(models.Model): website = models.ForeignKey(Website, verbose_name=_("web site"), - related_name='directives') + related_name='directives') name = models.CharField(_("name"), max_length=128, choices=SiteDirective.get_choices()) value = models.CharField(_("value"), max_length=256) diff --git a/orchestra/plugins/admin.py b/orchestra/plugins/admin.py index e58f8d21..f23fcd09 100644 --- a/orchestra/plugins/admin.py +++ b/orchestra/plugins/admin.py @@ -64,7 +64,8 @@ class SelectPluginAdminMixin(object): plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field) if not plugin_value and request.method == 'POST': # HACK baceuse django add_preserved_filters removes extising queryargs - value = re.search(r"type=([^&^']+)[&']", request.META.get('HTTP_REFERER', '')) + value = re.search(r"%s=([^&^']+)[&']" % self.plugin_field, + request.META.get('HTTP_REFERER', '')) if value: plugin_value = value.groups()[0] return plugin_value