diff --git a/README.md b/README.md
index 29ab9e6..8a9a1b3 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ and provides helpers to run (and replay) migrations until it works.
* [Command ``generate-module-analysis`` (BETA)](#command-generate-module-analysis)
* [Command ``estimate-workload`` (BETA)](#command-estimate-workload)
* [Command ``psql``](#command-psql)
+ * [Command ``copydb``](#command-copydb)
* [Command ``dumpdb``](#command-dumpdb)
@@ -437,6 +438,23 @@ Result:
See all the options here https://www.postgresql.org/docs/current/app-psql.html
+
+
+## Command: ``copydb``
+
+**Prerequites:** init
+
+```
+odoo-openupgrade-wizard copydb
+ --source DB_NAME
+ --dest NEW_DB_NAME
+```
+
+Create an Odoo database by copying an existing one.
+
+This script copies using postgres CREATEDB WITH TEMPLATE. It also copies
+the filestore.
+
## Command: ``dumpdb``
diff --git a/odoo_openupgrade_wizard/cli/cli.py b/odoo_openupgrade_wizard/cli/cli.py
index 40b6a96..c2b5d4e 100644
--- a/odoo_openupgrade_wizard/cli/cli.py
+++ b/odoo_openupgrade_wizard/cli/cli.py
@@ -72,6 +72,11 @@ def main(ctx, env_folder, filestore_folder, log_level):
# Define all the folder required by the tools
env_folder_path = Path(env_folder)
src_folder_path = env_folder_path / Path("./src/")
+ if filestore_folder:
+ filestore_folder_path = filestore_folder
+ else:
+ filestore_folder_path = env_folder_path / "filestore"
+
script_folder_path = env_folder_path / Path("./scripts/")
log_folder_path = env_folder_path / Path("./log/")
@@ -91,6 +96,7 @@ def main(ctx, env_folder, filestore_folder, log_level):
# Add all global values in the context
ctx.obj["env_folder_path"] = env_folder_path
ctx.obj["src_folder_path"] = src_folder_path
+ ctx.obj["filestore_folder_path"] = filestore_folder_path
ctx.obj["script_folder_path"] = script_folder_path
ctx.obj["log_folder_path"] = log_folder_path
ctx.obj["log_prefix"] = log_prefix
diff --git a/odoo_openupgrade_wizard/tools/tools_click_odoo_contrib.py b/odoo_openupgrade_wizard/tools/tools_click_odoo_contrib.py
index b118a3a..03b91d4 100644
--- a/odoo_openupgrade_wizard/tools/tools_click_odoo_contrib.py
+++ b/odoo_openupgrade_wizard/tools/tools_click_odoo_contrib.py
@@ -1,3 +1,7 @@
+import shutil
+
+from loguru import logger
+
from odoo_openupgrade_wizard.tools.tools_postgres import (
ensure_database,
execute_sql_request,
@@ -9,5 +13,17 @@ def copydb(ctx, source, dest):
ensure_database(ctx, dest, state="absent")
# Copy database
+ logger.info(f"Copy database {source} into {dest} ...")
request = f"CREATE DATABASE {dest} WITH TEMPLATE {source};"
execute_sql_request(ctx, request)
+
+ main_path = ctx.obj["filestore_folder_path"] / "filestore"
+ source_path = main_path / source
+ dest_path = main_path / dest
+ # Drop filestore if exist
+ logger.info(f"Remove filestore of '{dest}' if exists.")
+ shutil.rmtree(dest_path, ignore_errors=True)
+
+ # Copy Filestore
+ logger.info(f"Copy filestore of '{source}' into '{dest}' folder ...")
+ shutil.copytree(source_path, dest_path)
diff --git a/tests/cli_31_copydb_test.py b/tests/cli_31_copydb_test.py
new file mode 100644
index 0000000..0b2a163
--- /dev/null
+++ b/tests/cli_31_copydb_test.py
@@ -0,0 +1,48 @@
+import pathlib
+import shutil
+
+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_copydb():
+ move_to_test_folder()
+
+ db_name = "database_test_cli___copydb"
+ db_dest_name = "database_test_cli___copydb__COPY"
+ 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_dest_name}")
+ shutil.rmtree(dest_filestore_path, ignore_errors=True)
+
+ # Initialize database
+ cli_runner_invoke(
+ [
+ "--log-level=DEBUG",
+ "install-from-csv",
+ f"--database={db_name}",
+ ],
+ )
+
+ # Copy database
+ cli_runner_invoke(
+ [
+ "--log-level=DEBUG",
+ "copydb",
+ f"--source={db_name}",
+ f"--dest={db_dest_name}",
+ ],
+ )
+
+ # check filestore exists
+ assert dest_filestore_path.exists()
+
+ # Delete filestore
+ shutil.rmtree(dest_filestore_path)