Split Operation and BackendOperation
This commit is contained in:
parent
4a6d29ebd7
commit
bac2b94d70
16
TODO.md
16
TODO.md
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
* env vars instead of multiple settings files: https://devcenter.heroku.com/articles/config-vars ?
|
* env vars instead of multiple settings files: https://devcenter.heroku.com/articles/config-vars ?
|
||||||
|
|
||||||
# TODO Log changes from rest api (serialized objects)
|
|
||||||
|
|
||||||
* backend logs with hal logo
|
* backend logs with hal logo
|
||||||
|
|
||||||
* LAST version of this shit http://wkhtmltopdf.org/downloads.h otml
|
* LAST version of this shit http://wkhtmltopdf.org/downloads.h otml
|
||||||
|
@ -175,9 +173,8 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
* autoexpand mailbox.filter according to filtering options (js)
|
* autoexpand mailbox.filter according to filtering options (js)
|
||||||
|
|
||||||
* allow empty metric pack for default rates? changes on rating algo
|
* allow empty metric pack for default rates? changes on rating algo
|
||||||
# IMPORTANT make sure no order is created for mailboxes that include disk? or just 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?
|
# 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?
|
||||||
|
|
||||||
* Improve performance of admin change lists with debug toolbar and prefech_related
|
|
||||||
# DOMINI REGISTRE MIGRATION SCRIPTS
|
# DOMINI REGISTRE MIGRATION SCRIPTS
|
||||||
|
|
||||||
# lines too long on invoice, double lines or cut, and make margin wider
|
# lines too long on invoice, double lines or cut, and make margin wider
|
||||||
|
@ -191,7 +188,7 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
# display subline links on billlines, to show that they exists.
|
# display subline links on billlines, to show that they exists.
|
||||||
* update service orders on a celery task? because it take alot
|
* update service orders on a celery task? because it take alot
|
||||||
|
|
||||||
# billline quantity eval('10x100') instead of miningless description '(10*100)'
|
# 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
|
# 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.
|
* 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.
|
||||||
|
@ -211,12 +208,12 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
|
|
||||||
* modeladmin Default filter + search isn't working, prepend filter when searching
|
* modeladmin Default filter + search isn't working, prepend filter when searching
|
||||||
|
|
||||||
# IMPORTANT do all modles.py TODOs and create migrations for finished apps
|
|
||||||
|
|
||||||
* create service help templates based on urlqwargs with the most basic services.
|
* create service help templates based on urlqwargs with the most basic services.
|
||||||
|
|
||||||
# TDOO Base price: domini propi (all domains) + extra for other domains
|
# TDOO Base price: domini propi (all domains) + extra for other domains
|
||||||
|
|
||||||
|
# IMPORTANT op.instance = copy.deepcopy(instance) ValueError: Cannot assign "<SaaS: blog@WordPressService>": "SaaS" instance isn't saved in the database.
|
||||||
|
# Separate operation from models !! BackendOperation and Operation
|
||||||
|
|
||||||
Translation
|
Translation
|
||||||
-----------
|
-----------
|
||||||
|
@ -258,7 +255,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?
|
# 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
|
* implement delete All related services
|
||||||
|
|
||||||
# FIXME address name change does not remove old one :P
|
# FIXME address name change does not remove old one :P, readonly, perhaps we can regenerate all addresses using backend.prepare()?
|
||||||
|
|
||||||
* read https://docs.djangoproject.com/en/dev/releases/1.8/ and fix deprecation warnings
|
* read https://docs.djangoproject.com/en/dev/releases/1.8/ and fix deprecation warnings
|
||||||
* remove admin object display_links , like contents webapps
|
* remove admin object display_links , like contents webapps
|
||||||
|
@ -277,4 +274,5 @@ https://code.djangoproject.com/ticket/24576
|
||||||
|
|
||||||
* migrate to DRF3.x
|
* migrate to DRF3.x
|
||||||
|
|
||||||
* move all tests on django-orchestra/tests
|
* move all tests to django-orchestra/tests
|
||||||
|
* *natural keys: those fields that uniquely identify a service, list.name, website.name, webapp.name+account, make sure rest api can not edit thos things
|
||||||
|
|
|
@ -244,7 +244,7 @@ class ChangePasswordAdminMixin(object):
|
||||||
'save_as': False,
|
'save_as': False,
|
||||||
'show_save': True,
|
'show_save': True,
|
||||||
}
|
}
|
||||||
context.update(admin.site.each_context())
|
context.update(admin.site.each_context(request))
|
||||||
return TemplateResponse(request,
|
return TemplateResponse(request,
|
||||||
self.change_user_password_template,
|
self.change_user_password_template,
|
||||||
context, current_app=self.admin_site.name)
|
context, current_app=self.admin_site.name)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
||||||
from orchestra.contrib.orchestration.models import BackendOperation as Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
from orchestra.core import services, accounts
|
from orchestra.core import services, accounts
|
||||||
from orchestra.utils import send_email_template
|
from orchestra.utils import send_email_template
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class MySQLBackend(ServiceController):
|
||||||
if database.type != database.MYSQL:
|
if database.type != database.MYSQL:
|
||||||
return
|
return
|
||||||
context = self.get_context(database)
|
context = self.get_context(database)
|
||||||
self.append("mysql -e 'DROP DATABASE `%(database)s`;' || exit_code=1" % context)
|
self.append("mysql -e 'DROP DATABASE `%(database)s`;' || exit_code=$?" % context)
|
||||||
self.append("mysql mysql -e 'DELETE FROM db WHERE db = \"%(database)s\";'" % context)
|
self.append("mysql mysql -e 'DELETE FROM db WHERE db = \"%(database)s\";'" % context)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
@ -76,7 +76,7 @@ class MySQLUserBackend(ServiceController):
|
||||||
return
|
return
|
||||||
context = self.get_context(user)
|
context = self.get_context(user)
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
mysql -e 'DROP USER "%(username)s"@"%(host)s";' \
|
mysql -e 'DROP USER "%(username)s"@"%(host)s";' || exit_code=$? \
|
||||||
""") % context
|
""") % context
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import textwrap
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration import ServiceController, replace
|
from orchestra.contrib.orchestration import ServiceController, replace
|
||||||
from orchestra.contrib.orchestration.models import BackendOperation as Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
@ -41,10 +41,11 @@ class Bind9MasterDomainBackend(ServiceController):
|
||||||
|
|
||||||
def update_conf(self, context):
|
def update_conf(self, context):
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo '%(conf)s') || {
|
conf='%(conf)s'
|
||||||
|
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo "${conf}") || {
|
||||||
sed -i -e '/zone\s\s*"%(name)s".*/,/^\s*};/d' \\
|
sed -i -e '/zone\s\s*"%(name)s".*/,/^\s*};/d' \\
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s
|
||||||
echo '%(conf)s' >> %(conf_path)s
|
echo "${conf}" >> %(conf_path)s
|
||||||
UPDATED=1
|
UPDATED=1
|
||||||
}""") % context
|
}""") % context
|
||||||
)
|
)
|
||||||
|
@ -80,7 +81,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
||||||
def get_servers(self, domain, backend):
|
def get_servers(self, domain, backend):
|
||||||
""" Get related server IPs from registered backend routes """
|
""" Get related server IPs from registered backend routes """
|
||||||
from orchestra.contrib.orchestration.manager import router
|
from orchestra.contrib.orchestration.manager import router
|
||||||
operation = Operation.create(backend, domain, Operation.SAVE)
|
operation = Operation(backend, domain, Operation.SAVE)
|
||||||
servers = []
|
servers = []
|
||||||
for server in router.get_servers(operation):
|
for server in router.get_servers(operation):
|
||||||
servers.append(server.get_ip())
|
servers.append(server.get_ip())
|
||||||
|
|
|
@ -107,7 +107,8 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
'email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward',
|
'email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward',
|
||||||
)
|
)
|
||||||
list_filter = (HasMailboxListFilter, HasForwardListFilter)
|
list_filter = (HasMailboxListFilter, HasForwardListFilter)
|
||||||
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
|
fields = ('account_link', 'email_link', 'mailboxes', 'forward')
|
||||||
|
add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
|
||||||
inlines = [AutoresponseInline]
|
inlines = [AutoresponseInline]
|
||||||
search_fields = ('name', 'domain__name', 'forward', 'mailboxes__name', 'account__username')
|
search_fields = ('name', 'domain__name', 'forward', 'mailboxes__name', 'account__username')
|
||||||
readonly_fields = ('account_link', 'domain_link', 'email_link')
|
readonly_fields = ('account_link', 'domain_link', 'email_link')
|
||||||
|
|
|
@ -17,16 +17,14 @@ class Mailbox(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=64, unique=True,
|
name = models.CharField(_("name"), max_length=64, unique=True,
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."),
|
help_text=_("Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."),
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(r'^[\w.@+-]+$', _("Enter a valid mailbox name."))
|
RegexValidator(r'^[\w.@+-]+$', _("Enter a valid mailbox name.")),
|
||||||
])
|
])
|
||||||
password = models.CharField(_("password"), max_length=128)
|
password = models.CharField(_("password"), max_length=128)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='mailboxes')
|
related_name='mailboxes')
|
||||||
filtering = models.CharField(max_length=16,
|
filtering = models.CharField(max_length=16,
|
||||||
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
|
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
|
||||||
choices=[
|
choices=[(k, v[0]) for k,v in settings.MAILBOXES_MAILBOX_FILTERINGS.items()])
|
||||||
(k, v[0]) for k,v in settings.MAILBOXES_MAILBOX_FILTERINGS.items()
|
|
||||||
])
|
|
||||||
custom_filtering = models.TextField(_("filtering"), blank=True,
|
custom_filtering = models.TextField(_("filtering"), blank=True,
|
||||||
validators=[validators.validate_sieve],
|
validators=[validators.validate_sieve],
|
||||||
help_text=_("Arbitrary email filtering in sieve language. "
|
help_text=_("Arbitrary email filtering in sieve language. "
|
||||||
|
@ -80,7 +78,7 @@ class Mailbox(models.Model):
|
||||||
|
|
||||||
def get_local_address(self):
|
def get_local_address(self):
|
||||||
if not settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN:
|
if not settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN:
|
||||||
raise AttributeError("Mailboxes do not have a defined local address domain")
|
raise AttributeError("Mailboxes do not have a defined local address domain.")
|
||||||
return '@'.join((self.name, settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN))
|
return '@'.join((self.name, settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class MiscServiceAdmin(ExtendedModelAdmin):
|
||||||
num_instances.admin_order_field = 'instances__count'
|
num_instances.admin_order_field = 'instances__count'
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super(MiscServiceAdmin, self).queryset(request)
|
qs = super(MiscServiceAdmin, self).get_queryset(request)
|
||||||
return qs.annotate(models.Count('instances', distinct=True))
|
return qs.annotate(models.Count('instances', distinct=True))
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
|
|
|
@ -1 +1,60 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
from .backends import ServiceBackend, ServiceController, replace
|
from .backends import ServiceBackend, ServiceController, replace
|
||||||
|
|
||||||
|
|
||||||
|
class Operation():
|
||||||
|
DELETE = 'delete'
|
||||||
|
SAVE = 'save'
|
||||||
|
MONITOR = 'monitor'
|
||||||
|
EXCEEDED = 'exceeded'
|
||||||
|
RECOVERY = 'recovery'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
""" set() """
|
||||||
|
return hash(self.backend) + hash(self.instance) + hash(self.action)
|
||||||
|
|
||||||
|
def __eq__(self, operation):
|
||||||
|
""" set() """
|
||||||
|
return hash(self) == hash(operation)
|
||||||
|
|
||||||
|
def __init__(self, backend, instance, action, servers=None):
|
||||||
|
self.backend = backend
|
||||||
|
# instance should maintain any dynamic attribute until backend execution
|
||||||
|
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
|
||||||
|
self.instance = copy.deepcopy(instance)
|
||||||
|
self.action = action
|
||||||
|
self.servers = servers
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute(cls, operations, async=False):
|
||||||
|
from . import manager
|
||||||
|
scripts, block = manager.generate(operations)
|
||||||
|
return manager.execute(scripts, block=block, async=async)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute_action(cls, instance, action):
|
||||||
|
backends = ServiceBackend.get_backends(instance=instance, action=action)
|
||||||
|
operations = [cls(backend_cls, instance, action) for backend_cls in backends]
|
||||||
|
return cls.execute(operations)
|
||||||
|
|
||||||
|
def preload_context(self):
|
||||||
|
"""
|
||||||
|
Heuristic
|
||||||
|
Running get_context will prevent most of related objects do not exist errors
|
||||||
|
"""
|
||||||
|
if self.action == self.DELETE:
|
||||||
|
if hasattr(self.backend, 'get_context'):
|
||||||
|
self.backend().get_context(self.instance)
|
||||||
|
|
||||||
|
def create(self, log):
|
||||||
|
from .models import BackendOperation
|
||||||
|
return BackendOperation.objects.create(
|
||||||
|
log=log,
|
||||||
|
backend=self.backend.get_name(),
|
||||||
|
instance=self.instance,
|
||||||
|
action=self.action,
|
||||||
|
)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin, messages
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.html import monospace_format
|
from orchestra.admin.html import monospace_format
|
||||||
from orchestra.admin.utils import admin_link, admin_date, admin_colored
|
from orchestra.admin.utils import admin_link, admin_date, admin_colored
|
||||||
|
|
||||||
|
from . import settings
|
||||||
from .backends import ServiceBackend
|
from .backends import ServiceBackend
|
||||||
from .models import Server, Route, BackendLog, BackendOperation
|
from .models import Server, Route, BackendLog, BackendOperation
|
||||||
from .widgets import RouteBackendSelect
|
from .widgets import RouteBackendSelect
|
||||||
|
@ -66,6 +68,19 @@ class RouteAdmin(admin.ModelAdmin):
|
||||||
if obj:
|
if obj:
|
||||||
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
|
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
def show_orchestration_disabled(self, request):
|
||||||
|
if settings.ORCHESTRATION_DISABLE_EXECUTION:
|
||||||
|
msg = _("Orchestration execution is disabled by <tt>ORCHESTRATION_DISABLE_EXECUTION</tt> setting.")
|
||||||
|
self.message_user(request, mark_safe(msg), messages.WARNING)
|
||||||
|
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
self.show_orchestration_disabled(request)
|
||||||
|
return super(RouteAdmin, self).changelist_view(request, extra_context)
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
self.show_orchestration_disabled(request)
|
||||||
|
return super(RouteAdmin, self).changeform_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
class BackendOperationInline(admin.TabularInline):
|
class BackendOperationInline(admin.TabularInline):
|
||||||
|
|
|
@ -10,13 +10,13 @@ class Command(BaseCommand):
|
||||||
help = 'Runs orchestration backends.'
|
help = 'Runs orchestration backends.'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('model', nargs='+',
|
parser.add_argument('model',
|
||||||
help='App label of an application to synchronize the
|
help='Label of a model to execute the orchestration.')
|
||||||
parser.add_argument('query', nargs='?',
|
parser.add_argument('query', nargs='*',
|
||||||
help='Query arguments for filter().')
|
help='Query arguments for filter().')
|
||||||
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
|
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
|
||||||
help='Tells Django to NOT prompt the user for input of any kind.')
|
help='Tells Django to NOT prompt the user for input of any kind.')
|
||||||
parser.add_argument('--action', action='store', dest='database',
|
parser.add_argument('--action', action='store', dest='action',
|
||||||
default='save', help='Executes action. Defaults to "save".')
|
default='save', help='Executes action. Defaults to "save".')
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
@ -9,10 +9,10 @@ from django.core.mail import mail_admins
|
||||||
|
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from . import settings
|
from . import settings, Operation
|
||||||
from .backends import ServiceBackend
|
from .backends import ServiceBackend
|
||||||
from .helpers import send_report
|
from .helpers import send_report
|
||||||
from .models import BackendLog, BackendOperation as Operation
|
from .models import BackendLog
|
||||||
from .signals import pre_action, post_action
|
from .signals import pre_action, post_action
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,6 +95,9 @@ def generate(operations):
|
||||||
|
|
||||||
def execute(scripts, block=False, async=False):
|
def execute(scripts, block=False, async=False):
|
||||||
""" executes the operations on the servers """
|
""" executes the operations on the servers """
|
||||||
|
if settings.ORCHESTRATION_DISABLE_EXECUTION:
|
||||||
|
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.')
|
||||||
|
return []
|
||||||
# Execute scripts on each server
|
# Execute scripts on each server
|
||||||
threads = []
|
threads = []
|
||||||
executions = []
|
executions = []
|
||||||
|
@ -120,10 +123,9 @@ def execute(scripts, block=False, async=False):
|
||||||
if hasattr(execution, 'log'):
|
if hasattr(execution, 'log'):
|
||||||
for operation in operations:
|
for operation in operations:
|
||||||
logger.info("Executed %s" % str(operation))
|
logger.info("Executed %s" % str(operation))
|
||||||
operation.log = execution.log
|
if operation.instance.pk:
|
||||||
if operation.object_id:
|
# Not all backends are called with objects saved on the database
|
||||||
# Not all backends are call with objects saved on the database
|
operation.create(execution.log)
|
||||||
operation.save()
|
|
||||||
stdout = execution.log.stdout.strip()
|
stdout = execution.log.stdout.strip()
|
||||||
stdout and logger.debug('STDOUT %s', stdout)
|
stdout and logger.debug('STDOUT %s', stdout)
|
||||||
stderr = execution.log.stderr.strip()
|
stderr = execution.log.stderr.strip()
|
||||||
|
@ -157,7 +159,7 @@ def collect(instance, action, **kwargs):
|
||||||
candidates = [candidate]
|
candidates = [candidate]
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
# Check if a delete for candidate is in operations
|
# Check if a delete for candidate is in operations
|
||||||
delete_mock = Operation.create(backend_cls, candidate, Operation.DELETE)
|
delete_mock = Operation(backend_cls, candidate, Operation.DELETE)
|
||||||
if delete_mock not in operations:
|
if delete_mock not in operations:
|
||||||
# related objects with backend.model trigger save()
|
# related objects with backend.model trigger save()
|
||||||
instances.append((candidate, Operation.SAVE))
|
instances.append((candidate, Operation.SAVE))
|
||||||
|
@ -165,7 +167,7 @@ def collect(instance, action, **kwargs):
|
||||||
# Maintain consistent state of operations based on save/delete behaviour
|
# Maintain consistent state of operations based on save/delete behaviour
|
||||||
# Prevent creating a deleted selected by deleting existing saves
|
# Prevent creating a deleted selected by deleting existing saves
|
||||||
if iaction == Operation.DELETE:
|
if iaction == Operation.DELETE:
|
||||||
save_mock = Operation.create(backend_cls, selected, Operation.SAVE)
|
save_mock = Operation(backend_cls, selected, Operation.SAVE)
|
||||||
try:
|
try:
|
||||||
operations.remove(save_mock)
|
operations.remove(save_mock)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -185,7 +187,7 @@ def collect(instance, action, **kwargs):
|
||||||
break
|
break
|
||||||
if not execute:
|
if not execute:
|
||||||
continue
|
continue
|
||||||
operation = Operation.create(backend_cls, selected, iaction)
|
operation = Operation(backend_cls, selected, iaction)
|
||||||
# Only schedule operations if the router gives servers to execute into
|
# Only schedule operations if the router gives servers to execute into
|
||||||
servers = router.get_servers(operation, cache=route_cache)
|
servers = router.get_servers(operation, cache=route_cache)
|
||||||
if servers:
|
if servers:
|
||||||
|
|
|
@ -8,9 +8,9 @@ from django.http.response import HttpResponseServerError
|
||||||
|
|
||||||
from orchestra.utils.python import OrderedSet
|
from orchestra.utils.python import OrderedSet
|
||||||
|
|
||||||
from . import manager
|
from . import manager, Operation
|
||||||
from .helpers import message_user
|
from .helpers import message_user
|
||||||
from .models import BackendLog, BackendOperation as Operation
|
from .models import BackendLog
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import copy
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.module_loading import autodiscover_modules
|
from django.utils.module_loading import autodiscover_modules
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -98,12 +98,6 @@ class BackendOperation(models.Model):
|
||||||
"""
|
"""
|
||||||
Encapsulates an operation, storing its related object, the action and the backend.
|
Encapsulates an operation, storing its related object, the action and the backend.
|
||||||
"""
|
"""
|
||||||
DELETE = 'delete'
|
|
||||||
SAVE = 'save'
|
|
||||||
MONITOR = 'monitor'
|
|
||||||
EXCEEDED = 'exceeded'
|
|
||||||
RECOVERY = 'recovery'
|
|
||||||
|
|
||||||
log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
|
log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
|
||||||
backend = models.CharField(_("backend"), max_length=256)
|
backend = models.CharField(_("backend"), max_length=256)
|
||||||
action = models.CharField(_("action"), max_length=64)
|
action = models.CharField(_("action"), max_length=64)
|
||||||
|
@ -119,46 +113,7 @@ class BackendOperation(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
|
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
|
||||||
|
|
||||||
def __hash__(self):
|
@cached_property
|
||||||
""" set() """
|
|
||||||
backend = getattr(self, 'backend', self.backend)
|
|
||||||
return hash(backend) + hash(self.instance) + hash(self.action)
|
|
||||||
|
|
||||||
def __eq__(self, operation):
|
|
||||||
""" set() """
|
|
||||||
return hash(self) == hash(operation)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, backend, instance, action, servers=None):
|
|
||||||
op = cls(backend=backend.get_name(), instance=instance, action=action)
|
|
||||||
op.backend = backend
|
|
||||||
# instance should maintain any dynamic attribute until backend execution
|
|
||||||
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
|
|
||||||
op.instance = copy.deepcopy(instance)
|
|
||||||
op.servers = servers
|
|
||||||
return op
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def execute(cls, operations, async=False):
|
|
||||||
from . import manager
|
|
||||||
scripts, block = manager.generate(operations)
|
|
||||||
return manager.execute(scripts, block=block, async=async)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def execute_action(cls, instance, action):
|
|
||||||
backends = ServiceBackend.get_backends(instance=instance, action=action)
|
|
||||||
operations = [cls.create(backend_cls, instance, action) for backend_cls in backends]
|
|
||||||
return cls.execute(operations)
|
|
||||||
|
|
||||||
def preload_context(self):
|
|
||||||
"""
|
|
||||||
Heuristic
|
|
||||||
Running get_context will prevent most of related objects do not exist errors
|
|
||||||
"""
|
|
||||||
if self.action == self.DELETE:
|
|
||||||
if hasattr(self.backend, 'get_context'):
|
|
||||||
self.backend().get_context(self.instance)
|
|
||||||
|
|
||||||
def backend_class(self):
|
def backend_class(self):
|
||||||
return ServiceBackend.get_backend(self.backend)
|
return ServiceBackend.get_backend(self.backend)
|
||||||
|
|
||||||
|
@ -187,7 +142,7 @@ class Route(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s@%s" % (self.backend, self.host)
|
return "%s@%s" % (self.backend, self.host)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def backend_class(self):
|
def backend_class(self):
|
||||||
return ServiceBackend.get_backend(self.backend)
|
return ServiceBackend.get_backend(self.backend)
|
||||||
|
|
||||||
|
|
|
@ -23,3 +23,8 @@ ORCHESTRATION_ROUTER = getattr(settings, 'ORCHESTRATION_ROUTER',
|
||||||
ORCHESTRATION_TEMP_SCRIPT_PATH = getattr(settings, 'ORCHESTRATION_TEMP_SCRIPT_PATH',
|
ORCHESTRATION_TEMP_SCRIPT_PATH = getattr(settings, 'ORCHESTRATION_TEMP_SCRIPT_PATH',
|
||||||
'/dev/shm'
|
'/dev/shm'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ORCHESTRATION_DISABLE_EXECUTION = getattr(settings, 'ORCHESTRATION_DISABLE_EXECUTION',
|
||||||
|
False
|
||||||
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from orchestra.utils.tests import BaseTestCase
|
from orchestra.utils.tests import BaseTestCase
|
||||||
|
|
||||||
from .. import backends
|
from .. import backends, Operation
|
||||||
from ..models import Route, Server, BackendOperation as Operation
|
from ..models import Route, Server
|
||||||
|
|
||||||
|
|
||||||
class RouterTests(BaseTestCase):
|
class RouterTests(BaseTestCase):
|
||||||
|
|
|
@ -241,6 +241,7 @@ class MetricStorage(models.Model):
|
||||||
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
||||||
created_on = models.DateField(_("created"), auto_now_add=True)
|
created_on = models.DateField(_("created"), auto_now_add=True)
|
||||||
# default=lambda: timezone.now())
|
# default=lambda: timezone.now())
|
||||||
|
# TODO time field?
|
||||||
updated_on = models.DateTimeField(_("updated"))
|
updated_on = models.DateTimeField(_("updated"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services, accounts
|
from orchestra.core import services, accounts
|
||||||
|
@ -14,6 +15,9 @@ from . import rating
|
||||||
class Plan(models.Model):
|
class Plan(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
|
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
|
||||||
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
||||||
|
# TODO is_active = models.BooleanField(_("active"), default=True,
|
||||||
|
# help_text=_("Designates whether this account should be treated as active. "
|
||||||
|
# "Unselect this instead of deleting accounts."))
|
||||||
is_default = models.BooleanField(_("default"), default=False,
|
is_default = models.BooleanField(_("default"), default=False,
|
||||||
help_text=_("Designates whether this plan is used by default or not."))
|
help_text=_("Designates whether this plan is used by default or not."))
|
||||||
is_combinable = models.BooleanField(_("combinable"), default=True,
|
is_combinable = models.BooleanField(_("combinable"), default=True,
|
||||||
|
@ -42,6 +46,10 @@ class ContractedPlan(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.plan)
|
return str(self.plan)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def active(self):
|
||||||
|
return self.plan.is_active and self.account.is_active
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.pk and not self.plan.allow_multiple:
|
if not self.pk and not self.plan.allow_multiple:
|
||||||
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():
|
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
from orchestra.contrib.orchestration.models import BackendOperation as Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
from orchestra.models.utils import get_model_field_path
|
from orchestra.models.utils import get_model_field_path
|
||||||
|
|
||||||
from .backends import ServiceMonitor
|
from .backends import ServiceMonitor
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .services import SoftwareService
|
||||||
class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
|
class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('name', 'service', 'display_site_domain', 'account_link', 'is_active')
|
list_display = ('name', 'service', 'display_site_domain', 'account_link', 'is_active')
|
||||||
list_filter = ('service', 'is_active')
|
list_filter = ('service', 'is_active')
|
||||||
|
search_fields = ('name', 'account__username')
|
||||||
change_readonly_fields = ('service',)
|
change_readonly_fields = ('service',)
|
||||||
plugin = SoftwareService
|
plugin = SoftwareService
|
||||||
plugin_field = 'service'
|
plugin_field = 'service'
|
||||||
|
|
|
@ -18,8 +18,9 @@ class BSCWBackend(ServiceController):
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
if [[ $(%(bsadmin)s register %(email)s) ]]; then
|
if [[ $(%(bsadmin)s register %(email)s) ]]; then
|
||||||
echo 'ValidationError: email-exists'
|
echo 'ValidationError: email-exists'
|
||||||
elif [[ $(%(bsadmin)s users -n %(username)s) ]]; then
|
fi
|
||||||
echo 'ValidationError: username-exists'
|
if [[ $(%(bsadmin)s users -n %(username)s) ]]; then
|
||||||
|
echo 'ValidationError: user-exists'
|
||||||
fi""") % context
|
fi""") % context
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@ class SaaS(models.Model):
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
if not self.pk:
|
||||||
|
self.name = self.name.lower()
|
||||||
self.data = self.service_instance.clean_data()
|
self.data = self.service_instance.clean_data()
|
||||||
|
|
||||||
def get_site_domain(self):
|
def get_site_domain(self):
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.core.validators import RegexValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra import plugins
|
from orchestra import plugins
|
||||||
from orchestra.contrib.orchestration.models import BackendOperation as Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms import widgets
|
||||||
from orchestra.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
|
@ -109,7 +109,7 @@ class SoftwareService(plugins.Plugin):
|
||||||
errors = {}
|
errors = {}
|
||||||
if 'user-exists' in log.stdout:
|
if 'user-exists' in log.stdout:
|
||||||
errors['name'] = _("User with this username already exists.")
|
errors['name'] = _("User with this username already exists.")
|
||||||
elif 'email-exists' in log.stdout:
|
if 'email-exists' in log.stdout:
|
||||||
errors['email'] = _("User with this email address already exists.")
|
errors['email'] = _("User with this email address already exists.")
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
|
@ -7,6 +7,10 @@ from .options import SoftwareService, SoftwareServiceForm
|
||||||
|
|
||||||
class WordPressForm(SoftwareServiceForm):
|
class WordPressForm(SoftwareServiceForm):
|
||||||
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
|
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(WordPressForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['name'].label = _("Site name")
|
||||||
|
|
||||||
|
|
||||||
class WordPressDataSerializer(serializers.Serializer):
|
class WordPressDataSerializer(serializers.Serializer):
|
||||||
|
@ -18,5 +22,5 @@ class WordPressService(SoftwareService):
|
||||||
form = WordPressForm
|
form = WordPressForm
|
||||||
serializer = WordPressDataSerializer
|
serializer = WordPressDataSerializer
|
||||||
icon = 'orchestra/icons/apps/WordPress.png'
|
icon = 'orchestra/icons/apps/WordPress.png'
|
||||||
site_name_base_domain = 'blogs.orchestra.lan'
|
site_base_domain = 'blogs.orchestra.lan'
|
||||||
change_readonly_fileds = ('email',)
|
change_readonly_fileds = ('email',)
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import get_object_from_url
|
from orchestra.admin.utils import get_object_from_url
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def update_orders(modeladmin, request, queryset, extra_context=None):
|
def update_orders(modeladmin, request, queryset, extra_context=None):
|
||||||
if not queryset:
|
if not queryset:
|
||||||
|
|
|
@ -53,7 +53,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
bool(self.matches(obj))
|
bool(self.matches(obj))
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
name = type(exception).__name__
|
name = type(exception).__name__
|
||||||
raise ValidationError(': '.join((name, exception)))
|
raise ValidationError(': '.join((name, str(exception))))
|
||||||
|
|
||||||
def validate_metric(self, service):
|
def validate_metric(self, service):
|
||||||
try:
|
try:
|
||||||
|
@ -64,7 +64,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
bool(self.get_metric(obj))
|
bool(self.get_metric(obj))
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
name = type(exception).__name__
|
name = type(exception).__name__
|
||||||
raise ValidationError(': '.join((name, exception)))
|
raise ValidationError(': '.join((name, str(exception))))
|
||||||
|
|
||||||
def get_content_type(self):
|
def get_content_type(self):
|
||||||
if not self.model:
|
if not self.model:
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.decorators import action_with_confirmation
|
from orchestra.admin.decorators import action_with_confirmation
|
||||||
from orchestra.contrib.orchestration.models import BackendOperation as Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
|
|
||||||
|
|
||||||
class GrantPermissionForm(forms.Form):
|
class GrantPermissionForm(forms.Form):
|
||||||
|
|
|
@ -74,7 +74,7 @@ class UNIXUserBackend(ServiceController):
|
||||||
'shell': user.shell,
|
'shell': user.shell,
|
||||||
'mainuser': user.username if user.is_main else user.account.username,
|
'mainuser': user.username if user.is_main else user.account.username,
|
||||||
'home': user.get_home(),
|
'home': user.get_home(),
|
||||||
'base_home': self.get_base_home(),
|
'base_home': user.get_base_home(),
|
||||||
}
|
}
|
||||||
return replace(context, "'", '"')
|
return replace(context, "'", '"')
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class Exim4Traffic(ServiceMonitor):
|
||||||
script_executable = '/usr/bin/python'
|
script_executable = '/usr/bin/python'
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
mainlog = settings.LISTS_MAILMAN_POST_LOG_PATH
|
mainlog = settings.SYSTEMUSERS_MAIL_LOG_PATH
|
||||||
context = {
|
context = {
|
||||||
'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
|
'current_date': self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||||
'mainlogs': str((mainlog, mainlog+'.1')),
|
'mainlogs': str((mainlog, mainlog+'.1')),
|
||||||
|
|
|
@ -40,7 +40,6 @@ class SystemUser(models.Model):
|
||||||
groups = models.ManyToManyField('self', blank=True, symmetrical=False,
|
groups = models.ManyToManyField('self', blank=True, symmetrical=False,
|
||||||
help_text=_("A new group will be created for the user. "
|
help_text=_("A new group will be created for the user. "
|
||||||
"Which additional groups would you like them to be a member of?"))
|
"Which additional groups would you like them to be a member of?"))
|
||||||
# is_main = models.BooleanField(_("is main"), default=False)
|
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
|
|
|
@ -32,6 +32,10 @@ SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SYSTEMUSERS_MAIL_LOG_PATH = getattr(settings, 'SYSTEMUSERS_MAIL_LOG_PATH',
|
||||||
|
'/var/log/exim4/mainlog'
|
||||||
|
)
|
||||||
|
|
||||||
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
|
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
|
||||||
('www-data',)
|
('www-data',)
|
||||||
)
|
)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class PHPAppOption(AppOption):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PHPAppOption, self).validate()
|
super(PHPAppOption, self).validate()
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
php_version = self.instance.webapp.type_instance.get_php_version()
|
php_version = self.instance.webapp.type_instance.get_php_version_number()
|
||||||
if php_version and php_version > self.deprecated:
|
if php_version and php_version > self.deprecated:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("This option is deprecated since PHP version %s.") % str(self.deprecated)
|
_("This option is deprecated since PHP version %s.") % str(self.deprecated)
|
||||||
|
|
|
@ -163,9 +163,9 @@ class Apache2Backend(ServiceController):
|
||||||
return [(location, directives)]
|
return [(location, directives)]
|
||||||
|
|
||||||
def get_ssl(self, directives):
|
def get_ssl(self, directives):
|
||||||
cert = directives.get('ssl_cert')
|
cert = directives.get('ssl-cert')
|
||||||
key = directives.get('ssl_key')
|
key = directives.get('ssl-key')
|
||||||
ca = directives.get('ssl_ca')
|
ca = directives.get('ssl-ca')
|
||||||
if not (cert and key):
|
if not (cert and key):
|
||||||
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
||||||
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
|
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
|
||||||
|
@ -181,11 +181,11 @@ class Apache2Backend(ServiceController):
|
||||||
|
|
||||||
def get_security(self, directives):
|
def get_security(self, directives):
|
||||||
security = []
|
security = []
|
||||||
for rules in directives.get('sec_rule_remove', []):
|
for rules in directives.get('sec-rule-remove', []):
|
||||||
for rule in rules.value.split():
|
for rule in rules.value.split():
|
||||||
sec_rule = "SecRuleRemoveById %i" % int(rule)
|
sec_rule = "SecRuleRemoveById %i" % int(rule)
|
||||||
security.append(('', sec_rule))
|
security.append(('', sec_rule))
|
||||||
for location in directives.get('sec_engine', []):
|
for location in directives.get('sec-engine', []):
|
||||||
sec_rule = textwrap.dedent("""\
|
sec_rule = textwrap.dedent("""\
|
||||||
<Location %s>
|
<Location %s>
|
||||||
SecRuleEngine off
|
SecRuleEngine off
|
||||||
|
|
|
@ -80,7 +80,7 @@ class Proxy(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class ErrorDocument(SiteDirective):
|
class ErrorDocument(SiteDirective):
|
||||||
name = 'error_document'
|
name = 'error-document'
|
||||||
verbose_name = _("ErrorDocumentRoot")
|
verbose_name = _("ErrorDocumentRoot")
|
||||||
help_text = _("<error code> <URL/path/message><br>"
|
help_text = _("<error code> <URL/path/message><br>"
|
||||||
"<tt> 500 http://foo.example.com/cgi-bin/tester</tt><br>"
|
"<tt> 500 http://foo.example.com/cgi-bin/tester</tt><br>"
|
||||||
|
@ -93,7 +93,7 @@ class ErrorDocument(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class SSLCA(SiteDirective):
|
class SSLCA(SiteDirective):
|
||||||
name = 'ssl_ca'
|
name = 'ssl-ca'
|
||||||
verbose_name = _("SSL CA")
|
verbose_name = _("SSL CA")
|
||||||
help_text = _("Filesystem path of the CA certificate file.")
|
help_text = _("Filesystem path of the CA certificate file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
|
@ -102,7 +102,7 @@ class SSLCA(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class SSLCert(SiteDirective):
|
class SSLCert(SiteDirective):
|
||||||
name = 'ssl_cert'
|
name = 'ssl-cert'
|
||||||
verbose_name = _("SSL cert")
|
verbose_name = _("SSL cert")
|
||||||
help_text = _("Filesystem path of the certificate file.")
|
help_text = _("Filesystem path of the certificate file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
|
@ -111,7 +111,7 @@ class SSLCert(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class SSLKey(SiteDirective):
|
class SSLKey(SiteDirective):
|
||||||
name = 'ssl_key'
|
name = 'ssl-key'
|
||||||
verbose_name = _("SSL key")
|
verbose_name = _("SSL key")
|
||||||
help_text = _("Filesystem path of the key file.")
|
help_text = _("Filesystem path of the key file.")
|
||||||
regex = r'^[^ ]+$'
|
regex = r'^[^ ]+$'
|
||||||
|
@ -120,7 +120,7 @@ class SSLKey(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class SecRuleRemove(SiteDirective):
|
class SecRuleRemove(SiteDirective):
|
||||||
name = 'sec_rule_remove'
|
name = 'sec-rule-remove'
|
||||||
verbose_name = _("SecRuleRemoveById")
|
verbose_name = _("SecRuleRemoveById")
|
||||||
help_text = _("Space separated ModSecurity rule IDs.")
|
help_text = _("Space separated ModSecurity rule IDs.")
|
||||||
regex = r'^[0-9\s]+$'
|
regex = r'^[0-9\s]+$'
|
||||||
|
@ -128,7 +128,7 @@ class SecRuleRemove(SiteDirective):
|
||||||
|
|
||||||
|
|
||||||
class SecEngine(SiteDirective):
|
class SecEngine(SiteDirective):
|
||||||
name = 'sec_engine'
|
name = 'sec-engine'
|
||||||
verbose_name = _("SecRuleEngine Off")
|
verbose_name = _("SecRuleEngine Off")
|
||||||
help_text = _("URL path with disabled modsecurity engine.")
|
help_text = _("URL path with disabled modsecurity engine.")
|
||||||
regex = r'^/[^ ]*$'
|
regex = r'^/[^ ]*$'
|
||||||
|
|
|
@ -46,6 +46,10 @@ class Website(models.Model):
|
||||||
context = self.get_settings_context()
|
context = self.get_settings_context()
|
||||||
return settings.WEBSITES_UNIQUE_NAME_FORMAT % context
|
return settings.WEBSITES_UNIQUE_NAME_FORMAT % context
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def active(self):
|
||||||
|
return self.is_active and self.account.is_active
|
||||||
|
|
||||||
def get_settings_context(self):
|
def get_settings_context(self):
|
||||||
""" format settings strings """
|
""" format settings strings """
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in a new issue