Merge branch 'add_shell_command' into 'main'
Draft: [ADD] Add shell command See merge request odoo-openupgrade-wizard/odoo-openupgrade-wizard!105
This commit is contained in:
commit
cd62659375
|
|
@ -31,6 +31,7 @@ from odoo_openupgrade_wizard.cli.cli_psql import psql
|
||||||
from odoo_openupgrade_wizard.cli.cli_pull_submodule import pull_submodule
|
from odoo_openupgrade_wizard.cli.cli_pull_submodule import pull_submodule
|
||||||
from odoo_openupgrade_wizard.cli.cli_restoredb import restoredb
|
from odoo_openupgrade_wizard.cli.cli_restoredb import restoredb
|
||||||
from odoo_openupgrade_wizard.cli.cli_run import run
|
from odoo_openupgrade_wizard.cli.cli_run import run
|
||||||
|
from odoo_openupgrade_wizard.cli.cli_shell import shell
|
||||||
from odoo_openupgrade_wizard.cli.cli_upgrade import upgrade
|
from odoo_openupgrade_wizard.cli.cli_upgrade import upgrade
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_exists
|
from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_exists
|
||||||
|
|
||||||
|
|
@ -166,4 +167,5 @@ main.add_command(install_from_csv)
|
||||||
main.add_command(psql)
|
main.add_command(psql)
|
||||||
main.add_command(pull_submodule)
|
main.add_command(pull_submodule)
|
||||||
main.add_command(run)
|
main.add_command(run)
|
||||||
|
main.add_command(shell)
|
||||||
main.add_command(upgrade)
|
main.add_command(upgrade)
|
||||||
|
|
|
||||||
108
odoo_openupgrade_wizard/cli/cli_shell.py
Normal file
108
odoo_openupgrade_wizard/cli/cli_shell.py
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import click
|
||||||
|
import docker
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.cli.cli_options import (
|
||||||
|
database_option_required,
|
||||||
|
get_migration_step_from_options,
|
||||||
|
step_option,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
||||||
|
generate_odoo_command_options,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@step_option
|
||||||
|
@database_option_required
|
||||||
|
@click.option(
|
||||||
|
"--code",
|
||||||
|
default=None,
|
||||||
|
help="Python code to execute in the Odoo shell. "
|
||||||
|
"Example: 'print(env.user.name)'",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def shell(ctx, step, database, code):
|
||||||
|
"""Run an Odoo shell in the running Odoo container."""
|
||||||
|
|
||||||
|
migration_step = get_migration_step_from_options(ctx, step)
|
||||||
|
ensure_database(ctx, database, state="present")
|
||||||
|
|
||||||
|
config = ctx.obj.get("config", {})
|
||||||
|
project_name = config.get("project_name")
|
||||||
|
if not project_name:
|
||||||
|
click.echo("Unable to find the project name.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
expected_container_name_prefix = f"oow-{project_name}-{database}"
|
||||||
|
|
||||||
|
# Connect to the Docker daemon
|
||||||
|
docker_client = docker.from_env()
|
||||||
|
|
||||||
|
# List all running containers
|
||||||
|
running_containers = docker_client.containers.list(
|
||||||
|
filters={"status": "running"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find the running Odoo container
|
||||||
|
matching_container = next(
|
||||||
|
(
|
||||||
|
c
|
||||||
|
for c in running_containers
|
||||||
|
if expected_container_name_prefix in c.name and "step" in c.name
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not matching_container:
|
||||||
|
logger.error(
|
||||||
|
"No running Odoo container found. "
|
||||||
|
"Please run oow run before running the shell."
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info(f"Execute shell in {matching_container.name} container...")
|
||||||
|
|
||||||
|
common_options = generate_odoo_command_options(
|
||||||
|
ctx, migration_step, "regular", database
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the docker exec command
|
||||||
|
command = [
|
||||||
|
"docker",
|
||||||
|
"exec",
|
||||||
|
"-i", # Interactive mode to enable stdin
|
||||||
|
matching_container.name,
|
||||||
|
"/odoo_env/src/odoo/odoo-bin",
|
||||||
|
"shell",
|
||||||
|
"--no-http",
|
||||||
|
"--shell-interface=iptyhon",
|
||||||
|
] + common_options
|
||||||
|
|
||||||
|
logger.info(f"Command: {' '.join(command)}")
|
||||||
|
|
||||||
|
# If code is provided, send it via stdin
|
||||||
|
if code:
|
||||||
|
logger.info(f"Executing code: {code}")
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True, # Enables text mode
|
||||||
|
)
|
||||||
|
stdout, stderr = process.communicate(code)
|
||||||
|
if process.returncode != 0:
|
||||||
|
logger.error(f"Error executing code: {stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
click.echo(stdout)
|
||||||
|
else:
|
||||||
|
# Interactive session requires TTY
|
||||||
|
command.insert(2, "-t")
|
||||||
|
os.execvp(command[0], command)
|
||||||
|
|
@ -7,3 +7,4 @@ git+https://github.com/OCA/openupgradelib@master#egg=openupgradelib
|
||||||
# dependencies of the module OCA/server-tools 'upgrade_analysis'
|
# dependencies of the module OCA/server-tools 'upgrade_analysis'
|
||||||
odoorpc
|
odoorpc
|
||||||
mako
|
mako
|
||||||
|
ipython # used by default by shell command
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ def get_docker_container_name(ctx, database: str, migration_step: dict) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_odoo_command(
|
def generate_odoo_command_options(
|
||||||
ctx,
|
ctx,
|
||||||
migration_step: dict,
|
migration_step: dict,
|
||||||
execution_context: str,
|
execution_context: str,
|
||||||
|
|
@ -130,13 +130,13 @@ def generate_odoo_command(
|
||||||
update: str = False,
|
update: str = False,
|
||||||
init: str = False,
|
init: str = False,
|
||||||
stop_after_init: bool = False,
|
stop_after_init: bool = False,
|
||||||
shell: bool = False,
|
) -> list:
|
||||||
) -> str:
|
"""
|
||||||
|
Generate Odoo command options as a string to append to any command.
|
||||||
|
"""
|
||||||
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
|
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
|
||||||
|
|
||||||
# Compute 'server_wide_modules'
|
# 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"
|
custom_odoo_config_file = odoo_env_path / "odoo.conf"
|
||||||
parser = configparser.RawConfigParser()
|
parser = configparser.RawConfigParser()
|
||||||
parser.read(custom_odoo_config_file)
|
parser.read(custom_odoo_config_file)
|
||||||
|
|
@ -147,7 +147,7 @@ def generate_odoo_command(
|
||||||
migration_step, execution_context
|
migration_step, execution_context
|
||||||
)
|
)
|
||||||
|
|
||||||
# compute 'addons_path'
|
# Compute 'addons_path'
|
||||||
addons_path_list, empty_addons_path_list = get_odoo_addons_path(
|
addons_path_list, empty_addons_path_list = get_odoo_addons_path(
|
||||||
ctx, odoo_env_path, migration_step, execution_context
|
ctx, odoo_env_path, migration_step, execution_context
|
||||||
)
|
)
|
||||||
|
|
@ -161,49 +161,72 @@ def generate_odoo_command(
|
||||||
" because it doesn't contain any odoo module."
|
" because it doesn't contain any odoo module."
|
||||||
)
|
)
|
||||||
|
|
||||||
# compute 'log_file'
|
# Compute 'log_file'
|
||||||
log_file_name = "{}____{}.log".format(
|
log_file_name = (
|
||||||
ctx.obj["log_prefix"], migration_step["complete_name"]
|
f"{ctx.obj['log_prefix']}____{migration_step['complete_name']}.log"
|
||||||
)
|
)
|
||||||
log_file_docker_path = "/env/log/%s" % log_file_name
|
log_file_docker_path = f"/env/log/{log_file_name}"
|
||||||
|
|
||||||
database_cmd = database and "--database %s" % database or ""
|
# Build options string
|
||||||
load_cmd = (
|
options = [
|
||||||
server_wide_modules
|
"--config=/odoo_env/odoo.conf",
|
||||||
and "--load %s" % ",".join(server_wide_modules)
|
"--data-dir=/env/filestore/",
|
||||||
or ""
|
f"--addons-path={addons_path}",
|
||||||
|
f"--logfile={log_file_docker_path}",
|
||||||
|
"--db_host=db",
|
||||||
|
"--db_port=5432",
|
||||||
|
"--db_user=odoo",
|
||||||
|
"--db_password=odoo",
|
||||||
|
"--workers=0",
|
||||||
|
f"{'--without-demo=all' if not demo else ''}",
|
||||||
|
f"{'--load ' + ','.join(server_wide_modules) if server_wide_modules else ''}", # noqa
|
||||||
|
f"{'--database=' + database if database else ''}",
|
||||||
|
f"{'--update ' + update if update else ''}",
|
||||||
|
f"{'--init ' + init if init else ''}",
|
||||||
|
f"{'--stop-after-init' if stop_after_init else ''}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# remove empty strings
|
||||||
|
return [x for x in options if x]
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Generate the full Odoo command using options from
|
||||||
|
generate_odoo_command_options.
|
||||||
|
"""
|
||||||
|
options = generate_odoo_command_options(
|
||||||
|
ctx,
|
||||||
|
migration_step,
|
||||||
|
execution_context,
|
||||||
|
database,
|
||||||
|
demo,
|
||||||
|
update,
|
||||||
|
init,
|
||||||
|
stop_after_init,
|
||||||
)
|
)
|
||||||
update_cmd = update and "--update %s" % update or ""
|
|
||||||
init_cmd = init and "--init %s" % init or ""
|
base_command = (
|
||||||
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("/odoo_env")
|
||||||
/ Path(get_odoo_folder(migration_step, execution_context))
|
/ Path(get_odoo_folder(migration_step, execution_context))
|
||||||
/ Path(get_odoo_run_command(migration_step))
|
/ Path(get_odoo_run_command(migration_step))
|
||||||
)
|
)
|
||||||
|
|
||||||
result = (
|
options_as_string = " ".join(options)
|
||||||
f" {command}"
|
if shell:
|
||||||
f" {shell_cmd}"
|
return f"{base_command} shell {options_as_string}"
|
||||||
f" --config=/odoo_env/odoo.conf"
|
else:
|
||||||
f" --data-dir=/env/filestore/"
|
return f"{base_command} {options_as_string}"
|
||||||
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(
|
def run_odoo(
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ def cli_runner_invoke(cmd, expect_success=True):
|
||||||
|
|
||||||
|
|
||||||
def build_ctx_from_config_file() -> dict:
|
def build_ctx_from_config_file() -> dict:
|
||||||
env_folder_path = Path(".")
|
env_folder_path = Path(".").absolute()
|
||||||
|
|
||||||
class context:
|
class context:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
79
tests/cli_33_shell_test.py
Normal file
79
tests/cli_33_shell_test.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from pytest import raises
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.cli.cli_options import (
|
||||||
|
get_migration_step_from_options,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.tools.tools_odoo import run_odoo
|
||||||
|
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
build_ctx_from_config_file,
|
||||||
|
cli_runner_invoke,
|
||||||
|
move_to_test_folder,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_shell():
|
||||||
|
move_to_test_folder()
|
||||||
|
ctx = build_ctx_from_config_file()
|
||||||
|
migration_step = get_migration_step_from_options(ctx, 1)
|
||||||
|
|
||||||
|
# Ensure environment is clean
|
||||||
|
db_name = "database_test_cli___shell"
|
||||||
|
ensure_database(ctx, db_name, state="absent")
|
||||||
|
dest_filestore_path = pathlib.Path(f"./filestore/filestore/{db_name}")
|
||||||
|
shutil.rmtree(dest_filestore_path, ignore_errors=True)
|
||||||
|
|
||||||
|
# Set the log prefix
|
||||||
|
ctx.obj["log_prefix"] = "test_cli_shell"
|
||||||
|
|
||||||
|
# Initialize the database
|
||||||
|
stop_after_init = False
|
||||||
|
run_odoo(
|
||||||
|
ctx,
|
||||||
|
migration_step,
|
||||||
|
database=db_name,
|
||||||
|
detached_container=not stop_after_init,
|
||||||
|
stop_after_init=stop_after_init,
|
||||||
|
init="base",
|
||||||
|
execution_context="regular",
|
||||||
|
publish_ports=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that a simple script executes successfully
|
||||||
|
result = cli_runner_invoke(
|
||||||
|
[
|
||||||
|
"shell",
|
||||||
|
f"--database={db_name}",
|
||||||
|
"--step=1",
|
||||||
|
'--code="print("Hello, World!")"',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Hello, World!" in result.output
|
||||||
|
|
||||||
|
# Test with a script that queries a model
|
||||||
|
result = cli_runner_invoke(
|
||||||
|
[
|
||||||
|
"shell",
|
||||||
|
f"--database={db_name}",
|
||||||
|
"--step=1",
|
||||||
|
"--code=\"print(env['res.partner'].search_count([]))\"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output.strip().isdigit() # Should return a number
|
||||||
|
|
||||||
|
# Test with missing database
|
||||||
|
with raises(Exception):
|
||||||
|
cli_runner_invoke(
|
||||||
|
[
|
||||||
|
"shell",
|
||||||
|
"--database=nonexistent_database",
|
||||||
|
"--step=1",
|
||||||
|
'--script="print(env.user.name)"',
|
||||||
|
]
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue
Block a user