Fixes on payments and php webapps

This commit is contained in:
Marc Aymerich 2015-06-22 14:14:16 +00:00
parent 472eb70cb0
commit e0d31f67e4
22 changed files with 209 additions and 61 deletions

12
TODO.md
View file

@ -418,15 +418,19 @@ serailzer self.instance on create.
# set_password serializer: "just-the-password" not {"password": "password"} # set_password serializer: "just-the-password" not {"password": "password"}
# use namedtuples! # use namedtuples?
# Negative transactionsx # Negative transactionsx
# check certificate: websites directive ssl + domains search on miscellaneous * check certificate: websites directive ssl + domains search on miscellaneous
# IF modsecurity... and Merge websites locations # Merge websites locations
# backend email error log with link to backend log on admin
# ValueError: Unable to configure handler 'file': [Errno 13] Permission denied: '/home/orchestra/panel/orchestra.log' # ValueError: Unable to configure handler 'file': [Errno 13] Permission denied: '/home/orchestra/panel/orchestra.log'
# billing invoice link on related invoices not overflow nginx GET vars
* backendLog store method and language... and use it for display_script with correct lexer
# process monitor data to represent state, or maybe create new resource datas when period expires?

View file

@ -8,3 +8,13 @@ MONOSPACE_FONTS = ('Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans M
def monospace_format(text): def monospace_format(text):
style="font-family:%s;padding-left:110px;" % MONOSPACE_FONTS style="font-family:%s;padding-left:110px;" % MONOSPACE_FONTS
return mark_safe('<pre style="%s">%s</pre>' % (style, text)) return mark_safe('<pre style="%s">%s</pre>' % (style, text))
def code_format(text, language='bash'):
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
lexer = get_lexer_by_name(language, stripall=True)
formatter = HtmlFormatter(linenos=True)
code = highlight(text, lexer, formatter)
return mark_safe('<div style="padding-left:110px;">%s</div>' % code)

View file

@ -16,7 +16,7 @@ from orchestra.models.utils import get_field_value
from orchestra.utils import humanize from orchestra.utils import humanize
from .decorators import admin_field from .decorators import admin_field
from .html import monospace_format from .html import monospace_format, code_format
def get_modeladmin(model, import_module=True): def get_modeladmin(model, import_module=True):
@ -165,3 +165,10 @@ def display_mono(field):
return monospace_format(escape(getattr(log, field))) return monospace_format(escape(getattr(log, field)))
display.short_description = field display.short_description = field
return display return display
def display_code(field):
def display(self, log):
return code_format(getattr(log, field))
display.short_description = field
return display

View file

@ -9,6 +9,7 @@ from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils import translation
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ungettext, ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
@ -18,6 +19,7 @@ from orchestra.utils.html import html_to_pdf
from .forms import SelectSourceForm from .forms import SelectSourceForm
from .helpers import validate_contact from .helpers import validate_contact
from .models import Bill, BillLine
def download_bills(modeladmin, request, queryset): def download_bills(modeladmin, request, queryset):
@ -209,3 +211,44 @@ def move_lines(modeladmin, request, queryset, action=None):
def copy_lines(modeladmin, request, queryset): def copy_lines(modeladmin, request, queryset):
# same as move, but changing action behaviour # same as move, but changing action behaviour
return move_lines(modeladmin, request, queryset) return move_lines(modeladmin, request, queryset)
def amend_bills(modeladmin, request, queryset):
if queryset.filter(is_open=True).exists():
messages.warning(request, _("Selected bills should be in closed state"))
return
ids = []
for bill in queryset:
with translation.override(bill.account.language):
amend_type = bill.get_amend_type()
context = {
'related_type': _(bill.get_type_display()),
'number': bill.number,
'date': bill.created_on,
}
amend = Bill.objects.create(
account=bill.account,
type=amend_type
)
context['type'] = _(amend.get_type_display())
amend.comments = _("%(type)s of %(related_type)s %(number)s and creation date %(date)s") % context
amend.save(update_fields=('comments',))
for tax, subtotals in bill.compute_subtotals().items():
context['tax'] = tax
line = BillLine.objects.create(
bill=amend,
start_on=bill.created_on,
description=_("Amend of %(related_type)s %(number)s, tax %(tax)s%%") % context,
subtotal=subtotals[0],
tax=tax
)
ids.append(bill.pk)
amend_url = reverse('admin:bills_bill_changelist')
amend_url += '?id=%s' % ','.join(map(str, ids))
messages.success(request, mark_safe(ungettext(
_('<a href="%s">One amendment bill</a> have been generated.') % amend_url,
_('<a href="%s">%i amendment bills</a> have been generated.') % (amend_url, len(ids)),
len(ids)
)))
amend_bills.verbose_name = _("Amend")
amend_bills.url_name = 'amend'

View file

@ -196,10 +196,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
search_fields = ('number', 'account__username', 'comments') search_fields = ('number', 'account__username', 'comments')
change_view_actions = [ change_view_actions = [
actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills, actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills,
actions.close_bills actions.close_bills, actions.amend_bills,
] ]
actions = [ actions = [
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
actions.amend_bills,
] ]
change_readonly_fields = ('account_link', 'type', 'is_open') change_readonly_fields = ('account_link', 'type', 'is_open')
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')

View file

@ -20,7 +20,7 @@ class BillTypeListFilter(SimpleListFilter):
('invoice', _("Invoice")), ('invoice', _("Invoice")),
('amendmentinvoice', _("Amendment invoice")), ('amendmentinvoice', _("Amendment invoice")),
('fee', _("Fee")), ('fee', _("Fee")),
('fee', _("Amendment fee")), ('amendmentfee', _("Amendment fee")),
('proforma', _("Pro-forma")), ('proforma', _("Pro-forma")),
) )
@ -31,10 +31,11 @@ class BillTypeListFilter(SimpleListFilter):
return self.request.path.split('/')[-2] return self.request.path.split('/')[-2]
def choices(self, cl): def choices(self, cl):
query = self.request.GET.urlencode()
for lookup, title in self.lookup_choices: for lookup, title in self.lookup_choices:
yield { yield {
'selected': self.value() == lookup, 'selected': self.value() == lookup,
'query_string': reverse('admin:bills_%s_changelist' % lookup), 'query_string': reverse('admin:bills_%s_changelist' % lookup) + '?%s' % query,
'display': title, 'display': title,
} }

