diff --git a/TODO.md b/TODO.md index da145c6b..41056802 100644 --- a/TODO.md +++ b/TODO.md @@ -462,3 +462,5 @@ with open(file) as handler: # Bill amend and related transaction, what to do? allow edit transaction ammount of amends when their are pending execution # DASHBOARD: Show owned tickets, scheduled actions, maintenance operations (diff domains) + +# Add confirmation step on transaction actions like process transaction diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py index 55bd3569..16980240 100644 --- a/orchestra/contrib/bills/admin.py +++ b/orchestra/contrib/bills/admin.py @@ -26,11 +26,11 @@ from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForm PAYMENT_STATE_COLORS = { Bill.OPEN: 'grey', - Bill.CREATED: 'darkorange', + Bill.CREATED: 'magenta', Bill.PROCESSED: 'darkorange', Bill.AMENDED: 'blue', Bill.PAID: 'green', - Bill.EXECUTED: 'darkorange', + Bill.EXECUTED: 'olive', Bill.BAD_DEBT: 'red', Bill.INCOMPLETE: 'red', } @@ -318,7 +318,7 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin): ) list_filter = ( BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter, - AmendedListFilter + AmendedListFilter, 'account__is_active', ) add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments') change_list_template = 'admin/bills/bill/change_list.html' diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py index 80746d7b..80595a5e 100644 --- a/orchestra/contrib/domains/backends.py +++ b/orchestra/contrib/domains/backends.py @@ -77,12 +77,12 @@ class Bind9MasterDomainController(ServiceController): ) if 'zone_path' in context: context['zone_subdomains_path'] = re.sub(r'^(.*/)', r'\1*.', context['zone_path']) - self.append('rm -f %(zone_subdomains_path)s' % context) + self.append('rm -f -- %(zone_subdomains_path)s' % context) def delete(self, domain): context = self.get_context(domain) self.append('# Delete zone file for %(name)s' % context) - self.append('rm -f %(zone_path)s;' % context) + self.append('rm -f -- %(zone_path)s;' % context) self.delete_conf(context) def delete_conf(self, context): diff --git a/orchestra/contrib/letsencrypt/backends.py b/orchestra/contrib/letsencrypt/backends.py index a36b3443..218c0935 100644 --- a/orchestra/contrib/letsencrypt/backends.py +++ b/orchestra/contrib/letsencrypt/backends.py @@ -27,7 +27,7 @@ class LetsEncryptController(ServiceController): self.append(" --webroot-path %(webroot)s \\" % context) self.append(" --email %(email)s \\" % context) self.append(" -d %(domains)s \\" % context) - self.cleanup.append("rm -rf %(webroot)s/.well-known" % context) + self.cleanup.append("rm -rf -- %(webroot)s/.well-known" % context) def commit(self): self.append(" || exit_code=$?") @@ -49,6 +49,6 @@ class LetsEncryptController(ServiceController): return { 'letsencrypt_auto': settings.LETSENCRYPT_AUTO_PATH, 'webroot': content.webapp.get_path(), - 'email': website.account.email, + 'email': settings.LETSENCRYPT_EMAIL or website.account.email, 'domains': ' \\\n -d '.join(website.encrypt_domains), } diff --git a/orchestra/contrib/letsencrypt/settings.py b/orchestra/contrib/letsencrypt/settings.py index 5883d84a..d4ca3042 100644 --- a/orchestra/contrib/letsencrypt/settings.py +++ b/orchestra/contrib/letsencrypt/settings.py @@ -9,3 +9,9 @@ LETSENCRYPT_AUTO_PATH = Setting('LETSENCRYPT_AUTO_PATH', LETSENCRYPT_LIVE_PATH = Setting('LETSENCRYPT_LIVE_PATH', '/etc/letsencrypt/live' ) + + +LETSENCRYPT_EMAIL = Setting('LETSENCRYPT_EMAIL', + '', + help_text="Uses account.email by default", +) diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index f0eeb937..b8cf7e98 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -52,7 +52,7 @@ class MailmanVirtualDomainController(ServiceController): def delete(self, mail_list): context = self.get_context(mail_list) - self.include_virtual_alias_domain(context) + self.exclude_virtual_alias_domain(context) def commit(self): context = self.get_context_files() diff --git a/orchestra/contrib/mailboxes/backends.py b/orchestra/contrib/mailboxes/backends.py index ebef95e1..e26a696b 100644 --- a/orchestra/contrib/mailboxes/backends.py +++ b/orchestra/contrib/mailboxes/backends.py @@ -24,7 +24,7 @@ class SieveFilteringMixin: context['box'] = box self.append(textwrap.dedent(""" # Create %(box)s mailbox - su %(user)s --shell /bin/bash << 'EOF' + su - %(user)s --shell /bin/bash << 'EOF' mkdir -p "%(maildir)s/.%(box)s" EOF if ! grep '%(box)s' %(maildir)s/subscriptions > /dev/null; then @@ -39,7 +39,7 @@ class SieveFilteringMixin: context['filtering'] = ('# %(banner)s\n' + content) % context self.append(textwrap.dedent("""\ # Create and compile orchestra sieve filtering - su %(user)s --shell /bin/bash << 'EOF' + su - %(user)s --shell /bin/bash << 'EOF' mkdir -p $(dirname "%(filtering_path)s") cat << ' EOF' > %(filtering_path)s %(filtering)s @@ -97,7 +97,7 @@ class UNIXUserMaildirController(SieveFilteringMixin, ServiceController): #unit_to_bytes(mailbox.resources.disk.unit) self.append(textwrap.dedent(""" # Set Maildir quota for %(user)s - su %(user)s --shell /bin/bash << 'EOF' + su - %(user)s --shell /bin/bash << 'EOF' mkdir -p %(maildir)s EOF if [ ! -f %(maildir)s/maildirsize ]; then @@ -121,7 +121,7 @@ class UNIXUserMaildirController(SieveFilteringMixin, ServiceController): """) % context ) else: - self.append("rm -fr %(base_home)s" % context) + self.append("rm -fr -- %(base_home)s" % context) self.append(textwrap.dedent(""" nohup bash -c '{ sleep 2 && killall -u %(user)s -s KILL; }' &> /dev/null & killall -u %(user)s || true @@ -195,7 +195,7 @@ class UNIXUserMaildirController(SieveFilteringMixin, ServiceController): # if context['deleted_home']: # self.append("mv %(home)s %(deleted_home)s || exit_code=$?" % context) # else: -# self.append("rm -fr %(home)s" % context) +# self.append("rm -fr -- %(home)s" % context) # # def get_extra_fields(self, mailbox, context): # context['quota'] = self.get_quota(mailbox) diff --git a/orchestra/contrib/orchestration/manager.py b/orchestra/contrib/orchestration/manager.py index c955ee44..4a3fb719 100644 --- a/orchestra/contrib/orchestration/manager.py +++ b/orchestra/contrib/orchestration/manager.py @@ -45,9 +45,9 @@ def keep_log(execute, log, operations): if not log.is_success: send_report(execute, args, log) stdout = log.stdout.strip() - stdout and logger.debug('STDOUT %s', stdout) + stdout and logger.debug('STDOUT %s', stdout.encode('ascii', errors='replace').decode()) stderr = log.stderr.strip() - stderr and logger.debug('STDERR %s', stderr) + stderr and logger.debug('STDERR %s', stderr.encode('ascii', errors='replace').decode()) return wrapper @@ -197,6 +197,7 @@ def collect(instance, action, **kwargs): # Only schedule operations if the router has execution routes routes = router.objects.get_for_operation(operation, cache=route_cache) if routes: + logger.debug("Operation %s collected for execution" % operation) operation.routes = routes if iaction != Operation.DELETE: # usually we expect to be using last object state, diff --git a/orchestra/contrib/orchestration/middlewares.py b/orchestra/contrib/orchestration/middlewares.py index eb53ec25..afab235e 100644 --- a/orchestra/contrib/orchestration/middlewares.py +++ b/orchestra/contrib/orchestration/middlewares.py @@ -97,7 +97,7 @@ class OperationsMiddleware(object): def process_response(self, request, response): """ Processes pending backend operations """ - if not isinstance(response, HttpResponseServerError): + if response.status_code != 500: operations = self.get_pending_operations() if operations: try: diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py index c622798b..28ceb4fd 100644 --- a/orchestra/contrib/orders/models.py +++ b/orchestra/contrib/orders/models.py @@ -285,7 +285,7 @@ class MetricStorageQuerySet(models.QuerySet): last.save() else: error = decimal.Decimal(str(settings.ORDERS_METRIC_ERROR)) - if value > last.value+error or value < last.value-error: + if (value > last.value+error or value < last.value-error) or (value == 0 and last.value > 0): self.create(order=order, value=value, updated_on=now) else: last.updated_on = now diff --git a/orchestra/contrib/orders/settings.py b/orchestra/contrib/orders/settings.py index 91593b5e..2dbed0b5 100644 --- a/orchestra/contrib/orders/settings.py +++ b/orchestra/contrib/orders/settings.py @@ -26,6 +26,8 @@ ORDERS_EXCLUDED_APPS = Setting('ORDERS_EXCLUDED_APPS', 'orchestration', 'bills', 'services', + 'mailer', + 'issues', ), help_text="Prevent inspecting these apps for service accounting." ) diff --git a/orchestra/contrib/saas/README.md b/orchestra/contrib/saas/README.md index 1c7a5453..46fcb88f 100644 --- a/orchestra/contrib/saas/README.md +++ b/orchestra/contrib/saas/README.md @@ -77,7 +77,7 @@ Notice that two optional forms can be provided `form` and `change_form`. When no A backend class is required to interface with the web application and perform `save()` and `delete()` operations on it. - The more reliable way of interfacing with the application is by means of a CLI (e.g. [Moodle](backends/moodle.py)), but not all CMS come with this tool. -- The second preferable way is using some sort of API, possibly HTTP-based (e.g. [gitLab](backends/gitlab.py)). This is less reliable because additional moving parts are used underneath the interface; a busy web server can timeout our requests. +- The second preferable way is using some sort of networked API, possibly HTTP-based (e.g. [gitLab](backends/gitlab.py)). This is less reliable because additional moving parts are used underneath the interface; a busy web server can timeout our requests. - The least preferred way is interfacing with an HTTP-HTML interface designed for human consumption, really painful to implement but sometimes is the only way (e.g. [WordPress](backends/wordpressmu.py)). Some applications do not support multi-tenancy by default, but we can hack the configuration file of such apps and generate *table prefix* or *database name* based on some property of the URL. Example of this services are [moodle](backends/moodle.py) and [phplist](backends/phplist.py) respectively. diff --git a/orchestra/contrib/saas/backends/moodle.py b/orchestra/contrib/saas/backends/moodle.py index b6740531..339462db 100644 --- a/orchestra/contrib/saas/backends/moodle.py +++ b/orchestra/contrib/saas/backends/moodle.py @@ -88,7 +88,7 @@ class MoodleMuController(ServiceController): self.append(textwrap.dedent("""\ # Configuring Moodle crontabs if ! crontab -u %(user)s -l | grep 'Moodle:"%(site_name)s"' > /dev/null; then - cat << EOF | su %(user)s --shell /bin/bash -c 'crontab' + cat << EOF | su - %(user)s --shell /bin/bash -c 'crontab' $(crontab -u %(user)s -l) # %(banner)s - Moodle:"%(site_name)s" @@ -139,7 +139,7 @@ class MoodleMuController(ServiceController): self.append(textwrap.dedent("""\ crontab -u %(user)s -l \\ | grep -v 'Moodle:"%(site_name)s"\\|%(crontab_regex)s' \\ - | su %(user)s --shell /bin/bash -c 'crontab' + | su - %(user)s --shell /bin/bash -c 'crontab' """) % context ) self.delete_site_map(context) diff --git a/orchestra/contrib/saas/backends/phplist.py b/orchestra/contrib/saas/backends/phplist.py index a17bc08b..43e73220 100644 --- a/orchestra/contrib/saas/backends/phplist.py +++ b/orchestra/contrib/saas/backends/phplist.py @@ -88,7 +88,7 @@ class PhpListSaaSController(ServiceController): self.append(textwrap.dedent("""\ # Configuring phpList crontabs if ! crontab -u %(user)s -l | grep 'phpList:"%(site_name)s"' > /dev/null; then - cat << EOF | su %(user)s --shell /bin/bash -c 'crontab' + cat << EOF | su - %(user)s --shell /bin/bash -c 'crontab' $(crontab -u %(user)s -l) # %(banner)s - phpList:"%(site_name)s" @@ -105,7 +105,7 @@ class PhpListSaaSController(ServiceController): self.append(textwrap.dedent("""\ crontab -u %(user)s -l \\ | grep -v 'phpList:"%(site_name)s"\\|%(crontab_regex)s' \\ - | su %(user)s --shell /bin/bash -c 'crontab' + | su - %(user)s --shell /bin/bash -c 'crontab' """) % context ) diff --git a/orchestra/contrib/systemusers/actions.py b/orchestra/contrib/systemusers/actions.py index b37780c3..6c3b0e80 100644 --- a/orchestra/contrib/systemusers/actions.py +++ b/orchestra/contrib/systemusers/actions.py @@ -85,7 +85,7 @@ def create_link(modeladmin, request, queryset): messages.error(request, "Users from the same account should be selected.") return user = queryset[0] - form = LinkForm(user) + form = LinkForm(user, queryset=queryset) action_value = 'create_link' if request.POST.get('post') == 'generic_confirmation': form = LinkForm(user, request.POST, queryset=queryset) diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py index d126f807..033bd1ee 100644 --- a/orchestra/contrib/systemusers/backends.py +++ b/orchestra/contrib/systemusers/backends.py @@ -115,7 +115,7 @@ class UNIXUserController(ServiceController): """) % context ) else: - self.append("rm -fr '%(base_home)s'" % context) + self.append("rm -fr -- '%(base_home)s'" % context) def grant_permissions(self, user, context): context['perms'] = user.set_perm_perms @@ -206,7 +206,7 @@ class UNIXUserController(ServiceController): }) self.append(textwrap.dedent("""\ # Create link - su %(user)s --shell /bin/bash << 'EOF' || exit_code=1 + su - %(user)s --shell /bin/bash << 'EOF' || exit_code=1 if [[ ! -e '%(link_name)s' ]]; then ln -s '%(link_target)s' '%(link_name)s' else diff --git a/orchestra/contrib/systemusers/forms.py b/orchestra/contrib/systemusers/forms.py index c15a9f75..eaf1d2f8 100644 --- a/orchestra/contrib/systemusers/forms.py +++ b/orchestra/contrib/systemusers/forms.py @@ -96,8 +96,7 @@ class LinkForm(forms.Form): widget=forms.TextInput(attrs={'size':'70'}), help_text=_("Relative path to chosen directory.")) link_name = forms.CharField(label=_("Link name"), required=False, initial='', - widget=forms.TextInput(attrs={'size':'70'}), - help_text=_("If left blank or relative path: link will be created in each user home.")) + widget=forms.TextInput(attrs={'size':'70'})) def __init__(self, *args, **kwargs): self.instance = args[0] @@ -110,6 +109,12 @@ class LinkForm(forms.Form): self.fields['base_home'].choices = ( (user.get_base_home(), user.get_base_home()) for user in related_users ) + if len(self.queryset) == 1: + user = self.instance + help_text = _("If left blank or relative path: the link will be created in %s home.") % user + else: + help_text = _("If left blank or relative path: the link will be created in each user home.") + self.fields['link_name'].help_text = help_text def clean_home_extension(self): home_extension = self.cleaned_data['home_extension'] diff --git a/orchestra/contrib/webapps/backends/moodle.py b/orchestra/contrib/webapps/backends/moodle.py index 81994e54..b5d85305 100644 --- a/orchestra/contrib/webapps/backends/moodle.py +++ b/orchestra/contrib/webapps/backends/moodle.py @@ -70,7 +70,7 @@ class MoodleController(WebAppServiceMixin, ServiceController): # Run install moodle cli command on the background, because it takes so long... stdout=$(mktemp) stderr=$(mktemp) - nohup su %(user)s --shell /bin/bash << 'EOF' > $stdout 2> $stderr & + nohup su - %(user)s --shell /bin/bash << 'EOF' > $stdout 2> $stderr & php %(app_path)s/admin/cli/install_database.php \\ --fullname="%(site_name)s" \\ --shortname="%(site_name)s" \\