Merge branch 'ADD-restoredb' into 'main'

[ADD] restoredb function

See merge request odoo-openupgrade-wizard/odoo-openupgrade-wizard!40
This commit is contained in:
Rémy Taymans 2024-03-03 08:25:39 +00:00
commit 567ffd4428
10 changed files with 207 additions and 9 deletions

View File

@ -28,6 +28,7 @@ from odoo_openupgrade_wizard.cli.cli_init import init
from odoo_openupgrade_wizard.cli.cli_install_from_csv import install_from_csv
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_upgrade import upgrade
from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_exists
@ -114,6 +115,7 @@ def main(ctx, env_folder, filestore_folder, log_level):
main.add_command(copydb)
main.add_command(restoredb)
main.add_command(docker_build)
main.add_command(dropdb)
main.add_command(dumpdb)

View File

@ -0,0 +1,78 @@
from pathlib import Path
import click
from odoo_openupgrade_wizard.cli.cli_options import database_option_required
from odoo_openupgrade_wizard.tools.tools_postgres import execute_pg_restore
from odoo_openupgrade_wizard.tools.tools_system import restore_filestore
@click.command()
@database_option_required
@click.option(
"--database-path",
required=True,
type=click.Path(readable=True, resolve_path=True),
help="Path to the database dump relative project folder.",
)
@click.option(
"--database-format",
required=True,
type=click.Choice(("c", "d", "t")),
default="c",
help="Database format (see pg_dump options): "
"custom format compressed (c), directory (d), tar file (t)."
"plain sql text (p) is not implemented",
)
@click.option(
"--filestore-path",
required=True,
type=click.Path(readable=True, resolve_path=True),
help="Path to the filestore backup.",
)
@click.option(
"--filestore-format",
required=True,
type=click.Choice(("d", "t", "tgz")),
default="tgz",
help="Filestore format: directory (d), tar file (t), "
"tar file compressed with gzip (tgz)",
)
@click.pass_context
def restoredb(
ctx,
database,
database_path,
database_format,
filestore_path,
filestore_format,
):
"""Restore an Odoo database and associated filestore."""
database_path = Path(database_path)
filestore_path = Path(filestore_path)
# Check that database_path is inside the env_folder_path
absolute_env_folder_path = ctx.obj["env_folder_path"].resolve().absolute()
if not str(database_path).startswith(str(absolute_env_folder_path)):
ctx.fail(
"database-path should be inside the project path to allow "
"postgresql to read to it."
)
# Restore the database
output = execute_pg_restore(
ctx,
database_path.relative_to(absolute_env_folder_path),
database,
database_format,
)
if output:
click.echo(output)
# Restore the filestore
restore_filestore(
ctx,
database,
filestore_path,
filestore_format,
)

View File