File diff suppressed because one or more lines are too long

View file

@ -60,10 +60,17 @@ class BillManager(models.Manager):
class Bill(models.Model): class Bill(models.Model):
OPEN = '' OPEN = ''
CREATED = 'CREATED'
PROCESSED = 'PROCESSED'
AMENDED = 'AMENDED'
PAID = 'PAID' PAID = 'PAID'
PENDING = 'PENDING' PENDING = 'PENDING'
BAD_DEBT = 'BAD_DEBT' BAD_DEBT = 'BAD_DEBT'
PAYMENT_STATES = ( PAYMENT_STATES = (
(OPEN, _("Open")),
(CREATED, _("Created")),
(PROCESSED, _("Processed")),
(AMENDED, _("Amended")),
(PAID, _("Paid")), (PAID, _("Paid")),
(PENDING, _("Pending")), (PENDING, _("Pending")),
(BAD_DEBT, _("Bad debt")), (BAD_DEBT, _("Bad debt")),
@ -84,7 +91,8 @@ class Bill(models.Model):
number = models.CharField(_("number"), max_length=16, unique=True, blank=True) number = models.CharField(_("number"), max_length=16, unique=True, blank=True)
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='%(class)s') related_name='%(class)s')
# amend_of = models.ForeignKey('self', null=True, blank=True, verbose_name=_("amend of"), related_name='amends')
type = models.CharField(_("type"), max_length=16, choices=TYPES) type = models.CharField(_("type"), max_length=16, choices=TYPES)
created_on = models.DateField(_("created on"), auto_now_add=True) created_on = models.DateField(_("created on"), auto_now_add=True)
closed_on = models.DateField(_("closed on"), blank=True, null=True) closed_on = models.DateField(_("closed on"), blank=True, null=True)
@ -125,6 +133,9 @@ class Bill(models.Model):
def payment_state(self): def payment_state(self):
if self.is_open or self.get_type() == self.PROFORMA: if self.is_open or self.get_type() == self.PROFORMA:
return self.OPEN return self.OPEN
# elif self.amends.filter(is_open=False).exists():
# return self.AMENDED
# TODO optimize this with a single query
secured = self.transactions.secured().amount() or 0 secured = self.transactions.secured().amount() or 0
if abs(secured) >= abs(self.get_total()): if abs(secured) >= abs(self.get_total()):
return self.PAID return self.PAID
@ -151,6 +162,16 @@ class Bill(models.Model):
def get_type(self): def get_type(self):
return self.type or self.get_class_type() return self.type or self.get_class_type()
def get_amend_type(self):
amend_map = {
self.INVOICE: self.AMENDMENTINVOICE,
self.FEE: self.AMENDMENTFEE,
}
amend_type = amend_map.get(self.type)
if amend_type is None:
raise TypeError("%s has no associated amend type." % self.type)
return amend_type
def get_number(self): def get_number(self):
cls = type(self) cls = type(self)
bill_type = self.get_type() bill_type = self.get_type()
@ -298,7 +319,8 @@ class BillLine(models.Model):
bill = models.ForeignKey(Bill, verbose_name=_("bill"), related_name='lines') bill = models.ForeignKey(Bill, verbose_name=_("bill"), related_name='lines')
description = models.CharField(_("description"), max_length=256) description = models.CharField(_("description"), max_length=256)
rate = models.DecimalField(_("rate"), blank=True, null=True, max_digits=12, decimal_places=2) rate = models.DecimalField(_("rate"), blank=True, null=True, max_digits=12, decimal_places=2)
quantity = models.DecimalField(_("quantity"), max_digits=12, decimal_places=2) quantity = models.DecimalField(_("quantity"), blank=True, null=True, max_digits=12,
decimal_places=2)
verbose_quantity = models.CharField(_("Verbose quantity"), max_length=16) verbose_quantity = models.CharField(_("Verbose quantity"), max_length=16)
subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2) subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2)
tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2) tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2)

