Command refactored.

see #851
This commit is contained in:
Antoine Nguyen
2016-09-30 17:50:48 +02:00
parent 68bc929769
commit b44d24c6be
6 changed files with 133 additions and 202 deletions
+14
View File
@@ -3,7 +3,10 @@
from django.db.models import signals
from django.dispatch import receiver
from modoboa.core import signals as core_signals
from . import models
from . import postfix_maps
@receiver(signals.post_save, sender=models.Domain)
@@ -97,3 +100,14 @@ def remove_alias_for_mailbox(sender, instance, **kwargs):
"""Remove "self alias" for this mailbox."""
models.Alias.objects.filter(
address=instance.full_address).delete()
@receiver(core_signals.register_postfix_maps)
def register_postfix_maps(sender, **kwargs):
"""Register admin map files."""
return [
postfix_maps.DomainsMap, postfix_maps.DomainsAliasesMap,
postfix_maps.AliasesMap, postfix_maps.MaintainMap,
postfix_maps.SenderLoginAliasMap, postfix_maps.SenderLoginMailboxMap,
postfix_maps.SenderLoginMailboxExtraMap
]
-7
View File
@@ -1,7 +1,5 @@
"""Map file definitions for postfix."""
from modoboa.core.commands.postfix_maps import registry
class DomainsMap(object):
@@ -166,8 +164,3 @@ class SenderLoginAliasMap(object):
" INNER JOIN admin_alias al ON alr.alias_id=al.id "
"WHERE al.enabled=1 AND al.address='%s'"
)
registry.add_files([
DomainsMap, DomainsAliasesMap, AliasesMap, MaintainMap,
SenderLoginAliasMap, SenderLoginMailboxMap, SenderLoginMailboxExtraMap
])
-185
View File
@@ -1,185 +0,0 @@
# coding: utf-8
"""postfix_maps command."""
import datetime
import getpass
import os
import sys
import django
from django.template import Context, Template
import dj_database_url
from . import Command
MAP_FILE_TEMPLATE = """user = {{ dbuser }}
password = {{ dbpass }}
dbname = {{ dbname }}
hosts = {{ dbhost }}
query = {{ query|safe }}
"""
class MapFilesRegistry(object):
"""A registry of all map files."""
def __init__(self):
self.files = []
def add_files(self, files):
"""Add map files to registry."""
self.files += files
registry = MapFilesRegistry()
class MapFilesGenerator(object):
"""Map files generator."""
def __init__(self, dbinfo=None):
"""Constructor."""
self.template = Template(MAP_FILE_TEMPLATE)
if dbinfo is None:
self.tpl_context = {
'dbhost': raw_input('Database host: '),
'dbname': raw_input('Database name: '),
'dbuser': raw_input('Username: '),
'dbpass': getpass.getpass('Password: ')
}
else:
self.tpl_context = {
'dbhost': dbinfo['HOST'],
'dbname': dbinfo['NAME'],
'dbuser': dbinfo['USER'],
'dbpass': dbinfo['PASSWORD']
}
if not self.tpl_context['dbhost']:
self.tpl_context['dbhost'] = '127.0.0.1'
def __render_map(self, args, mapobject):
"""Render a map file.
:param args: command line arguments
:param mapobject: a ``MapFile`` subclass
"""
content = self.template.render(
Context(dict(
self.tpl_context.items(),
query=getattr(mapobject, args.dbtype)
))
)
with open("%s/%s" % (args.destdir, mapobject.filename), "w") as fp:
fp.write("""# This file was generated on {} by running:
# {} {}
# DO NOT EDIT!
"""
.format(datetime.datetime.now().isoformat(),
os.path.basename(sys.argv[0]),
' '.join(sys.argv[1:])))
fp.write("{}\n".format(content))
def __load_applications(self, applications):
"""Load specified applications."""
for app in applications:
extension = app.replace("-", "_")
try:
__import__(extension, locals(), globals(), ["postfix_maps"])
except ImportError:
sys.stderr.write("Unknown application {0}".format(extension))
def render(self, args):
"""Render all map files.
:param args: arguments received from the command line
"""
try:
os.mkdir(args.destdir)
except OSError:
pass
applications = ["modoboa.admin", "modoboa.relaydomains"]
if args.extensions:
if "all" in applications:
from modoboa.lib.api_client import ModoAPIClient
# Retrieve extension list from the API
url = "http://api.modoboa.org/"
official_exts = ModoAPIClient(url).list_extensions()
extensions = [
extension["name"] for extension in official_exts]
else:
extensions = args.extensions
applications += extensions
self.__load_applications(applications)
for mapfile in registry.files:
self.__render_map(args, mapfile)
class SQLiteMapFilesGenerator(MapFilesGenerator):
"""SQLite special generator."""
def __init__(self, dbinfo=None):
self.template = Template("""dbpath = {{ dbpath }}
query = {{ query|safe }}
""")
if dbinfo is None:
self.tpl_context = {
'dbpath': raw_input('Database path: ')
}
else:
self.tpl_context = {
'dbpath': dbinfo['NAME']
}
class PostfixMapsCommand(Command):
"""Base command to generate map files."""
help = "Generate ready-to-use postfix map files"
def __init__(self, *args, **kwargs):
super(PostfixMapsCommand, self).__init__(*args, **kwargs)
self._parser.add_argument(
'destdir', type=str,
help='Directory that will contain generated map files'
)
self._parser.add_argument(
'--dbtype', type=str, choices=['mysql', 'postgres', 'sqlite'],
default='mysql',
help='Used database type'
)
self._parser.add_argument(
'--dburl', type=str, nargs=1, default=None,
help="url of Modoboa's database"
)
self._parser.add_argument(
'--extensions', type=str, nargs='+',
help=(
'Generate map files for this extensions '
'("all" shortcut available)'
)
)
def handle(self, parsed_args):
"""Command entry point."""
django.setup()
dbinfo = None
if parsed_args.dburl:
dbinfo = dj_database_url.config(default=parsed_args.dburl[0])
if 'sqlite' in dbinfo['ENGINE']:
parsed_args.dbtype = 'sqlite'
elif 'psycopg2' in dbinfo['ENGINE']:
parsed_args.dbtype = 'postgres'
else:
parsed_args.dbtype = 'mysql'
if parsed_args.dbtype == 'sqlite':
generator = SQLiteMapFilesGenerator(dbinfo)
else:
generator = MapFilesGenerator(dbinfo)
generator.render(parsed_args)
@@ -0,0 +1,112 @@
"""Management command to generate/update postfix map files."""
import os
import sys
from django.conf import settings
from django.core.management.base import BaseCommand
from django.template import Context, Template
from django.utils import timezone
import dj_database_url
from ... import signals
MAP_FILE_TEMPLATE = """# This file was generated on {{ date }} by running:
# {{ commandline }}
# DO NOT EDIT!
"""
class Command(BaseCommand):
"""Command class."""
help = "Generate/update postfix map files."
def add_arguments(self, parser):
"""Add extra arguments."""
parser.add_argument(
"--dburl", help="Custom database url")
parser.add_argument(
"--destdir", default=".",
help="Directory where files will be created")
parser.add_argument(
"--extensions", type=str, nargs="+",
help=(
"Generate map files for those extensions "
"('all' shortcut available)"
)
)
def __register_map_files(self):
"""Load specified applications."""
responses = signals.register_postfix_maps.send(sender=self.__class__)
mapfiles = []
for response in responses:
mapfiles += response[1]
return mapfiles
def get_template(self, dbtype):
"""Return map file template."""
tplcontent = MAP_FILE_TEMPLATE
if dbtype == "sqlite":
tplcontent += """dbpath = {{ dbname }}
query = {{ query|safe }}
"""
else:
tplcontent += """user = {{ dbuser }}
password = {{ dbpass }}
dbname = {{ dbname }}
hosts = {{ dbhost }}
query = {{ query|safe }}
"""
return Template(tplcontent)
def get_template_context(self, options):
"""Build the context used to render templates."""
dburl = options.get("dburl")
db_settings = (
dj_database_url.config(default=dburl)
if dburl else settings.DATABASES["default"])
if "sqlite" in db_settings["ENGINE"]:
dbtype = "sqlite"
elif "psycopg2" in db_settings["ENGINE"]:
dbtype = "postgres"
else:
dbtype = "mysql"
commandline = "{} {}".format(
os.path.basename(sys.argv[0]), " ".join(sys.argv[1:]))
context = {
"date": timezone.now(),
"commandline": commandline,
"dbtype": dbtype,
"dbuser": db_settings["USER"],
"dbpass": db_settings["PASSWORD"],
"dbname": db_settings["NAME"],
"dbhost": db_settings.get("HOST", "127.0.0.1"),
}
return context
def __render_map_file(self, mapobject, destdir, context):
"""Render a map file."""
content = self.get_template(context["dbtype"]).render(
Context(
dict(context.items(),
query=getattr(mapobject, context["dbtype"]))
)
)
fullpath = os.path.join(destdir, mapobject.filename)
with open(fullpath, "w") as fp:
fp.write("{}\n".format(content))
def handle(self, *args, **options):
"""Command entry point."""
mapfiles = self.__register_map_files()
destdir = os.path.realpath(options["destdir"])
try:
os.mkdir(destdir)
except OSError:
pass
context = self.get_template_context(options)
for mapobject in mapfiles:
self.__render_map_file(mapobject, destdir, context)
+1
View File
@@ -4,3 +4,4 @@ import django.dispatch
can_create_object = django.dispatch.Signal(
providing_args=["context", "object_type", "count"])
register_postfix_maps = django.dispatch.Signal()
+6 -10
View File
@@ -1,13 +1,13 @@
"""Tools for top level testing (ie. when ran by Travis)."""
import os
import shutil
import tempfile
from .sysutils import exec_cmd
from django.core.management import call_command
class MapFilesTestCaseMixin(object):
"""A generic test case to check map files generation."""
MAP_FILES = None
@@ -18,17 +18,13 @@ class MapFilesTestCaseMixin(object):
self.workdir = tempfile.mkdtemp()
def tearDown(self):
exec_cmd("rm -rf {0}".format(self.workdir))
shutil.rmtree(self.workdir)
def _test_maps_generation(self, engine):
dburl = "{0}://user:password@localhost/testdb".format(engine)
code, output = exec_cmd(
"modoboa-admin.py postfix_maps {0}--dburl {1} {2}".format(
"--extensions {} ".format(self.extension)
if self.extension else "", dburl, self.workdir
)
)
self.assertEqual(code, 0)
call_command(
"generate_postfix_maps",
"--dburl", dburl, "--destdir", self.workdir)
for mapfile in self.MAP_FILES:
path = "{0}/{1}".format(self.workdir, mapfile)