Compare commits

...

5 Commits

Author SHA1 Message Date
Simon Maillard
ba4274dea4 [IMP] shell command: use ipython as python shell 2024-12-12 10:53:37 +00:00
Simon Maillard
0ba79d59ba [IMP] code layout 2024-12-12 10:40:10 +00:00
Simon Maillard
8b75c94d22 [FIX] Mount volume in docker need absolute path
Fixes

E       docker.errors.APIError: 400 Client Error for http://dind:2375/v1.47/containers/create?name=oow-test-cli-database_test_cli___shell-14.0-step-01: Bad Request ("create .: volume name is too short, names should be at least two alphanumeric characters")
/root/.cache/pypoetry/virtualenvs/odoo-openupgrade-wizard-0zCHEn-Y-py3.9/lib/python3.9/site-packages/docker/errors.py:39: APIError
2024-12-09 09:40:34 +00:00
Simon Maillard
5a854287c7 [ADD] Add shell command
To jump in an odoo shell on an instance already started using odoo run
2024-12-09 09:09:53 +00:00
Simon Maillard
cad882d58b [IMP] split generate_odoo_command
In 2 functions:

-  generate_odoo_command_options
-  generate_odoo_command

To be able to use generate_odoo_command_options from other places
2024-12-05 08:01:00 +00:00
6 changed files with 255 additions and 42 deletions

View File

@ -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)

View 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)

View File

@ -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

View File

@ -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(

View File

@ -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

View 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)"',
]
)