merge 1.3.1

This commit is contained in:
Matthias Lotz 2025-10-03 20:52:24 +02:00
parent bf07bc1865
commit a5f7f2e88c
36 changed files with 1083 additions and 407 deletions

View File

@ -6,6 +6,43 @@ This file compiles releases and changes made in
.. towncrier release notes start .. towncrier release notes start
odoo-openupgrade-wizard 1.3.1 (2025-09-18)
==========================================
Bugfixes
--------
- Fix BSD mkdir incompatibility on macOS by using single-character
flags for 'mode' and 'parents' for cross-platform folder creation (#39)
- Fix debian buster Dockerfile. It uses the old https://deb.debian.org
instead of https://archive.debian.org.
- Improve error handling for missing config file outside initialized
environment.
- Prevent creating a log file outside of an initialized directory.
- Set 'rm=True' when building Docker images, ensuring intermediate (i.e.
orphaned) containers are automatically removed after builds and keeping
local Docker environments clean.
By default, the docker build CLI now sets --rm=true, but the SDK has
kept the old default of False to preserve backward compatibility.
Documentation
-------------
- Many improvements/additions to README.md and help string, including missing
Command help, additional examples, a Quick Start Guide, standard technical
writing changes, etc.
- Remove obsolete ROADMAP file. Team now uses issues on gitlab.
Misc
----
- Do not set False to an argument that is hint typed as string. Using None
instead.
odoo-openupgrade-wizard 1.3.0 (2025-05-04) odoo-openupgrade-wizard 1.3.0 (2025-05-04)
========================================== ==========================================

View File

@ -11,6 +11,8 @@
* Boris GALLET, since September 2024 * Boris GALLET, since September 2024
* Ahmet YIĞIT BUDAK, from [Altinkaya](https://www.altinkaya.com/fr), since October 2024 * Ahmet YIĞIT BUDAK, from [Altinkaya](https://www.altinkaya.com/fr), since October 2024
* Alexandre AUBIN, from [Coopaname](https://www.coopaname.coop/), since October 2024 * Alexandre AUBIN, from [Coopaname](https://www.coopaname.coop/), since October 2024
* Sergio Zanchetta, from [PNLug](https://www.pnlug.it/), since January 2025
* Ken WOYCHESKO, since May 2025
# Reviewers # Reviewers

941
README.md

File diff suppressed because it is too large Load Diff

View File

@ -56,9 +56,9 @@ DEFAULT_MODULES_FILE = "modules.csv"
writable=True, writable=True,
resolve_path=True, resolve_path=True,
), ),
help="Folder that will contains all the configuration of the wizard" help="Directory that will contain all the configuration of the wizard "
" and all the Odoo code required to make the migrations. Let empty to" "and all the Odoo code required to perform the migrations. Leave "
" use current folder (./).", "empty to use current directory (./).",
) )
@click.option( @click.option(
"-c", "-c",
@ -69,7 +69,7 @@ DEFAULT_MODULES_FILE = "modules.csv"
), ),
help=( help=(
f"Configuration file to use. By default, a file named " f"Configuration file to use. By default, a file named "
f'"{DEFAULT_CONFIG_FILE}" in the environment folder will be used.' f'"{DEFAULT_CONFIG_FILE}" in the environment directory will be used.'
), ),
) )
@click.option( @click.option(
@ -80,7 +80,7 @@ DEFAULT_MODULES_FILE = "modules.csv"
), ),
help=( help=(
f"Modules file to use. By default, a file named " f"Modules file to use. By default, a file named "
f'"{DEFAULT_MODULES_FILE}" in the environment folder will be used.' f'"{DEFAULT_MODULES_FILE}" in the environment directory will be used.'
), ),
) )
@click.option( @click.option(
@ -88,22 +88,28 @@ DEFAULT_MODULES_FILE = "modules.csv"
type=click.Path( type=click.Path(
exists=True, file_okay=False, writable=True, resolve_path=True exists=True, file_okay=False, writable=True, resolve_path=True
), ),
help="Folder that contains the Odoo filestore of the database(s)" help="Directory that contains the Odoo filestore of the database(s) to "
" to migrate. Let empty to use the subfolder 'filestore' of the" "migrate. Leave empty to use the subdirectory 'filestore' of the "
" environment folder.", "environment directory.",
) )
@click.option("-l", "--log-level", type=LogLevel(), default=logging.INFO) @click.option("-l", "--log-level", type=LogLevel(), default=logging.INFO)
@click.pass_context @click.pass_context
def main( def main(
ctx, env_folder, config_file, modules_file, filestore_folder, log_level ctx, env_folder, config_file, modules_file, filestore_folder, log_level
): ):
""" """Provides a command set to perform Odoo Community Edition migrations.
Provides a command set to perform odoo Community Edition migrations.
Instructions are provided in the README.md file, or see the
help provided for each 'Command' listed below. For
example, to learn more about the `init` command,
run:
`oow init --help`
""" """
date_begin = datetime.datetime.now() date_begin = datetime.datetime.now()
logger.remove() logger.remove()
logger.add(sys.stderr, level=log_level) logger.add(sys.stderr, level=log_level)
logger.debug(f"Beginning script '{ctx.invoked_subcommand}' ...") logger.debug(f"Beginning script '{ctx.invoked_subcommand}'...")
if not isinstance(ctx.obj, dict): if not isinstance(ctx.obj, dict):
ctx.obj = {} ctx.obj = {}
@ -118,16 +124,6 @@ def main(
script_folder_path = env_folder_path / Path("./scripts/") script_folder_path = env_folder_path / Path("./scripts/")
log_folder_path = env_folder_path / Path("./log/") log_folder_path = env_folder_path / Path("./log/")
# ensure log folder exists
ensure_folder_exists(log_folder_path, git_ignore_content=True)
# Create log file
log_prefix = "{}__{}".format(
date_begin.strftime("%Y_%m_%d__%H_%M_%S"), ctx.invoked_subcommand
)
log_file_path = log_folder_path / Path(log_prefix + ".log")
logger.add(log_file_path)
if config_file: if config_file:
config_file_path = Path(config_file) config_file_path = Path(config_file)
else: else:
@ -138,24 +134,38 @@ def main(
else: else:
module_file_path = env_folder_path / Path(DEFAULT_MODULES_FILE) module_file_path = env_folder_path / Path(DEFAULT_MODULES_FILE)
if config_file_path.exists():
# Load the main configuration file
with open(config_file_path) as file:
config = yaml.safe_load(file)
ctx.obj["config"] = config
# ensure log folder exists
ensure_folder_exists(log_folder_path, git_ignore_content=True)
# Create log file
log_prefix = "{}__{}".format(
date_begin.strftime("%Y_%m_%d__%H_%M_%S"), ctx.invoked_subcommand
)
log_file_path = log_folder_path / Path(log_prefix + ".log")
logger.add(log_file_path)
ctx.obj["log_prefix"] = log_prefix
elif ctx.invoked_subcommand not in ("init", None):
ctx.fail(
f"Environment not initialized. "
f"Expected config file at: {config_file_path}",
)
# Add all global values in the context # Add all global values in the context
ctx.obj["env_folder_path"] = env_folder_path ctx.obj["env_folder_path"] = env_folder_path
ctx.obj["src_folder_path"] = src_folder_path ctx.obj["src_folder_path"] = src_folder_path
ctx.obj["filestore_folder_path"] = filestore_folder_path ctx.obj["filestore_folder_path"] = filestore_folder_path
ctx.obj["script_folder_path"] = script_folder_path ctx.obj["script_folder_path"] = script_folder_path
ctx.obj["log_folder_path"] = log_folder_path ctx.obj["log_folder_path"] = log_folder_path
ctx.obj["log_prefix"] = log_prefix
ctx.obj["config_file_path"] = config_file_path ctx.obj["config_file_path"] = config_file_path
ctx.obj["module_file_path"] = module_file_path ctx.obj["module_file_path"] = module_file_path
# Load the main configuration file
if config_file_path.exists():
with open(config_file_path) as file:
config = yaml.safe_load(file)
ctx.obj["config"] = config
elif ctx.invoked_subcommand != "init":
raise
main.add_command(copydb) main.add_command(copydb)
main.add_command(restoredb) main.add_command(restoredb)

View File

@ -10,17 +10,20 @@ from odoo_openupgrade_wizard.tools import (
"-s", "-s",
"--source", "--source",
type=str, type=str,
help="Name of the database to copy", help="Name of the source database to copy.",
) )
@click.option( @click.option(
"-d", "-d",
"--dest", "--dest",
type=str, type=str,
help="Name of the new database", help="Name of the destination database to create.",
) )
@click.pass_context @click.pass_context
def copydb(ctx, source, dest): def copydb(ctx, source, dest):
"""Create an Odoo database by copying an existing one. """Create a new Odoo database by copying another.
it will copy the postgres database and the filestore.
This command duplicates both the PostgreSQL database and its associated
filestore.
""" """
click_odoo_contrib.copydb(ctx, source, dest) click_odoo_contrib.copydb(ctx, source, dest)

View File

@ -17,11 +17,16 @@ from odoo_openupgrade_wizard.tools.tools_system import get_local_user_id
@versions_options @versions_options
@click.pass_context @click.pass_context
def docker_build(ctx, versions): def docker_build(ctx, versions):
"""Build Odoo Docker Images and pull Postgres image""" """Build Docker images and pull PostgreSQL image.
This command prepares the Docker environment needed for running migration
steps. Make sure to run `oow get-code` first to ensure all required code
and requirements files are available for each version.
"""
# Pull DB image # Pull DB image
logger.info( logger.info(
"Pulling the postgresql docker image. This can take a while..." "Pulling the PostgreSQL Docker image. This can take a while..."
) )
pull_image(ctx.obj["config"]["postgres_image_name"]) pull_image(ctx.obj["config"]["postgres_image_name"])
@ -32,16 +37,16 @@ def docker_build(ctx, versions):
) )
if not odoo_requirement_file_path.exists(): if not odoo_requirement_file_path.exists():
logger.error( logger.error(
"Building Odoo docker image for version {odoo_version}, " "Cannot build Odoo Docker image for version {odoo_version}, "
"because file {odoo_requirement_file_path} cannot be found. " "because file {odoo_requirement_file_path} cannot be found. "
"Have your run the get-code command ?", "Have you run the get-code command?",
odoo_version=odoo_version, odoo_version=odoo_version,
odoo_requirement_file_path=odoo_requirement_file_path, odoo_requirement_file_path=odoo_requirement_file_path,
) )
continue continue
logger.info( logger.info(
f"Building Odoo docker image for version '{odoo_version}'." f"Building Odoo Docker image for version '{odoo_version}'."
" This can take a while..." " This can take a while..."
) )
image = build_image( image = build_image(

View File

@ -17,33 +17,34 @@ from odoo_openupgrade_wizard.tools.tools_system import dump_filestore
"--database-path", "--database-path",
type=click.Path(writable=True, resolve_path=True), type=click.Path(writable=True, resolve_path=True),
required=True, required=True,
help="Path to the database dump relative project folder.", help="Destination path for the database dump relative to the project "
"directory.",
) )
@click.option( @click.option(
"--database-format", "--database-format",
type=click.Choice(("p", "c", "d", "t")), type=click.Choice(("p", "c", "d", "t")),
default="c", default="c",
help="Database format (see pg_dump options): plain sql text (p), " help="Database format (see pg_dump options): plain SQL (p), "
"custom format compressed (c), directory (d), tar file (t).", "custom format compressed (c), directory (d), or tar file (t).",
) )
@click.option( @click.option(
"--filestore-path", "--filestore-path",
type=click.Path(writable=True, resolve_path=True), type=click.Path(writable=True, resolve_path=True),
required=True, required=True,
help="Path to the filestore backup.", help="Destination path for the filestore backup.",
) )
@click.option( @click.option(
"--filestore-format", "--filestore-format",
type=click.Choice(("d", "t", "tgz")), type=click.Choice(("d", "t", "tgz")),
default="tgz", default="tgz",
help="Filestore format: directory (d), tar file (t), " help="Filestore format: directory (d), tar file (t), "
"tar file compressed with gzip (tgz)", "or tar file compressed with gzip (tgz)",
) )
@click.option( @click.option(
"--force", "--force",
is_flag=True, is_flag=True,
default=False, default=False,
help="Overwrite file if they already exists.", help="Overwrite files if they already exist.",
) )
@click.pass_context @click.pass_context
def dumpdb( def dumpdb(
@ -55,7 +56,14 @@ def dumpdb(
filestore_format, filestore_format,
force, force,
): ):
"""Create an dump of an Odoo database and its filestore.""" """Create a dump of an Odoo database and filestore.
Creates a backup of the selected Odoo database and
its associated filestore. Both output locations must
reside inside the project directory. Existing files
will be overwritten if --force is specified.
"""
database_path = pathlib.Path(database_path) database_path = pathlib.Path(database_path)
filestore_path = pathlib.Path(filestore_path) filestore_path = pathlib.Path(filestore_path)
@ -67,7 +75,7 @@ def dumpdb(
): ):
ctx.fail( ctx.fail(
"database-path should be inside the project path to allow " "database-path should be inside the project path to allow "
"postgresql to write to it." "PostgreSQL to write to it."
) )
# Fail if dumps already exists and force argument not given # Fail if dumps already exists and force argument not given