View file

@ -78,7 +78,7 @@
<br> <br>
{% for line in lines %} {% for line in lines %}
{% with sublines=line.sublines.all description=line.description|slice:"38:" %} {% with sublines=line.sublines.all description=line.description|slice:"38:" %}
<span class="{% if not sublines and not description %}last {% endif %}column-id">{% if not line.order_id %}L{% endif %}{{ line.order_id }}</span> <span class="{% if not sublines and not description %}last {% endif %}column-id">{% if not line.order_id %}L{% endif %}{{ line.order_id|default:line.pk }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-description">{{ line.description|slice:":38" }}</span> <span class="{% if not sublines and not description %}last {% endif %}column-description">{{ line.description|slice:":38" }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-period">{{ line.get_verbose_period }}</span> <span class="{% if not sublines and not description %}last {% endif %}column-period">{{ line.get_verbose_period }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-quantity">{{ line.get_verbose_quantity|default:"&nbsp;"|safe }}</span> <span class="{% if not sublines and not description %}last {% endif %}column-quantity">{{ line.get_verbose_quantity|default:"&nbsp;"|safe }}</span>

View file

@ -36,7 +36,10 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
'name', 'account_link', 'display_filtering', 'display_addresses', 'display_active', 'name', 'account_link', 'display_filtering', 'display_addresses', 'display_active',
) )
list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering') list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering')
search_fields = ('account__username', 'account__short_name', 'account__full_name', 'name') search_fields = (
'account__username', 'account__short_name', 'account__full_name', 'name',
'addresses__name', 'addresses__domain__name',
)
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
'fields': ('account_link', 'name', 'password1', 'password2', 'filtering'), 'fields': ('account_link', 'name', 'password1', 'password2', 'filtering'),
@ -111,6 +114,13 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
form.modeladmin = self form.modeladmin = self
return form return form
def get_search_results(self, request, queryset, search_term):
# Remove local domain from the search term if present (implicit local addreç)
search_term = search_term.replace('@'+settings.MAILBOXES_LOCAL_DOMAIN, '')
# Split address name from domain in order to support address searching
search_term = search_term.replace('@', ' ')
return super(MailboxAdmin, self).get_search_results(request, queryset, search_term)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" save hacky mailbox.addresses """ """ save hacky mailbox.addresses """
super(MailboxAdmin, self).save_model(request, obj, form, change) super(MailboxAdmin, self).save_model(request, obj, form, change)

