mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
@@ -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
|
||||
]
|
||||
|
||||
@@ -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
|
||||
])
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user