View File

@ -17,35 +17,43 @@ from odoo_openupgrade_wizard.tools.tools_system import (
dir_okay=False, dir_okay=False,
), ),
default="./analysis.html", default="./analysis.html",
help="Path where the HTML analysis report will be saved. "
"Default is './analysis.html'",
) )
@click.option( @click.option(
"--extra-modules", "--extra-modules",
"extra_modules_list", "extra_modules_list",
# TODO, add a callback to check the quality of the argument # TODO, add a callback to check the quality of the argument
help="Coma separated modules to analyse. If not set, the modules.csv" help="Comma-separated list of modules to analyze. If not set, "
" file will be used to define the list of module to analyse." "modules.csv will be used. Example: 'account,product,base'.",
"Ex: 'account,product,base'",
) )
@click.option( @click.option(
"--time-unit", "--time-unit",
type=click.Choice(["hour", "minute", "separator"]), type=click.Choice(["hour", "minute", "separator"]),
default="separator", default="separator",
show_default=True, show_default=True,
help="Select unit to display time in the report. " help="Format to use for displaying time in the report. "
"*separator* display time as `HHH<sep>MM`, " "*separator* display time as `HHH<sep>MM`, "
"*hour* display time as decimal hour, " "*hour* display time as decimal hour, "
"*min* display time as minute (rounded).", "*min* display time as minutes (rounded).",
) )
@click.option( @click.option(
"--time-separator", "--time-separator",
default=":", default=":",
help="Specify time separator, if time-unit is separator. " help="Character to use as a separator in time output. "
"Default to `:` (it will produce time like this HHH:MM).", "Used only if --time-unit=separator. Default is ':' (e.g. HHH:MM).",
) )
@click.pass_context @click.pass_context
def estimate_workload( def estimate_workload(
ctx, analysis_file_path, extra_modules_list, time_unit, time_separator ctx, analysis_file_path, extra_modules_list, time_unit, time_separator
): ):
"""Estimate workload and create an analysis report.
This command estimates the workload required for an Odoo
migration based on the module set provided. The output is
an HTML report showing time estimations for each module.
"""
# Analyse # Analyse
analysis = Analysis(ctx) analysis = Analysis(ctx)

