Added create initial superuser prompt on accounts post migrate

This commit is contained in:
Marc Aymerich 2015-05-01 18:05:34 +00:00
parent 94941a633f
commit f5e80d680c
20 changed files with 169 additions and 96 deletions

37
TODO.md
View file

@ -300,30 +300,31 @@ https://code.djangoproject.com/ticket/24576
# accounts.migrations link to last auth migration instead of first # accounts.migrations link to last auth migration instead of first
# DNS allow transfer other NS servers instead of masters and slaves!
Replace celery by a custom solution? Replace celery by a custom solution?
# TODO create periodic task like settings, but parsing cronfiles!
# TODO create decorator wrapper that abstract the task away from the backen (cron/celery)
# TODO crontab model localhost/autoadded attribute
* No more jumbo dependencies and wierd bugs * No more jumbo dependencies and wierd bugs
1) Periodic Monitoring: 1) Periodic Monitoring:
* runtask management command + crontab scheduling or high performance beat crontab (not loading bloated django system) * runtask management command + crontab scheduling or high performance beat crontab (not loading bloated django system)
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('method', help='')
parser.add_argument('args', nargs='*', help='')
def handle(self, *args, **options):
method = import_class(options['method'])
kwargs = {}
arguments = []
for arg in args:
if '=' in args:
name, value = arg.split('=')
kwargs[name] = value
else:
arguments.append(arg)
args = arguments
method(*args, **kwargs)
2) Single time shot: 2) Single time shot:
sys.run("python3 manage.py runtas 'task' args") sys.run("python3 manage.py runtas 'task' args")
3) Emails: 3) Emails:
Custom backend that distinguishes between priority and bulk mail Custom backend that distinguishes between priority and bulk mail
priority: custom Thread backend *priority: custom Thread backend
bulk: wrapper arround django-mailer to avoid loading django system *bulk: wrapper arround django-mailer to avoid loading django system
# uwsgi enable threads
# Create superuser on migrate
# register signals in app ready()
def ready(self):
if self.has_attr('ready_run'): return
self.ready_run = True
# database_ready(): connect to the database or inspect django connection
# beat.sh
# do settings validation on orchestra.apps.ready(), not during startime

View file

@ -16,6 +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
def get_modeladmin(model, import_module=True): def get_modeladmin(model, import_module=True):
@ -153,3 +154,10 @@ def get_object_from_url(modeladmin, request):
return None return None
else: else:
return modeladmin.model.objects.get(pk=object_id) return modeladmin.model.objects.get(pk=object_id)
def display_mono(field):
def display(self, log):
return monospace_format(escape(getattr(log, field)))
display.short_description = field
return display

View file

