[ADD] dumpdb command
This commit is contained in:
parent
0764b811d5
commit
debf57bac3
|
|
@ -11,6 +11,7 @@ from loguru import logger
|
||||||
import odoo_openupgrade_wizard
|
import odoo_openupgrade_wizard
|
||||||
from odoo_openupgrade_wizard.cli.cli_copydb import copydb
|
from odoo_openupgrade_wizard.cli.cli_copydb import copydb
|
||||||
from odoo_openupgrade_wizard.cli.cli_docker_build import docker_build
|
from odoo_openupgrade_wizard.cli.cli_docker_build import docker_build
|
||||||
|
from odoo_openupgrade_wizard.cli.cli_dumpdb import dumpdb
|
||||||
from odoo_openupgrade_wizard.cli.cli_estimate_workload import estimate_workload
|
from odoo_openupgrade_wizard.cli.cli_estimate_workload import estimate_workload
|
||||||
from odoo_openupgrade_wizard.cli.cli_execute_script_python import (
|
from odoo_openupgrade_wizard.cli.cli_execute_script_python import (
|
||||||
execute_script_python,
|
execute_script_python,
|
||||||
|
|
@ -107,6 +108,7 @@ def main(ctx, env_folder, filestore_folder, log_level):
|
||||||
|
|
||||||
main.add_command(copydb)
|
main.add_command(copydb)
|
||||||
main.add_command(docker_build)
|
main.add_command(docker_build)
|
||||||
|
main.add_command(dumpdb)
|
||||||
main.add_command(estimate_workload)
|
main.add_command(estimate_workload)
|
||||||
main.add_command(execute_script_python)
|
main.add_command(execute_script_python)
|
||||||
main.add_command(execute_script_sql)
|
main.add_command(execute_script_sql)
|
||||||
|
|
|
||||||
96
odoo_openupgrade_wizard/cli/cli_dumpdb.py
Normal file
96
odoo_openupgrade_wizard/cli/cli_dumpdb.py
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
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_dump
|
||||||
|
from odoo_openupgrade_wizard.tools.tools_system import dump_filestore
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@database_option_required
|
||||||
|
@click.option(
|
||||||
|
"--database-path",
|
||||||
|
type=click.Path(writable=True, resolve_path=True),
|
||||||
|
required=True,
|
||||||
|
help="Path to the database dump relative project folder.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--database-format",
|
||||||
|
type=click.Choice(("p", "c", "d", "t")),
|
||||||
|
default="c",
|
||||||
|
help="Database format (see pg_dump options): plain sql text (p), "
|
||||||
|
"custom format compressed (c), directory (d), tar file (t).",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--filestore-path",
|
||||||
|
type=click.Path(writable=True, resolve_path=True),
|
||||||
|
required=True,
|
||||||
|
help="Path to the filestore backup.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--filestore-format",
|
||||||
|
type=click.Choice(("d", "t", "tgz")),
|
||||||
|
default="tgz",
|
||||||
|
help="Filestore format: directory (d), tar file (t), "
|
||||||
|
"tar file compressed with gzip (tgz)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--force",
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
help="Overwrite file if they already exists.",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def dumpdb(
|
||||||
|
ctx,
|
||||||
|
database,
|
||||||
|
database_path,
|
||||||
|
database_format,
|
||||||
|
filestore_path,
|
||||||
|
filestore_format,
|
||||||
|
force,
|
||||||
|
):
|
||||||
|
"""Create an dump of an Odoo database and its filestore."""
|
||||||
|
database_path = Path(database_path)
|
||||||
|
filestore_path = Path(filestore_path)
|
||||||
|
|
||||||
|
# Fail if dumps already exists and force argument not given
|
||||||
|
if not force and database_path.exists():
|
||||||
|
ctx.fail(f"{database_path} exists, use --force to overwrite it.")
|
||||||
|
if not force and filestore_path.exists():
|
||||||
|
ctx.fail(f"{filestore_path} exists, use --force to overwrite it.")
|
||||||
|
|
||||||
|
# Check that database_path is inside the env_folder_path
|
||||||
|
absolute_database_path = database_path.absolute()
|
||||||
|
absolute_env_folder_path = ctx.obj["env_folder_path"].resolve().absolute()
|
||||||
|
if not str(absolute_database_path).startswith(
|
||||||
|
str(absolute_env_folder_path)
|
||||||
|
):
|
||||||
|
ctx.fail(
|
||||||
|
"database-path should be inside the project path to allow "
|
||||||
|
"postgresql to write to it."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Normalise database_path
|
||||||
|
database_path = absolute_database_path.relative_to(
|
||||||
|
absolute_env_folder_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# dump the database
|
||||||
|
output = execute_pg_dump(
|
||||||
|
ctx,
|
||||||
|
database=database,
|
||||||
|
dumpformat=database_format,
|
||||||
|
filename=str(database_path),
|
||||||
|
)
|
||||||
|
if output:
|
||||||
|
click.echo(output)
|
||||||
|
|
||||||
|
# dump the filestore
|
||||||
|
dump_filestore(
|
||||||
|
ctx,
|
||||||
|
database=database,
|
||||||
|
destpath=filestore_path,
|
||||||
|
copyformat=filestore_format,
|
||||||
|
)
|
||||||
|
|
@ -180,3 +180,58 @@ def execute_sql_files_pre_migration(
|
||||||
|
|
||||||
for sql_file in sql_files:
|
for sql_file in sql_files:
|
||||||
execute_sql_file(ctx, database, sql_file)
|
execute_sql_file(ctx, database, sql_file)
|
||||||
|
|
||||||
|
|
||||||
|
def chown_to_local_user(ctx, filepath: os.PathLike):
|
||||||
|
"""Chown a filepath in the postgres container to the local user"""
|
||||||
|
container = get_postgres_container(ctx)
|
||||||
|
user_uid = os.getuid()
|
||||||
|
command = "chown -R {uid}:{uid} {filepath}".format(
|
||||||
|
uid=user_uid, filepath=filepath
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
"Executing the following command in postgres container: %s"
|
||||||
|
% (command,)
|
||||||
|
)
|
||||||
|
chown_result = exec_container(container, command)
|
||||||
|
return chown_result.output.decode("utf8")
|
||||||
|
|
||||||
|
|
||||||
|
def execute_pg_dump(
|
||||||
|
ctx,
|
||||||
|
database: str,
|
||||||
|
dumpformat: str,
|
||||||
|
filename: str,
|
||||||
|
pg_dump_args="--no-owner",
|
||||||
|
):
|
||||||
|
"""Execute pg_dump command on the postgres container and dump the
|
||||||
|
result to dumpfile.
|
||||||
|
"""
|
||||||
|
if pg_dump_args and not isinstance(pg_dump_args, str):
|
||||||
|
pg_dump_args = " ".join(pg_dump_args)
|
||||||
|
container = get_postgres_container(ctx)
|
||||||
|
# Generate path for the output file
|
||||||
|
filepath = Path("/env") / Path(filename)
|
||||||
|
# Generate pg_dump command
|
||||||
|
command = (
|
||||||
|
"pg_dump"
|
||||||
|
" --username=odoo"
|
||||||
|
" --format {dumpformat}"
|
||||||
|
" --file {filepath}"
|
||||||
|
" {pg_dump_args}"
|
||||||
|
" {database}"
|
||||||
|
).format(
|
||||||
|
dumpformat=dumpformat,
|
||||||
|
filepath=filepath,
|
||||||
|
database=database,
|
||||||
|
pg_dump_args=pg_dump_args,
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
"Executing the following command in postgres container: %s"
|
||||||
|
% (command,)
|
||||||
|
)
|
||||||
|
pg_dump_result = exec_container(container, command)
|
||||||
|
|
||||||
|
chown_to_local_user(ctx, filepath)
|
||||||
|
|
||||||
|
return pg_dump_result.output.decode("utf8")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tarfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import importlib_resources
|
import importlib_resources
|
||||||
|
|
@ -109,3 +111,36 @@ def get_local_user_id():
|
||||||
def execute_check_output(args_list, working_directory=False):
|
def execute_check_output(args_list, working_directory=False):
|
||||||
logger.debug("Execute %s" % " ".join(args_list))
|
logger.debug("Execute %s" % " ".join(args_list))
|
||||||
subprocess.check_output(args_list, cwd=working_directory)
|
subprocess.check_output(args_list, cwd=working_directory)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_filestore(
|
||||||
|
ctx,
|
||||||
|
database: str,
|
||||||
|
destpath: os.PathLike,
|
||||||
|
copyformat: str = "d",
|
||||||
|
):
|
||||||
|
"""Copy filestore of database to destpath using copyformat.
|
||||||
|
copyformat can be 'd' for directory, a normal copy, or 't' for a
|
||||||
|
copy into a tar achive, or 'tgz' to copy to a compressed tar file.
|
||||||
|
"""
|
||||||
|
valid_format = ("d", "t", "tgz", "txz")
|
||||||
|
if copyformat not in valid_format:
|
||||||
|
raise ValueError(
|
||||||
|
f"copyformat should be one of the following {valid_format}"
|
||||||
|
)
|
||||||
|
|
||||||
|
filestore_folder_path = ctx.obj["env_folder_path"] / "filestore/filestore"
|
||||||
|
filestore_path = filestore_folder_path / database
|
||||||
|
|
||||||
|
if copyformat == "d":
|
||||||
|
shutil.copytree(filestore_path, destpath)
|
||||||
|
|
||||||
|
elif copyformat.startswith("t"):
|
||||||
|
wmode = "w"
|
||||||
|
if copyformat.endswith("gz"):
|
||||||
|
wmode += ":gz"
|
||||||
|
elif copyformat.endswith("xz"):
|
||||||
|
wmode += ":xz"
|
||||||
|
|
||||||
|
with tarfile.open(destpath, wmode) as tar:
|
||||||
|
tar.add(filestore_path, arcname="filestore")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user