[ADD] dumpdb command

This commit is contained in:
Rémy Taymans 2023-03-22 20:58:36 +01:00
parent 0764b811d5
commit debf57bac3
4 changed files with 188 additions and 0 deletions

View File

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

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

View File

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

View File

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