View file

@ -240,13 +240,13 @@ class PostfixAddressVirtualDomainBackend(ServiceController):
('MAILBOXES_LOCAL_DOMAIN', 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH') ('MAILBOXES_LOCAL_DOMAIN', 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH')
) )
def is_local_domain(self, domain): def is_hosted_domain(self, domain):
""" whether or not domain MX points to this server """ """ whether or not domain MX points to this server """
return domain.has_default_mx() return domain.has_default_mx()
def include_virtual_alias_domain(self, context): def include_virtual_alias_domain(self, context):
domain = context['domain'] domain = context['domain']
if domain.name != context['local_domain'] and self.is_local_domain(domain): if domain.name != context['local_domain'] and self.is_hosted_domain(domain):
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# %(domain)s is a virtual domain belonging to this server # %(domain)s is a virtual domain belonging to this server
if [[ ! $(grep '^\s*%(domain)s\s*$' %(virtual_alias_domains)s) ]]; then if [[ ! $(grep '^\s*%(domain)s\s*$' %(virtual_alias_domains)s) ]]; then

View file

@ -4,7 +4,7 @@ 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 import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_link, admin_date, admin_colored, display_mono from orchestra.admin.utils import admin_link, admin_date, admin_colored, display_mono, display_code
from . import settings, helpers from . import settings, helpers
from .backends import ServiceBackend from .backends import ServiceBackend
@ -122,7 +122,7 @@ class BackendLogAdmin(admin.ModelAdmin):
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
inlines = (BackendOperationInline,) inlines = (BackendOperationInline,)
fields = ( fields = (
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout', 'backend', 'server_link', 'state', 'display_script', 'mono_stdout',
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created', 'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
'execution_time' 'execution_time'
) )
@ -131,11 +131,16 @@ class BackendLogAdmin(admin.ModelAdmin):
server_link = admin_link('server') server_link = admin_link('server')
display_created = admin_date('created_at', short_description=_("Created")) display_created = admin_date('created_at', short_description=_("Created"))
display_state = admin_colored('state', colors=STATE_COLORS) display_state = admin_colored('state', colors=STATE_COLORS)
mono_script = display_mono('script') display_script = display_code('script')
mono_stdout = display_mono('stdout') mono_stdout = display_mono('stdout')
mono_stderr = display_mono('stderr') mono_stderr = display_mono('stderr')
mono_traceback = display_mono('traceback') mono_traceback = display_mono('traceback')
class Media:
css = {
'all': ('orchestra/css/pygments/github.css',)
}
def get_queryset(self, request): def get_queryset(self, request):
""" Order by structured name and imporve performance """ """ Order by structured name and imporve performance """
qs = super(BackendLogAdmin, self).get_queryset(request) qs = super(BackendLogAdmin, self).get_queryset(request)

