Merge pull request #930 from tonioo/feature/map_files_upgrade

Upgrade mode.
This commit is contained in:
Antoine Nguyen
2016-10-03 15:35:24 +02:00
committed by GitHub
3 changed files with 112 additions and 3 deletions
+40
View File
@@ -1,7 +1,12 @@
"""Test map files generation."""
import os
from django.conf import settings
from django.core.management import call_command
from django.test import TestCase
from modoboa.core.utils import parse_map_file
from modoboa.lib.test_utils import MapFilesTestCaseMixin
@@ -14,3 +19,38 @@ class MapFilesTestCase(MapFilesTestCaseMixin, TestCase):
"sql-maintain.cf", "sql-sender-login-mailboxes.cf",
"sql-sender-login-mailboxes-extra.cf", "sql-sender-login-aliases.cf"
]
def test_map_upgrade(self):
"""Check that map content is used."""
dburl = "postgres://user:password@localhost/testdb"
call_command(
"generate_postfix_maps",
"--dburl", dburl, "--destdir", self.workdir)
# Now upgrade files. Map credentials should be preserved.
call_command("generate_postfix_maps", "--destdir", self.workdir)
for f in self.MAP_FILES:
mapcontent = parse_map_file(os.path.join(self.workdir, f))
self.assertEqual(mapcontent["user"], "user")
self.assertEqual(mapcontent["password"], "password")
self.assertEqual(mapcontent["dbname"], "testdb")
# Now force overwrite, credentials should be different
call_command(
"generate_postfix_maps", "--destdir", self.workdir,
"--force-overwrite")
dbsettings = settings.DATABASES["default"]
for f in self.MAP_FILES:
mapcontent = parse_map_file(os.path.join(self.workdir, f))
self.assertEqual(mapcontent["user"], dbsettings["USER"])
self.assertEqual(mapcontent["password"], dbsettings["PASSWORD"])
self.assertEqual(mapcontent["dbname"], dbsettings["NAME"])
# Now modify a file manually
path = os.path.join(self.workdir, "sql-domains.cf")
with open(path, "a") as fp:
fp.write("pouet")
call_command("generate_postfix_maps", "--destdir", self.workdir)
with open(path) as fp:
content = fp.read()
self.assertIn("pouet", content)
@@ -1,5 +1,7 @@
"""Management command to generate/update postfix map files."""
import copy
import hashlib
import os
import sys
@@ -11,6 +13,7 @@ from django.utils import timezone
import dj_database_url
from ... import signals
from ... import utils
MAP_FILE_TEMPLATE = """# This file was generated on {{ date }} by running:
# {{ commandline }}
@@ -30,6 +33,23 @@ class Command(BaseCommand):
parser.add_argument(
"--destdir", default=".",
help="Directory where files will be created")
parser.add_argument(
"--force-overwrite", action="store_true", default=False,
help="Force overwrite of existing map files")
def __load_checksums(self, destdir):
"""Load existing checksums if possible."""
self.__checksums_file = os.path.join(
destdir, "modoboa-postfix-maps.chk")
self.__checksums = {}
if not os.path.exists(self.__checksums_file):
return
with open(self.__checksums_file) as fp:
for line in fp:
fname, dbtype, checksum = line.split(":")
self.__checksums[fname.strip()] = {
"dbtype": dbtype, "checksum": checksum.strip()
}
def __register_map_files(self):
"""Load specified applications."""
@@ -39,6 +59,18 @@ class Command(BaseCommand):
mapfiles += response[1]
return mapfiles
def __check_file(self, path):
"""Check if map file has been modified."""
fname = os.path.basename(path)
condition = (
not self.__checksums or
fname not in self.__checksums)
if condition:
return True
with open(path) as fp:
checksum = hashlib.md5(fp.read()).hexdigest()
return checksum == self.__checksums[fname]["checksum"]
def get_template(self, dbtype):
"""Return map file template."""
tplcontent = MAP_FILE_TEMPLATE
@@ -80,8 +112,23 @@ query = {{ query|safe }}
}
return context
def __render_map_file(self, mapobject, destdir, context):
def __render_map_file(
self, mapobject, destdir, context, force_overwrite=False):
"""Render a map file."""
fullpath = os.path.join(destdir, mapobject.filename)
if os.path.exists(fullpath) and not force_overwrite:
if not self.__check_file(fullpath):
print(
"Cannot upgrade '{}' map because it has been modified."
.format(mapobject.filename))
return self.__checksums[mapobject.filename]
mapcontent = utils.parse_map_file(fullpath)
context = copy.deepcopy(context)
context["dbtype"] = self.__checksums[mapobject.filename]["dbtype"]
context["dbuser"] = mapcontent["user"]
context["dbpass"] = mapcontent["password"]
context["dbname"] = mapcontent["dbname"]
context["dbhost"] = mapcontent["hosts"]
content = self.get_template(context["dbtype"]).render(
Context(
dict(context.items(),
@@ -90,7 +137,8 @@ query = {{ query|safe }}
)
fullpath = os.path.join(destdir, mapobject.filename)
with open(fullpath, "w") as fp:
fp.write("{}\n".format(content))
fp.write(content)
return hashlib.md5(content).hexdigest()
def handle(self, *args, **options):
"""Command entry point."""
@@ -100,6 +148,15 @@ query = {{ query|safe }}
os.mkdir(destdir)
except OSError:
pass
self.__load_checksums(destdir)
context = self.get_template_context(options)
checksums = {}
for mapobject in mapfiles:
self.__render_map_file(mapobject, destdir, context)
checksum = self.__render_map_file(
mapobject, destdir, context,
force_overwrite=options["force_overwrite"])
checksums[mapobject.filename] = checksum
with open(self.__checksums_file, "w") as fp:
for fname, checksum in checksums.items():
fp.write("{}:{}:{}\n".format(
fname, context["dbtype"], checksum))
+12
View File
@@ -11,6 +11,18 @@ from modoboa.lib.api_client import ModoAPIClient
from . import models
def parse_map_file(path):
"""Parse a postfix map file and return values."""
content = {}
with open(path) as fp:
for line in fp:
if not line or line.startswith("#"):
continue
name, value = line.split("=", 1)
content[name.strip()] = value.strip()
return content
def check_for_updates(request):
"""Check if a new version of Modoboa is available."""
local_config = models.LocalConfig.objects.first()