@ -89,6 +89,7 @@ INSTALLED_APPS = (
'orchestra.contrib.miscellaneous', 'orchestra.contrib.miscellaneous',
'orchestra.contrib.bills', 'orchestra.contrib.bills',
'orchestra.contrib.payments', 'orchestra.contrib.payments',
'orchestra.contrib.tasks',
# Third-party apps # Third-party apps
'django_extensions', 'django_extensions',
@ -103,6 +104,7 @@ INSTALLED_APPS = (
'rest_framework.authtoken', 'rest_framework.authtoken',
'passlib.ext.django', 'passlib.ext.django',
'django_countries', 'django_countries',
'django_mailer',
# Django.contrib # Django.contrib
'django.contrib.auth', 'django.contrib.auth',

View file

@ -0,0 +1 @@
default_app_config = 'orchestra.contrib.accounts.apps.AccountConfig'

View file

@ -0,0 +1,14 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate
from django.utils.translation import ugettext_lazy as _
from .management import create_initial_superuser
class AccountConfig(AppConfig):
name = 'orchestra.contrib.accounts'
verbose_name = _("Accounts")
def ready(self):
post_migrate.connect(create_initial_superuser,
dispatch_uid="orchestra.contrib.accounts.management.createsuperuser")

View file

@ -0,0 +1,19 @@
import sys
import textwrap
from django.contrib.auth import get_user_model
from django.core.management import execute_from_command_line
def create_initial_superuser(**kwargs):
if '--noinput' not in sys.argv and '--fake' not in sys.argv and '--fake-initial' not in sys.argv and 'accounts' in sys.argv:
model = get_user_model()
if not model.objects.filter(is_superuser=True).exists():
sys.stdout.write(textwrap.dedent("""
It appears that you just installed Accounts application.
You can now create a superuser:
""")
)
manager = sys.argv[0]
execute_from_command_line(argv=[manager, 'createsuperuser'])

View file

@ -1,4 +1,5 @@
import re import re
import socket
import textwrap import textwrap
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -8,12 +9,13 @@ from orchestra.contrib.orchestration import Operation
from orchestra.utils.python import OrderedSet from orchestra.utils.python import OrderedSet
from . import settings from . import settings
from .models import Record, Domain
class Bind9MasterDomainBackend(ServiceController): class Bind9MasterDomainBackend(ServiceController):
""" """
Bind9 zone and config generation. Bind9 zone and config generation.
It auto-discovers slave Bind9 servers based on your routing configuration or you can use DOMAINS_SLAVES to explicitly configure the slaves. It auto-discovers slave Bind9 servers based on your routing configuration and NS servers.
""" """
CONF_PATH = settings.DOMAINS_MASTERS_PATH CONF_PATH = settings.DOMAINS_MASTERS_PATH
@ -25,7 +27,7 @@ class Bind9MasterDomainBackend(ServiceController):
) )
ignore_fields = ['serial'] ignore_fields = ['serial']
doc_settings = (settings, doc_settings = (settings,
('DOMAINS_SLAVES', 'DOMAINS_MASTERS_PATH') ('DOMAINS_MASTERS_PATH',)
) )
@classmethod @classmethod
@ -100,10 +102,32 @@ class Bind9MasterDomainBackend(ServiceController):
servers.append(server.get_ip()) servers.append(server.get_ip())
return servers return servers
def get_masters(self, domain):
ips = list(settings.DOMAINS_MASTERS)
if not ips:
ips += self.get_servers(domain, Bind9MasterDomainBackend)
return OrderedSet(sorted(ips))
def get_slaves(self, domain): def get_slaves(self, domain):
ips = list(settings.DOMAINS_SLAVES) ips = []
ips += self.get_servers(domain, Bind9SlaveDomainBackend) masters = self.get_masters(domain)
return OrderedSet(ips) for ns in domain.records.filter(type=Record.NS):
hostname = ns.value.rstrip('.')
# First try with a DNS query, a more reliable source
try:
addr = socket.gethostbyname(hostname)
except socket.gaierror:
# check if domain is declared
try:
domain = Domain.objects.get(name=ns)
except Domain.DoesNotExist:
continue
else:
a_record = domain.records.filter(name=Record.A) or [settings.DOMAINS_DEFAULT_NS]
addr = a_record[0]
if addr not in masters:
ips.append(addr)
return OrderedSet(sorted(ips))
def get_context(self, domain): def get_context(self, domain):
slaves = self.get_slaves(domain) slaves = self.get_slaves(domain)
@ -154,11 +178,6 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
""" ideally slave should be restarted after master """ """ ideally slave should be restarted after master """
self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi') self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi')
def get_masters(self, domain):
ips = list(settings.DOMAINS_MASTERS)
ips += self.get_servers(domain, Bind9MasterDomainBackend)
return OrderedSet(ips)
def get_context(self, domain): def get_context(self, domain):
context = { context = {
'name': domain.name, 'name': domain.name,

View file

@ -121,10 +121,3 @@ DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
validators=[lambda masters: map(validate_ip_address, masters)], validators=[lambda masters: map(validate_ip_address, masters)],
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()." help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
) )
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES',
(),
validators=[lambda slaves: map(validate_ip_address, slaves)],
help_text="Additional slave server ip addresses other than autodiscovered by router.get_servers()."
)

View file

@ -3,8 +3,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe 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.utils import admin_link, admin_date, admin_colored, display_mono
from orchestra.admin.utils import admin_link, admin_date, admin_colored
from . import settings, helpers from . import settings, helpers
from .backends import ServiceBackend from .backends import ServiceBackend
@ -109,13 +108,6 @@ class BackendOperationInline(admin.TabularInline):
return queryset.prefetch_related('instance') return queryset.prefetch_related('instance')
def display_mono(field):
def display(self, log):
return monospace_format(escape(getattr(log, field)))
display.short_description = _(field)
return display
class BackendLogAdmin(admin.ModelAdmin): class BackendLogAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'id', 'backend', 'server_link', 'display_state', 'exit_code', 'id', 'backend', 'server_link', 'display_state', 'exit_code',
@ -123,12 +115,12 @@ class BackendLogAdmin(admin.ModelAdmin):
) )
list_display_links = ('id', 'backend') list_display_links = ('id', 'backend')
list_filter = ('state', 'backend') list_filter = ('state', 'backend')
inlines = [BackendOperationInline] inlines = (BackendOperationInline,)
fields = [ fields = (
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout', 'backend', 'server_link', 'state', 'mono_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'
] )
readonly_fields = fields readonly_fields = fields
server_link = admin_link('server') server_link = admin_link('server')

