From facd1c928e0c449ce31523fe5b4116396c40bdfe Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 13 Feb 2026 03:46:27 -0500 Subject: [PATCH] [IMP] script update config from database and implement TODO configuration - execute script remote endline from output --- .../database/get_module_list_from_database.py | 66 ++++++ script/database/get_repo_from_backup.py | 16 +- script/database/get_repo_from_module.py | 80 +++++++ script/execute/execute.py | 7 +- script/git/git_repo_update_group.py | 36 ++- script/git/git_tool.py | 4 +- script/odoo/migration/README.md | 2 +- script/todo/todo.py | 209 ++++++++++++++---- 8 files changed, 354 insertions(+), 66 deletions(-) create mode 100755 script/database/get_module_list_from_database.py create mode 100755 script/database/get_repo_from_module.py diff --git a/script/database/get_module_list_from_database.py b/script/database/get_module_list_from_database.py new file mode 100755 index 0000000..9819aaf --- /dev/null +++ b/script/database/get_module_list_from_database.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# © 2021-2025 TechnoLibre (http://www.technolibre.ca) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import argparse +import logging +import os +import sys + +new_path = os.path.normpath( + os.path.join(os.path.dirname(__file__), "..", "..") +) +sys.path.append(new_path) + +from script.execute import execute +from script.git.git_tool import GitTool + +_logger = logging.getLogger(__name__) + + +def get_config(): + """Parse command line arguments, extracting the config file name, + returning the union of config file and command line arguments + + :return: dict of config file settings and command line arguments + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description="""\ + Will print list of installed module from database +""", + epilog="""\ +""", + ) + parser.add_argument( + "--database", + required=True, + help="The database name to extract", + ) + args = parser.parse_args() + return args + + +def main(): + config = get_config() + execute_cmd = execute.Execute() + + if config.database: + cmd = f"cat ./script/odoo/util/show_installed_module.py | ./odoo_bin.sh shell -d {config.database}" + status, output = execute_cmd.exec_command_live( + cmd, + quiet=True, + source_erplibre=False, + single_source_erplibre=True, + return_status_and_output=True, + ) + has_find = False + for line in output: + if has_find: + print(line.strip()) + if line.strip() == "Installed modules:": + has_find = True + + +if __name__ == "__main__": + main() diff --git a/script/database/get_repo_from_backup.py b/script/database/get_repo_from_backup.py index d497f4c..4b17782 100755 --- a/script/database/get_repo_from_backup.py +++ b/script/database/get_repo_from_backup.py @@ -68,27 +68,15 @@ def main(): json_manifest_file = json.load(manifest_file) lst_module = set(json_manifest_file.get("modules").keys()) - - cmd = "parallel ::: " + " ".join( - [ - f"'./script/addons/check_addons_exist.py --output_path -m \"{a}\"'" - for a in lst_module - ] - ) + str_module = ";".join(lst_module) + cmd = f'./script/database/get_repo_from_module.py --module "{str_module}"' execute_cmd = execute.Execute() status, output = execute_cmd.exec_command_live( cmd, - quiet=not config.debug, source_erplibre=False, single_source_erplibre=True, return_status_and_output=True, ) - set_path_repo = set() - for line_module_path in output: - line_repo = os.path.dirname(line_module_path) - set_path_repo.add(line_repo) - for path_repo in set_path_repo: - print(path_repo) def die(cond, message, code=1): diff --git a/script/database/get_repo_from_module.py b/script/database/get_repo_from_module.py new file mode 100755 index 0000000..7d7b326 --- /dev/null +++ b/script/database/get_repo_from_module.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# © 2021-2025 TechnoLibre (http://www.technolibre.ca) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import argparse +import json +import logging +import os +import sys +import zipfile + +new_path = os.path.normpath( + os.path.join(os.path.dirname(__file__), "..", "..") +) +sys.path.append(new_path) + +from script.execute import execute + +logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) + +_logger = logging.getLogger(__name__) + + +def get_config(): + """Parse command line arguments, extracting the config file name, + returning the union of config file and command line arguments + + :return: dict of config file settings and command line arguments + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description="""\ + Extract all repo path from installed module into backup. + Use --backup_path or --backup_name +""", + epilog="""\ +""", + ) + parser.add_argument("--module", help="Module list separate by ;") + parser.add_argument("--debug", action="store_true") + args = parser.parse_args() + + return args + + +def main(): + config = get_config() + + lst_module = set(config.module.split(";")) + + cmd = "parallel ::: " + " ".join( + [ + f"'./script/addons/check_addons_exist.py --output_path -m \"{a}\"'" + for a in lst_module + ] + ) + execute_cmd = execute.Execute() + status, output = execute_cmd.exec_command_live( + cmd, + quiet=not config.debug, + source_erplibre=False, + single_source_erplibre=True, + return_status_and_output=True, + ) + set_path_repo = set() + for line_module_path in output: + line_repo = os.path.dirname(line_module_path) + set_path_repo.add(line_repo) + for path_repo in set_path_repo: + print(path_repo) + + +def die(cond, message, code=1): + if cond: + print(message, file=sys.stderr) + sys.exit(code) + + +if __name__ == "__main__": + main() diff --git a/script/execute/execute.py b/script/execute/execute.py index 3774252..a7f0309 100644 --- a/script/execute/execute.py +++ b/script/execute/execute.py @@ -138,7 +138,12 @@ class Execute: return_status_and_output or return_status_and_output_and_command ): - lst_output.append(ligne) + # Remove last \n char + lst_output.append( + ligne.removesuffix("\r\n") + .removesuffix("\n") + .removesuffix("\r") + ) process.wait() # Attendre la fin du process return_status = process.returncode diff --git a/script/git/git_repo_update_group.py b/script/git/git_repo_update_group.py index 94b35c7..1b64cf9 100755 --- a/script/git/git_repo_update_group.py +++ b/script/git/git_repo_update_group.py @@ -7,9 +7,6 @@ import logging import os import sys -import git -from git import Repo - new_path = os.path.normpath( os.path.join(os.path.dirname(__file__), "..", "..") ) @@ -27,9 +24,6 @@ def get_config(): :return: dict of config file settings and command line arguments """ - config = GitTool.get_project_config() - - # TODO update description parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description="""\ @@ -65,6 +59,10 @@ def get_config(): "--add_repo", help="Add repo, separate by ; for list", ) + parser.add_argument( + "--database", + help="Add repo from module found into database.", + ) args = parser.parse_args() return args @@ -72,12 +70,13 @@ def get_config(): def main(): config = get_config() git_tool = GitTool() + execute_cmd = execute.Execute() + index_to_remove = len(os.getcwd()) + 1 filter_group = config.group if config.group else None lst_whitelist = [] if config.from_backup_path or config.from_backup_name: - execute_cmd = execute.Execute() # script/database/get_repo_from_backup.py # --backup_name bpir_prod_5_dec_2025_2026-02-04_14h27m54s.zip if config.from_backup_path: @@ -91,7 +90,28 @@ def main(): single_source_erplibre=True, return_status_and_output=True, ) - index_to_remove = len(os.getcwd()) + 1 + for line in output: + repo_name = line[index_to_remove:].strip() + lst_whitelist.append(repo_name) + + if config.database: + cmd = f"./script/database/get_module_list_from_database.py --database {config.database}" + status, output = execute_cmd.exec_command_live( + cmd, + quiet=True, + source_erplibre=False, + single_source_erplibre=True, + return_status_and_output=True, + ) + str_module = ";".join(output) + cmd = f'./script/database/get_repo_from_module.py --module "{str_module}"' + status, output = execute_cmd.exec_command_live( + cmd, + quiet=True, + source_erplibre=False, + single_source_erplibre=True, + return_status_and_output=True, + ) for line in output: repo_name = line[index_to_remove:].strip() lst_whitelist.append(repo_name) diff --git a/script/git/git_tool.py b/script/git/git_tool.py index e490884..73d360a 100644 --- a/script/git/git_tool.py +++ b/script/git/git_tool.py @@ -457,7 +457,9 @@ class GitTool: continue # groups = repo.get("group") # Use variable instead of hardcoded path - if update_repo.startswith(os.path.join(filter_group, "addons")): + if update_repo.startswith( + os.path.join(self.odoo_version_long, "addons") + ): lst_path = update_repo.split("/", 1) update_repo = f"${{EL_HOME_ODOO_PROJECT}}/" + lst_path[1] # str_repo = ( diff --git a/script/odoo/migration/README.md b/script/odoo/migration/README.md index 105e6e6..c4892c1 100644 --- a/script/odoo/migration/README.md +++ b/script/odoo/migration/README.md @@ -3,7 +3,7 @@ Run this script when doing database migration. Example : ```bash -source ./.venv.odoo15.0_python3.8.20/bin/activate && cat ./script/migration/fix_migration_odoo140_to_odoo150.py | ./odoo15.0/odoo/odoo-bin shell -d DATABASE +source ./.venv.odoo15.0_python3.8.20/bin/activate && cat ./script/odoo/migration/fix_migration_odoo140_to_odoo150.py | ./odoo15.0/odoo/odoo-bin shell -d DATABASE ``` Check [uninstall_module_list_odoo140_to_odoo150.txt](uninstall_module_list_odoo140_to_odoo150.txt) diff --git a/script/todo/todo.py b/script/todo/todo.py index a9f4b26..b91abdc 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -206,6 +206,7 @@ class TODO: [5] Doc - Recherche de documentation [6] Database - Outils sur les bases de données [7] Process - Outils sur les exécutions +[8] Config - Traitement du fichier de configuration [0] Retour """ while True: @@ -241,6 +242,10 @@ class TODO: status = self.prompt_execute_process() if status is not False: return + elif status == "8": + status = self.prompt_execute_config() + if status is not False: + return else: print("Commande non trouvée 🤖!") @@ -436,10 +441,10 @@ class TODO: if callback: callback(dct_instance) - def fill_help_info(self, lst_instance): + def fill_help_info(self, lst_choice): help_info = "Commande :\n" help_end = "[0] Retour\n" - for i, dct_instance in enumerate(lst_instance): + for i, dct_instance in enumerate(lst_choice): help_info += ( f"[{i + 1}] " + dct_instance["prompt_description"] + "\n" ) @@ -450,16 +455,16 @@ class TODO: # TODO proposer le déploiement à distance # TODO proposer l'exécution de docker # TODO proposer la création de docker - lst_instance = self.config_file.get_config("instance") - init_len = len(lst_instance) + lst_choice = self.config_file.get_config("instance") + init_len = len(lst_choice) if os.path.exists(MOBILE_HOME_PATH): dct_upgrade_odoo_database = { "prompt_description": "Mobile - Compile and run software", "callback": self.callback_make_mobile_home, } - lst_instance.append(dct_upgrade_odoo_database) - help_info = self.fill_help_info(lst_instance) + lst_choice.append(dct_upgrade_odoo_database) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) @@ -475,15 +480,15 @@ class TODO: status = click.confirm( "Voulez-vous une nouvelle instance?" ) - dct_instance = lst_instance[int_cmd - 1] + dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration( dct_instance, exec_run_db=True, ignore_makefile=not bool(status), ) - elif int_cmd <= len(lst_instance): + elif int_cmd <= len(lst_choice): # Execute dynamic instance - dct_instance = lst_instance[int_cmd - 1] + dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration( dct_instance, ) @@ -493,8 +498,8 @@ class TODO: print("Commande non trouvée 🤖!") def prompt_execute_fonction(self): - lst_instance = self.config_file.get_config("function") - help_info = self.fill_help_info(lst_instance) + lst_choice = self.config_file.get_config("function") + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) @@ -505,9 +510,9 @@ class TODO: cmd_no_found = True try: int_cmd = int(status) - if 0 < int_cmd <= len(lst_instance): + if 0 < int_cmd <= len(lst_choice): cmd_no_found = False - dct_instance = lst_instance[int_cmd - 1] + dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration(dct_instance) except ValueError: pass @@ -525,34 +530,34 @@ class TODO: # TODO faire la mise à jour de ERPLibre # TODO faire l'upgrade d'un odoo vers un autre - lst_instance = self.config_file.get_config("update_from_makefile") + lst_choice = self.config_file.get_config("update_from_makefile") dct_upgrade_odoo_database = { "prompt_description": "Upgrade Odoo - Migration Database", } - lst_instance.append(dct_upgrade_odoo_database) + lst_choice.append(dct_upgrade_odoo_database) dct_upgrade_poetry = { "prompt_description": "Upgrade Poetry - Dependency of Odoo", } - lst_instance.append(dct_upgrade_poetry) - help_info = self.fill_help_info(lst_instance) + lst_choice.append(dct_upgrade_poetry) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) print() if status == "0": return False - elif status == str(len(lst_instance) - 1): + elif status == str(len(lst_choice) - 1): upgrade = todo_upgrade.TodoUpgrade(self) upgrade.execute_odoo_upgrade() - elif status == str(len(lst_instance)): + elif status == str(len(lst_choice)): self.upgrade_poetry() else: cmd_no_found = True try: int_cmd = int(status) - 1 - if 0 < int_cmd <= len(lst_instance): + if 0 < int_cmd <= len(lst_choice): cmd_no_found = False - dct_instance = lst_instance[int_cmd - 1] + dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration(dct_instance) except ValueError: pass @@ -573,27 +578,27 @@ class TODO: # [0] Retour # """ - lst_instance = self.config_file.get_config("code_from_makefile") + lst_choice = self.config_file.get_config("code_from_makefile") dct_upgrade_odoo_database = { "prompt_description": "Upgrade Module", } - lst_instance.append(dct_upgrade_odoo_database) - help_info = self.fill_help_info(lst_instance) + lst_choice.append(dct_upgrade_odoo_database) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) print() if status == "0": return False - elif status == str(len(lst_instance)): + elif status == str(len(lst_choice)): self.upgrade_module() else: cmd_no_found = True try: int_cmd = int(status) - if 0 < int_cmd <= len(lst_instance): + if 0 < int_cmd <= len(lst_choice): cmd_no_found = False - dct_instance = lst_instance[int_cmd - 1] + dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration(dct_instance) except ValueError: pass @@ -602,13 +607,13 @@ class TODO: def prompt_execute_doc(self): print("🤖 Vous cherchez de la documentation?") - lst_instance = [ + lst_choice = [ {"prompt_description": "Migration module coverage"}, {"prompt_description": "What change between version"}, {"prompt_description": "OCA guidelines"}, {"prompt_description": "OCA migration Odoo 19 milestone"}, ] - help_info = self.fill_help_info(lst_instance) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) @@ -651,13 +656,13 @@ class TODO: def prompt_execute_database(self): print("🤖 Faites des modifications sur les bases de données!") - lst_instance = [ + lst_choice = [ { "prompt_description": "Download database to create backup (.zip)" }, {"prompt_description": "Restore from backup (.zip)"}, ] - help_info = self.fill_help_info(lst_instance) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) @@ -673,10 +678,10 @@ class TODO: def prompt_execute_process(self): print("🤖 Manipuler les processus d'exécution!") - lst_instance = [ + lst_choice = [ {"prompt_description": "Kill process from actual port"}, ] - help_info = self.fill_help_info(lst_instance) + help_info = self.fill_help_info(lst_choice) while True: status = click.prompt(help_info) @@ -688,6 +693,122 @@ class TODO: else: print("Commande non trouvée 🤖!") + def prompt_execute_config(self): + print("🤖 Manipuler la configuration ERPLibre et Odoo!") + lst_choice = [ + {"prompt_description": "Generate all configuration"}, + {"prompt_description": "Generate from pre-configuration"}, + {"prompt_description": "Generate from backup file"}, + {"prompt_description": "Generate from database"}, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + self.generate_config() + elif status == "2": + self.generate_config_from_preconfiguration() + elif status == "3": + self.generate_config_from_backup() + elif status == "4": + self.generate_config_from_database() + else: + print("Commande non trouvée 🤖!") + + def generate_config(self, add_arg=None): + # Repeating to get all item before get group + cmd = ( + f"./script/git/git_repo_update_group.py;" + f"./script/generate_config.sh" + ) + if add_arg: + cmd += ( + f";./script/git/git_repo_update_group.py {add_arg};" + f"./script/generate_config.sh" + ) + self.execute.exec_command_live( + cmd, + source_erplibre=False, + single_source_erplibre=True, + ) + + def generate_config_from_preconfiguration(self): + lst_choice = [ + {"prompt_description": "base"}, + {"prompt_description": "base + code_generator"}, + {"prompt_description": "base + image_db"}, + {"prompt_description": "all"}, + # {"prompt_description": "base + migration"}, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + group = "base" + str_group = f"--group {group}" + self.generate_config(add_arg=str_group) + elif status == "2": + group = "base,code_generator" + str_group = f"--group {group}" + self.generate_config(add_arg=str_group) + elif status == "3": + group = "base,image_db" + str_group = f"--group {group}" + self.generate_config(add_arg=str_group) + elif status == "4": + self.generate_config() + # elif status == "5": + # group = "base,migration" + # str_group = f"--group {group}" + # self.generate_config(add_arg=str_group) + else: + print("Commande non trouvée 🤖!") + + def generate_config_from_backup(self): + file_name = self.open_file_image_db() + add_arg = f"--from_backup_name {file_name} --add_repo odoo18.0/addons/MathBenTech_development" + self.generate_config(add_arg=add_arg) + + def generate_config_from_database(self): + cmd_server = f"./odoo_bin.sh db --list" + status, output = self.execute.exec_command_live( + cmd_server, + return_status_and_output=True, + source_erplibre=False, + single_source_erplibre=True, + ) + lst_choice = [{"prompt_description": a.strip()} for a in output] + + help_info = self.fill_help_info(lst_choice) + + lst_str_choice = [str(a) for a in range(len(lst_choice) + 1) if a] + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status in lst_str_choice: + database_name = output[int(status) - 1].strip() + print(database_name) + str_arg = f"--database {database_name}" + self.generate_config(add_arg=str_arg) + return False + else: + print("Commande non trouvée 🤖!") + # file_name = self.open_file_image_db() + # add_arg = f"--from_backup_name {file_name} --add_repo odoo18.0/addons/MathBenTech_development" + # + # self.generate_config(add_arg=add_arg) + def get_odoo_version(self): with open(VERSION_DATA_FILE) as txt: data_version = json.load(txt) @@ -899,14 +1020,7 @@ class TODO: if status == "1": file_name = status else: - self.dir_path = "" - - file_browser = todo_file_browser.FileBrowser( - path_image_db, self.on_dir_selected - ) - file_browser.run_main_frame() - file_name = os.path.basename(self.dir_path) - print(file_name) + file_name = self.open_file_image_db database_name = input("💬 Database name : ") if not database_name: @@ -943,6 +1057,19 @@ class TODO: source_erplibre=False, ) + def open_file_image_db(self): + self.dir_path = "" + path_image_db = os.path.join(os.getcwd(), "image_db") + + # self.dir_path is over-write into on_dir_selected + file_browser = todo_file_browser.FileBrowser( + path_image_db, self.on_dir_selected + ) + file_browser.run_main_frame() + file_name = os.path.basename(self.dir_path) + print(file_name) + return file_name + def process_kill_from_port(self): cfg = configparser.ConfigParser() cfg.read("./config.conf")