View file

@ -61,8 +61,9 @@ def send_report(method, args, log):
backend = method.__self__.__class__.__name__ backend = method.__self__.__class__.__name__
subject = '[Orchestra] %s execution %s on %s' % (backend, log.state, server) subject = '[Orchestra] %s execution %s on %s' % (backend, log.state, server)
separator = "\n%s\n\n" % ('~ '*40,) separator = "\n%s\n\n" % ('~ '*40,)
print(log.operations.all())
operations = '\n'.join([' '.join((op.action, get_instance_url(op))) for op in log.operations.all()]) operations = '\n'.join([' '.join((op.action, get_instance_url(op))) for op in log.operations.all()])
log_url = reverse('admin:orchestration_backendlog_change', args=(log.pk,))
log_url = orchestra_settings.ORCHESTRA_SITE_URL + log_url
message = separator.join([ message = separator.join([
"[EXIT CODE] %s" % log.exit_code, "[EXIT CODE] %s" % log.exit_code,
"[STDERR]\n%s" % log.stderr, "[STDERR]\n%s" % log.stderr,
@ -70,6 +71,7 @@ def send_report(method, args, log):
"[SCRIPT]\n%s" % log.script, "[SCRIPT]\n%s" % log.script,
"[TRACEBACK]\n%s" % log.traceback, "[TRACEBACK]\n%s" % log.traceback,
"[OPERATIONS]\n%s" % operations, "[OPERATIONS]\n%s" % operations,
"[BACKEND LOG] %s" % log_url,
]) ])
html_message = '\n\n'.join([ html_message = '\n\n'.join([
'<h4 style="color:#505050;">Exit code %s</h4>' % log.exit_code, '<h4 style="color:#505050;">Exit code %s</h4>' % log.exit_code,
@ -83,6 +85,7 @@ def send_report(method, args, log):
'<pre style="margin-left:20px;font-size:11px">%s</pre>' % escape(log.traceback), '<pre style="margin-left:20px;font-size:11px">%s</pre>' % escape(log.traceback),
'<h4 style="color:#505050;">Operations</h4>' '<h4 style="color:#505050;">Operations</h4>'
'<pre style="margin-left:20px;font-size:11px">%s</pre>' % escape(operations), '<pre style="margin-left:20px;font-size:11px">%s</pre>' % escape(operations),
'<h4 style="color:#505050;">Backend log <a href="%s">%s</h4>' % (log_url, log_url),
]) ])
mail_admins(subject, message, html_message=html_message) mail_admins(subject, message, html_message=html_message)

View file

@ -63,7 +63,11 @@ class Command(BaseCommand):
server = Server(name=server, address=server) server = Server(name=server, address=server)
server.full_clean() server.full_clean()
server.save() server.save()
routes.append(AttrDict(host=server, async=False)) routes.append(AttrDict(
host=server,
async=False,
action_is_async=lambda self: False,
))
# Generate operations for the given backend # Generate operations for the given backend
for instance in queryset: for instance in queryset:
for backend in backends: for backend in backends:
@ -79,7 +83,7 @@ class Command(BaseCommand):
route, __, __ = key route, __, __ = key
backend, operations = value backend, operations = value
servers.append(str(route.host)) servers.append(str(route.host))
self.stdout.write('# Execute on %s' % route.host) self.stdout.write('# Execute %s on %s' % (backend.get_name(), route.host))
for method, commands in backend.scripts: for method, commands in backend.scripts:
script = '\n'.join(commands) script = '\n'.join(commands)
self.stdout.write(script) self.stdout.write(script)

View file

@ -96,6 +96,7 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
change_readonly_fields = ('amount', 'currency') change_readonly_fields = ('amount', 'currency')
readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link', 'source_link') readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link', 'source_link')
list_select_related = ('source', 'bill__account') list_select_related = ('source', 'bill__account')
date_hierarchy = 'created_at'
bill_link = admin_link('bill') bill_link = admin_link('bill')
source_link = admin_link('source') source_link = admin_link('source')