View File

@ -22,11 +22,29 @@ from odoo_openupgrade_wizard.tools.tools_odoo import (
exists=True, exists=True,
dir_okay=False, dir_okay=False,
), ),
help="List of python files that will be executed, replacing the default" help="""List of Python files to execute, with either an absolute path
" scripts placed in the migration step folder.", or path relative to the project directory. With either method, the
path must be located inside the project directory so that the
Docker container can access it.
Files will be executed in
the order listed. If no files are specified, all Python (.py) files
in the `step` directory will be sorted alphabetically and then
executed in order.
See README.md for more information and examples.""",
) )
@click.pass_context @click.pass_context
def execute_script_python(ctx, step, database, script_file_path): def execute_script_python(ctx, step, database, script_file_path):
"""Execute Python scripts for a migration step.
Executes one or more custom Python scripts in the context of a specific
migration step, using the Odoo shell (with full ORM access). This command
allows you to manually run Python logic outside of the default
post-migration.py file for a given step. It allows fine-tuning
of migration behavior by manually specifying logic.
"""
migration_step = get_migration_step_from_options(ctx, step) migration_step = get_migration_step_from_options(ctx, step)
execute_click_odoo_python_files( execute_click_odoo_python_files(

View File

@ -22,11 +22,21 @@ from odoo_openupgrade_wizard.tools.tools_postgres import (
exists=True, exists=True,
dir_okay=False, dir_okay=False,
), ),
help="List of SQL files that will be executed, replacing the default" help="List of SQL files to execute. Files will be executed in the order "
" scripts placed in the migration step folder.", "listed. If no files are specified, all SQL files (.sql) in the "
"step's directory will be sorted alphabetically and then executed "
"in order.",
) )
@click.pass_context @click.pass_context
def execute_script_sql(ctx, step, database, script_file_path): def execute_script_sql(ctx, step, database, script_file_path):
"""Execute SQL scripts for a migration step.
Executes one or more custom SQL scripts against the specified database,
using the PostgreSQL Docker container. This command allows you to manually
run SQL logic, allowing you to test or apply SQL changes in the context
of a specific migration step outside the automatic oow upgrade process.
"""
migration_step = get_migration_step_from_options(ctx, step) migration_step = get_migration_step_from_options(ctx, step)
execute_sql_files_pre_migration( execute_sql_files_pre_migration(

View File

@ -28,11 +28,24 @@ from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_writable
"-m", "-m",
"--modules", "--modules",
type=str, type=str,
help="Coma-separated list of modules to analysis." help="Comma-separated list of modules to analyse."
" Let empty to analyse all the Odoo modules.", " Leave empty to analyse all the Odoo modules.",
) )
@click.pass_context @click.pass_context
def generate_module_analysis(ctx, step, database, modules): def generate_module_analysis(ctx, step, database, modules):
"""Analyzes changes in data model & module data.
Performs an analysis between the target version (represented by the step
parameter) and the previous version to indicate how the data model and
module data have changed between the two versions (uses the
OCA/server-tools `upgrade_analysis` tool internally).
You can also use this function to analyze differences for custom & OCA
modules between several versions (e.g. in case of refactoring).
See the README.md for more information, including the location of the
resultant analysis files.
"""
migration_steps = get_migration_steps_from_options(ctx, step - 1, step) migration_steps = get_migration_steps_from_options(ctx, step - 1, step)
initial_step = migration_steps[0].copy() initial_step = migration_steps[0].copy()

View File

@ -19,11 +19,15 @@ from odoo_openupgrade_wizard.tools.tools_system import git_aggregate
type=int, type=int,
default=10, default=10,
help="Jobs used to call the git-aggregate command." help="Jobs used to call the git-aggregate command."
" reasonably set to 10 by default.", " Reasonably set to 10 by default.",
) )
@click.pass_context @click.pass_context
def get_code(ctx, versions, jobs): def get_code(ctx, versions, jobs):
"""Get code by running gitaggregate command for each version""" """Get all required source code for each version.
Downloads all required Odoo source code and dependencies for each version
defined in your migration (uses the `gitaggregate` tools internally).
"""
for odoo_version in get_odoo_versions_from_options(ctx, versions): for odoo_version in get_odoo_versions_from_options(ctx, versions):
folder_path = get_odoo_env_path(ctx, odoo_version) folder_path = get_odoo_env_path(ctx, odoo_version)

View File

@ -17,12 +17,23 @@ from odoo_openupgrade_wizard.tools.tools_system import (
"--extra-modules", "--extra-modules",
"extra_modules_list", "extra_modules_list",
# TODO, add a callback to check the quality of the argument # TODO, add a callback to check the quality of the argument
help="Coma separated modules to analyse. If not set, the modules.csv" help="Comma-separated list of modules to analyze. If not set, "
" file will be used to define the list of module to analyse." "modules.csv will be used. Example: 'account,product,base'.",
"Ex: 'account,product,base'",
) )
@click.pass_context @click.pass_context
def guess_requirement(ctx, extra_modules_list): def guess_requirement(ctx, extra_modules_list):
"""Guess system & Python requirements for modules.
Analyzes the list of modules defined in your modules.csv file
to generate the required Python and Debian package dependencies per
environment.
For each module and each version, this command tries to parse the
corresponding __manifest__.py file (and, if present, the setup.py
file). It then appends any discovered requirements to the appropriate
addons_debian_requirements.txt and addons_python_requirements.txt files
present in each env directory.
"""
# Analyse # Analyse
analysis = Analysis(ctx) analysis = Analysis(ctx)

View File