@ -235,5 +235,28 @@ def execute_pg_dump(
pg_dump_result = exec_container(container, command)
chown_to_local_user(ctx, filepath)
return pg_dump_result.output.decode("utf8")
def execute_pg_restore(
ctx,
filepath: Path,
database: str,
database_format: str,
):
"""Execute pg_restore command on the postgres container"""
container = get_postgres_container(ctx)
ensure_database(ctx, database, "absent")
ensure_database(ctx, database, "present")
command = (
"pg_restore"
f" {Path('/env') / filepath}"
f" --dbname={database}"
" --schema=public"
" --username=odoo"
" --no-owner"
f" --format {database_format}"
)
logger.info(f"Restoring database '{database}'...")
pg_dump_result = exec_container(container, command)
return pg_dump_result.output.decode("utf8")

View File

@ -143,3 +143,33 @@ def dump_filestore(
with tarfile.open(destpath, wmode) as tar:
tar.add(filestore_path, arcname="filestore")
def restore_filestore(
ctx,
database: str,
src_path: Path,
file_format: str = "d",
):
"""Restore filestore of database from src_path using file_format.
file_format can be :
'd' for 'directory': a normal copy,
't' / 'tgz' for 'tar': an extraction from a tar achive
"""
valid_format = ("d", "t", "tgz")
if file_format not in valid_format:
raise ValueError(
f"file_format should be one of the following {valid_format}"
)
filestore_path = (
ctx.obj["env_folder_path"] / "filestore/filestore" / database
)
logger.info(f"Restoring filestore of '{database}'...")
if file_format == "d":
shutil.copytree(src_path, filestore_path)
else: # works for "t" and "tgz"
tar = tarfile.open(src_path)
tar.extractall(path=filestore_path)

View File

@ -7,6 +7,7 @@ from click.testing import CliRunner
from plumbum.cmd import mkdir
from odoo_openupgrade_wizard.cli.cli import main
from odoo_openupgrade_wizard.tools.tools_postgres import execute_sql_request
_logger = logging.getLogger()
@ -91,3 +92,12 @@ def mock_odoo_rpc_url(mocker):
"odoo_openupgrade_wizard.tools.tools_odoo_instance._ODOO_RPC_URL",
odoo_rpc_url,
)
def assert_database(ctx, db_name, state):
request = "select datname FROM pg_database WHERE datistemplate = false;"
results = execute_sql_request(ctx, request)
if state == "present":
assert [db_name] in results
else:
assert [db_name] not in results

View File

@ -0,0 +1,54 @@
import pathlib
import shutil
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
from . import (
assert_database,
build_ctx_from_config_file,
cli_runner_invoke,
mock_odoo_rpc_url,
move_to_test_folder,
)
def test_cli_restoredb(mocker):
move_to_test_folder()
mock_odoo_rpc_url(mocker)
db_name = "database_test_cli___restoredb"
ctx = build_ctx_from_config_file()
# Ensure environment is clean
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)
# Copy database and filestore data in a accessible folder
database_path = pathlib.Path("./restoredb.dump")
filestore_path = pathlib.Path("./restoredb.tar.gz")
shutil.copyfile(pathlib.Path("../restoredb/test.dump"), database_path)
shutil.copyfile(pathlib.Path("../restoredb/test.tar.gz"), filestore_path)
cli_runner_invoke(
[
"--log-level=DEBUG",
"restoredb",
f"--database={db_name}",
f"--database-path={database_path}",
"--database-format=c",
f"--filestore-path={filestore_path}",
"--filestore-format=tgz",
],
)
# check filestore exists
assert dest_filestore_path.exists()
# Check database exists
assert_database(ctx, db_name, "present")
# Delete filestore and database
shutil.rmtree(dest_filestore_path)
ensure_database(ctx, db_name, state="absent")

View File

@ -4,6 +4,7 @@ import shutil
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
from . import (
assert_database,
build_ctx_from_config_file,
cli_runner_invoke,
mock_odoo_rpc_url,
@ -46,5 +47,9 @@ def test_cli_copydb(mocker):
# check filestore exists
assert dest_filestore_path.exists()
# Delete filestore
# Check database exists
assert_database(ctx, db_dest_name, "present")
# Delete filestore and database
shutil.rmtree(dest_filestore_path)
ensure_database(ctx, db_dest_name, state="absent")

View File

@ -1,12 +1,10 @@
import pathlib
import shutil
from odoo_openupgrade_wizard.tools.tools_postgres import (
ensure_database,
execute_sql_request,
)
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
from . import (
assert_database,
build_ctx_from_config_file,
cli_runner_invoke,
mock_odoo_rpc_url,
@ -45,9 +43,7 @@ def test_cli_dropdb(mocker):
)
# Check database does not exists
request = "select datname FROM pg_database WHERE datistemplate = false;"
results = execute_sql_request(ctx, request)
assert [db_name] not in results
assert_database(ctx, db_name, "absent")
# Check filestore does not exists
assert not filestore_path.exists()

Binary file not shown.

Binary file not shown.