Added ability to update configfile
This commit is contained in:
@@ -320,8 +320,8 @@ def get_entry_value(entry, interactive):
|
|||||||
return user_value if user_value else default_value
|
return user_value if user_value else default_value
|
||||||
|
|
||||||
|
|
||||||
def gen_config(dest, interactive=False):
|
def load_config_template(interactive):
|
||||||
"""Create config file from dict template"""
|
"""Instanciate a configParser object with the predefined template."""
|
||||||
tpl_dict = config_dict_template.ConfigDictTemplate
|
tpl_dict = config_dict_template.ConfigDictTemplate
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
# only ask about options we need, else still generate default
|
# only ask about options we need, else still generate default
|
||||||
@@ -337,6 +337,72 @@ def gen_config(dest, interactive=False):
|
|||||||
for config_entry in section["values"]:
|
for config_entry in section["values"]:
|
||||||
value = get_entry_value(config_entry, interactive_section)
|
value = get_entry_value(config_entry, interactive_section)
|
||||||
config.set(section["name"], config_entry["option"], value)
|
config.set(section["name"], config_entry["option"], value)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def update_config(path):
|
||||||
|
"""Update an existing config file."""
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
with open(path) as fp:
|
||||||
|
config.read_file(fp)
|
||||||
|
new_config = load_config_template(False)
|
||||||
|
|
||||||
|
old_sections = config.sections()
|
||||||
|
new_sections = new_config.sections()
|
||||||
|
|
||||||
|
update = False
|
||||||
|
|
||||||
|
dropped_sections = list(set(old_sections) - set(new_sections))
|
||||||
|
|
||||||
|
if len(dropped_sections) > 0:
|
||||||
|
printcolor("Follow section(s) will not be ported "
|
||||||
|
"due to being deleted or renamed: " +
|
||||||
|
', '.join(dropped_sections),
|
||||||
|
RED)
|
||||||
|
|
||||||
|
for section in new_sections:
|
||||||
|
if section in old_sections:
|
||||||
|
new_options = new_config.options(section)
|
||||||
|
old_options = config.options(section)
|
||||||
|
|
||||||
|
dropped_options = list(set(old_options) - set(new_options))
|
||||||
|
|
||||||
|
if len(dropped_options) > 0:
|
||||||
|
printcolor(f"Following option(s) from section: {section}, "
|
||||||
|
"will not be ported due to being "
|
||||||
|
"deleted or renamed: " +
|
||||||
|
', '.join(dropped_options),
|
||||||
|
RED)
|
||||||
|
|
||||||
|
for option in new_options:
|
||||||
|
if option in old_options:
|
||||||
|
value = config.get(section, option, raw=True)
|
||||||
|
if value != new_config.get(section, option, raw=True):
|
||||||
|
update = True
|
||||||
|
new_config.set(section, option, value)
|
||||||
|
|
||||||
|
if update:
|
||||||
|
# Backing up old config file
|
||||||
|
date = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||||
|
dest = f"{os.path.splitext(path)[0]}_{date}.old"
|
||||||
|
shutil.copy(path, dest)
|
||||||
|
|
||||||
|
# Overwritting old config file
|
||||||
|
with open(path, "w") as configfile:
|
||||||
|
new_config.write(configfile)
|
||||||
|
|
||||||
|
# Set file owner to running user and group, and set config file permission to 600
|
||||||
|
current_username = getpass.getuser()
|
||||||
|
current_user = pwd.getpwnam(current_username)
|
||||||
|
os.chown(dest, current_user[2], current_user[3])
|
||||||
|
os.chmod(dest, stat.S_IRUSR | stat.S_IWUSR)
|
||||||
|
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
|
def gen_config(dest, interactive=False):
|
||||||
|
"""Create config file from dict template"""
|
||||||
|
config = load_config_template(interactive)
|
||||||
|
|
||||||
with open(dest, "w") as configfile:
|
with open(dest, "w") as configfile:
|
||||||
config.write(configfile)
|
config.write(configfile)
|
||||||
|
|||||||
19
run.py
19
run.py
@@ -134,6 +134,10 @@ def main(input_args):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--stop-after-configfile-check", action="store_true", default=False,
|
"--stop-after-configfile-check", action="store_true", default=False,
|
||||||
help="Check configuration, generate it if needed and exit")
|
help="Check configuration, generate it if needed and exit")
|
||||||
|
parser.add_argument(
|
||||||
|
"--update-configfile", action="store_true", default=False,
|
||||||
|
help="Attempt to update the config file. "
|
||||||
|
"Installer will stop after performing the update.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--interactive", action="store_true", default=False,
|
"--interactive", action="store_true", default=False,
|
||||||
help="Generate configuration file with user interaction")
|
help="Generate configuration file with user interaction")
|
||||||
@@ -153,7 +157,8 @@ def main(input_args):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--silent-backup", action="store_true", default=False,
|
"--silent-backup", action="store_true", default=False,
|
||||||
help="For script usage, do not require user interaction "
|
help="For script usage, do not require user interaction "
|
||||||
"backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M if --backup-path is not provided")
|
"backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M "
|
||||||
|
"if --backup-path is not provided")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--restore", type=str, metavar="path",
|
"--restore", type=str, metavar="path",
|
||||||
help="Restore a previously backup up modoboa instance on a NEW machine. "
|
help="Restore a previously backup up modoboa instance on a NEW machine. "
|
||||||
@@ -178,6 +183,18 @@ def main(input_args):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
utils.success("Welcome to Modoboa installer!\n")
|
utils.success("Welcome to Modoboa installer!\n")
|
||||||
|
|
||||||
|
# Update configfile
|
||||||
|
if args.update_configfile:
|
||||||
|
backup_location = utils.update_config(args.configfile)
|
||||||
|
utils.printcolor("Update complete. It seems successful.",
|
||||||
|
utils.BLUE)
|
||||||
|
if backup_location is not None:
|
||||||
|
utils.printcolor("You will find your old config file "
|
||||||
|
f"here: {backup_location}",
|
||||||
|
utils.BLUE)
|
||||||
|
return
|
||||||
|
|
||||||
is_config_file_available = utils.check_config_file(
|
is_config_file_available = utils.check_config_file(
|
||||||
args.configfile, args.interactive, args.upgrade, args.backup, is_restoring)
|
args.configfile, args.interactive, args.upgrade, args.backup, is_restoring)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
codecov
|
codecov
|
||||||
mock
|
mock
|
||||||
six
|
|
||||||
|
|||||||
46
tests.py
46
tests.py
@@ -6,8 +6,13 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from six import StringIO
|
from io import StringIO
|
||||||
from six.moves import configparser
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
try:
|
try:
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -26,7 +31,11 @@ class ConfigFileTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Delete temp dir."""
|
"""Delete temp dir."""
|
||||||
shutil.rmtree(self.workdir)
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
print(self.workdir)
|
||||||
|
#shutil.rmtree(self.workdir)
|
||||||
|
pass
|
||||||
|
|
||||||
def test_configfile_generation(self):
|
def test_configfile_generation(self):
|
||||||
"""Check simple case."""
|
"""Check simple case."""
|
||||||
@@ -57,6 +66,37 @@ class ConfigFileTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(config.get("certificate", "type"), "self-signed")
|
self.assertEqual(config.get("certificate", "type"), "self-signed")
|
||||||
self.assertEqual(config.get("database", "engine"), "postgres")
|
self.assertEqual(config.get("database", "engine"), "postgres")
|
||||||
|
|
||||||
|
def test_updating_configfile(self):
|
||||||
|
"""Check configfile update mechanism."""
|
||||||
|
cfgfile_temp = os.path.join(self.workdir, "installer_old.cfg")
|
||||||
|
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
run.main([
|
||||||
|
"--stop-after-configfile-check",
|
||||||
|
"--configfile", cfgfile_temp,
|
||||||
|
"example.test"])
|
||||||
|
self.assertTrue(os.path.exists(cfgfile_temp))
|
||||||
|
|
||||||
|
# Adding a dummy section
|
||||||
|
with open(cfgfile_temp, "a") as fp:
|
||||||
|
fp.write(
|
||||||
|
"""
|
||||||
|
[dummy]
|
||||||
|
weird_old_option = "hey
|
||||||
|
""")
|
||||||
|
print("here")
|
||||||
|
print(os.path.isfile(cfgfile_temp))
|
||||||
|
|
||||||
|
out = StringIO()
|
||||||
|
sys.stdout = out
|
||||||
|
run.main([
|
||||||
|
"--update-configfile",
|
||||||
|
"--configfile", cfgfile_temp,
|
||||||
|
"example.test"])
|
||||||
|
self.assertIn("dummy", out.getvalue())
|
||||||
|
self.assertTrue(Path(self.workdir).glob("*.old"))
|
||||||
|
|
||||||
@patch("modoboa_installer.utils.user_input")
|
@patch("modoboa_installer.utils.user_input")
|
||||||
def test_interactive_mode_letsencrypt(self, mock_user_input):
|
def test_interactive_mode_letsencrypt(self, mock_user_input):
|
||||||
"""Check interactive mode."""
|
"""Check interactive mode."""
|
||||||
|
|||||||
Reference in New Issue
Block a user