View file

@ -1,5 +1,3 @@
import sys
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db.models.loading import get_model from django.db.models.loading import get_model

View file

@ -3,9 +3,9 @@ import threading
import traceback import traceback
from collections import OrderedDict from collections import OrderedDict
from django import db
from django.core.mail import mail_admins from django.core.mail import mail_admins
from orchestra.utils.db import close_connection
from orchestra.utils.python import import_class, OrderedSet from orchestra.utils.python import import_class, OrderedSet
from . import settings, Operation from . import settings, Operation
@ -42,20 +42,6 @@ def as_task(execute):
return wrapper return wrapper
def close_connection(execute):
""" Threads have their own connection pool, closing it when finishing """
def wrapper(*args, **kwargs):
try:
log = execute(*args, **kwargs)
except Exception as e:
pass
else:
wrapper.log = log
finally:
db.connection.close()
return wrapper
def generate(operations): def generate(operations):
scripts = OrderedDict() scripts = OrderedDict()
cache = {} cache = {}

View file

@ -68,13 +68,11 @@ class BackendLog(models.Model):
) )
backend = models.CharField(_("backend"), max_length=256) backend = models.CharField(_("backend"), max_length=256)
state = models.CharField(_("state"), max_length=16, choices=STATES, state = models.CharField(_("state"), max_length=16, choices=STATES, default=RECEIVED)
default=RECEIVED) server = models.ForeignKey(Server, verbose_name=_("server"), related_name='execution_logs')
server = models.ForeignKey(Server, verbose_name=_("server"),
related_name='execution_logs')
script = models.TextField(_("script")) script = models.TextField(_("script"))
stdout = models.TextField(_("stdout")) stdout = models.TextField(_("stdout"))
stderr = models.TextField(_("stdin")) stderr = models.TextField(_("stderr"))
traceback = models.TextField(_("traceback")) traceback = models.TextField(_("traceback"))
exit_code = models.IntegerField(_("exit code"), null=True) exit_code = models.IntegerField(_("exit code"), null=True)
task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True, task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True,

View file

