diff --git a/DEVELOP.rst b/DEVELOP.rst index e010603fec5a60f95061a99adbd29944dcf83698_REVWRUxPUC5yc3Q=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_REVWRUxPUC5yc3Q= 100644 --- a/DEVELOP.rst +++ b/DEVELOP.rst @@ -1,4 +1,4 @@ -To install for development purpose: +As :pep:517 does not provide a way to install for development purpose, it is necessary to follow these steps to install for development : .. code-block:: SH diff --git a/NEWS.rst b/NEWS.rst index e010603fec5a60f95061a99adbd29944dcf83698_TkVXUy5yc3Q=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_TkVXUy5yc3Q= 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -15,6 +15,8 @@ Add docker_prettier. +Add odoo_conf_inject_env_var entry point. + 15.0.0 ------ diff --git a/README.rst b/README.rst index e010603fec5a60f95061a99adbd29944dcf83698_UkVBRE1FLnJzdA==..d1586d7d2eefd18f616befaf9ebf4bba06470a96_UkVBRE1FLnJzdA== 100644 --- a/README.rst +++ b/README.rst @@ -148,6 +148,11 @@ create_date and write_date is updated as needed, and so is the date_update in ``ir.model.data``. This is part of the import_sql section. +odoo_conf_inject_env_var +------------------------ + +This script takes an odoo configuration file and a path and will use environment variables values to set some values in the produced odoo configuration file. + setup.cfg ========= diff --git a/odoo_scripts/docker_client.py b/odoo_scripts/docker_client.py index e010603fec5a60f95061a99adbd29944dcf83698_b2Rvb19zY3JpcHRzL2RvY2tlcl9jbGllbnQucHk=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_b2Rvb19zY3JpcHRzL2RvY2tlcl9jbGllbnQucHk= 100644 --- a/odoo_scripts/docker_client.py +++ b/odoo_scripts/docker_client.py @@ -1,4 +1,4 @@ -"""Class that encapslulate the docker client""" +"""Class that encapsulate the docker client""" import atexit import logging import os diff --git a/odoo_scripts/list_modules.py b/odoo_scripts/odoo_conf_inject_env_var.py similarity index 16% copy from odoo_scripts/list_modules.py copy to odoo_scripts/odoo_conf_inject_env_var.py index e010603fec5a60f95061a99adbd29944dcf83698_b2Rvb19zY3JpcHRzL2xpc3RfbW9kdWxlcy5weQ==..d1586d7d2eefd18f616befaf9ebf4bba06470a96_b2Rvb19zY3JpcHRzL29kb29fY29uZl9pbmplY3RfZW52X3Zhci5weQ== 100644 --- a/odoo_scripts/list_modules.py +++ b/odoo_scripts/odoo_conf_inject_env_var.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 -"""Create the file odoo_modules_list from setup.cfg -""" -# vim: set shiftwidth=4 softtabstop=4: -# -# template version 2.7 +"""Method to inject environment variables into an Odoo configuration file.""" +import logging +import os.path +import sys +from configparser import ConfigParser +from typing import Collection, Optional, Tuple @@ -7,7 +8,3 @@ -import logging -import sys - -from .config import Config from .parsing import apply, basic_parser @@ -12,6 +9,20 @@ from .parsing import apply, basic_parser -MODULES_LIST_FILE = "odoo_modules_list" +__version__ = "1.0.0" +__date__ = "2022-02-22" +__updated__ = "2022-02-23" + +# tuple with environment variable nome, section and option +_environment_variables: Collection[Tuple[str, str, str]] = ( + ("ODOO_OPTIONS_DB_USER", "options", "db_user"), + ("ODOO_OPTIONS_DB_PASSWORD", "options", "db_password"), + ("ODOO_OPTIONS_DB_NAME", "options", "db_name"), + ("ODOO_OPTIONS_DB_HOST", "options", "db_host"), + ("ODOO_OPTIONS_DATA_DIR", "options", "data_dir"), + ("ODOO_OPTIONS_MAX_CRON_THREADS", "options", "max_cron_threads"), + ("ODOO_OPTIONS_LOAD_LANGUAGE", "options", "load_language"), + ("ODOO_OPTIONS_ADDONS_PATH", "options", "addons_path"), +) _logger = logging.getLogger(__name__) @@ -15,8 +26,20 @@ _logger = logging.getLogger(__name__) -__version__ = "1.0.3" -__date__ = "2020-02-26" -__updated__ = "2020-09-11" + +def inject_env_var(source_path: str, target_path: str, overwrite: bool = False): + """Inject environment variables into an Odoo configuration file.""" + if os.path.exists(target_path) and not overwrite: + raise Exception(f"{target_path} already exists") + config_parser = ConfigParser() + config_parser.read(source_path) + with open(target_path, "w", encoding="UTF-8") as file: + for env_var_name, section, option in _environment_variables: + if env_var_name in os.environ: + _logger.debug("Using %s", env_var_name) + if not config_parser.has_section(section): + config_parser.add_section(section) + config_parser.set(section, option, os.environ[env_var_name]) + config_parser.write(file) @@ -21,9 +44,7 @@ -def main(argv=None): # IGNORE:C0111 - """Parse arguments and launch conversion""" - if argv is None: - argv = sys.argv - else: - sys.argv.extend(argv) +def main(args: Optional[Collection[str]] = None) -> int: + """Parse arguments and launch injection""" + if args is None: + args = [] @@ -29,10 +50,5 @@ - program_version = __version__ - program_build_date = str(__updated__) - program_version_message = "%%(prog)s %s (%s)" % ( - program_version, - program_build_date, - ) - program_shortdesc = __doc__.split("\n", maxsplit=2)[1] - program_license = """%s + program_version_message = f"%(prog)s {__version__} ({__updated__})" + program_short_description = __doc__.split(".", maxsplit=1)[0] + program_license = f"""{program_short_description} @@ -38,6 +54,6 @@ - Created by Hugo Monnerie on %s. - Copyright 2020 XCG Consulting. All rights reserved. + Created by Vincent Hatakeyama on {__date__}. + Copyright 2022 XCG Consulting. All rights reserved. Licensed under the MIT License @@ -45,9 +61,6 @@ or conditions of any kind, either express or implied. USAGE -""" % ( - program_shortdesc, - str(__date__), - ) +""" # Argument parsing parser = basic_parser(program_license, program_version_message) @@ -52,7 +65,19 @@ # Argument parsing parser = basic_parser(program_license, program_version_message) - nmspc = parser.parse_args() - apply(nmspc) - list_modules(None) - + parser.add_argument( + "--source", + help="source file", + required=True, + ) + parser.add_argument( + "--target", + help="target file", + required=True, + ) + parser.add_argument( + "--overwrite", + help="Overwrite target file [default: %(default)s]", + action="store_true", + default=False, + ) @@ -58,11 +83,8 @@ -def list_modules(filename: str = MODULES_LIST_FILE): - output = ",".join(Config().module_list) - if filename: - with open(filename, "w") as f: - f.write(output) - else: - print(output) + namespace = parser.parse_args(args) + apply(namespace) + inject_env_var(namespace.source, namespace.target, namespace.overwrite) + return 0 if __name__ == "__main__": @@ -66,4 +88,4 @@ if __name__ == "__main__": - main() + sys.exit(main(sys.argv[1:])) diff --git a/pyproject.toml b/pyproject.toml index e010603fec5a60f95061a99adbd29944dcf83698_cHlwcm9qZWN0LnRvbWw=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_cHlwcm9qZWN0LnRvbWw= 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ minversion = 3.15 envlist = py36,py37,py38,py39,py310,coverage-report skip_missing_interpreters = true +isolated_build = True [testenv:py{36,37,38,39,310}] setenv = diff --git a/setup.py b/setup.py index e010603fec5a60f95061a99adbd29944dcf83698_c2V0dXAucHk=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_c2V0dXAucHk= 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ "docker_prettier=odoo_scripts.docker_prettier:main [docker]", "docker_pylint=odoo_scripts.docker_pylint:main [docker]", "conf2reST=odoo_scripts.conf2reST:main [conf2reST]", + "odoo_conf_inject_env_var=odoo_scripts.odoo_conf_inject_env_var:main", "list_modules=odoo_scripts.list_modules:main", "update_duplicate_sources=" "odoo_scripts.update_duplicate_sources:main [source_control]", diff --git a/tests/test_config.py b/tests/test_odoo_conf_inject_env_var.py similarity index 3% copy from tests/test_config.py copy to tests/test_odoo_conf_inject_env_var.py index e010603fec5a60f95061a99adbd29944dcf83698_dGVzdHMvdGVzdF9jb25maWcucHk=..d1586d7d2eefd18f616befaf9ebf4bba06470a96_dGVzdHMvdGVzdF9vZG9vX2NvbmZfaW5qZWN0X2Vudl92YXIucHk= 100644 --- a/tests/test_config.py +++ b/tests/test_odoo_conf_inject_env_var.py @@ -1,2 +1,4 @@ -"""Tests of the config module""" +"""Tests of the Odoo conf module""" +import os +import tempfile import unittest @@ -2,2 +4,3 @@ import unittest +from configparser import ConfigParser @@ -3,4 +6,19 @@ -from odoo_scripts.config import Configuration +from odoo_scripts.odoo_conf_inject_env_var import inject_env_var, main + + +def clean_up_env(): + """Clean up environment""" + for name in ( + "ODOO_OPTIONS_DB_USER", + "ODOO_OPTIONS_DB_PASSWORD", + "ODOO_OPTIONS_DB_NAME", + "ODOO_OPTIONS_DB_HOST", + "ODOO_OPTIONS_MAX_CRON_THREADS", + "ODOO_OPTIONS_DATA_DIR", + "ODOO_OPTIONS_LOAD_LANGUAGE", + "ODOO_OPTIONS_ADDONS_PATH", + ): + os.environ.pop(name, None) @@ -5,5 +23,41 @@ -class ConfigTestCase(unittest.TestCase): - """Tests of the config module""" +class OdooConfTestCase(unittest.TestCase): + """Tests of the Odoo conf env var class""" + + @classmethod + def setUpClass(cls) -> None: + """Create the conf file used in the tests""" + cls.conf_path = "odoo.conf" + with open(cls.conf_path, "w", encoding="UTF-8") as file_io: + file_io.write( + """# This is a test file similar to an Odoo configuration file +[options] +db_user = db_user +db_password = db_password +db_name = db_name +max_cron_threads = 3 +load_language = fr_FR,en +data_dir = /var/lib/odoo +db_host = db_host +addons_path = /mnt/extra-addons +something_else = something_else +[anothersection] +anothersection_key = value +""" + ) + + @classmethod + def tearDownClass(cls) -> None: + """Clean up temporary files""" + if os.path.exists(cls.conf_path): + os.remove(cls.conf_path) + + def test_no_env_var(self): + """Test when there is no environment variable""" + with tempfile.TemporaryDirectory() as tmpdirname: + new_conf = os.path.join(tmpdirname, "odoo.conf") + inject_env_var(self.conf_path, new_conf) + config_parser = ConfigParser() + config_parser.read(new_conf) @@ -9,3 +63,51 @@ - def test_no_conf(self): + self.assertTrue(config_parser.has_section("options")) + self.assertEqual(config_parser.get("options", "db_user"), "db_user") + self.assertEqual(config_parser.get("options", "db_password"), "db_password") + self.assertEqual(config_parser.get("options", "db_name"), "db_name") + self.assertEqual(config_parser.get("options", "max_cron_threads"), "3") + self.assertEqual(config_parser.get("options", "load_language"), "fr_FR,en") + self.assertEqual(config_parser.get("options", "data_dir"), "/var/lib/odoo") + self.assertEqual(config_parser.get("options", "db_host"), "db_host") + self.assertEqual( + config_parser.get("options", "addons_path"), "/mnt/extra-addons" + ) + self.assertEqual( + config_parser.get("options", "something_else"), "something_else" + ) + self.assertTrue(config_parser.has_section("anothersection")) + self.assertEqual( + config_parser.get("anothersection", "anothersection_key"), "value" + ) + + os.remove(new_conf) + + def test_target_exists(self): + """Test when the target file exists""" + with tempfile.TemporaryDirectory() as tmpdirname: + new_conf = os.path.join(tmpdirname, "odoo.conf") + with open(new_conf, "w", encoding="UTF-8") as file: + file.write("\n") + with self.assertRaises(Exception): + inject_env_var(self.conf_path, new_conf) + + def test_no_options_section(self): + """Test when the source file does not have the options section""" + self.addCleanup(clean_up_env) + + with tempfile.TemporaryDirectory() as tmpdirname: + empty_conf = os.path.join(tmpdirname, "odoo-empty.conf") + with open(empty_conf, "w", encoding="UTF-8") as file: + file.write("\n") + new_conf = os.path.join(tmpdirname, "odoo.conf") + os.environ["ODOO_OPTIONS_DB_USER"] = "user" + inject_env_var(empty_conf, new_conf) + + config_parser = ConfigParser() + config_parser.read(new_conf) + + self.assertTrue(config_parser.has_section("options")) + self.assertEqual(config_parser.get("options", "db_user"), "user") + + def test_env_var(self): """Test when there is no configuration file""" @@ -11,4 +113,18 @@ """Test when there is no configuration file""" - configuration = Configuration("config-no-file/") - self.assertEqual(configuration.modules, []) + self.addCleanup(clean_up_env) + + with tempfile.TemporaryDirectory() as tmpdirname: + new_conf = os.path.join(tmpdirname, "odoo.conf") + os.environ["ODOO_OPTIONS_DB_USER"] = "another" + os.environ["ODOO_OPTIONS_DB_PASSWORD"] = "password" + os.environ["ODOO_OPTIONS_DB_NAME"] = "name" + os.environ["ODOO_OPTIONS_DB_HOST"] = "host" + os.environ["ODOO_OPTIONS_MAX_CRON_THREADS"] = "2" + os.environ["ODOO_OPTIONS_DATA_DIR"] = "/mnt" + os.environ["ODOO_OPTIONS_LOAD_LANGUAGE"] = "en_US" + os.environ["ODOO_OPTIONS_ADDONS_PATH"] = "/addons" + # use main rather than inject_env_var to also test its code + main(["--source", self.conf_path, "--target", new_conf]) + config_parser = ConfigParser() + config_parser.read(new_conf) @@ -14,8 +130,22 @@ - def test_empty_conf(self): - """Test when there is no configuration file""" - configuration = Configuration("config-empty/") - self.assertEqual(configuration.modules, []) + self.assertTrue(config_parser.has_section("options")) + self.assertEqual(config_parser.get("options", "db_user"), "another") + self.assertEqual(config_parser.get("options", "db_password"), "password") + self.assertEqual(config_parser.get("options", "db_name"), "name") + self.assertEqual(config_parser.get("options", "max_cron_threads"), "2") + self.assertEqual(config_parser.get("options", "load_language"), "en_US") + self.assertEqual(config_parser.get("options", "data_dir"), "/mnt") + self.assertEqual(config_parser.get("options", "db_host"), "host") + self.assertEqual(config_parser.get("options", "addons_path"), "/addons") + self.assertEqual( + config_parser.get("options", "something_else"), "something_else" + ) + self.assertTrue(config_parser.has_section("anothersection")) + self.assertEqual( + config_parser.get("anothersection", "anothersection_key"), "value" + ) + + os.remove(new_conf) if __name__ == "__main__":