[ADD] dumpdb command
This commit is contained in:
parent
0764b811d5
commit
debf57bac3
|
|
@ -11,6 +11,7 @@ from loguru import logger
|
|||
import odoo_openupgrade_wizard
|
||||
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_dumpdb import dumpdb
|
||||
from odoo_openupgrade_wizard.cli.cli_estimate_workload import estimate_workload
|
||||
from odoo_openupgrade_wizard.cli.cli_execute_script_python import (
|
||||
execute_script_python,
|
||||
|
|
@ -107,6 +108,7 @@ def main(ctx, env_folder, filestore_folder, log_level):
|
|||
|
||||
main.add_command(copydb)
|
||||
main.add_command(docker_build)
|
||||
main.add_command(dumpdb)
|
||||
main.add_command(estimate_workload)
|
||||
main.add_command(execute_script_python)
|
||||
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:
|
||||
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 os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
|
||||
import importlib_resources
|
||||
|
|
@ -109,3 +111,36 @@ def get_local_user_id():
|
|||
def execute_check_output(args_list, working_directory=False):
|
||||
logger.debug("Execute %s" % " ".join(args_list))
|
||||
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