@ -16,6 +16,7 @@ def run_monitor(modeladmin, request, queryset):
modeladmin.log_change(request, resource, _("Run monitors")) modeladmin.log_change(request, resource, _("Run monitors"))
if async: if async:
num = len(queryset) num = len(queryset)
# TODO listfilter by uuid: task.request.id + ?task_id__in=ids
link = reverse('admin:djcelery_taskstate_changelist') link = reverse('admin:djcelery_taskstate_changelist')
msg = ungettext( msg = ungettext(
_("One selected resource has been <a href='%s'>scheduled for monitoring</a>.") % link, _("One selected resource has been <a href='%s'>scheduled for monitoring</a>.") % link,

View file

@ -6,7 +6,7 @@ from django.db.models.loading import get_model
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from djcelery.models import PeriodicTask, CrontabSchedule from djcelery.models import CrontabSchedule
from orchestra.core import validators from orchestra.core import validators
from orchestra.models import queryset, fields from orchestra.models import queryset, fields
@ -14,7 +14,7 @@ from orchestra.models.utils import get_model_field_path
from orchestra.utils.paths import get_project_dir from orchestra.utils.paths import get_project_dir
from orchestra.utils.sys import run from orchestra.utils.sys import run
from . import tasks from . import tasks, settings
from .backends import ServiceMonitor from .backends import ServiceMonitor
from .aggregations import Aggregation from .aggregations import Aggregation
from .validators import validate_scale from .validators import validate_scale
@ -129,6 +129,12 @@ class Resource(models.Model):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs) super(Resource, self).delete(*args, **kwargs)
name = 'monitor.%s' % str(self) name = 'monitor.%s' % str(self)
self.sync_periodic_task()
def sync_periodic_task(self):
name = 'monitor.%s' % str(self)
sync = import_class(settings.RESOURCES_TASK_BACKEND)
return sync(self, name)
def get_model_path(self, monitor): def get_model_path(self, monitor):
""" returns a model path between self.content_type and monitor.model """ """ returns a model path between self.content_type and monitor.model """
@ -136,28 +142,6 @@ class Resource(models.Model):
monitor_model = ServiceMonitor.get_backend(monitor).model_class() monitor_model = ServiceMonitor.get_backend(monitor).model_class()
return get_model_field_path(monitor_model, resource_model) return get_model_field_path(monitor_model, resource_model)
def sync_periodic_task(self):
name = 'monitor.%s' % str(self)
if self.pk and self.crontab:
try:
task = PeriodicTask.objects.get(name=name)
except PeriodicTask.DoesNotExist:
if self.is_active:
PeriodicTask.objects.create(
name=name,
task='resources.Monitor',
args=[self.pk],
crontab=self.crontab
)
else:
if task.crontab != self.crontab:
task.crontab = self.crontab
task.save(update_fields=['crontab'])
else:
PeriodicTask.objects.filter(
name=name,
).delete()
def get_scale(self): def get_scale(self):
return eval(self.scale) return eval(self.scale)

View file

@ -0,0 +1,6 @@
from orchestra.settings import Setting
RESOURCES_TASK_BACKEND = Setting('RESOURCES_TASK_BACKEND',
'orchestra.contrib.resources.utils.cron_sync'
)

View file

@ -30,6 +30,7 @@ def monitor(resource_id, ids=None, async=True):
op = Operation(backend, obj, Operation.MONITOR) op = Operation(backend, obj, Operation.MONITOR)
monitorings.append(op) monitorings.append(op)
# TODO async=True only when running with celery # TODO async=True only when running with celery
# monitor.request.id
logs += Operation.execute(monitorings, async=async) logs += Operation.execute(monitorings, async=async)
kwargs = {'id__in': ids} if ids else {} kwargs = {'id__in': ids} if ids else {}

View file

@ -0,0 +1,41 @@
from orchestra.contrib.crons.utils import apply_local
from . import settings
def celery_sync(resource, name):
from djcelery.models import PeriodicTask
if resource.pk and resource.crontab:
try:
task = PeriodicTask.objects.get(name=name)
except PeriodicTask.DoesNotExist:
if resource.is_active:
PeriodicTask.objects.create(
name=name,
task='resources.Monitor',
args=[resource.pk],
crontab=resource.crontab
)
else:
if task.crontab != resource.crontab:
task.crontab = resource.crontab
task.save(update_fields=['crontab'])
else:
PeriodicTask.objects.filter(
name=name,
).delete()
def cron_sync(resource, name):
if resource.pk and resource.crontab:
context = {
'manager': os.path.join(paths.get_project_dir(), 'manage.py'),
'id': resource.pk,
}
apply_local(resource.crontab,
'python3 %(manager)s runmethod orchestra.contrib.resources.tasks.monitor %(id)s',
'orchestra', # TODO
name
)
else:
apply_local(resource.crontab, '', 'orchestra', name, action='delete')

View file

@ -91,6 +91,16 @@ class SettingFileView(generic.TemplateView):
template_name = 'admin/settings/view.html' template_name = 'admin/settings/view.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
from orchestra.contrib.tasks import shared_task
import time
@shared_task(name='rata')
def counter(num, log):
for i in range(1, num):
with open(log, 'a') as handler:
handler.write(str(i))
time.sleep(1)
counter.apply_async(10, '/tmp/kakas')
context = super(SettingFileView, self).get_context_data(**kwargs) context = super(SettingFileView, self).get_context_data(**kwargs)
settings_file = parser.get_settings_file() settings_file = parser.get_settings_file()
with open(settings_file, 'r') as handler: with open(settings_file, 'r') as handler:
@ -106,4 +116,3 @@ class SettingFileView(generic.TemplateView):
admin.site.register_url(r'^settings/setting/view/$', SettingFileView.as_view(), 'settings_setting_view') admin.site.register_url(r'^settings/setting/view/$', SettingFileView.as_view(), 'settings_setting_view')
admin.site.register_url(r'^settings/setting/$', SettingView.as_view(), 'settings_setting_change') admin.site.register_url(r'^settings/setting/$', SettingView.as_view(), 'settings_setting_change')
OrchestraIndexDashboard.register_link('Administration', 'settings_setting_change', _("Settings")) OrchestraIndexDashboard.register_link('Administration', 'settings_setting_change', _("Settings"))

View file

@ -58,6 +58,7 @@ def get_eval_context():
'_': _, '_': _,
} }
def serialize(obj, init=True): def serialize(obj, init=True):
if isinstance(obj, NotSupported): if isinstance(obj, NotSupported):
return obj return obj

View file

@ -50,8 +50,7 @@ def database_ready():
# Celerybeat has yet to stablish a connection at AppConf.ready() # Celerybeat has yet to stablish a connection at AppConf.ready()
'celerybeat' not in sys.argv and 'celerybeat' not in sys.argv and
# Allow to run python manage.py without a database # Allow to run python manage.py without a database
len(sys.argv) <= 1 and sys.argv != ['manage.py'] and '--help' not in sys.argv)
'--help' not in sys.argv)
def dict_setting_to_choices(choices): def dict_setting_to_choices(choices):