webapp php complete

This commit is contained in:
jorgepastorr 2023-08-01 16:41:03 +02:00 committed by Marc Aymerich
parent 230b9f24ca
commit d69a53ad66
12 changed files with 146 additions and 84 deletions

View File

@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from orchestra.forms import UserCreationForm, UserChangeForm from orchestra.forms import UserCreationForm, UserChangeForm
from orchestra.contrib.webapps.settings import WEBAPP_NEW_SERVERS from orchestra.settings import NEW_SERVERS
from . import settings from . import settings
from .models import SystemUser from .models import SystemUser
@ -176,7 +176,7 @@ class WebappUserFormMixin(object):
if not self.instance.pk: if not self.instance.pk:
server = self.cleaned_data.get('target_server') server = self.cleaned_data.get('target_server')
if server: if server:
if server.name not in WEBAPP_NEW_SERVERS: if server.name not in NEW_SERVERS:
self.add_error("target_server", _(f"{server} does not belong to the new servers")) self.add_error("target_server", _(f"{server} does not belong to the new servers"))
return self.cleaned_data return self.cleaned_data

View File

@ -109,7 +109,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
if not change: if not change:
user = form.cleaned_data['username'] user = form.cleaned_data.get('username')
if user: if user:
user = WebappUsers( user = WebappUsers(
username=form.cleaned_data['username'], username=form.cleaned_data['username'],

View File

@ -1,8 +1,8 @@
import pkgutil import pkgutil
import textwrap import textwrap
from django.template import Template, Context
from .. import settings from .. import settings
from orchestra.settings import NEW_SERVERS
class WebAppServiceMixin(object): class WebAppServiceMixin(object):
model = 'webapps.WebApp' model = 'webapps.WebApp'
@ -19,6 +19,7 @@ class WebAppServiceMixin(object):
CREATED=0 CREATED=0
if [[ ! -e %(app_path)s ]]; then if [[ ! -e %(app_path)s ]]; then
mkdir -p %(app_path)s mkdir -p %(app_path)s
chown %(sftpuser)s:%(sftpuser)s %(app_path)s
CREATED=1 CREATED=1
elif [[ -z $( ls -A %(app_path)s ) ]]; then elif [[ -z $( ls -A %(app_path)s ) ]]; then
CREATED=1 CREATED=1
@ -38,6 +39,15 @@ class WebAppServiceMixin(object):
def set_under_construction(self, context): def set_under_construction(self, context):
if context['under_construction_path']: if context['under_construction_path']:
# cambios de permisos en servidores nuevos
perms = Template(textwrap.dedent("""\
{% if sftpuser %}
chown -R {{ sftpuser }}:{{ sftpuser }} {{ app_path }}/* {% else %}
chown -R {{ user }}:{{ group }} {{ app_path }}/*
{% endif %}
"""
))
context.update({'perms' : perms.render(Context(context))})
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Set under construction if needed # Set under construction if needed
if [[ $CREATED == 1 && ! $(ls -A %(app_path)s | head -n1) ]]; then if [[ $CREATED == 1 && ! $(ls -A %(app_path)s | head -n1) ]]; then
@ -46,7 +56,7 @@ class WebAppServiceMixin(object):
sleep 2 sleep 2
if [[ ! $(ls -A %(app_path)s | head -n1) ]]; then if [[ ! $(ls -A %(app_path)s | head -n1) ]]; then
cp -r %(under_construction_path)s %(app_path)s cp -r %(under_construction_path)s %(app_path)s
chown -R %(user)s:%(group)s %(app_path)s/* %(perms)s
fi' &> /dev/null & fi' &> /dev/null &
fi""") % context fi""") % context
) )
@ -68,8 +78,8 @@ class WebAppServiceMixin(object):
def get_context(self, webapp): def get_context(self, webapp):
context = webapp.type_instance.get_directive_context() context = webapp.type_instance.get_directive_context()
context.update({ context.update({
'user': webapp.sftpuser.username if webapp.target_server.name in settings.WEBAPP_NEW_SERVERS else webapp.get_username(), 'user': webapp.get_username(),
'group': webapp.sftpuser.username if webapp.target_server.name in settings.WEBAPP_NEW_SERVERS else webapp.get_groupname(), 'group': webapp.get_groupname(),
'app_name': webapp.name, 'app_name': webapp.name,
'app_type': webapp.type, 'app_type': webapp.type,
'app_path': webapp.get_path(), 'app_path': webapp.get_path(),
@ -77,6 +87,7 @@ class WebAppServiceMixin(object):
'under_construction_path': settings.WEBAPPS_UNDER_CONSTRUCTION_PATH, 'under_construction_path': settings.WEBAPPS_UNDER_CONSTRUCTION_PATH,
'is_mounted': webapp.content_set.exists(), 'is_mounted': webapp.content_set.exists(),
'target_server': webapp.target_server, 'target_server': webapp.target_server,
'sftpuser' : webapp.sftpuser.username if webapp.target_server.name in NEW_SERVERS else None
}) })
context['deleted_app_path'] = settings.WEBAPPS_MOVE_ON_DELETE_PATH % context context['deleted_app_path'] = settings.WEBAPPS_MOVE_ON_DELETE_PATH % context
return context return context

View File

@ -5,6 +5,7 @@ from collections import OrderedDict
from django.template import Template, Context from django.template import Template, Context
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from orchestra.settings import NEW_SERVERS
from orchestra.contrib.orchestration import ServiceController from orchestra.contrib.orchestration import ServiceController
from . import WebAppServiceMixin from . import WebAppServiceMixin
@ -34,7 +35,12 @@ class PHPController(WebAppServiceMixin, ServiceController):
def save(self, webapp): def save(self, webapp):
self.delete_old_config(webapp) self.delete_old_config(webapp)
context = self.get_context(webapp) context = self.get_context(webapp)
if context.get('target_server').name in NEW_SERVERS:
self.check_webapp_dir(context)
else:
self.create_webapp_dir(context) self.create_webapp_dir(context)
if webapp.type_instance.is_fpm: if webapp.type_instance.is_fpm:
self.save_fpm(webapp, context) self.save_fpm(webapp, context)
elif webapp.type_instance.is_fcgid: elif webapp.type_instance.is_fcgid:
@ -122,10 +128,9 @@ class PHPController(WebAppServiceMixin, ServiceController):
def delete(self, webapp): def delete(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
self.delete_old_config(webapp) self.delete_old_config(webapp)
# if webapp.type_instance.is_fpm: if context.get('target_server').name in NEW_SERVERS:
# self.delete_fpm(webapp, context) webapp.sftpuser.delete()
# elif webapp.type_instance.is_fcgid: else:
# self.delete_fcgid(webapp, context)
self.delete_webapp_dir(context) self.delete_webapp_dir(context)
def has_sibilings(self, webapp, context): def has_sibilings(self, webapp, context):
@ -229,12 +234,21 @@ class PHPController(WebAppServiceMixin, ServiceController):
fpm_config = Template(textwrap.dedent("""\ fpm_config = Template(textwrap.dedent("""\
;; {{ banner }} ;; {{ banner }}
[{{ user }}-{{app_name}}] [{{ user }}-{{app_name}}]
{% if sftpuser %}
user = {{ sftpuser }}
group = {{ sftpuser }}
listen = {{ fpm_listen | safe }}
listen.owner = root
listen.group = {{ sftpuser }}
{% else %}
user = {{ user }} user = {{ user }}
group = {{ group }} group = {{ group }}
listen = {{ fpm_listen | safe }} listen = {{ fpm_listen | safe }}
listen.owner = {{ user }} listen.owner = {{ user }}
listen.group = {{ group }} listen.group = {{ group }}
{% endif %}
pm = ondemand pm = ondemand
pm.max_requests = {{ max_requests }} pm.max_requests = {{ max_requests }}
@ -313,6 +327,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
context = super().get_context(webapp) context = super().get_context(webapp)
context.update({ context.update({
'max_requests': settings.WEBAPPS_PHP_MAX_REQUESTS, 'max_requests': settings.WEBAPPS_PHP_MAX_REQUESTS,
'target_server': webapp.target_server,
}) })
self.update_fpm_context(webapp, context) self.update_fpm_context(webapp, context)
self.update_fcgid_context(webapp, context) self.update_fcgid_context(webapp, context)

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
from orchestra.contrib.orchestration import ServiceController from orchestra.contrib.orchestration import ServiceController
from . import WebAppServiceMixin from . import WebAppServiceMixin
from ..settings import WEBAPP_NEW_SERVERS from orchestra.settings import NEW_SERVERS
class StaticController(WebAppServiceMixin, ServiceController): class StaticController(WebAppServiceMixin, ServiceController):
""" """
@ -15,7 +15,7 @@ class StaticController(WebAppServiceMixin, ServiceController):
def save(self, webapp): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
if context.get('target_server').name in WEBAPP_NEW_SERVERS: if context.get('target_server').name in NEW_SERVERS:
self.check_webapp_dir(context) self.check_webapp_dir(context)
self.set_under_construction(context) self.set_under_construction(context)
else: else:
@ -24,7 +24,7 @@ class StaticController(WebAppServiceMixin, ServiceController):
def delete(self, webapp): def delete(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
if context.get('target_server').name in WEBAPP_NEW_SERVERS: if context.get('target_server').name in NEW_SERVERS:
webapp.sftpuser.delete() webapp.sftpuser.delete()
else: else:
self.delete_webapp_dir(context) self.delete_webapp_dir(context)

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.28 on 2023-07-28 14:39
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('orchestration', '__first__'),
('webapps', '0002_webapp_sftpuser'),
]
operations = [
migrations.AlterUniqueTogether(
name='webapp',
unique_together={('name', 'account', 'target_server')},
),
]

View File

@ -34,7 +34,7 @@ class WebApp(models.Model):
databaseusers = VirtualDatabaseUserRelation('databases.DatabaseUser') databaseusers = VirtualDatabaseUserRelation('databases.DatabaseUser')
class Meta: class Meta:
unique_together = ('name', 'account') unique_together = ('name', 'account', 'target_server')
verbose_name = _("Web App") verbose_name = _("Web App")
verbose_name_plural = _("Web Apps") verbose_name_plural = _("Web Apps")

View File

@ -33,7 +33,7 @@ WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = Setting('WEBAPPS_FPM_DEFAULT_MAX_CHILDREN',
WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH', WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH',
'/etc/php%(php_version_number)s/fpm/pool.d/%(user)s-%(app_name)s.conf', '/etc/php/%(php_version_number)s/fpm/pool.d/%(user)s-%(app_name)s.conf',
help_text="Available fromat names: <tt>%s</tt>" % ', '.join(_php_names), help_text="Available fromat names: <tt>%s</tt>" % ', '.join(_php_names),
validators=[Setting.string_format_validator(_php_names)], validators=[Setting.string_format_validator(_php_names)],
) )
@ -283,10 +283,3 @@ WEBAPPS_CMS_CACHE_DIR = Setting('WEBAPPS_CMS_CACHE_DIR',
help_text="Server-side cache directori for CMS tarballs.", help_text="Server-side cache directori for CMS tarballs.",
) )
WEBAPP_NEW_SERVERS = Setting('WEBAPP_NEW_SERVERS',
(
'bookworm',
'web-11.pangea.lan',
'web-12.pangea.lan',
)
)

View File

@ -2,64 +2,14 @@ import os
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from rest_framework import serializers from rest_framework import serializers
from orchestra.plugins.forms import ExtendedPluginDataForm
from orchestra.core import validators
from orchestra.plugins.forms import PluginDataForm
from orchestra.utils.python import random_ascii
from ..options import AppOption from ..options import AppOption
from ..settings import WEBAPP_NEW_SERVERS
from . import AppType from . import AppType
from .php import PHPApp, PHPAppForm, PHPAppSerializer from .php import PHPApp, PHPAppForm, PHPAppSerializer
class StaticForm(PluginDataForm):
username = forms.CharField(label=_("Username"), max_length=16,
required=False, validators=[validators.validate_name],
help_text=_("Required. 16 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
error_messages={
'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and "
"@/./+/-/_ characters.")})
password1 = forms.CharField(label=_("Password"), required=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
validators=[validators.validate_password],
help_text=_("Suggestion: %s") % random_ascii(15))
password2 = forms.CharField(label=_("Password confirmation"), required=False,
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
def __init__(self, *args, **kwargs):
super(StaticForm, self).__init__(*args, **kwargs)
self.fields['sftpuser'].widget = forms.HiddenInput()
if self.instance.id is not None:
self.fields['username'].widget = forms.HiddenInput()
self.fields['password1'].widget = forms.HiddenInput()
self.fields['password2'].widget = forms.HiddenInput()
def clean(self):
if not self.instance.id:
webapp_server = self.cleaned_data.get("target_server")
username = self.cleaned_data.get('username')
if webapp_server is None:
self.add_error("target_server", _("choice some target_server"))
else:
if webapp_server.name in WEBAPP_NEW_SERVERS and username == '':
self.add_error("username", _("SFTP user is required by new webservers"))
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
msg = _("The two password fields didn't match.")
raise ValidationError(msg)
return password2
class StaticApp(AppType): class StaticApp(AppType):
name = 'static' name = 'static'
verbose_name = "Static" verbose_name = "Static"
@ -67,7 +17,7 @@ class StaticApp(AppType):
"Apache2 will be used to serve static content and execute CGI files.") "Apache2 will be used to serve static content and execute CGI files.")
icon = 'orchestra/icons/apps/Static.png' icon = 'orchestra/icons/apps/Static.png'
option_groups = (AppOption.FILESYSTEM,) option_groups = (AppOption.FILESYSTEM,)
form = StaticForm form = ExtendedPluginDataForm
def get_directive(self): def get_directive(self):
return ('static', self.instance.get_path()) return ('static', self.instance.get_path())

View File

@ -5,9 +5,10 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.plugins.forms import PluginDataForm from orchestra.plugins.forms import PluginDataForm, ExtendedPluginDataForm
from orchestra.utils.functional import cached from orchestra.utils.functional import cached
from orchestra.utils.python import OrderedSet from orchestra.utils.python import OrderedSet, random_ascii
from orchestra.settings import NEW_SERVERS
from .. import settings, utils from .. import settings, utils
from ..options import AppOption from ..options import AppOption
@ -19,13 +20,23 @@ help_message = _("Version of PHP used to execute this webapp. <br>"
"Changing the PHP version may result in application malfunction, " "Changing the PHP version may result in application malfunction, "
"make sure that everything continue to work as expected.") "make sure that everything continue to work as expected.")
class PHPAppForm(ExtendedPluginDataForm):
class PHPAppForm(PluginDataForm):
php_version = forms.ChoiceField(label=_("PHP version"), php_version = forms.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_VERSIONS, choices=settings.WEBAPPS_PHP_VERSIONS,
initial=settings.WEBAPPS_DEFAULT_PHP_VERSION, initial=settings.WEBAPPS_DEFAULT_PHP_VERSION,
help_text=help_message) help_text=help_message)
# def clean_php_version(self):
# # TODO: restriccin PHP diferentes servers
# if not self.instance.id:
# webapp_server = self.cleaned_data.get("target_server")
# php_version = self.cleaned_data.get('php_version')
# if webapp_server is None:
# pass
# else:
# if webapp_server.name in NEW_SERVERS and not username:
# self.add_error("php_version", _(f"Server {webapp_server} not allow {php_version}"))
class PHPAppSerializer(serializers.Serializer): class PHPAppSerializer(serializers.Serializer):
php_version = serializers.ChoiceField(label=_("PHP version"), php_version = serializers.ChoiceField(label=_("PHP version"),

View File

@ -4,6 +4,12 @@ from django.utils.encoding import force_str
from orchestra.admin.utils import admin_link from orchestra.admin.utils import admin_link
from orchestra.forms.widgets import SpanWidget from orchestra.forms.widgets import SpanWidget
from django.core.exceptions import ValidationError
from orchestra.core import validators
from orchestra.utils.python import random_ascii
from orchestra.settings import NEW_SERVERS
from django.utils.translation import gettext_lazy as _
class PluginForm(forms.ModelForm): class PluginForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -71,3 +77,51 @@ class PluginModelAdapterForm(PluginForm):
display = '%s <a href=".">change</a>' % link display = '%s <a href=".">change</a>' % link
self.fields[self.plugin_field].widget = SpanWidget(original=value, display=display) self.fields[self.plugin_field].widget = SpanWidget(original=value, display=display)
help_text = self.fields[self.plugin_field].help_text help_text = self.fields[self.plugin_field].help_text
# --------------------------------------------------
class ExtendedPluginDataForm(PluginDataForm):
# añade campos de username para creacion de sftpuser en servidores nuevos
username = forms.CharField(label=_("Username"), max_length=16,
required=False, validators=[validators.validate_name],
help_text=_("Required. 16 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
error_messages={
'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and "
"@/./+/-/_ characters.")})
password1 = forms.CharField(label=_("Password"), required=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
validators=[validators.validate_password],
help_text=_("Suggestion: %s") % random_ascii(15))
password2 = forms.CharField(label=_("Password confirmation"), required=False,
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
def __init__(self, *args, **kwargs):
super(ExtendedPluginDataForm, self).__init__(*args, **kwargs)
self.fields['sftpuser'].widget = forms.HiddenInput()
if self.instance.id is not None:
self.fields['username'].widget = forms.HiddenInput()
self.fields['password1'].widget = forms.HiddenInput()
self.fields['password2'].widget = forms.HiddenInput()
def clean_username(self):
if not self.instance.id:
webapp_server = self.cleaned_data.get("target_server")
username = self.cleaned_data.get('username')
if webapp_server is None:
self.add_error("target_server", _("choice some server"))
else:
if webapp_server.name in NEW_SERVERS and not username:
self.add_error("username", _("SFTP user is required by new webservers"))
return username
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
msg = _("The two password fields didn't match.")
raise ValidationError(msg)
return password2

View File

@ -90,3 +90,11 @@ ORCHESTRA_SSH_CONTROL_PATH = Setting('ORCHESTRA_SSH_CONTROL_PATH',
'~/.ssh/orchestra-%r-%h-%p', '~/.ssh/orchestra-%r-%h-%p',
help_text='Location for the control socket used by the multiplexed sessions, used for SSH connection reuse.' help_text='Location for the control socket used by the multiplexed sessions, used for SSH connection reuse.'
) )
NEW_SERVERS = Setting('NEW_SERVERS',
(
'bookworm',
'web-11.pangea.lan',
'web-12.pangea.lan',
)
)