View file

@ -76,7 +76,7 @@ class TransactionQuerySet(models.QuerySet):
return self.exclude(state=Transaction.REJECTED) return self.exclude(state=Transaction.REJECTED)
def amount(self): def amount(self):
return next(iter(self.aggregate(models.Sum('amount')).values())) return next(iter(self.aggregate(models.Sum('amount')).values())) or 0
def processing(self): def processing(self):
return self.filter(state__in=[Transaction.EXECUTED, Transaction.WAITTING_EXECUTION]) return self.filter(state__in=[Transaction.EXECUTED, Transaction.WAITTING_EXECUTION])

View file

@ -141,17 +141,19 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
def prepare(self): def prepare(self):
super(PHPBackend, self).prepare() super(PHPBackend, self).prepare()
# Coordinate apache restart with php backend in order not to overdo it # Coordinate apache restart with php backend in order not to overdo it
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""
backend="PHPBackend" backend="PHPBackend"
echo "$backend" >> /dev/shm/restart.apache2 echo "$backend" >> /dev/shm/restart.apache2""")
""")
) )
def commit(self): def commit(self):
context = {
'reload_pool': settings.WEBAPPS_PHPFPM_RELOAD_POOL,
}
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Apply changes if needed # Apply changes if needed
if [[ $UPDATED_FPM -eq 1 ]]; then if [[ $UPDATED_FPM -eq 1 ]]; then
service php5-fpm reload %(reload_pool)s
fi fi
# Coordinate Apache restart with other concurrent backends (e.g. Apache2Backend) # Coordinate Apache restart with other concurrent backends (e.g. Apache2Backend)
@ -182,7 +184,7 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
mv /dev/shm/restart.apache2.locked /dev/shm/restart.apache2 mv /dev/shm/restart.apache2.locked /dev/shm/restart.apache2
fi fi
# End of coordination # End of coordination
""") """) % context
) )
super(PHPBackend, self).commit() super(PHPBackend, self).commit()
@ -207,13 +209,10 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
pm = ondemand pm = ondemand
pm.max_requests = {{ max_requests }} pm.max_requests = {{ max_requests }}
pm.max_children = {{ max_children }} pm.max_children = {{ max_children }}
{% if request_terminate_timeout %}
{% if request_terminate_timeout %}\ request_terminate_timeout = {{ request_terminate_timeout }}{% endif %}
request_terminate_timeout = {{ request_terminate_timeout }}\ {% for name, value in init_vars.items %}
{% endif %} php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}
{% for name, value in init_vars.items %}\
php_admin_value[{{ name | safe }}] = {{ value | safe }}\
{% endfor %}
""" """
)) ))
return fpm_config.render(Context(context)) return fpm_config.render(Context(context))

View file

@ -33,6 +33,9 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
echo "ERROR: execution returned non-zero code: $exit_code. cmd was:\\n$cmd\\n"; echo "ERROR: execution returned non-zero code: $exit_code. cmd was:\\n$cmd\\n";
exit($exit_code); exit($exit_code);
} }
}
function wp_new_blog_notification($blog_title, $blog_url, $user_id, $password){
// do nothing
}""") }""")
) )
@ -113,9 +116,6 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
$_POST['admin_password'] = "%(password)s"; $_POST['admin_password'] = "%(password)s";
$_POST['admin_password2'] = "%(password)s"; $_POST['admin_password2'] = "%(password)s";
function wp_new_blog_notification($blog_title, $blog_url, $user_id, $password){
// do nothing
}
ob_start(); ob_start();
require_once('%(app_path)s/wp-admin/install.php'); require_once('%(app_path)s/wp-admin/install.php');
$response = ob_get_contents(); $response = ob_get_contents();