@ -19,42 +19,44 @@ from odoo_openupgrade_wizard.tools.tools_system import (
required=True, required=True,
prompt=True, prompt=True,
type=str, type=str,
help="Name of your project without spaces neither special" help="Name of your project without spaces, special"
" chars or uppercases. exemple 'my-customer-9-12'." " characters, or uppercases. Example: 'my-customer-9-12'."
" This will be used to tag with a friendly" " This will be used to tag the Odoo Docker images "
" name the odoo docker images.", " with a friendly name.",
) )
@click.option( @click.option(
"--initial-version", "--initial-version",
required=True, required=True,
prompt=True, prompt=True,
type=click.Choice(get_version_options("initial")), type=click.Choice(get_version_options("initial")),
help="Initial Odoo version to migrate from.",
) )
@click.option( @click.option(
"--final-version", "--final-version",
required=True, required=True,
prompt=True, prompt=True,
type=click.Choice(get_version_options("final")), type=click.Choice(get_version_options("final")),
help="Target Odoo version to migrate to.",
) )
@click.option( @click.option(
"--postgresql-version", "--postgresql-version",
required=True, required=True,
prompt=True, prompt=True,
help="The version of postgresql that will be used" help="""The version of PostgreSQL that will be used
" to create the postgresql container.Ex : '9.1', '16', ..." to create the PostgreSQL container. Example: '9.1', '16', etc.
" The version should be available in docker hub." The version should be available in Docker hub.
" (https://hub.docker.com/_/postgres)" (https://hub.docker.com/_/postgres)
" avoid the 'latest' version if you want a deterministic installation." Avoid the 'latest' version if you want a deterministic installation.
" Key Point: If your current production server uses Postgresql version A" Key Point: If your current production server uses PostgreSQL version A
" and if your future production server usees Postgresql version B," and your future production server will use PostgreSQL version B,
" you should select here a version X, with A <= X <= B.", you should select here a version X, with A <= X <= B.""",
) )
@click.option( @click.option(
"--extra-repository", "--extra-repository",
"extra_repository_list", "extra_repository_list",
# TODO, add a callback to check the quality of the argument # TODO, add a callback to check the quality of the argument
help="Coma separated extra repositories to use in the odoo environment." help="Comma-separated extra repositories to use in the Odoo environment."
"Ex: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'", "Example: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'",
) )
@click.pass_context @click.pass_context
def init( def init(
@ -65,8 +67,10 @@ def init(
postgresql_version, postgresql_version,
extra_repository_list, extra_repository_list,
): ):
"""Initialize OOW Environment based on the initial and """Initialize the OOW project environment.
the final version of Odoo you want to migrate.
This command sets up the project folder structure, configuration
files, and default templates needed to begin an Odoo migration.
""" """
# Handle arguments # Handle arguments

View File

@ -20,6 +20,12 @@ from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
@demo_option @demo_option
@click.pass_context @click.pass_context
def install_from_csv(ctx, database, with_demo): def install_from_csv(ctx, database, with_demo):
"""Install modules from a CSV file.
This command reads the modules.csv file and installs the
modules listed for a specific Odoo version. The database will be
created if it doesn't exist.
"""
migration_step = get_migration_step_from_options(ctx, 1) migration_step = get_migration_step_from_options(ctx, 1)
ensure_database(ctx, database, state="present") ensure_database(ctx, database, state="present")
@ -30,7 +36,7 @@ def install_from_csv(ctx, database, with_demo):
logger.debug(module_names) logger.debug(module_names)
try: try:
logger.info(f"Install 'base' module on {database} database ...") logger.info(f"Install 'base' module on {database} database...")
run_odoo( run_odoo(
ctx, ctx,
migration_step, migration_step,
@ -58,7 +64,7 @@ def install_from_csv(ctx, database, with_demo):
raise Exception( raise Exception(
f"Unable to find a country, based on the" f"Unable to find a country, based on the"
f" code {odoo_default_company['country_code']}." f" code {odoo_default_company['country_code']}."
f" Countries found :" f" Countries found:"
f" {', '.join([x.name for x in countries])}" f" {', '.join([x.name for x in countries])}"
) )
vals = { vals = {

View File

@ -6,10 +6,8 @@ def versions_options(function):
"-v", "-v",
"--versions", "--versions",
type=str, type=str,
help="Coma-separated values of odoo versions for which" help="Comma-separated Odoo versions to target. Leave empty to "
" you want to perform the operation." "perform the operation on all versions in the project",
" Let empty to perform the operation on all the versions"
" of the project",
)(function) )(function)
return function return function
@ -30,7 +28,7 @@ def first_step_option(function):
function = click.option( function = click.option(
"--first-step", "--first-step",
type=int, type=int,
help="First step for which to perform the operation", help="First step to include in the operation.",
)(function) )(function)
return function return function
@ -39,7 +37,7 @@ def last_step_option(function):
function = click.option( function = click.option(
"--last-step", "--last-step",
type=int, type=int,
help="Last step for which to perform the operation", help="Last step to include in the operation.",
)(function) )(function)
return function return function
@ -48,7 +46,7 @@ def demo_option(function):
function = click.option( function = click.option(
"--with-demo/--without-demo", "--with-demo/--without-demo",
default=False, default=False,
help="Create database with or without demo data.", help="Whether to include demo data when creating the database.",
)(function) )(function)
return function return function
@ -61,7 +59,7 @@ def database_option_required(function):
prompt=True, prompt=True,
default="postgres", default="postgres",
type=str, type=str,
help="Odoo Database for which you want to perform the operation.", help="Name of the Odoo database to operate on. Default is 'postgres'.",
)(function) )(function)
return function return function

View File

@ -6,14 +6,29 @@ from odoo_openupgrade_wizard.tools.tools_postgres import execute_psql_command
@click.command(context_settings={"ignore_unknown_options": True}) @click.command(context_settings={"ignore_unknown_options": True})
@database_option_required @database_option_required
@click.option("-c", "--command", "request") @click.option(
@click.option("--pager/--no-pager", default=True) "-c",
"--command",
"request",
help="SQL command to execute inside the container.",
)
@click.option(
"--pager/--no-pager",
default=True,
help="Enable or disable pager when displaying output.",
)
@click.argument("psql_args", nargs=-1, type=click.UNPROCESSED) @click.argument("psql_args", nargs=-1, type=click.UNPROCESSED)
@click.pass_context @click.pass_context
def psql(ctx, request, database, pager, psql_args): def psql(ctx, request, database, pager, psql_args):
"""Run psql in the postgres container. Fill any parameters of psql """Run a SQL command in the PostgreSQL container.
as PSQLARGS.
This command executes the provided SQL command using `psql`
within the database container. Use --command for inline SQL
or pass additional arguments directly to psql via PSQLARGS.
See the README.md for more information.
""" """
result = execute_psql_command(ctx, request, database, psql_args) result = execute_psql_command(ctx, request, database, psql_args)
if pager: if pager:
click.echo_via_pager(result) click.echo_via_pager(result)

View File

@ -13,8 +13,12 @@ from odoo_openupgrade_wizard.tools.tools_system import execute_check_output
@versions_options @versions_options
@click.pass_context @click.pass_context
def pull_submodule(ctx, versions): def pull_submodule(ctx, versions):
"""Pull submodule that contains repos.yml file, if define in config.yml""" """Pull the repos.yml file from a Git repository.
This command runs `git submodule update --init --recursive`
for the submodule that contains your `repos.yml` file.
This ensures all nested submodules are also fetched.
"""
for odoo_version in get_odoo_versions_from_options(ctx, versions): for odoo_version in get_odoo_versions_from_options(ctx, versions):
version_cfg = ( version_cfg = (
ctx.obj["config"]["odoo_version_settings"][odoo_version] or {} ctx.obj["config"]["odoo_version_settings"][odoo_version] or {}
@ -22,7 +26,7 @@ def pull_submodule(ctx, versions):
if version_cfg.get("repo_url"): if version_cfg.get("repo_url"):
logger.info( logger.info(
f"Pull repos.yml from git repository" f"Pull repos.yml from git repository"
f" for version {odoo_version} ..." f" for version {odoo_version}..."
) )
submodule_path = ( submodule_path = (
get_odoo_env_path(ctx, odoo_version) / "repo_submodule" get_odoo_env_path(ctx, odoo_version) / "repo_submodule"
@ -53,5 +57,5 @@ def pull_submodule(ctx, versions):
else: else:
logger.warning( logger.warning(
f"No submodule configuration found" f"No submodule configuration found"
f" for version {odoo_version} ..." f" for version {odoo_version}..."
) )

View File

@ -13,30 +13,29 @@ from odoo_openupgrade_wizard.tools.tools_system import restore_filestore
"--database-path", "--database-path",
required=True, required=True,
type=click.Path(readable=True, resolve_path=True, exists=True), type=click.Path(readable=True, resolve_path=True, exists=True),
help="Path to the database dump relative project folder.", help="Path to the database dump (inside the environment folder).",
) )
@click.option( @click.option(
"--database-format", "--database-format",
required=True, required=True,
type=click.Choice(("c", "d", "t", "p")), type=click.Choice(("c", "d", "t", "p")),
default="c", default="c",
help="Database format (see pg_dump options): " help="Format of the database dump: custom (c), directory (d), tar (t), "
"custom format compressed (c), directory (d), tar file (t)," "or plain SQL (p).",
" plain sql text (p).",
) )
@click.option( @click.option(
"--filestore-path", "--filestore-path",
required=True, required=True,
type=click.Path(readable=True, resolve_path=True, exists=True), type=click.Path(readable=True, resolve_path=True, exists=True),
help="Path to the filestore backup.", help="Path to the filestore backup (inside the environment folder).",
) )
@click.option( @click.option(
"--filestore-format", "--filestore-format",
required=True, required=True,
type=click.Choice(("d", "t", "tgz")), type=click.Choice(("d", "t", "tgz")),
default="tgz", default="tgz",
help="Filestore format: directory (d), tar file (t), " help="Format of the filestore: directory (d), tar (t), or gzip-compressed "
"tar file compressed with gzip (tgz)", "tar (tgz).",
) )
@click.pass_context @click.pass_context
def restoredb( def restoredb(
@ -47,7 +46,13 @@ def restoredb(
filestore_path, filestore_path,
filestore_format, filestore_format,
): ):
"""Restore an Odoo database and associated filestore.""" """Restore a database and its associated filestore.
This command restores a PostgreSQL database and its matching Odoo
filestore into the current OOW environment. The filestore and
database dump must be accessible from within the environment directory
so that the Docker container can read them during restore.
"""
database_path = Path(database_path) database_path = Path(database_path)
filestore_path = Path(filestore_path) filestore_path = Path(filestore_path)
@ -57,7 +62,7 @@ def restoredb(
if not str(database_path).startswith(str(absolute_env_folder_path)): if not str(database_path).startswith(str(absolute_env_folder_path)):
ctx.fail( ctx.fail(
"database-path should be inside the project path to allow " "database-path should be inside the project path to allow "
"postgresql to read to it." "PostgreSQL to read it."
) )
# Restore the database # Restore the database

View File

@ -27,13 +27,13 @@ from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
"-i", "-i",
"--init-modules", "--init-modules",
type=str, type=str,
help="List of modules to install. Equivalent to -i odoo options.", help="List of modules to install. Equivalent to -i Odoo options.",
) )
@click.option( @click.option(
"-u", "-u",
"--update-modules", "--update-modules",
type=str, type=str,
help="List of modules to update. Equivalent to -u odoo options.", help="List of modules to update. Equivalent to -u Odoo options.",
) )
@click.option( @click.option(
"-e", "-e",
@ -41,7 +41,7 @@ from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
type=click.Choice(["regular", "openupgrade"]), type=click.Choice(["regular", "openupgrade"]),
help="Force to use an openupgrade (OCA/openupgrade)" help="Force to use an openupgrade (OCA/openupgrade)"
" or a regular (odoo/odoo or OCA/OCB) base code when running odoo." " or a regular (odoo/odoo or OCA/OCB) base code when running odoo."
" Let empty to use the defaut execution of the migration step.", " Leave empty to use the default execution of the migration step.",
) )
@click.pass_context @click.pass_context
def run( def run(
@ -54,6 +54,14 @@ def run(
update_modules, update_modules,
execution_context, execution_context,
): ):
"""Run Odoo for a specific migration step.
The database will be created if it doesn't already exist. Unless the
`stop-after-init` flag is used, the Odoo instance will be available
on your host at the following URL: http://localhost:9069
(port depends on the `host_odoo_xmlrpc_port` setting in your
`config.yml` file).
"""
migration_step = get_migration_step_from_options(ctx, step) migration_step = get_migration_step_from_options(ctx, step)
ensure_database(ctx, database, state="present") ensure_database(ctx, database, state="present")
try: try:
@ -74,7 +82,7 @@ def run(
"Odoo is available on your host at http://localhost:" "Odoo is available on your host at http://localhost:"
f"{ctx.obj['config']['odoo_host_xmlrpc_port']}" f"{ctx.obj['config']['odoo_host_xmlrpc_port']}"
) )
input("Press 'Enter' to kill the odoo container and exit ...") input("Press 'Enter' to kill the odoo container and exit...")
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
logger.info("Received Keyboard Interrupt or System Exiting...") logger.info("Received Keyboard Interrupt or System Exiting...")
finally: finally:

View File

@ -25,6 +25,16 @@ from odoo_openupgrade_wizard.tools.tools_postgres import (
@demo_option @demo_option
@click.pass_context @click.pass_context
def upgrade(ctx, first_step, last_step, database, with_demo): def upgrade(ctx, first_step, last_step, database, with_demo):
"""Performs the full db migration across all steps.
For each step, this will:
1. Run `pre-migration.sql` scripts.
2. Apply "update all" (in an upgrade or update context).
3. Run `post-migration.py` scripts via XML-RPC/Odoo shell (via `odoorpc`).
"""
migration_steps = get_migration_steps_from_options( migration_steps = get_migration_steps_from_options(
ctx, first_step, last_step ctx, first_step, last_step
) )

View File

@ -51,7 +51,7 @@ def get_odoo_run_command(migration_step: dict) -> str:
def get_odoo_folder( def get_odoo_folder(
migration_step: dict, execution_context: str = False migration_step: dict, execution_context: str = None
) -> str: ) -> str:
"""return the main odoo folder, depending on the migration step. """return the main odoo folder, depending on the migration step.
(./src/odoo, ./src/openupgrade, ...)""" (./src/odoo, ./src/openupgrade, ...)"""
@ -100,7 +100,7 @@ def skip_addon_path(migration_step: dict, path: Path) -> bool:
def get_server_wide_modules_upgrade( def get_server_wide_modules_upgrade(
migration_step: dict, execution_context: str = False migration_step: dict, execution_context: str = None
) -> list: ) -> list:
"""return a list of modules to load, depending on the migration step.""" """return a list of modules to load, depending on the migration step."""
if ( if (
@ -125,8 +125,8 @@ def get_upgrade_analysis_module(migration_step: dict) -> str:
def generate_records(odoo_instance, migration_step: dict): def generate_records(odoo_instance, migration_step: dict):
logger.info( logger.info(
"Generate Records in version %s ..." "Generate Records in version %s..."
" (It can take a while)" % (migration_step["version"]) " (This may take a while)" % (migration_step["version"])
) )
if migration_step["version"] < 14.0: if migration_step["version"] < 14.0:
wizard = odoo_instance.browse_by_create( wizard = odoo_instance.browse_by_create(
@ -165,7 +165,7 @@ def generate_analysis_files(
): ):
logger.info( logger.info(
"Generate analysis files for" "Generate analysis files for"
" the modules installed on %s ..." % (initial_database) " the modules installed on %s..." % (initial_database)
) )
proxy_vals = { proxy_vals = {
"name": "Proxy to Previous version", "name": "Proxy to Previous version",
@ -176,12 +176,12 @@ def generate_analysis_files(
"password": "admin", "password": "admin",
} }
if final_step["version"] < 14.0: if final_step["version"] < 14.0:
logger.info("> Create proxy ...") logger.info("> Create proxy...")
proxy = final_odoo_instance.browse_by_create( proxy = final_odoo_instance.browse_by_create(
"openupgrade.comparison.config", proxy_vals "openupgrade.comparison.config", proxy_vals
) )
logger.info("> Create wizard ...") logger.info("> Create wizard...")
wizard = final_odoo_instance.browse_by_create( wizard = final_odoo_instance.browse_by_create(
"openupgrade.analysis.wizard", "openupgrade.analysis.wizard",
{ {
@ -189,16 +189,16 @@ def generate_analysis_files(
"write_files": True, "write_files": True,
}, },
) )
logger.info("> Launch analysis. This can take a while ...") logger.info("> Launch analysis. This can take a while...")
wizard.get_communication() wizard.get_communication()
else: else:
logger.info("> Create proxy ...") logger.info("> Create proxy...")
proxy = final_odoo_instance.browse_by_create( proxy = final_odoo_instance.browse_by_create(
"upgrade.comparison.config", proxy_vals "upgrade.comparison.config", proxy_vals
) )
logger.info("> Create wizard ...") logger.info("> Create wizard...")
analysis = final_odoo_instance.browse_by_create( analysis = final_odoo_instance.browse_by_create(
"upgrade.analysis", "upgrade.analysis",
{ {
@ -206,7 +206,7 @@ def generate_analysis_files(
}, },
) )
logger.info("> Launch analysis. This can take a while ...") logger.info("> Launch analysis. This can take a while...")
analysis.analyze() analysis.analyze()
@ -267,6 +267,6 @@ def get_openupgrade_analysis_files(
module_name = file.parent.parent.name module_name = file.parent.parent.name
result[module_name] = file result[module_name] = file
logger.debug( logger.debug(
"Version %s : %d analysis files found." % (version, len(result)) "Version %s: %d analysis files found." % (version, len(result))
) )
return result return result

View File

@ -1,6 +1,9 @@
# <OOW> : Copy of https://github.com/odoo/odoo/blob/13.0/setup/package.dfsrc # <OOW> : Copy of https://github.com/odoo/odoo/blob/13.0/setup/package.dfsrc
FROM debian:buster FROM debian:buster
RUN sed -i -- 's/security.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN sed -i -- 's/deb.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y locales && \ apt-get install -y locales && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@ -1,6 +1,9 @@
# <OOW> : Copy of https://github.com/odoo/odoo/blob/14.0/setup/package.dfsrc # <OOW> : Copy of https://github.com/odoo/odoo/blob/14.0/setup/package.dfsrc
FROM debian:buster FROM debian:buster
RUN sed -i -- 's/security.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN sed -i -- 's/deb.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y locales && \ apt-get install -y locales && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@ -1,6 +1,9 @@
# <OOW> : Copy of https://github.com/odoo/odoo/blob/15.0/setup/package.dfsrc # <OOW> : Copy of https://github.com/odoo/odoo/blob/15.0/setup/package.dfsrc
FROM debian:buster FROM debian:buster
RUN sed -i -- 's/security.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN sed -i -- 's/deb.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y locales && \ apt-get install -y locales && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@ -1,6 +1,9 @@
# <OOW> : Copy of https://github.com/odoo/odoo/blob/16.0/setup/package.dfsrc # <OOW> : Copy of https://github.com/odoo/odoo/blob/16.0/setup/package.dfsrc
FROM debian:buster FROM debian:buster
RUN sed -i -- 's/security.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN sed -i -- 's/deb.debian.org/archive.debian.org/g' /etc/apt/**.list
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y locales && \ apt-get install -y locales && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@ -1,7 +1,7 @@
import logging import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
_logger.info("Executing post-migration.py script ...") _logger.info("Executing post-migration.py script...")
env = env # noqa: F821 env = env # noqa: F821

View File

@ -27,7 +27,7 @@ def copydb(ctx, source, dest):
shutil.rmtree(dest_path, ignore_errors=True) shutil.rmtree(dest_path, ignore_errors=True)
# Copy Filestore # Copy Filestore
logger.info(f"Copy filestore of '{source}' into '{dest}' folder ...") logger.info(f"Copy filestore of '{source}' into '{dest}' directory...")
shutil.copytree(source_path, dest_path) shutil.copytree(source_path, dest_path)

View File

@ -16,7 +16,7 @@ def pull_image(image_name):
def build_image(path, tag, buildargs={}): def build_image(path, tag, buildargs={}):
logger.debug( logger.debug(
f"Building image named based on {path}/Dockerfile." f"Building image named based on {path}/Dockerfile."
" This can take a big while ..." " This can take a long time..."
) )
debug_docker_command = f"docker build {path} --tag {tag}" debug_docker_command = f"docker build {path} --tag {tag}"
for arg_name, arg_value in buildargs.items(): for arg_name, arg_value in buildargs.items():
@ -30,6 +30,7 @@ def build_image(path, tag, buildargs={}):
path=str(path), path=str(path),
tag=tag, tag=tag,
buildargs=buildargs, buildargs=buildargs,
rm=True,
) )
logger.debug("Image build done.") logger.debug("Image build done.")
except docker.errors.BuildError as buildError: except docker.errors.BuildError as buildError:
@ -58,7 +59,7 @@ def run_container(
" Did you run 'odoo-openupgrade-wizard docker-build' ?" " Did you run 'odoo-openupgrade-wizard docker-build' ?"
) )
logger.debug(f"Launching Docker container named {image_name} ...") logger.debug(f"Launching Docker container named {image_name}...")
debug_docker_command = f"docker run --name {container_name}\\\n" debug_docker_command = f"docker run --name {container_name}\\\n"
for k, v in ports.items(): for k, v in ports.items():
@ -105,8 +106,8 @@ def exec_container(container, command):
if docker_result.exit_code != 0: if docker_result.exit_code != 0:
raise Exception( raise Exception(
f"The command failed in the container {container.name}.\n" f"The command failed in the container {container.name}.\n"
f"- Command : {command}\n" f"- Command: {command}\n"
f"- Exit Code : {docker_result.exit_code}\n" f"- Exit Code: {docker_result.exit_code}\n"
f"- Output: {docker_result.output}" f"- Output: {docker_result.output}"
) )
return docker_result return docker_result

View File

@ -52,7 +52,7 @@ def get_odoo_addons_path(
ctx, ctx,
odoo_env_path: Path, odoo_env_path: Path,
migration_step: dict, migration_step: dict,
execution_context: str = False, execution_context: str = None,
) -> str: ) -> str:
"""Return """Return
- addons_path: a list of Path of that contains odoo module - addons_path: a list of Path of that contains odoo module
@ -146,12 +146,12 @@ def generate_odoo_command_options(
execution_context: str, execution_context: str,
database: str, database: str,
demo: bool = False, demo: bool = False,
update: str = False, update: str = None,
init: str = False, init: str = None,
stop_after_init: bool = False, stop_after_init: bool = False,
) -> list: ) -> list:
""" """
Generate Odoo command options as a list of strings to append to any command. Generate Odoo command options as a list of string to append to any command.
""" """
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"]) odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
@ -230,8 +230,8 @@ def generate_odoo_command(
execution_context: str, execution_context: str,
database: str, database: str,
demo: bool = False, demo: bool = False,
update: str = False, update: str = None,
init: str = False, init: str = None,
stop_after_init: bool = False, stop_after_init: bool = False,
shell: bool = False, shell: bool = False,
) -> str: ) -> str:
@ -267,13 +267,13 @@ def run_odoo(
ctx, ctx,
migration_step: dict, migration_step: dict,
detached_container: bool = False, detached_container: bool = False,
database: str = False, database: str = None,
update: str = False, update: str = None,
init: str = False, init: str = None,
stop_after_init: bool = False, stop_after_init: bool = False,
shell: bool = False, shell: bool = False,
demo: bool = False, demo: bool = False,
execution_context: str = False, execution_context: str = None,
alternative_xml_rpc_port: int = False, alternative_xml_rpc_port: int = False,
links: dict = {}, links: dict = {},
publish_ports: bool = False, publish_ports: bool = False,
@ -290,8 +290,8 @@ def run_odoo(
or migration_step["execution_context"], or migration_step["execution_context"],
demo_text=demo and "enabled" or "disabled", demo_text=demo and "enabled" or "disabled",
stop_text=stop_after_init and " (stop-after-init)" or "", stop_text=stop_after_init and " (stop-after-init)" or "",
init_text=init and " (Init : %s)" % init or "", init_text=init and " (Init: %s)" % init or "",
update_text=update and " (Update : %s)" % update or "", update_text=update and " (Update: %s)" % update or "",
) )
) )
@ -325,9 +325,9 @@ def run_container_odoo(
migration_step: dict, migration_step: dict,
command: str, command: str,
detached_container: bool = False, detached_container: bool = False,
database: str = False, database: str = None,
alternative_xml_rpc_port: int = False, alternative_xml_rpc_port: int = False,
execution_context: str = False, execution_context: str = None,
links: dict = {}, links: dict = {},
publish_ports: bool = False, publish_ports: bool = False,
): ):
@ -371,7 +371,7 @@ def execute_click_odoo_python_files(
database: str, database: str,
migration_step: dict, migration_step: dict,
python_files: list = [], python_files: list = [],
execution_context: str = False, execution_context: str = None,
): ):
if not python_files: if not python_files:
# Get post-migration python scripts to execute # Get post-migration python scripts to execute
@ -397,7 +397,7 @@ def execute_click_odoo_python_files(
try: try:
logger.info( logger.info(
f"Step {migration_step['complete_name']}." f"Step {migration_step['complete_name']}."
f" Executing script {python_file} ..." f" Executing script {python_file}..."
) )
run_container_odoo( run_container_odoo(
ctx, ctx,
@ -419,7 +419,7 @@ def execute_click_odoo_python_files(
def get_odoo_modules_from_csv(module_file_path: Path) -> list: def get_odoo_modules_from_csv(module_file_path: Path) -> list:
logger.debug(f"Reading '{module_file_path}' file ...") logger.debug(f"Reading '{module_file_path}' file...")
module_names = [] module_names = []
csvfile = open(module_file_path, "r") csvfile = open(module_file_path, "r")
spamreader = csv.reader(csvfile, delimiter=",", quotechar='"') spamreader = csv.reader(csvfile, delimiter=",", quotechar='"')

View File

@ -39,7 +39,7 @@ class OdooInstance:
logger.debug( logger.debug(
f"{x}/{_ODOO_RPC_MAX_TRY}" f"{x}/{_ODOO_RPC_MAX_TRY}"
" Unable to connect to the server." " Unable to connect to the server."
" Retrying in 1 second ..." " Retrying in 1 second..."
) )
time.sleep(1) time.sleep(1)
else: else:
@ -96,7 +96,7 @@ class OdooInstance:
while installed is False: while installed is False:
try_qty += 1 try_qty += 1
try_qty_text = f" (try #{try_qty})" if try_qty != 1 else "" try_qty_text = f" (try #{try_qty})" if try_qty != 1 else ""
logger.info(f"{log_prefix}': Installing ...{try_qty_text}") logger.info(f"{log_prefix}': Installing... {try_qty_text}")
try: try:
module.button_immediate_install() module.button_immediate_install()
installed = True installed = True

View File

@ -106,13 +106,13 @@ class Analysis(object):
last_module_version.analyse_missing_module() last_module_version.analyse_missing_module()
def estimate_workload(self, ctx): def estimate_workload(self, ctx):
logger.info("Estimate workload ...") logger.info("Estimate workload...")
for odoo_module in self.modules: for odoo_module in self.modules:
for module_version in odoo_module.module_versions.values(): for module_version in odoo_module.module_versions.values():
module_version.estimate_workload(ctx) module_version.estimate_workload(ctx)
def get_requirements(self, ctx): def get_requirements(self, ctx):
logger.info("Get requirements ...") logger.info("Get requirements...")
result = {x: {"python": {}, "bin": {}} for x in self.all_versions} result = {x: {"python": {}, "bin": {}} for x in self.all_versions}
for odoo_module in self.modules: for odoo_module in self.modules:
@ -201,14 +201,14 @@ class Analysis(object):
state = "renamed" state = "renamed"
new_module_name = renamed_modules[odoo_module.name] new_module_name = renamed_modules[odoo_module.name]
logger.debug( logger.debug(
f"{previous_version} -> {current_version} :" f"{previous_version} -> {current_version}:"
f" {odoo_module.name} renamed into {new_module_name}" f" {odoo_module.name} renamed into {new_module_name}"
) )
elif odoo_module.name in merged_modules: elif odoo_module.name in merged_modules:
state = "merged" state = "merged"
new_module_name = merged_modules[odoo_module.name] new_module_name = merged_modules[odoo_module.name]
logger.debug( logger.debug(
f"{previous_version} -> {current_version} :" f"{previous_version} -> {current_version}:"
f" {odoo_module.name} merged into {new_module_name}" f" {odoo_module.name} merged into {new_module_name}"
) )
@ -349,7 +349,7 @@ class OdooModule(object):
return res return res
def get_odoo_apps_url(self): def get_odoo_apps_url(self):
logger.info(f"Searching {self.name} in the Odoo appstore ...") logger.info(f"Searching {self.name} in the Odoo appstore...")
url = ( url = (
f"https://apps.odoo.com/apps/modules/" f"https://apps.odoo.com/apps/modules/"
f"{self.analyse.initial_version}/{self.name}/" f"{self.analyse.initial_version}/{self.name}/"
@ -364,7 +364,7 @@ class OdooModule(object):
return False return False
def get_odoo_code_search_url(self): def get_odoo_code_search_url(self):
logger.info(f"Searching {self.name} in Odoo-Code-Search ...") logger.info(f"Searching {self.name} in Odoo-Code-Search...")
url = ( url = (
f"https://odoo-code-search.com/ocs/search?" f"https://odoo-code-search.com/ocs/search?"
f"q=name%3A%3D{self.name}+version%3A{self.analyse.initial_version}" f"q=name%3A%3D{self.name}+version%3A{self.analyse.initial_version}"

View File

@ -42,7 +42,7 @@ def get_postgres_container(ctx):
# Check if volume exists # Check if volume exists
try: try:
client.volumes.get(volume_name) client.volumes.get(volume_name)
logger.debug(f"Recovering existing postgres volume: {volume_name}") logger.debug(f"Recovering existing PostgreSQL volume: {volume_name}")
except docker.errors.NotFound: except docker.errors.NotFound:
logger.info(f"Creating Postgres volume: {volume_name}") logger.info(f"Creating Postgres volume: {volume_name}")
client.volumes.create(volume_name) client.volumes.create(volume_name)
@ -53,7 +53,7 @@ def get_postgres_container(ctx):
for key, value in postgres_extra_settings.items(): for key, value in postgres_extra_settings.items():
command += f" -c {key}={value}" command += f" -c {key}={value}"
logger.info(f"Launching Postgres Container. (Image {image_name})") logger.info(f"Launching PostgreSQL Container. (Image {image_name})")
# base environement variables # base environement variables
environments = { environments = {
@ -69,7 +69,7 @@ def get_postgres_container(ctx):
postgres_version = float(image_name.split(":")[1]) postgres_version = float(image_name.split(":")[1])
except ValueError: except ValueError:
raise Exception( raise Exception(
"Unable to extract postgres version " "Unable to extract PostgreSQL version "
f"from image name {image_name}. " f"from image name {image_name}. "
"Define version in the image name is mandatory." "Define version in the image name is mandatory."
) )
@ -120,8 +120,8 @@ def execute_sql_file(ctx, database, sql_file):
if str(ctx.obj["env_folder_path"]) not in str(sql_file): if str(ctx.obj["env_folder_path"]) not in str(sql_file):
raise Exception( raise Exception(
f"The SQL file {sql_file} is not in the" f"The SQL file {sql_file} is not in the"
f" main folder {ctx.obj['env_folder_path']} available" f" main directory {ctx.obj['env_folder_path']} available"
" in the postgres container." " in the PostgreSQL container."
) )
relative_path = Path( relative_path = Path(
str(sql_file).replace(str(ctx.obj["env_folder_path"]), ".") str(sql_file).replace(str(ctx.obj["env_folder_path"]), ".")
@ -132,7 +132,7 @@ def execute_sql_file(ctx, database, sql_file):
"psql --username=odoo --dbname={database} --file {file_path}" "psql --username=odoo --dbname={database} --file {file_path}"
).format(database=database, file_path=container_path) ).format(database=database, file_path=container_path)
logger.info( logger.info(
f"Executing the script '{relative_path}' in postgres container" f"Executing the script '{relative_path}' in PostgreSQL container"
f" on database {database}" f" on database {database}"
) )
exec_container(container, command) exec_container(container, command)
@ -163,7 +163,7 @@ def execute_psql_command(
f" {' '.join(psql_args)}" f" {' '.join(psql_args)}"
) )
logger.debug( logger.debug(
f"Executing the following command in postgres container\n{command}" f"Executing the following command in PostgreSQL container\n{command}"
) )
docker_result = exec_container(container, command) docker_result = exec_container(container, command)
return docker_result.output.decode("utf-8") return docker_result.output.decode("utf-8")
@ -241,7 +241,7 @@ def chown_to_local_user(ctx, filepath: os.PathLike):
uid=user_uid, filepath=filepath uid=user_uid, filepath=filepath
) )
logger.debug( logger.debug(
f"Executing the following command in postgres container:\n{command}" f"Executing the following command in PostgreSQL container:\n{command}"
) )
chown_result = exec_container(container, command) chown_result = exec_container(container, command)
return chown_result.output.decode("utf8") return chown_result.output.decode("utf8")
@ -277,7 +277,7 @@ def execute_pg_dump(
pg_dump_args=pg_dump_args, pg_dump_args=pg_dump_args,
) )
logger.debug( logger.debug(
f"Executing the following command in postgres container:\n{command}" f"Executing the following command in PostgreSQL container:\n{command}"
) )
pg_dump_result = exec_container(container, command) pg_dump_result = exec_container(container, command)

View File

@ -19,7 +19,7 @@ def get_script_folder(ctx, migration_step: dict) -> Path:
def ensure_folder_writable(folder_path: Path): def ensure_folder_writable(folder_path: Path):
logger.info(f"Make writable the folder '{folder_path}'") logger.info(f"Make writable the directory '{folder_path}'")
try: try:
chmod(["--silent", "--recursive", "o+w", str(folder_path)]) chmod(["--silent", "--recursive", "o+w", str(folder_path)])
except ProcessExecutionError: except ProcessExecutionError:
@ -35,9 +35,9 @@ def ensure_folder_exists(
- a log is done at INFO level. - a log is done at INFO level.
""" """
if not folder_path.exists(): if not folder_path.exists():
cmd = ["--parents", folder_path] cmd = ["-p", folder_path]
cmd = ["--mode", mode] + cmd cmd = ["-m", mode] + cmd
logger.info(f"Creating folder '{folder_path}' ...") logger.info(f"Creating directory '{folder_path}'...")
mkdir(cmd) mkdir(cmd)
if git_ignore_content: if git_ignore_content:
@ -74,9 +74,9 @@ def ensure_file_exists_from_template(
if data == output: if data == output:
return return
log_text = f"Updating file '{file_path}' from template ..." log_text = f"Updating file '{file_path}' from template..."
else: else:
log_text = f"Creating file '{file_path}' from template ..." log_text = f"Creating file '{file_path}' from template..."
with open(file_path, "w") as f: with open(file_path, "w") as f:
logger.info(log_text) logger.info(log_text)
@ -90,7 +90,7 @@ def git_aggregate(folder_path: Path, config_path: Path, jobs: int):
jobs=jobs, jobs=jobs,
dirmatch=None, dirmatch=None,
do_push=False, do_push=False,
expand_env=True, expand_env=False,
env_file=None, env_file=None,
force=True, force=True,
) )
@ -98,7 +98,7 @@ def git_aggregate(folder_path: Path, config_path: Path, jobs: int):
os.chdir(folder_path) os.chdir(folder_path)
logger.info( logger.info(
f"Gitaggregate source code for {config_path}." f"Gitaggregate source code for {config_path}."
" This can take a while ..." " This can take a while..."
) )
gitaggregate_cmd.run(args) gitaggregate_cmd.run(args)

View File

@ -1,6 +1,6 @@
[project] [project]
name = "odoo-openupgrade-wizard" name = "odoo-openupgrade-wizard"
version = "1.3.0" version = "1.3.1"
description = "CLI tool to manage Odoo Major Upgrades" description = "CLI tool to manage Odoo Major Upgrades"
authors = [ authors = [
{name = "Sylvain LE GAL", email = "sylvain.legal@grap.coop"}, {name = "Sylvain LE GAL", email = "sylvain.legal@grap.coop"},
@ -64,7 +64,6 @@ pylint = "*"
tox = "*" tox = "*"
towncrier = "*" towncrier = "*"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"

View File

@ -25,7 +25,7 @@ def move_to_test_folder():
if os.getcwd().endswith("tests/data/output"): if os.getcwd().endswith("tests/data/output"):
return return
test_folder_path = Path("tests/data/output") test_folder_path = Path("tests/data/output")
mkdir([test_folder_path, "--parents"]) mkdir(["-p", test_folder_path])
os.chdir(test_folder_path) os.chdir(test_folder_path)