odoo-openupgrade-wizard/odoo_openupgrade_wizard/tools/tools_odoo.py
2024-04-30 15:10:43 +02:00

384 lines
12 KiB
Python

import configparser
import csv
import os
import sys
import traceback
from pathlib import Path
import yaml
from loguru import logger
from odoo_openupgrade_wizard.configuration_version_dependant import (
get_base_module_folder,
get_manifest_name,
get_odoo_folder,
get_odoo_run_command,
get_server_wide_modules_upgrade,
skip_addon_path,
)
from odoo_openupgrade_wizard.tools.tools_docker import (
kill_container,
run_container,
)
from odoo_openupgrade_wizard.tools.tools_postgres import get_postgres_container
from odoo_openupgrade_wizard.tools.tools_system import get_script_folder
DEFAULT_ODOO_HTTP_PORT = 8069
def get_repo_file_path(ctx, odoo_version: float) -> Path:
"""return the relative path of the repos.yml file
of a given odoo version"""
repo_file = False
# Check if submodule path exists
version_cfg = (
ctx.obj["config"]["odoo_version_settings"][odoo_version] or {}
)
submodule_path = get_odoo_env_path(ctx, odoo_version) / "repo_submodule"
if submodule_path.exists():
repo_file = submodule_path / version_cfg["repo_file_path"]
if repo_file.exists():
return repo_file
else:
logger.warning(f"Unable to find the repo file {repo_file}.")
repo_file = get_odoo_env_path(ctx, odoo_version) / Path("repos.yml")
if not repo_file.exists():
raise Exception(f"Unable to find the repo file {repo_file}.")
return repo_file
def get_odoo_addons_path(
ctx,
odoo_env_path: Path,
migration_step: dict,
execution_context: str = False,
) -> str:
"""Return
- addons_path: a list of Path of that contains odoo module
for the current migration_step,
based on the analysis of the repos.yml file
- empty_addons_path: a list of Path of empty folders.
(without any odoo module)"""
repo_file = get_repo_file_path(ctx, migration_step["version"])
base_module_folder = get_base_module_folder(migration_step)
stream = open(repo_file, "r")
data = yaml.safe_load(stream)
data = data
addons_path = []
empty_addons_path = []
odoo_folder = get_odoo_folder(migration_step, execution_context)
for key in data.keys():
path = Path(key)
if str(path).endswith(odoo_folder):
# Add two folder for odoo folder
addons_path.append(path / Path("addons"))
addons_path.append(
path / Path(base_module_folder) / Path("addons")
)
elif skip_addon_path(migration_step, path):
pass
elif is_addons_path(ctx, odoo_env_path / path, migration_step):
addons_path.append(path)
else:
empty_addons_path.append(path)
return addons_path, empty_addons_path
def is_addons_path(ctx, path: Path, migration_step: dict):
for folder in [x for x in path.iterdir() if x.is_dir()]:
if (folder / "__init__.py").exists() and (
folder / get_manifest_name(migration_step)
).exists():
return True
return False
def get_odoo_env_path(ctx, odoo_version: float) -> Path:
folder_name = "env_%s" % str(odoo_version).rjust(4, "0")
return ctx.obj["src_folder_path"] / folder_name
def get_docker_image_tag(ctx, odoo_version: float) -> str:
"""Return a docker image tag, based on project name and odoo version"""
return "odoo-openupgrade-wizard-image__%s__%s" % (
ctx.obj["config"]["project_name"],
str(odoo_version).rjust(4, "0"),
)
def get_docker_container_name(ctx, database: str, migration_step: dict) -> str:
"""Return a docker container name, based on project name, database name,
odoo version and migration step"""
return "oow-{project}-{database}-{version}-step-{step}".format(
project=ctx.obj["config"]["project_name"],
database=database,
# FIXME: version should be a string, but it is a float
version=str(migration_step["version"]).rjust(4, "0"),
step=str(migration_step["name"]).rjust(2, "0"),
)
def generate_odoo_command(
ctx,
migration_step: dict,
execution_context: str,
database: str,
demo: bool = False,
update: str = False,
init: str = False,
stop_after_init: bool = False,
shell: bool = False,
) -> str:
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
# Compute 'server_wide_modules'
# For that purpose, read the custom odoo.conf file
# to know if server_wide_modules is defined
custom_odoo_config_file = odoo_env_path / "odoo.conf"
parser = configparser.RawConfigParser()
parser.read(custom_odoo_config_file)
server_wide_modules = parser.get(
"options", "server_wide_modules", fallback=[]
)
server_wide_modules += get_server_wide_modules_upgrade(
migration_step, execution_context
)
# compute 'addons_path'
addons_path_list, empty_addons_path_list = get_odoo_addons_path(
ctx, odoo_env_path, migration_step, execution_context
)
addons_path = ",".join(
[str(Path("/odoo_env") / x) for x in addons_path_list]
)
for empty_addons_path in empty_addons_path_list:
logger.info(
"Skipping addons path"
f" '{(odoo_env_path / empty_addons_path).resolve()}'"
" because it doesn't contain any odoo module."
)
# compute 'log_file'
log_file_name = "{}____{}.log".format(
ctx.obj["log_prefix"], migration_step["complete_name"]
)
log_file_docker_path = "/env/log/%s" % log_file_name
database_cmd = database and "--database %s" % database or ""
load_cmd = (
server_wide_modules
and "--load %s" % ",".join(server_wide_modules)
or ""
)
update_cmd = update and "--update %s" % update or ""
init_cmd = init and "--init %s" % init or ""
stop_after_init_cmd = stop_after_init and "--stop-after-init" or ""
shell_cmd = shell and "shell" or ""
demo_cmd = not demo and "--without-demo all" or ""
command = (
Path("/odoo_env")
/ Path(get_odoo_folder(migration_step, execution_context))
/ Path(get_odoo_run_command(migration_step))
)
result = (
f" {command}"
f" {shell_cmd}"
f" --config=/odoo_env/odoo.conf"
f" --data-dir=/env/filestore/"
f" --addons-path={addons_path}"
f" --logfile={log_file_docker_path}"
f" --db_host=db"
f" --db_port=5432"
f" --db_user=odoo"
f" --db_password=odoo"
f" --workers=0"
f" {demo_cmd}"
f" {load_cmd}"
f" {database_cmd}"
f" {update_cmd}"
f" {init_cmd}"
f" {stop_after_init_cmd}"
)
return result
def run_odoo(
ctx,
migration_step: dict,
detached_container: bool = False,
database: str = False,
update: str = False,
init: str = False,
stop_after_init: bool = False,
shell: bool = False,
demo: bool = False,
execution_context: str = False,
alternative_xml_rpc_port: int = False,
links: dict = {},
publish_ports: bool = False,
):
# Ensure that Postgres container exist
get_postgres_container(ctx)
logger.info(
"Launching Odoo Container (Version {version}) for {db_text}"
" in {execution_context} mode. Demo Data is {demo_text}"
" {stop_text} {init_text} {update_text}".format(
version=migration_step["version"],
db_text=database and "database '%s'" % database or "any databases",
execution_context=execution_context
or migration_step["execution_context"],
demo_text=demo and "enabled" or "disabled",
stop_text=stop_after_init and " (stop-after-init)" or "",
init_text=init and " (Init : %s)" % init or "",
update_text=update and " (Update : %s)" % update or "",
)
)
command = generate_odoo_command(
ctx,
migration_step,
execution_context,
database,
demo=demo,
update=update,
init=init,
stop_after_init=stop_after_init,
shell=shell,
)
return run_container_odoo(
ctx,
migration_step,
command,
detached_container=detached_container,
database=database,
execution_context=execution_context,
alternative_xml_rpc_port=alternative_xml_rpc_port,
links=links,
publish_ports=publish_ports,
)
def run_container_odoo(
ctx,
migration_step: dict,
command: str,
detached_container: bool = False,
database: str = False,
alternative_xml_rpc_port: int = False,
execution_context: str = False,
links: dict = {},
publish_ports: bool = False,
):
env_path = ctx.obj["env_folder_path"]
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
host_xmlrpc_port = (
alternative_xml_rpc_port
and alternative_xml_rpc_port
or ctx.obj["config"]["odoo_host_xmlrpc_port"]
)
links.update({ctx.obj["config"]["postgres_container_name"]: "db"})
if publish_ports:
ports = {host_xmlrpc_port: DEFAULT_ODOO_HTTP_PORT}
else:
ports = {}
return run_container(
get_docker_image_tag(ctx, migration_step["version"]),
get_docker_container_name(ctx, database, migration_step),
command=command,
ports=ports,
volumes={
env_path: "/env/",
odoo_env_path: "/odoo_env/",
},
links=links,
detach=detached_container,
auto_remove=True,
)
def kill_odoo(ctx, database, migration_step: dict):
kill_container(get_docker_container_name(ctx, database, migration_step))
def execute_click_odoo_python_files(
ctx,
database: str,
migration_step: dict,
python_files: list = [],
execution_context: str = False,
):
if not python_files:
# Get post-migration python scripts to execute
script_folder = get_script_folder(ctx, migration_step)
python_files = [
Path("scripts") / Path(migration_step["complete_name"]) / Path(f)
for f in os.listdir(script_folder)
if os.path.isfile(os.path.join(script_folder, f))
and f[-3:] == ".py"
]
python_files = sorted(python_files)
command = generate_odoo_command(
ctx,
migration_step,
execution_context,
database,
shell=True,
)
for python_file in python_files:
command = ("/bin/bash -c 'cat /env/{python_file} | {command}'").format(
command=command,
python_file=str(python_file),
)
try:
logger.info(
f"Step {migration_step['complete_name']}."
f" Executing script {python_file} ..."
)
return run_container_odoo(
ctx,
migration_step,
command,
detached_container=False,
database=database,
)
except Exception as e:
traceback.print_exc()
logger.error(
"An error occured. Exiting. %s\n%s"
% (e, traceback.print_exception(*sys.exc_info()))
)
raise e
finally:
kill_odoo(ctx, database, migration_step)
def get_odoo_modules_from_csv(module_file_path: Path) -> list:
logger.debug(f"Reading '{module_file_path}' file ...")
module_names = []
csvfile = open(module_file_path, "r")
spamreader = csv.reader(csvfile, delimiter=",", quotechar='"')
for row in spamreader:
# Try to guess that a line is not correct
if not row:
continue
if not row[0]:
continue
if " " in row[0]:
continue
if any([x.isupper() for x in row[0]]):
continue
module_names.append(row[0])
return module_names