Compare commits
5 Commits
main
...
add_shell_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba4274dea4 | ||
|
|
0ba79d59ba | ||
|
|
8b75c94d22 | ||
|
|
5a854287c7 | ||
|
|
cad882d58b |
|
|
@ -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_restoredb import restoredb
|
||||
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.tools.tools_system import ensure_folder_exists
|
||||
|
||||
|
|
@ -166,4 +167,5 @@ main.add_command(install_from_csv)
|
|||
main.add_command(psql)
|
||||
main.add_command(pull_submodule)
|
||||
main.add_command(run)
|
||||
main.add_command(shell)
|
||||
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'
|
||||
odoorpc
|
||||
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,
|
||||
migration_step: dict,
|
||||
execution_context: str,
|
||||
|
|
@ -130,13 +130,13 @@ def generate_odoo_command(
|
|||
update: str = False,
|
||||
init: str = False,
|
||||
stop_after_init: bool = False,
|
||||
shell: bool = False,
|
||||
) -> str:
|
||||
) -> list:
|
||||
"""
|
||||
Generate Odoo command options as a string to append to any command.
|
||||
"""
|
||||
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)
|
||||
|
|
@ -147,7 +147,7 @@ def generate_odoo_command(
|
|||
migration_step, execution_context
|
||||
)
|
||||
|
||||
# compute 'addons_path'
|
||||
# Compute 'addons_path'
|
||||
addons_path_list, empty_addons_path_list = get_odoo_addons_path(
|
||||
ctx, odoo_env_path, migration_step, execution_context
|
||||
)
|
||||
|
|
@ -161,49 +161,72 @@ def generate_odoo_command(
|
|||
" because it doesn't contain any odoo module."
|
||||
)
|
||||
|
||||
# compute 'log_file'
|
||||
log_file_name = "{}____{}.log".format(
|
||||
ctx.obj["log_prefix"], migration_step["complete_name"]
|
||||
# Compute 'log_file'
|
||||
log_file_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 ""
|
||||
load_cmd = (
|
||||
server_wide_modules
|
||||
and "--load %s" % ",".join(server_wide_modules)
|
||||
or ""
|
||||
# Build options string
|
||||
options = [
|
||||
"--config=/odoo_env/odoo.conf",
|
||||
"--data-dir=/env/filestore/",
|
||||
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 ""
|
||||
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 = (
|
||||
|
||||
base_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
|
||||
options_as_string = " ".join(options)
|
||||
if shell:
|
||||
return f"{base_command} shell {options_as_string}"
|
||||
else:
|
||||
return f"{base_command} {options_as_string}"
|
||||
|
||||
|
||||
def run_odoo(
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ def cli_runner_invoke(cmd, expect_success=True):
|
|||
|
||||
|
||||
def build_ctx_from_config_file() -> dict:
|
||||
env_folder_path = Path(".")
|
||||
env_folder_path = Path(".").absolute()
|
||||
|
||||
class context:
|
||||
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