From 9903fffda35711a29ad0a66dad04238965c6e261 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Thu, 16 Apr 2015 13:15:21 +0000 Subject: [PATCH] Added python backend --- TODO.md | 20 +- orchestra/admin/options.py | 3 + orchestra/contrib/lists/backends.py | 2 +- orchestra/contrib/lists/settings.py | 4 +- orchestra/contrib/mailboxes/backends.py | 4 +- orchestra/contrib/orchestration/admin.py | 9 +- orchestra/contrib/orchestration/models.py | 25 +- orchestra/contrib/plans/admin.py | 1 + orchestra/contrib/resources/admin.py | 6 +- orchestra/contrib/services/handlers.py | 7 +- orchestra/contrib/systemusers/backends.py | 2 +- orchestra/contrib/webapps/admin.py | 5 + orchestra/contrib/webapps/api.py | 2 +- orchestra/contrib/webapps/backends/php.py | 3 +- orchestra/contrib/webapps/backends/python.py | 92 +++ orchestra/contrib/webapps/models.py | 5 +- orchestra/contrib/webapps/settings.py | 39 +- orchestra/contrib/webapps/types/misc.py | 2 - orchestra/contrib/webapps/types/php.py | 12 +- orchestra/contrib/webapps/types/python.py | 61 ++ orchestra/contrib/webapps/types/wordpress.py | 6 +- orchestra/contrib/websites/backends/apache.py | 9 + orchestra/plugins/forms.py | 10 +- .../orchestra/icons/apps/DokuWikiMu.png | Bin 3844 -> 0 bytes .../orchestra/icons/apps/DokuWikiMu.svg | 630 ------------------ .../static/orchestra/icons/apps/DrupalMu.png | Bin 2313 -> 0 bytes .../static/orchestra/icons/apps/DrupalMu.svg | 160 ----- .../static/orchestra/icons/apps/MoodleMu.png | Bin 3265 -> 0 bytes .../static/orchestra/icons/apps/MoodleMu.svg | 538 --------------- .../static/orchestra/icons/apps/PHPFCGI.png | Bin 4353 -> 0 bytes .../static/orchestra/icons/apps/PHPFCGI.svg | 182 ----- .../static/orchestra/icons/apps/PHPFPM.png | Bin 4282 -> 0 bytes .../static/orchestra/icons/apps/PHPFPM.svg | 182 ----- .../static/orchestra/icons/apps/Python.png | Bin 0 -> 3272 bytes .../orchestra/icons/apps/WordPressMu.png | Bin 2874 -> 0 bytes .../orchestra/icons/apps/WordPressMu.svg | 127 ---- .../admin/orchestra/change_password.html | 2 +- orchestra/urls.py | 7 +- 38 files changed, 271 insertions(+), 1886 deletions(-) create mode 100644 orchestra/contrib/webapps/backends/python.py create mode 100644 orchestra/contrib/webapps/types/python.py delete mode 100644 orchestra/static/orchestra/icons/apps/DokuWikiMu.png delete mode 100644 orchestra/static/orchestra/icons/apps/DokuWikiMu.svg delete mode 100644 orchestra/static/orchestra/icons/apps/DrupalMu.png delete mode 100644 orchestra/static/orchestra/icons/apps/DrupalMu.svg delete mode 100644 orchestra/static/orchestra/icons/apps/MoodleMu.png delete mode 100644 orchestra/static/orchestra/icons/apps/MoodleMu.svg delete mode 100644 orchestra/static/orchestra/icons/apps/PHPFCGI.png delete mode 100644 orchestra/static/orchestra/icons/apps/PHPFCGI.svg delete mode 100644 orchestra/static/orchestra/icons/apps/PHPFPM.png delete mode 100644 orchestra/static/orchestra/icons/apps/PHPFPM.svg create mode 100644 orchestra/static/orchestra/icons/apps/Python.png delete mode 100644 orchestra/static/orchestra/icons/apps/WordPressMu.png delete mode 100644 orchestra/static/orchestra/icons/apps/WordPressMu.svg diff --git a/TODO.md b/TODO.md index 91cae3f7..19ad5cae 100644 --- a/TODO.md +++ b/TODO.md @@ -175,9 +175,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * allow empty metric pack for default rates? changes on rating algo # don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill? -# DOMINI REGISTRE MIGRATION SCRIPTS -# IMPORTANT delete domain xina: missing FROM-clause entry for table "t3" LINE 1: SELECT (CONCAT(T3.name, domains_domain.name)) AS "structured... - # lines too long on invoice, double lines or cut, and make margin wider * PHP_TIMEOUT env variable in sync with fcgid idle timeout http://foaa.de/old-blog/2010/11/php-apache-and-fastcgi-a-comprehensive-overview/trackback/index.html#pni-top0 @@ -188,8 +185,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * TODO raise404, here and everywhere * update service orders on a celery task? because it take alot -# billline quantity eval('10x100') instead of miningless description '(10*100)' line.verbose_quantity - # FIXME do more test, make sure billed until doesn't get uodated whhen services are billed with les metric, and don't upgrade billed_until when undoing under this circumstances * line 513: change threshold and one time service metric change should update last value if not billed, only record for recurring invoicing. postpay services should store the last metric for pricing period. * add ini, end dates on bill lines and breakup quanity into size(defaut:1) and metric @@ -201,23 +196,14 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * write down insights -# use english on services defs and so on, an translate them on render time - python3 manage.py dumpdata services.Service --indent 4 --natural - * websites directives get_location() and use it on last change view validation stage to compare with contents.location and also on the backend ? * modeladmin Default filter + search isn't working, prepend filter when searching * create service help templates based on urlqwargs with the most basic services. -# TDOO Base price: domini propi (all domains) + extra for other domains - -# IMPORTANT op.instance = copy.deepcopy(instance) ValueError: Cannot assign "": "SaaS" instance isn't saved in the database. -# Separate operation from models !! BackendOperation and Operation - Translation ----------- - mkdir locale django-admin.py makemessages -l ca django-admin.py compilemessages -l ca @@ -254,7 +240,7 @@ https://code.djangoproject.com/ticket/24576 # FIXME what to do when deleting accounts? set fk null and fill a username charfield? issues, invoices.. we whant all this to go away? * implement delete All related services -# FIXME address name change does not remove old one :P, readonly, perhaps we can regenerate all addresses using backend.prepare()? +# FIXME address name change does not remove old one :P, readonly or perhaps we can regenerate all addresses using backend.prepare()? * read https://docs.djangoproject.com/en/dev/releases/1.8/ and fix deprecation warnings * remove admin object display_links , like contents webapps @@ -281,7 +267,6 @@ https://code.djangoproject.com/ticket/24576 * MultiCHoiceField proper serialization # 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? * add a delay to changes on the webserver apache to no overwelm it with backend executions? @@ -293,5 +278,6 @@ https://code.djangoproject.com/ticket/24576 * rename resource.monitors to resource.backends ? * abstract model classes enabling overriding? -# Ignore superusers & co on billing +# Ignore superusers & co on billing: list filter doesn't work nor ignore detection # bill.totals make it 100% computed? +* joomla: wget https://github.com/joomla/joomla-cms/releases/download/3.4.1/Joomla_3.4.1-Stable-Full_Package.tar.gz -O - | tar xvfz - diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index 26348067..a70dd06f 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -14,6 +14,8 @@ from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ from django.views.decorators.debug import sensitive_post_parameters +from ..utils.python import random_ascii + from .forms import AdminPasswordChangeForm #from django.contrib.auth.forms import AdminPasswordChangeForm from .utils import set_url_query, action_to_view @@ -243,6 +245,7 @@ class ChangePasswordAdminMixin(object): 'original': user, 'save_as': False, 'show_save': True, + 'password': random_ascii(10), } context.update(admin.site.each_context(request)) return TemplateResponse(request, diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index cf8dbde0..3eb577b0 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -144,7 +144,7 @@ class MailmanBackend(ServiceController): 'address_domain': mail_list.address_domain, 'address_regex': '\|'.join(self.addresses), 'admin': mail_list.admin_email, - 'mailman_root': settings.LISTS_MAILMAN_ROOT_PATH, + 'mailman_root': settings.LISTS_MAILMAN_ROOT_DIR, }) return replace(context, "'", '"') diff --git a/orchestra/contrib/lists/settings.py b/orchestra/contrib/lists/settings.py index 472f808b..5fb943f2 100644 --- a/orchestra/contrib/lists/settings.py +++ b/orchestra/contrib/lists/settings.py @@ -23,8 +23,8 @@ LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH', ) -LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH', - '/var/lib/mailman/' +LISTS_MAILMAN_ROOT_DIR = getattr(settings, 'LISTS_MAILMAN_ROOT_DIR', + '/var/lib/mailman' ) diff --git a/orchestra/contrib/mailboxes/backends.py b/orchestra/contrib/mailboxes/backends.py index 4278e056..b37324cf 100644 --- a/orchestra/contrib/mailboxes/backends.py +++ b/orchestra/contrib/mailboxes/backends.py @@ -406,7 +406,7 @@ class PostfixMailscannerTraffic(ServiceMonitor): except KeyError: pass else: - targets[req_id] = (username, None) + targets[req_id] = (username, 0) reverse[username].add(req_id) # Look for the mail size and count the number of recipients of each email else: @@ -438,7 +438,7 @@ class PostfixMailscannerTraffic(ServiceMonitor): except IOError as e: sys.stderr.write(e) - for username, opts in users.items(): + for username, opts in users.iteritems(): size = 0 for req_id in reverse[username]: size += targets[req_id][1] * counter.get(req_id, 0) diff --git a/orchestra/contrib/orchestration/admin.py b/orchestra/contrib/orchestration/admin.py index 955f8c3d..0c0d8c6d 100644 --- a/orchestra/contrib/orchestration/admin.py +++ b/orchestra/contrib/orchestration/admin.py @@ -23,13 +23,12 @@ STATE_COLORS = { } - class RouteAdmin(admin.ModelAdmin): - list_display = [ + list_display = ( 'backend', 'host', 'match', 'display_model', 'display_actions', 'is_active' - ] - list_editable = ['host', 'match', 'is_active'] - list_filter = ['host', 'is_active', 'backend'] + ) + list_editable = ('host', 'match', 'is_active') + list_filter = ('host', 'is_active', 'backend') ordering = ('backend',) BACKEND_HELP_TEXT = { diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py index 14012d96..806c138c 100644 --- a/orchestra/contrib/orchestration/models.py +++ b/orchestra/contrib/orchestration/models.py @@ -149,24 +149,25 @@ class Route(models.Model): @classmethod def get_servers(cls, operation, **kwargs): cache = kwargs.get('cache', {}) + if not cache: + for route in cls.objects.filter(is_active=True).select_related('host'): + for action in route.backend_class.get_actions(): + key = (route.backend, action) + try: + cache[key].append(route) + except KeyError: + cache[key] = [route] servers = [] backend_cls = operation.backend key = (backend_cls.get_name(), operation.action) try: routes = cache[key] except KeyError: - cache[key] = [] - for route in cls.objects.filter(is_active=True, backend=backend_cls.get_name()): - for action in backend_cls.get_actions(): - _key = (route.backend, action) - try: - cache[_key].append(route) - except KeyError: - cache[_key] = [route] - routes = cache[key] - for route in routes: - if route.matches(operation.instance): - servers.append(route.host) + pass + else: + for route in routes: + if route.matches(operation.instance): + servers.append(route.host) return servers def clean(self): diff --git a/orchestra/contrib/plans/admin.py b/orchestra/contrib/plans/admin.py index e30dc1c9..80ba716d 100644 --- a/orchestra/contrib/plans/admin.py +++ b/orchestra/contrib/plans/admin.py @@ -27,6 +27,7 @@ class PlanAdmin(ExtendedModelAdmin): class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = ('plan', 'account_link') list_filter = ('plan__name',) + list_select_related = ('plan', 'account') admin.site.register(Plan, PlanAdmin) diff --git a/orchestra/contrib/resources/admin.py b/orchestra/contrib/resources/admin.py index 6685490d..609d29d6 100644 --- a/orchestra/contrib/resources/admin.py +++ b/orchestra/contrib/resources/admin.py @@ -13,6 +13,7 @@ from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_ from orchestra.contrib.orchestration.models import Route from orchestra.core import services from orchestra.utils import database_ready +from orchestra.utils.functional import cached from .actions import run_monitor from .forms import ResourceForm @@ -179,12 +180,13 @@ admin.site.register(MonitorData, MonitorDataAdmin) def resource_inline_factory(resources): class ResourceInlineFormSet(generic.BaseGenericInlineFormSet): def total_form_count(self, resources=resources): - return len(resources) + return len(resources) + @cached def get_queryset(self): """ Filter disabled resources """ queryset = super(ResourceInlineFormSet, self).get_queryset() - return queryset.filter(resource__is_active=True) + return queryset.filter(resource__is_active=True).select_related('resource') @cached_property def forms(self, resources=resources): diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py index a5390914..6ada4d4e 100644 --- a/orchestra/contrib/services/handlers.py +++ b/orchestra/contrib/services/handlers.py @@ -105,9 +105,10 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): def get_ignore(self, instance): if self.ignore_superusers: account = getattr(instance, 'account', instance) - if (account.type in settings.SERVICES_IGNORE_ACCOUNT_TYPE or - 'superuser' in settings.SERVICES_IGNORE_ACCOUNT_TYPE): - return True + if account.type in settings.SERVICES_IGNORE_ACCOUNT_TYPE: + return True + if 'superuser' in settings.SERVICES_IGNORE_ACCOUNT_TYPE and account.is_superuser: + return True return False def get_metric(self, instance): diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py index ffd50393..a4fe2d81 100644 --- a/orchestra/contrib/systemusers/backends.py +++ b/orchestra/contrib/systemusers/backends.py @@ -165,7 +165,7 @@ class Exim4Traffic(ServiceMonitor): except IOError as e: sys.stderr.write(e) - for username, opts in users.items(): + for username, opts in users.iteritems(): __, object_id, size = opts print object_id, size """).format(**context) diff --git a/orchestra/contrib/webapps/admin.py b/orchestra/contrib/webapps/admin.py index 19f53b16..d592f7a1 100644 --- a/orchestra/contrib/webapps/admin.py +++ b/orchestra/contrib/webapps/admin.py @@ -79,6 +79,11 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin) return webapp.type_instance.get_detail() display_detail.short_description = _("detail") +# def get_form(self, request, obj=None, **kwargs): +# form = super(WebAppAdmin, self).get_form(request, obj, **kwargs) +# if obj: +# + # def formfield_for_dbfield(self, db_field, **kwargs): # """ Make value input widget bigger """ # if db_field.name == 'type': diff --git a/orchestra/contrib/webapps/api.py b/orchestra/contrib/webapps/api.py index b9d31381..4681413d 100644 --- a/orchestra/contrib/webapps/api.py +++ b/orchestra/contrib/webapps/api.py @@ -16,7 +16,7 @@ class WebAppViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet): def metadata(self, request): ret = super(WebAppViewSet, self).metadata(request) names = [ - 'WEBAPPS_BASE_ROOT', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS', + 'WEBAPPS_BASE_DIR', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS', 'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE' ] ret['settings'] = { diff --git a/orchestra/contrib/webapps/backends/php.py b/orchestra/contrib/webapps/backends/php.py index fe86731d..72fdf5c6 100644 --- a/orchestra/contrib/webapps/backends/php.py +++ b/orchestra/contrib/webapps/backends/php.py @@ -99,7 +99,8 @@ class PHPBackend(WebAppServiceMixin, ServiceController): if [[ $UPDATED_FPM -eq 1 ]]; then service php5-fpm reload fi - # Coordinate apache restart with apache backend + # Coordinate apache restart with Apache2Backend + # FIXME race condition locked=1 state="$(grep -v 'PHPBackend' /dev/shm/restart.apache2)" || locked=0 echo -n "$state" > /dev/shm/restart.apache2 diff --git a/orchestra/contrib/webapps/backends/python.py b/orchestra/contrib/webapps/backends/python.py new file mode 100644 index 00000000..b69271b7 --- /dev/null +++ b/orchestra/contrib/webapps/backends/python.py @@ -0,0 +1,92 @@ +import os +import textwrap + +from django.template import Template, Context +from django.utils.translation import ugettext_lazy as _ + +from orchestra.contrib.orchestration import ServiceController, replace + +from . import WebAppServiceMixin +from .. import settings + + +class PythonBackend(WebAppServiceMixin, ServiceController): + verbose_name = _("Python uWSGI") + default_route_match = "webapp.type.endswith('python')" + + def save(self, webapp): + context = self.get_context(webapp) + self.create_webapp_dir(context) + self.set_under_construction(context) + self.save_uwsgi(webapp, context) + + def delete(self, webapp): + context = self.get_context(webapp) + self.delete_uwsgi(webapp, context) + self.delete_webapp_dir(context) + + def save_uwsgi(self, webapp, context): + self.append(textwrap.dedent("""\ + uwsgi_config='%(uwsgi_config)s' + { + echo -e "${uwsgi_config}" | diff -N -I'^\s*;;' %(uwsgi_path)s - + } || { + echo -e "${uwsgi_config}" > %(uwsgi_path)s + UPDATED_UWSGI=1 + } + ln -s %(uwsgi_path)s %(uwsgi_enabled)s + """) % context + ) + + def delete_uwsgi(self, webapp, context): + self.append("rm -f %(uwsgi_path)s" % context) + self.append("rm -f %(uwsgi_enabled)s" % context) + + def get_uwsgi_ini(self, context): + # TODO switch to this http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html + # TODO http://uwsgi-docs.readthedocs.org/en/latest/Changelog-1.9.1.html#on-demand-vassals + return textwrap.dedent("""\ + [uwsgi] + plugins = python{python_version_number} + chdir = {app_path} + module = {app_name}.wsgi + chmod-socket = 660 + stats = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/statsocket + vacuum = true + uid = {user} + gid = {group} + env = HOME={home} + harakiri = {timeout} + max-requests = {max_requests} + + cheaper-algo = spare + cheaper = 1 + workers = {workers} + cheaper-step = 1 + cheaper-overload = 5""" + ).format(context) + + def update_uwsgi_context(self, webapp, context): + context.update({ + 'uwsgi_ini': self.get_uwsgi_ini(context), + 'uwsgi_dir': settings.WEBAPPS_UWSGI_BASE_DIR, + 'uwsgi_path': os.path.join(settings.WEBAPPS_UWSGI_BASE_DIR, + 'apps-available/%s.ini'% context['app_name']), + 'uwsgi_enabled': os.path.join(settings.WEBAPPS_UWSGI_BASE_DIR, + 'apps-enabled/%s.ini'% context['app_name']), + }) + return context + + def get_context(self, webapp): + context = super(PHPBackend, self).get_context(webapp) + options = webapp.get_options() + context.update({ + 'python_version': webapp.type_instance.get_python_version(), + 'python_version_number': webapp.type_instance.get_python_version_number(), + 'max_requests': settings.WEBAPPS_PYTHON_MAX_REQUESTS, + 'workers': options.get('processes', settings.WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS), + 'timeout': options.get('timeout', settings.WEBAPPS_PYTHON_DEFAULT_TIMEOUT), + }) + self.update_uwsgi_context(webapp, context) + replace(context, "'", '"') + return context diff --git a/orchestra/contrib/webapps/models.py b/orchestra/contrib/webapps/models.py index bb132e20..1249c5aa 100644 --- a/orchestra/contrib/webapps/models.py +++ b/orchestra/contrib/webapps/models.py @@ -19,7 +19,8 @@ from .types import AppType class WebApp(models.Model): """ Represents a web application """ - name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) + name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name], + help_text=_("The app will be installed in %s") % settings.WEBAPPS_BASE_DIR) type = models.CharField(_("type"), max_length=32, choices=AppType.get_choices()) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='webapps') @@ -76,7 +77,7 @@ class WebApp(models.Model): 'home': self.get_user().get_home(), 'app_name': self.name, } - path = settings.WEBAPPS_BASE_ROOT % context + path = settings.WEBAPPS_BASE_DIR % context public_root = self.options.filter(name='public-root').first() if public_root: path = os.path.join(path, public_root.value) diff --git a/orchestra/contrib/webapps/settings.py b/orchestra/contrib/webapps/settings.py index a2fd8d3b..a5af0c90 100644 --- a/orchestra/contrib/webapps/settings.py +++ b/orchestra/contrib/webapps/settings.py @@ -3,8 +3,8 @@ from django.conf import settings from orchestra.settings import ORCHESTRA_BASE_DOMAIN -WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', - '%(home)s/webapps/%(app_name)s/' +WEBAPPS_BASE_DIR = getattr(settings, 'WEBAPPS_BASE_DIR', + '%(home)s/webapps/%(app_name)s' ) @@ -13,13 +13,15 @@ WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN', '/opt/php/5.4/socks/%(user)s-%(app_name)s.sock' ) + WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = getattr(settings, 'WEBAPPS_FPM_DEFAULT_MAX_CHILDREN', 3 ) WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH', - '/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf') + '/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf' +) WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH', @@ -59,6 +61,7 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', ( 'orchestra.contrib.webapps.types.misc.WebalizerApp', 'orchestra.contrib.webapps.types.misc.SymbolicLinkApp', 'orchestra.contrib.webapps.types.wordpress.WordPressApp', + 'orchestra.contrib.webapps.types.python.PythonApp', )) @@ -95,6 +98,36 @@ WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR', ) +WEBAPPS_PYTHON_VERSIONS = getattr(settings, 'WEBAPPS_PYTHON_VERSIONS', ( + ('3.4-uwsgi', 'Python 3.4 uWSGI'), + ('2.7-uwsgi', 'Python 2.7 uWSGI'), +)) + + +WEBAPPS_DEFAULT_PYTHON_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PYTHON_VERSION', + '3.4-uwsgi' +) + + +WEBAPPS_UWSGI_SOCKET = getattr(settings, 'WEBAPPS_UWSGI_SOCKET', + '/var/run/uwsgi/app/%(app_name)s/socket' +) + + +WEBAPPS_PYTHON_MAX_REQUESTS = getattr(settings, 'WEBAPPS_PYTHON_MAX_REQUESTS', + 500 +) + + +WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS = getattr(settings, 'WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS', + 3 +) + + +WEBAPPS_PYTHON_DEFAULT_TIMEOUT = getattr(settings, 'WEBAPPS_PYTHON_DEFAULT_TIMEOUT', + 30 +) + WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH', # Server-side path where a under construction stock page is diff --git a/orchestra/contrib/webapps/types/misc.py b/orchestra/contrib/webapps/types/misc.py index e51e5f05..f94b879a 100644 --- a/orchestra/contrib/webapps/types/misc.py +++ b/orchestra/contrib/webapps/types/misc.py @@ -53,5 +53,3 @@ class SymbolicLinkApp(PHPApp): serializer = SymbolicLinkSerializer icon = 'orchestra/icons/apps/SymbolicLink.png' change_readonly_fileds = ('path',) - - diff --git a/orchestra/contrib/webapps/types/php.py b/orchestra/contrib/webapps/types/php.py index 69374370..e2bbb065 100644 --- a/orchestra/contrib/webapps/types/php.py +++ b/orchestra/contrib/webapps/types/php.py @@ -22,16 +22,16 @@ help_message = _("Version of PHP used to execute this webapp.
" 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) + 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) + choices=settings.WEBAPPS_PHP_VERSIONS, + default=settings.WEBAPPS_DEFAULT_PHP_VERSION, + help_text=help_message) class PHPApp(AppType): diff --git a/orchestra/contrib/webapps/types/python.py b/orchestra/contrib/webapps/types/python.py new file mode 100644 index 00000000..36eb0156 --- /dev/null +++ b/orchestra/contrib/webapps/types/python.py @@ -0,0 +1,61 @@ +import os +import re +from collections import OrderedDict + +from django import forms +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from orchestra.plugins.forms import PluginDataForm +from orchestra.utils.functional import cached + +from .. import settings +from ..options import AppOption + +from . import AppType + + +help_message = _("Version of Python used to execute this webapp.
" + "Changing the Python version may result in application malfunction, " + "make sure that everything continue to work as expected.") + + +class PythonAppForm(PluginDataForm): + python_version = forms.ChoiceField(label=_("Python version"), + choices=settings.WEBAPPS_PYTHON_VERSIONS, + initial=settings.WEBAPPS_DEFAULT_PYTHON_VERSION, + help_text=help_message) + + +class PythonAppSerializer(serializers.Serializer): + python_version = serializers.ChoiceField(label=_("Python version"), + choices=settings.WEBAPPS_PYTHON_VERSIONS, + default=settings.WEBAPPS_DEFAULT_PYTHON_VERSION, + help_text=help_message) + + +class PythonApp(AppType): + name = 'python' + verbose_name = "Python" + help_text = _("This creates a Python application under ~/webapps/<app_name>
") + form = PythonAppForm + serializer = PythonAppSerializer + option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS) + icon = 'orchestra/icons/apps/Python.png' + + def get_directive(self): + context = self.get_directive_context() + return ('uwsgi', settings.WEBAPPS_UWSGI_SOCKET % context) + + def get_python_version(self): + default_version = self.DEFAULT_PYTHON_VERSION + return self.instance.data.get('python_version', default_version) + + def get_python_version_number(self): + python_version = self.get_python_version() + number = re.findall(r'[0-9]+\.?[0-9]?', python_version) + if not number: + raise ValueError("No version number matches for '%s'" % python_version) + if len(number) > 1: + raise ValueError("Multiple version number matches for '%s'" % python_version) + return number[0] diff --git a/orchestra/contrib/webapps/types/wordpress.py b/orchestra/contrib/webapps/types/wordpress.py index 20394a5e..0b55f546 100644 --- a/orchestra/contrib/webapps/types/wordpress.py +++ b/orchestra/contrib/webapps/types/wordpress.py @@ -7,7 +7,9 @@ class WordPressApp(CMSApp): name = 'wordpress-php' verbose_name = "WordPress" help_text = _( - "Visit http://<domain.lan>/wp-admin/install.php to finish the installation.
" - "A database and database user will automatically be created for this webapp." + "This installs the latest version of WordPress into the webapp directory.
" + "A database and database user will automatically be created for this webapp.
" + "This installer creates a user 'admin' with a randomly generated password.
" + "The password will be visible in the 'password' field after the installer has finished." ) icon = 'orchestra/icons/apps/WordPress.png' diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py index 4ad78ee6..a180b2eb 100644 --- a/orchestra/contrib/websites/backends/apache.py +++ b/orchestra/contrib/websites/backends/apache.py @@ -216,6 +216,15 @@ class Apache2Backend(ServiceController): (context['location'], directives), ] + def get_uwsgi_directives(self, context, socket): + # requires apache2 mod_proxy_uwsgi + context['socket'] = socket + directives = "ProxyPass / unix:%(socket)s|uwsgi://" % context + directives += self.get_location_filesystem_map(context) + return [ + (context['location'], directives), + ] + def get_ssl(self, directives): cert = directives.get('ssl-cert') key = directives.get('ssl-key') diff --git a/orchestra/plugins/forms.py b/orchestra/plugins/forms.py index c9d7780d..5528f7eb 100644 --- a/orchestra/plugins/forms.py +++ b/orchestra/plugins/forms.py @@ -13,12 +13,20 @@ class PluginDataForm(forms.ModelForm): value = self.plugin.get_name() display = '%s change' % force_text(self.plugin.verbose_name) self.fields[self.plugin_field].widget = ReadOnlyWidget(value, display) - self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', '') + help_text = self.fields[self.plugin_field].help_text + self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text) if self.instance: for field in self.declared_fields: initial = self.fields[field].initial self.fields[field].initial = self.instance.data.get(field, initial) if self.instance.pk: + # Admin Readonly fields are not availeble in self.fields, so we use Meta + plugin = getattr(self.instance, '%s_class' % self.plugin_field) + plugin_help_text = getattr(plugin, 'help_text', '') + model_help_text = self.instance._meta.get_field_by_name(self.plugin_field)[0].help_text + self._meta.help_texts = { + self.plugin_field: plugin_help_text or model_help_text + } for field in self.plugin.get_change_readonly_fileds(): value = getattr(self.instance, field, None) or self.instance.data[field] display = value diff --git a/orchestra/static/orchestra/icons/apps/DokuWikiMu.png b/orchestra/static/orchestra/icons/apps/DokuWikiMu.png deleted file mode 100644 index 38d91789b3410db7a8b6e27d4be8f712e588e7ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3844 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 - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/DrupalMu.png b/orchestra/static/orchestra/icons/apps/DrupalMu.png deleted file mode 100644 index b8ca4053def735487754dca429c017f41a30a771..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2313 zcmV+k3HJ7hP)fO zCL!cOcK06txVvFrcOQY74l{md&fLA{`Td^X?>z4cDJAzZl+T;Mo#OujK5yLf0*pf~ zXtCO+0gnSu06BngCrO|J_z*ZySdf0{PDzG>h;|mTSZ(uwcY!Gp7P$x%78ax*i;h}k zh**HdYBK{{fai(c^;BRRu(7ZpeIUxRArX-Pi`AA4l%ToFouCX@QCN_E`3~zDMA)9R zSZzk&Peei9rK)V*dlZ+d`X|i*{_M8qT|p$S@H((Cno3VzyvmwK^Vzh!1XT^N@J!&% zXzB}vkS=7g+QtI4KwLDHp0B>j+^OSPUwn~Kjy7IfyTCt-M4P?-4lDz{31k8$v>9wa zu&1yf-F2q|EWmS-5^wJ4rmel5m%jQCYj&1%rlAX@B(cAp<#`jBot5F8y$NUort4H! z11u>lNWT)Q&2TRC*~Ts{Q3!6vrSQRp&)E3K7=ysj7PH_ICdElcg-kOyzoygYxDZ1Q^yBOP4u? zKb&i2^WNih-0TZiHNbeZF{le&7qVDwj{-+R6+U#fitS&Y%Y@{305046*>SF!H;-Os zz!@+D`M}WI&UfZzl07~xWImt|7+YA7-We=0lndpCs-zG9fu0DZzTJi?>eH z^Xrl;VaWry;*)vzR0Ag~uZ7DR2Ry8o7-}!1E8s$H1G~OEpIDjiHu30_(C)Hj~d4Pqeq3xI#Mqolm$|QMIJ4yU#cGQMenAkk-EufF#F#l)*-m|DKZmQ^D5;_Pa>l8(ApaqpAci<)P{Vr(-OV%8d)+qnI)5x zIZ|Wi`}-=WYwv?wH?^KuUF2k>@cQZkme10L-kLdCq^*COl%x@n&;f05uQ3wg)k;c& ziRvH%wmzQeL*5@>${a=6^K;pBpp4%gsv&)B5}Q}fVO4&XkE@MJj3qyFOnCS6MQ+6R zLi46%P}*1nX5XoFMb3R;%O=Da*tR;CHXeSSBS#Jx$MuSircHB!(NDcn>k7 zB1lb)30qc5C_Yuq`zI?&PK;ygx_P7}`I7DIy+uNNe8kM%m}o@1c$yYT?!{Bn*jIT2 z4B9m8R3(+IJ>-nhccD|Y&HQM`F;3UD491mSYU0StD;b|Ud@yeJi3(d9#XBFCP}9<(SweL&E-8)``I(F!5l?GZ zFUKnzDX(usm6Gg?WPb423|=X=($zP>&z_jY{xh{yHQ6y56rP-uMb)(yzO{UDwB!L^ z6-A|uF~B(>V4tCf=C)3X{_+Xkx12+1)CZ#zV|ZcRSBN!-A4@?nO>y52I`0MiQ#(i?@85+Q_=nL9V;*KdDA z>+0o}1cbKHH5nA7>cVUgq$Uk#(wG#M&Cf*^JBeaF8-QkuPMVG48(Pp%Na1RMjgj*J{_k@${l{ zzzGZrFnWu~U%G18*Pi?NGjR#S9x=w4XA2>sUSaMgRF|{5=SJu6j}^c7!9RBvbpkF= z2(W-xFan0O@tNk8KYndi@|cVqDWyrOs-mi@B0WKB?;zdpQjt=6{KNBTC3F2CP;f>gjM z-P32l-KY@4yO)(9&tSnr*z+450sfWt;5q;!yi}8^3b{U})yvcjuFGBTt`ipR(d3IQGQGX{MEGUFGQ9Tj<@Fwp) jCWwS=P#*XC-);T}@c5C&Q)1!c00000NkvXXu0mjf5d>DU diff --git a/orchestra/static/orchestra/icons/apps/DrupalMu.svg b/orchestra/static/orchestra/icons/apps/DrupalMu.svg deleted file mode 100644 index 11b394f2..00000000 --- a/orchestra/static/orchestra/icons/apps/DrupalMu.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/MoodleMu.png b/orchestra/static/orchestra/icons/apps/MoodleMu.png deleted file mode 100644 index 3881b0ee6ca2981380e45383b09bfadad7d785fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3265 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 diff --git a/orchestra/static/orchestra/icons/apps/MoodleMu.svg b/orchestra/static/orchestra/icons/apps/MoodleMu.svg deleted file mode 100644 index 3493ba5b..00000000 --- a/orchestra/static/orchestra/icons/apps/MoodleMu.svg +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/PHPFCGI.png b/orchestra/static/orchestra/icons/apps/PHPFCGI.png deleted file mode 100644 index 02ea63ca5cd22dec69176ccf22ef731492c8eef2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4353 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 diff --git a/orchestra/static/orchestra/icons/apps/PHPFCGI.svg b/orchestra/static/orchestra/icons/apps/PHPFCGI.svg deleted file mode 100644 index 677aac42..00000000 --- a/orchestra/static/orchestra/icons/apps/PHPFCGI.svg +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FCGI - diff --git a/orchestra/static/orchestra/icons/apps/PHPFPM.png b/orchestra/static/orchestra/icons/apps/PHPFPM.png deleted file mode 100644 index 71d3fca793b628e056f2340554404bd944ff6cf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4282 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 diff --git a/orchestra/static/orchestra/icons/apps/PHPFPM.svg b/orchestra/static/orchestra/icons/apps/PHPFPM.svg deleted file mode 100644 index 82cd01ea..00000000 --- a/orchestra/static/orchestra/icons/apps/PHPFPM.svg +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FPM - diff --git a/orchestra/static/orchestra/icons/apps/Python.png b/orchestra/static/orchestra/icons/apps/Python.png new file mode 100644 index 0000000000000000000000000000000000000000..fe1be277b52cb23123ebeb0ddd8518b3a4560133 GIT binary patch literal 3272 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 YU(a4z;D8@V!T - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/templates/admin/orchestra/change_password.html b/orchestra/templates/admin/orchestra/change_password.html index dc5abddd..b35c8f9a 100644 --- a/orchestra/templates/admin/orchestra/change_password.html +++ b/orchestra/templates/admin/orchestra/change_password.html @@ -8,7 +8,7 @@
{% if is_popup %}{% endif %} {% if to_field %}{% endif %} -

{% blocktrans with username=original %}Enter a new password for the user {{ username }}.{% endblocktrans %}

+

{% blocktrans with username=original %}Enter a new password for the user {{ username }}, suggestion '{{ password }}'.{% endblocktrans %}

{% if errors %}

diff --git a/orchestra/urls.py b/orchestra/urls.py index 455d2d9c..74cc45ae 100644 --- a/orchestra/urls.py +++ b/orchestra/urls.py @@ -20,9 +20,10 @@ urlpatterns = patterns('', name='api-token-auth' ), # TODO make this private - url(r'^media/(?P.*)$', 'django.views.static.serve', - {'document_root': settings.MEDIA_ROOT, 'show_indexes': True} - ) + url(r'^media/(?P.*)$', 'django.views.static.serve', { + 'document_root': settings.MEDIA_ROOT, + 'show_indexes': True + }) )