View file

@ -35,6 +35,10 @@ WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH',
validators=[Setting.string_format_validator(_php_names)], validators=[Setting.string_format_validator(_php_names)],
) )
WEBAPPS_PHPFPM_RELOAD_POOL = Setting('WEBAPPS_PHPFPM_RELOAD_POOL',
'service php5-fpm reload'
)
WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH', WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH',
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper', '/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper',

View file

@ -65,10 +65,11 @@ class PHPApp(AppType):
'webapp_id': self.instance.pk, 'webapp_id': self.instance.pk,
} }
if merge: if merge:
php_version = self.instance.data.get('php_version', self.DEFAULT_PHP_VERSION)
kwargs = { kwargs = {
# webapp__type is not used because wordpress != php != symlink... # webapp__type is not used because wordpress != php != symlink...
'webapp__account': self.instance.account_id, 'webapp__account': self.instance.account_id,
'webapp__data__contains': '"php_version":"%s"' % self.instance.data['php_version'], 'webapp__data__contains': '"php_version":"%s"' % php_version,
} }
return self.instance.get_options(**kwargs) return self.instance.get_options(**kwargs)

View file

@ -40,6 +40,7 @@ class Apache2Backend(ServiceController):
def render_virtual_host(self, site, context, ssl=False): def render_virtual_host(self, site, context, ssl=False):
context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
context['vhost_wrapper_dirs'] = []
extra_conf = self.get_content_directives(site, context) extra_conf = self.get_content_directives(site, context)
directives = site.get_directives() directives = site.get_directives()
if ssl: if ssl:
@ -141,10 +142,9 @@ class Apache2Backend(ServiceController):
def prepare(self): def prepare(self):
super(Apache2Backend, self).prepare() super(Apache2Backend, self).prepare()
# Coordinate apache restart with php backend in order not to overdo it # Coordinate apache restart with php backend in order not to overdo it
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""
backend="Apache2Backend" backend="Apache2Backend"
echo "$backend" >> /dev/shm/restart.apache2\ echo "$backend" >> /dev/shm/restart.apache2""")
""")
) )
def commit(self): def commit(self):
@ -187,8 +187,8 @@ class Apache2Backend(ServiceController):
try: try:
method = getattr(self, 'get_%s_directives' % method) method = getattr(self, 'get_%s_directives' % method)
except AttributeError: except AttributeError:
raise AttributeError("%s does not has suport for '%s' directive." % context = (self.__class__.__name__, method)
(self.__class__.__name__, method)) raise AttributeError("%s does not has suport for '%s' directive." % context)
return method(context, *args) return method(context, *args)
def get_content_directives(self, site, context): def get_content_directives(self, site, context):
@ -238,10 +238,10 @@ class Apache2Backend(ServiceController):
directives = '' directives = ''
# This Action trick is used instead of FcgidWrapper because we don't want to define # This Action trick is used instead of FcgidWrapper because we don't want to define
# a new fcgid process class each time an app is mounted (num proc limits enforcement). # a new fcgid process class each time an app is mounted (num proc limits enforcement).
if 'wrapper_dir' not in context: context['wrapper_dir'] = os.path.dirname(wrapper_path)
if context['wrapper_dir'] not in context['vhost_wrapper_dirs']:
# fcgi-bin only needs to be defined once per vhots # fcgi-bin only needs to be defined once per vhots
# We assume that all account wrapper paths will share the same dir # We assume that all account wrapper paths will share the same dir
context['wrapper_dir'] = os.path.dirname(wrapper_path)
directives = textwrap.dedent("""\ directives = textwrap.dedent("""\
Alias /fcgi-bin/ %(wrapper_dir)s/ Alias /fcgi-bin/ %(wrapper_dir)s/
<Location /fcgi-bin/> <Location /fcgi-bin/>
@ -249,6 +249,7 @@ class Apache2Backend(ServiceController):
Options +ExecCGI Options +ExecCGI
</Location> </Location>
""") % context """) % context
context['vhost_wrapper_dirs'].append(context['wrapper_dir'])
directives += self.get_location_filesystem_map(context) directives += self.get_location_filesystem_map(context)
directives += textwrap.dedent(""" directives += textwrap.dedent("""
ProxyPass %(location)s/ ! ProxyPass %(location)s/ !
@ -279,26 +280,35 @@ class Apache2Backend(ServiceController):
ca = [settings.WEBSITES_DEFAULT_SSL_CA] ca = [settings.WEBSITES_DEFAULT_SSL_CA]
if not (cert and key): if not (cert and key):
return [] return []
config = "SSLEngine on\n" ssl_config = [
config += "SSLCertificateFile %s\n" % cert[0] "SSLEngine on",
config += "SSLCertificateKeyFile %s\n" % key[0] "SSLCertificateFile %s" % cert[0],
"SSLCertificateKeyFile %s" % key[0],
]
if ca: if ca:
config += "SSLCACertificateFile %s\n" % ca[0] ssl_config.append("SSLCACertificateFile %s" % ca[0])
return [ return [
('', config), ('', '\n'.join(ssl_config)),
] ]
def get_security(self, directives): def get_security(self, directives):
security = [] remove_rules = []
for values in directives.get('sec-rule-remove', []): for values in directives.get('sec-rule-remove', []):
for rule in values.split(): for rule in values.split():
sec_rule = "SecRuleRemoveById %i" % int(rule) sec_rule = " SecRuleRemoveById %i" % int(rule)
security.append(('', sec_rule)) remove_rules.append(sec_rule)
security = []
if remove_rules:
remove_rules.insert(0, '<IfModule mod_security2.c>')
remove_rules.append('</IfModule>')
security.append(('', '\n'.join(remove_rules)))
for location in directives.get('sec-engine', []): for location in directives.get('sec-engine', []):
sec_rule = textwrap.dedent("""\ sec_rule = textwrap.dedent("""\
<Location %s> <IfModule mod_security2.c>
SecRuleEngine off <Location %s>
</Location>""") % location SecRuleEngine Off
</Location>
</IfModule>""") % location
security.append((location, sec_rule)) security.append((location, sec_rule))
return security return security
@ -466,9 +476,8 @@ class Apache2Traffic(ServiceMonitor):
self.append('monitor {object_id} "{last_date}" {log_file}'.format(**context)) self.append('monitor {object_id} "{last_date}" {log_file}'.format(**context))
def get_context(self, site): def get_context(self, site):
context = { return {
'log_file': '%s{,.1}' % site.get_www_access_log_path(), 'log_file': '%s{,.1}' % site.get_www_access_log_path(),
'last_date': self.get_last_date(site.pk).strftime("%Y-%m-%d %H:%M:%S %Z"), 'last_date': self.get_last_date(site.pk).strftime("%Y-%m-%d %H:%M:%S %Z"),
'object_id': site.pk, 'object_id': site.pk,
} }
return context

View file

@ -105,9 +105,9 @@ WEBSITES_TRAFFIC_IGNORE_HOSTS = Setting('WEBSITES_TRAFFIC_IGNORE_HOSTS',
WEBSITES_SAAS_DIRECTIVES = Setting('WEBSITES_SAAS_DIRECTIVES', WEBSITES_SAAS_DIRECTIVES = Setting('WEBSITES_SAAS_DIRECTIVES',
{ {
'wordpress-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock', '/home/httpd/wordpress-mu/'), 'wordpress-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock', '/home/httpd/wordpress-mu/'),
'drupal-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/drupal-mu/'), 'drupal-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock','/home/httpd/drupal-mu/'),
'dokuwiki-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/moodle-mu/'), 'dokuwiki-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock','/home/httpd/moodle-mu/'),
}, },
) )