Compare commits
21 Commits
main
...
TEST-virtu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7daa04688f | ||
|
|
2668e54fb0 | ||
|
|
930f9f8dd7 | ||
|
|
e8a7e7fdc8 | ||
|
|
c362390902 | ||
|
|
906c0f3ea7 | ||
|
|
dd08967f5e | ||
|
|
6c3c555286 | ||
|
|
7580a93023 | ||
|
|
613eeb9d93 | ||
|
|
c069757168 | ||
|
|
a27fcabbba | ||
|
|
e298b73efb | ||
|
|
4d144d557f | ||
|
|
f78f0677ae | ||
|
|
5f14b50a31 | ||
|
|
1d52b51b54 | ||
|
|
507d368b30 | ||
|
|
5c98761086 | ||
|
|
e81e22e91b | ||
|
|
d64140487f |
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,10 +1,7 @@
|
||||||
env
|
env
|
||||||
.gitlab-ci-venv
|
|
||||||
__pycache__
|
__pycache__
|
||||||
.tox
|
.tox
|
||||||
dist
|
|
||||||
.coverage
|
.coverage
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
/tests/data/output/*
|
/tests/output/*
|
||||||
log/
|
log/
|
||||||
_auto_generated_odoo.cfg
|
|
||||||
|
|
|
||||||
174
.gitlab-ci.yml
174
.gitlab-ci.yml
|
|
@ -1,131 +1,69 @@
|
||||||
|
---
|
||||||
|
image: python3.6
|
||||||
|
|
||||||
|
cache:
|
||||||
|
key: one-key-to-rule-them-all
|
||||||
|
paths:
|
||||||
|
- .venv
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- lint
|
- prepare
|
||||||
- test
|
- linting
|
||||||
- build
|
- tests
|
||||||
- publish
|
|
||||||
- release
|
|
||||||
|
|
||||||
pre-commit:
|
|
||||||
image: python:alpine
|
install_tools:
|
||||||
stage: lint
|
stage: prepare
|
||||||
rules:
|
|
||||||
# Run only if merge request
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
# Run if commit on default branch
|
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
||||||
before_script:
|
|
||||||
- apk add git
|
|
||||||
- pip install pre-commit
|
|
||||||
script:
|
script:
|
||||||
- pre-commit run --all --show-diff-on-failure --verbose --color always
|
- python -m venv .venv
|
||||||
|
- source .venv/bin/activate
|
||||||
check_version:
|
|
||||||
stage: lint
|
|
||||||
image: python:alpine
|
|
||||||
rules:
|
|
||||||
# Run if commit that start with a version number is pushed
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+.*/
|
|
||||||
before_script:
|
|
||||||
- pip install poetry
|
- pip install poetry
|
||||||
- poetry --version
|
- poetry --version
|
||||||
script:
|
- poetry install -v
|
||||||
# Ensure tag is the same as the program version
|
- echo $PATH
|
||||||
- test $(poetry version --short) = $CI_COMMIT_TAG
|
- echo $PYTHONPATH
|
||||||
|
|
||||||
check_changelog:
|
black:
|
||||||
stage: lint
|
stage: linting
|
||||||
image: python:alpine
|
|
||||||
rules:
|
|
||||||
# Run if commit that start with a version number is pushed
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/
|
|
||||||
before_script:
|
|
||||||
- pip install poetry
|
|
||||||
- poetry --version
|
|
||||||
script:
|
script:
|
||||||
# Ensure change log is completed correctly
|
# Intall pipx to install black
|
||||||
- cat CHANGES.rst | grep $CI_COMMIT_TAG
|
# otherwise, it fails.
|
||||||
|
# TODO, check with Coop It Easy
|
||||||
|
- pip install --user pipx
|
||||||
|
- python -m pipx ensurepath
|
||||||
|
- source ~/.profile
|
||||||
|
# Classic CI
|
||||||
|
- pipx install black
|
||||||
|
- black --version
|
||||||
|
- black --check .
|
||||||
|
|
||||||
|
pylint:
|
||||||
|
stage: linting
|
||||||
|
script:
|
||||||
|
- source .venv/bin/activate
|
||||||
|
- pylint --version
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
image:
|
stage: tests
|
||||||
name: python:$PYTHON_VERSION-alpine
|
image: python
|
||||||
stage: test
|
cache: {}
|
||||||
tags:
|
script:
|
||||||
- cie-oow-dind-runner
|
# Install python3.6, because it is required to
|
||||||
rules:
|
# run virtualenv of odoo 14.0
|
||||||
# Run if merge request
|
- apt-get install python3.6
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
# Classic CI
|
||||||
# Run if commit on default branch
|
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
||||||
services:
|
|
||||||
- name: docker:dind
|
|
||||||
alias: dind
|
|
||||||
variables:
|
|
||||||
ODOO_RPC_URL: dind
|
|
||||||
DOCKER_HOST: tcp://dind:2375/
|
|
||||||
DOCKER_DRIVER: overlay2
|
|
||||||
DOCKER_TLS_CERTDIR: ""
|
|
||||||
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+)\%/'
|
|
||||||
before_script:
|
|
||||||
- apk add git
|
|
||||||
- pip install poetry
|
- pip install poetry
|
||||||
- poetry --version
|
- poetry --version
|
||||||
- export PATH="$HOME/.local/bin:$PATH"
|
- poetry install -v
|
||||||
- poetry install --all-extras
|
- poetry run pytest --version
|
||||||
script:
|
- poetry run pytest --cov odoo_openupgrade_wizard -v
|
||||||
- poetry run pytest -vv -x --cov=odoo_openupgrade_wizard
|
|
||||||
parallel:
|
|
||||||
matrix:
|
|
||||||
- PYTHON_VERSION:
|
|
||||||
- "3.9"
|
|
||||||
- "3.13"
|
|
||||||
|
|
||||||
build:
|
tox:
|
||||||
stage: build
|
stage: tests
|
||||||
image: python:alpine
|
image: themattrix/tox
|
||||||
rules:
|
cache: {}
|
||||||
# Run if merge request
|
script:
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
- pip install poetry tox
|
||||||
# Run if commit on default branch
|
- tox --version
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
||||||
# Run if commit that start with a version number is pushed
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+.*/
|
|
||||||
before_script:
|
|
||||||
- pip install poetry
|
|
||||||
- poetry --version
|
- poetry --version
|
||||||
script:
|
- tox
|
||||||
- poetry build
|
|
||||||
artifacts:
|
|
||||||
untracked: true
|
|
||||||
paths:
|
|
||||||
- dist/
|
|
||||||
|
|
||||||
publish:
|
|
||||||
stage: publish
|
|
||||||
image: python:alpine
|
|
||||||
rules:
|
|
||||||
# Run if commit that start with a version number is pushed
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+.*/
|
|
||||||
before_script:
|
|
||||||
- pip install poetry
|
|
||||||
- poetry --version
|
|
||||||
- ls -l dist
|
|
||||||
# Uncomment for testing build publication on test.pypi.org
|
|
||||||
#- poetry config repo.pypitest https://test.pypi.org/legacy/
|
|
||||||
script:
|
|
||||||
- poetry publish --skip-existing --username $PYPI_USER --password $PYPI_TOKEN
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
||||||
rules:
|
|
||||||
# Run only for a patch, minor or major release
|
|
||||||
# This avoid creating a release for alpha, beta, or other special
|
|
||||||
# releases
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/
|
|
||||||
script:
|
|
||||||
- echo "running release_job for $CI_COMMIT_TAG"
|
|
||||||
release:
|
|
||||||
name: "$CI_COMMIT_TAG"
|
|
||||||
description: "Change log here: ${CI_PROJECT_URL}/-/blob/main/CHANGES.rst"
|
|
||||||
tag_name: "$CI_COMMIT_TAG"
|
|
||||||
ref: "$CI_COMMIT_SHA"
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
|
---
|
||||||
exclude: '^tests/data/output_expected/'
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v3.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: exclude
|
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
|
|
@ -19,23 +19,18 @@ repos:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-symlinks
|
- id: check-symlinks
|
||||||
- repo: https://github.com/pre-commit/mirrors-isort
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
rev: v5.10.1
|
rev: v5.7.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.11.0
|
rev: 20.8b1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: "6.1.0"
|
rev: "3.9.2"
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
# - repo: https://gitlab.com/smop/pre-commit-hooks
|
# - repo: https://gitlab.com/smop/pre-commit-hooks
|
||||||
# rev: v1.0.0
|
# rev: v1.0.0
|
||||||
# hooks:
|
# hooks:
|
||||||
# - id: check-gitlab-ci
|
# - id: check-gitlab-ci
|
||||||
# - repo: https://github.com/kadrach/pre-commit-gitlabci-lint
|
|
||||||
# rev: 22d0495c9894e8b27cc37c2ed5d9a6b46385a44c
|
|
||||||
# hooks:
|
|
||||||
# - id: gitlabci-lint
|
|
||||||
# # args: ["https://custom.gitlab.host.com"]
|
|
||||||
|
|
|
||||||
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
|
|
||||||
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
|
|
||||||
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Python-Debugger: Remoteanfügung",
|
|
||||||
"type": "debugpy",
|
|
||||||
"request": "attach",
|
|
||||||
"connect": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 5678
|
|
||||||
},
|
|
||||||
"pathMappings": [
|
|
||||||
{
|
|
||||||
"localRoot": "${workspaceFolder}",
|
|
||||||
"remoteRoot": "/home/lotzm/odoo-openupgrade-wizard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"python.defaultInterpreterPath": "/home/lotzm/.local/share/pipx/venvs/odoo-openupgrade-wizard/bin/python"
|
|
||||||
}
|
|
||||||
285
CHANGES.rst
285
CHANGES.rst
|
|
@ -1,285 +0,0 @@
|
||||||
odoo-openupgrade-wizard changes
|
|
||||||
*******************************
|
|
||||||
|
|
||||||
This file compiles releases and changes made in
|
|
||||||
``odoo-openupgrade-wizard``.
|
|
||||||
|
|
||||||
.. 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)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Disable default Odoo memory limit (of 2560 MiB) by default.
|
|
||||||
This can be adjusted in the `odoo.conf` file for each version.
|
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
- Improve release documentation for developpers.
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- set a default 'postgres' database in ``oow psql`` command, to avoid
|
|
||||||
force user to set a database, in case of dbless command.
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.2.0 (2025-04-12)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Add an option ``update`` in the configuration file at migration_step level.
|
|
||||||
If disabled, the update=all step will be skipped during this step.
|
|
||||||
That can be interesting to save time, during the first and the last steps of the migration
|
|
||||||
process (when ``execution_context='regular'``).
|
|
||||||
- Improve workload analysis file with button to hide done module.
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Handles the case where the filestore data provided in the source is in a "filestore" subfolder.
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- restoredb: Use builtins click feature to check if provided paths exist
|
|
||||||
- Refactoring of the function tools.tools_odoo.generate_odoo_command.
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.1.0 (2024-11-07)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Support 17.0 serie.
|
|
||||||
- Support 18.0 serie.
|
|
||||||
- add a new command ``odoo-openupgrade-wizard guess-requirement`` that
|
|
||||||
initialize the python and bin requirements files, based on the odoo
|
|
||||||
modules present in the modules.csv file, and analyzing code.
|
|
||||||
(``__manifest__.py`` and ``setup.py`` files)
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- Add support of python 3.13.
|
|
||||||
- Test only on the first and the last supported python version. (python3.9 and
|
|
||||||
python3.13).
|
|
||||||
This change allow to save time and resources on CI execution.
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.0.3 (2024-10-09)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Fix crash when building container fails.
|
|
||||||
- Make odoo openupgrade wizard working in the following combination:
|
|
||||||
Odoo version <= 12 + Postgresql version >= 14
|
|
||||||
- New fix for error that append randomly when removing a container.
|
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
- Add towncrier and newsfragments info in dev documentation.
|
|
||||||
- Improve the README.md file, hightlighting code section.
|
|
||||||
- Update contributors list until October 2024
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- Update of python libraries version, using ``poetry update``.
|
|
||||||
|
|
||||||
* Removing incremental (22.10.0)
|
|
||||||
* Updating attrs (23.2.0 -> 24.2.0)
|
|
||||||
* Updating certifi (2024.2.2 -> 2024.8.30)
|
|
||||||
* Updating filelock (3.13.2 -> 3.16.1)
|
|
||||||
* Updating idna (3.6 -> 3.10)
|
|
||||||
* Updating packaging (24.0 -> 24.1)
|
|
||||||
* Updating platformdirs (4.2.0 -> 4.3.6)
|
|
||||||
* Updating pygments (2.17.2 -> 2.18.0)
|
|
||||||
* Updating pyyaml (6.0.1 -> 6.0.2)
|
|
||||||
* Updating tomli (2.0.1 -> 2.0.2)
|
|
||||||
* Updating typing-extensions (4.10.0 -> 4.12.2)
|
|
||||||
* Updating urllib3 (2.2.1 -> 2.2.3)
|
|
||||||
* Updating zipp (3.18.1 -> 3.20.2)
|
|
||||||
* Updating argcomplete (3.2.3 -> 3.5.1)
|
|
||||||
* Updating astroid (3.1.0 -> 3.3.5)
|
|
||||||
* Updating coverage (7.4.4 -> 7.6.1)
|
|
||||||
* Updating dill (0.3.8 -> 0.3.9)
|
|
||||||
* Updating gitpython (3.1.42 -> 3.1.43)
|
|
||||||
* Updating jinja2 (3.1.3 -> 3.1.4)
|
|
||||||
* Updating requests (2.31.0 -> 2.32.3)
|
|
||||||
* Updating rich (13.7.1 -> 13.9.2)
|
|
||||||
* Updating setuptools (69.2.0 -> 75.1.0)
|
|
||||||
* Updating tomlkit (0.12.4 -> 0.13.2)
|
|
||||||
* Updating virtualenv (20.25.1 -> 20.26.6)
|
|
||||||
* Updating docker (7.0.0 -> 7.1.0)
|
|
||||||
* Updating plumbum (1.8.2 -> 1.9.0)
|
|
||||||
* Updating pylint (3.1.0 -> 3.3.1)
|
|
||||||
* Updating towncrier (23.11.0 -> 24.8.0)
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.0.2 (2024-10-06)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Added a check to ensure the source exists before database operations,
|
|
||||||
preventing the destination from being dropped if the source is missing.
|
|
||||||
(check-db-exist-before-operations)
|
|
||||||
- Fix error that append randomly when removing a container. (container-removal)
|
|
||||||
- Require to specify the --database arg for every command that needs it
|
|
||||||
(install_from_csv, psql, run, generate_module_analysis)
|
|
||||||
(require-database-arg)
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.0.1 (2024-10-01)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Avoid to crash if postgresql-version is not set, adding prompt option
|
|
||||||
and add extra text to mention postgresql version constraints.
|
|
||||||
(postgresql-version-prompt)
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- Refactor to simplify configuration_version_dependant.py file.
|
|
||||||
(version-simplification)
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 1.0.0 (2024-09-30)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Add option ``p`` (SQL format) allowing use from ``--database-format`` CLI.
|
|
||||||
This allows you to restore database in SQL format (used by odoo full backup)
|
|
||||||
(add-sql-option-for-database-format-cli)
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Allow to run multiple `post-*.py` script for each steps.
|
|
||||||
(allow-run-multiple-post-scripts)
|
|
||||||
- Fix metadata of the python package on PyPI. (fix-package-metadata)
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 0.7.0 (2024-05-02)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Add ``--config-file`` and ``--modules-file`` CLI options. This allows to use
|
|
||||||
different files than the default ones. This is useful when using the same
|
|
||||||
environment for different databases. (add-config-file-cli-option)
|
|
||||||
- Add database name to container name and publish Docker ports only when needed
|
|
||||||
to allow to upgrade multiple databases in parallel.
|
|
||||||
(allow-to-upgrade-multiple-databases-in-parallel)
|
|
||||||
- Drop support for python version < 3.9. Update dependencies and fix some
|
|
||||||
issue liked to that. (drop-old-python-support)
|
|
||||||
- Add a new option ``--postgresql-version`` in ``oow init`` command to
|
|
||||||
define the version of the postgresql image to be used for the project.
|
|
||||||
(option-postgresql-version)
|
|
||||||
- Factorize code. Allways set --log-level=DEBUG in tests.
|
|
||||||
(set-log-level-debug-default-in-cli_runner_invoke)
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Allow hyphen-minus character in database names.
|
|
||||||
(allow-minus-in-database-names)
|
|
||||||
|
|
||||||
|
|
||||||
odoo-openupgrade-wizard 0.6.0 (2024-03-20)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
- Add ``dropdb`` command to easily delete database and filestore of existing
|
|
||||||
Odoo databases in PostgreSQL container. (add-dropdb)
|
|
||||||
- With ``install-from-csv`` and the ``--with-demo / --without-demo``, you
|
|
||||||
can control if the created database will be populated with demo data or
|
|
||||||
not. (add-install-from-csv-demo)
|
|
||||||
- Add ``restoredb`` command to restore database and filestore in the
|
|
||||||
PostgreSQL container. (add-restoredb)
|
|
||||||
- Add ``--update-modules`` option to ``run`` command. (imp-run-update-modules)
|
|
||||||
- ``run`` and ``upgrade`` command are now harmonized with the option
|
|
||||||
``--with-demo / --without-demo``. By default demo data is always false.
|
|
||||||
(imp-run-upgrade)
|
|
||||||
|
|
||||||
|
|
||||||
Bugfixes
|
|
||||||
--------
|
|
||||||
|
|
||||||
- ``copydb`` now copy also the filestore of the database. (copydb-filestore)
|
|
||||||
- Fix warning message for ``estimate-workload`` command.
|
|
||||||
(fix-estimate-workload-warning-message)
|
|
||||||
- Fix getting url on apps.odoo.com that prevent from running
|
|
||||||
``estimate-workload`` command. (fix-getting-url)
|
|
||||||
- Fix crash when a addons-path directory does not contain modules.
|
|
||||||
Directory that does not contains odoo modules are now removed from
|
|
||||||
addons-path option of odoo. (fix-repo)
|
|
||||||
|
|
||||||
|
|
||||||
Misc
|
|
||||||
----
|
|
||||||
|
|
||||||
- ci-improvement
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Developers
|
|
||||||
|
|
||||||
* Sylvain LE GAL, from [GRAP](http://www.grap.coop), since March 2022
|
|
||||||
* Rémy TAYMANS, from [Coop It Easy](https://coopiteasy.be/), since June 2022
|
|
||||||
|
|
||||||
* Cyril JEANNERET, from [Camptocamp](https://www.camptocamp.com), since April 2023
|
|
||||||
* Simon MAILLARD, form [Ogesta](https://ogesta.fr/), since July 2023
|
|
||||||
|
|
||||||
* Hugues DE KEYSER, from [Coop It Easy](https://coopiteasy.be/), since April 2024
|
|
||||||
* Gabriel PICKENHAYN, since May 2024
|
|
||||||
* Boris GALLET, since September 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
|
|
||||||
* Sergio Zanchetta, from [PNLug](https://www.pnlug.it/), since January 2025
|
|
||||||
* Ken WOYCHESKO, since May 2025
|
|
||||||
|
|
||||||
# Reviewers
|
|
||||||
|
|
||||||
* Sébastien BEAU, from Akretion (https://akretion.com)
|
|
||||||
246
DEVELOP.md
246
DEVELOP.md
|
|
@ -1,54 +1,32 @@
|
||||||
# Installation to develop
|
# Requirements
|
||||||
|
|
||||||
## Basic installation
|
TODO (poetry, etc...)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://gitlab.com/odoo-openupgrade-wizard/odoo-openupgrade-wizard/
|
git clone https://gitlab.com/odoo-openupgrade-wizard/odoo-openupgrade-wizard/
|
||||||
cd odoo-openupgrade-wizard
|
cd odoo-openupgrade-wizard
|
||||||
virtualenv env --python=python3
|
virtualenv env --python=python3.X
|
||||||
. ./env/bin/activate
|
. ./env/bin/activate
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
|
Note : ``python3.X`` should be >= to ``python3.6``
|
||||||
|
|
||||||
|
|
||||||
``odoo-openupgrade-wizard`` commands are now available in your virutalenv.
|
``odoo-openupgrade-wizard`` commands are now available in your virutalenv.
|
||||||
|
|
||||||
## Advanced installation
|
|
||||||
|
|
||||||
If you want to use this library without installing anything in your
|
|
||||||
system, execute the following steps, otherwise, go to 'Installation' part.
|
|
||||||
|
|
||||||
1. Run a docker container:
|
|
||||||
|
|
||||||
``docker run -it ubuntu:focal``
|
|
||||||
|
|
||||||
2. Execute the following commnands
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
apt-get update
|
|
||||||
apt-get install git python3 python3-pip python3-venv
|
|
||||||
|
|
||||||
python3 -m pip install --user pipx
|
|
||||||
python3 -m pipx ensurepath
|
|
||||||
|
|
||||||
su root
|
|
||||||
|
|
||||||
pipx install virtualenv
|
|
||||||
pipx install poetry
|
|
||||||
```
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
|
|
||||||
## Via pytest (simple)
|
## Via pytest
|
||||||
|
|
||||||
This will run tests only for the current ``python3.X`` version.
|
This will run tests only for the current ``python3.X`` version.
|
||||||
|
|
||||||
(in your virtualenv)
|
(in your virtualenv)
|
||||||
```
|
```
|
||||||
poetry run pytest --cov odoo_openupgrade_wizard --verbosity=2 --exitfirst
|
poetry run pytest --cov odoo_openupgrade_wizard -v
|
||||||
```
|
```
|
||||||
|
## Via Tox
|
||||||
## Via Tox (advanced)
|
|
||||||
|
|
||||||
This will run tests for all the python versions put in the ``tox.ini`` folder.
|
This will run tests for all the python versions put in the ``tox.ini`` folder.
|
||||||
|
|
||||||
|
|
@ -59,206 +37,4 @@ tox
|
||||||
|
|
||||||
Note : you should have all the python versions available in your local system.
|
Note : you should have all the python versions available in your local system.
|
||||||
|
|
||||||
|
# Structure of the project
|
||||||
```
|
|
||||||
sudo apt-get install python3.6 python3.6-distutils
|
|
||||||
sudo apt-get install python3.7 python3.7-distutils
|
|
||||||
sudo apt-get install python3.8 python3.8-distutils
|
|
||||||
sudo apt-get install python3.9 python3.9-distutils
|
|
||||||
```
|
|
||||||
|
|
||||||
## Via Gitlab Runner locally
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
# Install tools
|
|
||||||
pipx install gitlabci-local
|
|
||||||
|
|
||||||
# Run new available command
|
|
||||||
gitlabci-local
|
|
||||||
```
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
|
|
||||||
Some docker command could help you when using / developping this tools.
|
|
||||||
|
|
||||||
**Enter the postgres container**
|
|
||||||
|
|
||||||
docker exec -it POSTGRES_CONTAINER_NAME /bin/bash
|
|
||||||
|
|
||||||
# Contribute
|
|
||||||
|
|
||||||
## Provide newsfragments in your merge requests
|
|
||||||
|
|
||||||
If you propose a merge request, please add a newsfragments with it.
|
|
||||||
|
|
||||||
A newsfragment is a small file describing what is done in the merge
|
|
||||||
request. The file has a extension (e.g. `.feature`, `.bugfix`, etc) that
|
|
||||||
describe witch type of modification you are doing. This newsfragment
|
|
||||||
file will populate the CHANGES.rst file for the next release.
|
|
||||||
Documentation and the full list of default available extension can be
|
|
||||||
found [here](https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments).
|
|
||||||
|
|
||||||
Newsfragments must be put in the `newsfragments` directory at the root
|
|
||||||
of the project. You can install `towncrier` via `pipx install
|
|
||||||
towncrier`.
|
|
||||||
|
|
||||||
The newsfragments file will be processed with `towncrier` by the
|
|
||||||
maintainers during the release process.
|
|
||||||
|
|
||||||
Use `towncrier create --help` to see the available extension for the
|
|
||||||
newsfragement files. The name of the file can be a number referring an
|
|
||||||
issue of the project or a
|
|
||||||
[slug](https://en.wikipedia.org/wiki/Clean_URL#Slug) that start with
|
|
||||||
a `+` symbol.
|
|
||||||
|
|
||||||
This is a example of newsfragments.
|
|
||||||
|
|
||||||
`newsfragments/+sub-command-cowsay.feature`:
|
|
||||||
```
|
|
||||||
Adds a new subcommand `cowsay` to allow poeple to speak like a cow.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Add python dependencies
|
|
||||||
|
|
||||||
If you add new dependencies, you have to:
|
|
||||||
|
|
||||||
- add the reference in the file ``pyproject.toml``
|
|
||||||
|
|
||||||
- run the following command in your virtualenv : ``poetry update``
|
|
||||||
|
|
||||||
## Release on Gitlab and publish on PyPI
|
|
||||||
|
|
||||||
Building, releasing and publishing a new version works with tags.
|
|
||||||
|
|
||||||
Tags that trigger a build and a publication on PyPI must have a name
|
|
||||||
equal to the version of the program found in `pyproject.toml`.
|
|
||||||
|
|
||||||
Tags that matches an change in major, minor or patch version will
|
|
||||||
trigger a release on gitlab.
|
|
||||||
|
|
||||||
Tags that are alpha, beta, pre-release, etc does not trigger a release
|
|
||||||
on gitlab, but they trigger a publication on PyPI.
|
|
||||||
|
|
||||||
Before creating a tag, ensure that the version of the program is
|
|
||||||
updated. To update the program version you can use the command:
|
|
||||||
|
|
||||||
```
|
|
||||||
poetry version {major,minor,patch}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure that the `CHANGES.rst` file contains information about this new
|
|
||||||
release, if it is a major, minor or patch release. For alpha, beta, etc
|
|
||||||
release information will be published with the next major, minor or
|
|
||||||
patch release.
|
|
||||||
|
|
||||||
To generate the `CHANGES.rst`, run the towncrier tool:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
towncrier build
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `--draft` option to preview the result.
|
|
||||||
|
|
||||||
Then push a commit with the version and the changlog updated on the main
|
|
||||||
branch.
|
|
||||||
|
|
||||||
When everything is good on the main branch and that tests succeed,
|
|
||||||
create a new tag with the same name as the version in `pyproject.toml`
|
|
||||||
file. Tags can be created via Code > Tags > New tag.
|
|
||||||
|
|
||||||
To see if the publication on PyPI and the release on gitlab were done
|
|
||||||
correctly, go in Build > Pipelines. You will find a pipeline for the tag
|
|
||||||
you just created.
|
|
||||||
|
|
||||||
|
|
||||||
# Understanding the library
|
|
||||||
|
|
||||||
## Tools to understand
|
|
||||||
|
|
||||||
The library is using many tools. It is recommanded to understand that tools
|
|
||||||
to contribute to that project:
|
|
||||||
|
|
||||||
* Docker (https://www.docker.com/)
|
|
||||||
* Gitlab CI (https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
|
||||||
* openupgrade project (https://github.com/oca/openupgrade) and related openupgradelib (https://github.com/oca/openupgradelib)
|
|
||||||
* poetry (https://python-poetry.org/)
|
|
||||||
* odoorpc (https://github.com/OCA/odoorpc)
|
|
||||||
* git-aggregator (https://github.com/acsone/git-aggregator)
|
|
||||||
|
|
||||||
Also this project is inspired by the following tools:
|
|
||||||
|
|
||||||
* click-odoo-contrib (https://github.com/acsone/click-odoo-contrib)
|
|
||||||
|
|
||||||
|
|
||||||
# Dockerfile information
|
|
||||||
|
|
||||||
### From version 5 to 7
|
|
||||||
|
|
||||||
There are no plans to make the tool work for these versions.
|
|
||||||
|
|
||||||
### From version 8 to 10 (Python2)
|
|
||||||
|
|
||||||
Try to create dockerfile, based on the odoo official ones fails. Any help welcome.
|
|
||||||
|
|
||||||
### From version 11.0 to latest version. (Python3)
|
|
||||||
|
|
||||||
The Dockerfile of the version 11 to the lastest version of Odoo are written this way :
|
|
||||||
|
|
||||||
- Copy the content of https://github.com/odoo/odoo/blob/ODOO_VERSION/setup/package.dfsrc
|
|
||||||
- remove all the part after the big ``apt-get install``
|
|
||||||
- install debian package ``git`` to have the possibility to pip install from git url.
|
|
||||||
- install custom debian packages
|
|
||||||
- install python odoo requirements
|
|
||||||
- install python ``setuptools-scm`` lib to have the possibility to pip install ``openupgradelib`` from git url.
|
|
||||||
- install python custom requirements
|
|
||||||
- makes link between external user and docker odoo user
|
|
||||||
|
|
||||||
## Réferences
|
|
||||||
|
|
||||||
- how to install gitlab runner locally:
|
|
||||||
|
|
||||||
https://docs.gitlab.com/runner/install/linux-manually.html
|
|
||||||
|
|
||||||
- Check your CI locally. (French)
|
|
||||||
|
|
||||||
https://blog.stephane-robert.info/post/gitlab-valider-ci-yml/
|
|
||||||
|
|
||||||
https://blog.callr.tech/building-docker-images-with-gitlab-ci-best-practices/
|
|
||||||
|
|
||||||
|
|
||||||
## Python version settings depending on the debian version
|
|
||||||
|
|
||||||
This part can help you if you want to change your autogenerated Dockerfiles.
|
|
||||||
|
|
||||||
See (https://github.com/odoo/odoo/blob/ODOO_VERSION/setup/package.dfdebian)
|
|
||||||
|
|
||||||
### debian:wheezy (V7) (for Odoo: 8.0)
|
|
||||||
- Ubuntu release : 12.04 xxx, 12.10 xxx, 13.04 xxx, 14.10 xxx
|
|
||||||
- python2.7
|
|
||||||
- First release : 04/05/2013
|
|
||||||
- End LTS : May 2018
|
|
||||||
|
|
||||||
### debian:jessie (V8) (for Odoo: 9.0, 10.0)
|
|
||||||
- Ubuntu release : 14.04 trusty, 14.10 utopic, 15.04 vivid, 15.10 wily
|
|
||||||
- python2.7
|
|
||||||
- First release : 26/04/2015
|
|
||||||
- End LTS : June 2020
|
|
||||||
|
|
||||||
### debian:stretch (V9) (for Odoo: 11.0, 12.0)
|
|
||||||
- Ubuntu releases : 16.04 xenial, 16.10 yakkety, 17.04 zesty, 17.10 artful
|
|
||||||
- python2.7 and python3.5
|
|
||||||
- First release : 17/06/2017
|
|
||||||
- End LTS : June 2022
|
|
||||||
|
|
||||||
### debian:buster (13.0, 14.0)
|
|
||||||
- Ubuntu release : 18.04 bionic, 18.10 cosmic, 19.04 disco, 19.10 eoan
|
|
||||||
- python2.7 and python3.7
|
|
||||||
- First release : 06/07/2019
|
|
||||||
- End LTS : Undefined.
|
|
||||||
|
|
||||||
## debian:bullseye (15.0, 16.0)
|
|
||||||
- Ubuntu release : 20.04 focal, 20.10 groovy, 21.04 hirsute, 21.10 impish
|
|
||||||
- python3.9
|
|
||||||
- First release : 14/07/2021
|
|
||||||
- End LTS : Undefined.
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
# TODO
|
|
||||||
|
|
||||||
* with coop it easy :
|
|
||||||
- short_help of group decorator ? seems useless...
|
|
||||||
|
|
||||||
* add constrains on ``--step`` option.
|
|
||||||
|
|
||||||
* revert : set 777 to log and filestore to be able to write on this folder
|
|
||||||
inside the containers. TODO, ask to coop it easy or commown for better alternative.
|
|
||||||
|
|
||||||
* allow to call odoo-bin shell, via : https://github.com/d11wtq/dockerpty
|
|
||||||
(see https://github.com/docker/docker-py/issues/247)
|
|
||||||
|
|
||||||
|
|
||||||
# List of the series of odoo
|
|
||||||
# python version is defined, based on the OCA CI.
|
|
||||||
# https://github.com/OCA/oca-addons-repo-template/blob/master/src/.github/workflows/%7B%25%20if%20ci%20%3D%3D%20'GitHub'%20%25%7Dtest.yml%7B%25%20endif%20%25%7D.jinja
|
|
||||||
|
|
||||||
|
|
||||||
# tips
|
|
||||||
```
|
|
||||||
# execute sql request in postgres docker
|
|
||||||
docker exec db psql --username=odoo --dbname=test_v12 -c "update res_partner set ""email"" = 'bib@bqsdfqsdf.txt';"
|
|
||||||
```
|
|
||||||
|
|
||||||
# TODO Nice To have
|
|
||||||
|
|
||||||
- Fix gitlabci-local. For the time being, it is not possible to debug
|
|
||||||
locally. (there are extra bugs locally that doesn't occures on gitlab,
|
|
||||||
in ``cli_B_03_run_test.py``...
|
|
||||||
|
|
||||||
|
|
||||||
- add
|
|
||||||
|
|
||||||
# Try gitlab runner
|
|
||||||
|
|
||||||
curl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb"
|
|
||||||
|
|
||||||
sudo dpkg -i gitlab-runner_amd64.deb
|
|
||||||
|
|
||||||
(https://docs.gitlab.com/runner/install/linux-manually.html)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
- check dynamic user id with
|
|
||||||
https://github.com/camptocamp/docker-odoo-project/blob/master/bin/docker-entrypoint.sh
|
|
||||||
|
|
||||||
|
|
||||||
in modules.csv.j2 :
|
|
||||||
# TODO, this value are usefull for test for analyse between 13 and 14.
|
|
||||||
# move that values in data/extra_script/modules.csv
|
|
||||||
# and let this template with only 'base' module.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Without postgres optimization
|
|
||||||
2022-07-13 19:42
|
|
||||||
2022-07-13 21:20
|
|
||||||
|
|
||||||
Duration : 1:37 (107)
|
|
||||||
|
|
||||||
## With postgres optimization
|
|
||||||
|
|
||||||
2022-07-13 21:52
|
|
||||||
2022-07-14 23:11
|
|
||||||
|
|
||||||
duration : 1:19 (79)
|
|
||||||
|
|
||||||
16%
|
|
||||||
19
PATCH.md
19
PATCH.md
|
|
@ -1,19 +0,0 @@
|
||||||
# Loakale Entwicklung an oow
|
|
||||||
Das geht am einfachsten mit **pipx** im "editable mode" (`--editable`), sodass Änderungen im Arbeitsverzeichnis sofort wirksam sind.
|
|
||||||
|
|
||||||
Angenommen, dein Arbeitsverzeichnis ist odoo-openupgrade-wizard, dann führe im Terminal aus:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pipx install --editable /home/lotzm/gitea.hobbyhimmel/odoo-openupgrade-wizard
|
|
||||||
```
|
|
||||||
|
|
||||||
Dadurch wird ein Symlink auf dein Arbeitsverzeichnis erstellt und du kannst direkt am Code arbeiten.
|
|
||||||
Das Kommando `oow` ist dann wie gewohnt verfügbar, aber nutzt immer den aktuellen Stand deines Codes.
|
|
||||||
|
|
||||||
**Hinweis:**
|
|
||||||
Falls du vorher schon eine pipx-Installation hattest, führe vorher aus:
|
|
||||||
```bash
|
|
||||||
pipx uninstall odoo-openupgrade-wizard
|
|
||||||
```
|
|
||||||
|
|
||||||
Danach kannst du Änderungen im Arbeitsverzeichnis machen und direkt testen!
|
|
||||||
45
ROADMAP.md
45
ROADMAP.md
|
|
@ -1,45 +0,0 @@
|
||||||
# Python Version
|
|
||||||
|
|
||||||
* py310 is not available, due to dependencies to ``odoorpc`` that raise an error :
|
|
||||||
``ERROR tests/cli_A_init_test.py - AttributeError: module 'collections' has no attribute 'MutableMapping'``
|
|
||||||
Follow bug : https://stackoverflow.com/questions/69512672/getting-attributeerror-module-collections-has-no-attribute-mutablemapping-w
|
|
||||||
|
|
||||||
# openUpgradelib Versions
|
|
||||||
|
|
||||||
* ``openupgradelib`` requires a new feature psycopg2.sql since
|
|
||||||
(21 Aug 2019)
|
|
||||||
https://github.com/OCA/openupgradelib/commit/7408580e4469ba4b0cabb923da7facd71567a2fb
|
|
||||||
so we pin openupgradelib==2.0.0 (21 Jul 2018)
|
|
||||||
|
|
||||||
The python version in the Odoo:12 docker image is : ``Python 3.5.3 (default, Apr 5 2021, 09:00:41)`` that is very old.
|
|
||||||
|
|
||||||
|
|
||||||
- https://github.com/OCA/openupgradelib/issues/248
|
|
||||||
- https://github.com/OCA/openupgradelib/issues/288
|
|
||||||
- https://github.com/OCA/openupgradelib.git@ed01555b8ae20f66b3af178c8ecaf6edd110ce75#egg=openupgradelib
|
|
||||||
|
|
||||||
TODO : Fix via another way (other way than pining ``openuppgradelib`` version) the problem of old odoo versions. (it makes the upgrade failing for old revision (V8, etc...))
|
|
||||||
|
|
||||||
# Gitlab-CI
|
|
||||||
|
|
||||||
* for the time being, Features requiring ``odoorpc`` are failing in gitlab-CI.
|
|
||||||
Tests are working locally but there is a network problem. For that reason, tests witch names
|
|
||||||
begins by ``cli_2`` like (``cli_20_install_from_csv_test.py``) are disabled in ``.gitlab-ci.yml``.
|
|
||||||
|
|
||||||
TODO : work with Pierrick Brun, to run gitlab-runner on Akretion CI (without docker), to see if it is
|
|
||||||
fixing the problem.
|
|
||||||
|
|
||||||
# Features Work In Progress
|
|
||||||
|
|
||||||
* Add a tools to analyze workload.
|
|
||||||
|
|
||||||
# Possible Improvments
|
|
||||||
|
|
||||||
* select ``without-demo all`` depending on if the database
|
|
||||||
is created or not (and if current database contains demo data, checking if base.user_demo exists ?)
|
|
||||||
|
|
||||||
# Other points not in the scope of GRAP work
|
|
||||||
|
|
||||||
* Allow to use custom docker images.
|
|
||||||
|
|
||||||
* Check if there are default values for containers, limiting ressources.
|
|
||||||
1
newsfragments/.gitignore
vendored
1
newsfragments/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
!.gitignore
|
|
||||||
97
odoo_openupgrade_wizard/cli.py
Normal file
97
odoo_openupgrade_wizard/cli.py
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
import yaml
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
import odoo_openupgrade_wizard
|
||||||
|
from odoo_openupgrade_wizard.cli_build import build
|
||||||
|
from odoo_openupgrade_wizard.cli_init import init
|
||||||
|
from odoo_openupgrade_wizard.tools_system import ensure_folder_exists
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.version_option(version=odoo_openupgrade_wizard.__version__)
|
||||||
|
@click.option(
|
||||||
|
"-ef",
|
||||||
|
"--env-folder",
|
||||||
|
default="./",
|
||||||
|
type=click.Path(
|
||||||
|
exists=True,
|
||||||
|
dir_okay=True,
|
||||||
|
file_okay=False,
|
||||||
|
writable=True,
|
||||||
|
resolve_path=True,
|
||||||
|
),
|
||||||
|
help="Folder that will contains all the configuration of the wizard"
|
||||||
|
" and all the Odoo code required to make the migrations. Let empty to"
|
||||||
|
" use current folder (./).",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-fs",
|
||||||
|
"--filestore-folder",
|
||||||
|
type=click.Path(dir_okay=True, file_okay=False, resolve_path=True),
|
||||||
|
help="Folder that contains the Odoo filestore of the database(s)"
|
||||||
|
" to migrate. Let empty to use the subfolder 'filestore' of the"
|
||||||
|
" environment folder.",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def main(ctx, env_folder, filestore_folder):
|
||||||
|
"""
|
||||||
|
Provides a command set to perform odoo Community Edition migrations.
|
||||||
|
"""
|
||||||
|
date_begin = datetime.datetime.now()
|
||||||
|
logger.debug("Beginning script '%s' ..." % (ctx.invoked_subcommand))
|
||||||
|
if not isinstance(ctx.obj, dict):
|
||||||
|
ctx.obj = {}
|
||||||
|
|
||||||
|
# Define all the folder required by the tools
|
||||||
|
env_folder_path = Path(env_folder)
|
||||||
|
src_folder_path = env_folder_path / Path("./src/")
|
||||||
|
repo_folder_path = env_folder_path / Path("./repos/")
|
||||||
|
requirement_folder_path = env_folder_path / Path("./requirements/")
|
||||||
|
script_folder_path = env_folder_path / Path("./scripts/")
|
||||||
|
config_file_path = env_folder_path / Path("config.yml")
|
||||||
|
log_folder_path = env_folder_path / Path("./log/")
|
||||||
|
if not filestore_folder:
|
||||||
|
filestore_folder_path = env_folder_path / Path("./filestore/")
|
||||||
|
else:
|
||||||
|
filestore_folder_path = Path(filestore_folder)
|
||||||
|
|
||||||
|
# ensure log folder exists
|
||||||
|
ensure_folder_exists(log_folder_path)
|
||||||
|
|
||||||
|
# Create log file
|
||||||
|
log_file_path = log_folder_path / Path(
|
||||||
|
"{}__{}.log".format(
|
||||||
|
date_begin.strftime("%Y_%m_%d__%H_%M_%S"), ctx.invoked_subcommand
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.add(log_file_path)
|
||||||
|
|
||||||
|
# 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["repo_folder_path"] = repo_folder_path
|
||||||
|
ctx.obj["script_folder_path"] = script_folder_path
|
||||||
|
ctx.obj["filestore_folder_path"] = filestore_folder_path
|
||||||
|
ctx.obj["config_file_path"] = config_file_path
|
||||||
|
ctx.obj["requirement_folder_path"] = requirement_folder_path
|
||||||
|
|
||||||
|
# Load the main configuration file
|
||||||
|
if config_file_path.exists():
|
||||||
|
with open(config_file_path) as file:
|
||||||
|
config = yaml.safe_load(file)
|
||||||
|
for step in config["migration_steps"]:
|
||||||
|
step["local_path"] = src_folder_path / Path(
|
||||||
|
"env_%s" % step["version"]
|
||||||
|
)
|
||||||
|
ctx.obj["config"] = config
|
||||||
|
elif ctx.invoked_subcommand != "init":
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
main.add_command(init)
|
||||||
|
main.add_command(build)
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
#import debugpy
|
|
||||||
#debugpy.listen(("0.0.0.0", 5678))
|
|
||||||
#print("⏳ Warten auf Debugger-Anbindung (VS Code: Python: Remote Attach)...")
|
|
||||||
#debugpy.wait_for_client()
|
|
||||||
#print("✅ Debugger verbunden!")
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
import yaml
|
|
||||||
from click_loglevel import LogLevel
|
|
||||||
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_dropdb import dropdb
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_execute_script_sql import (
|
|
||||||
execute_script_sql,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_generate_module_analysis import (
|
|
||||||
generate_module_analysis,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_get_code import get_code
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_guess_requirement import guess_requirement
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_init import init
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_install_from_csv import install_from_csv
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_psql import psql
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_pull_submodule import pull_submodule
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_restoredb import restoredb
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_run import run
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_upgrade import upgrade
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_exists
|
|
||||||
|
|
||||||
DEFAULT_CONFIG_FILE = "config.yml"
|
|
||||||
DEFAULT_MODULES_FILE = "modules.csv"
|
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
|
||||||
@click.version_option(version=odoo_openupgrade_wizard.__version__)
|
|
||||||
@click.option(
|
|
||||||
"--env-folder",
|
|
||||||
default="./",
|
|
||||||
type=click.Path(
|
|
||||||
exists=True,
|
|
||||||
file_okay=False,
|
|
||||||
writable=True,
|
|
||||||
resolve_path=True,
|
|
||||||
),
|
|
||||||
help="Directory that will contain all the configuration of the wizard "
|
|
||||||
"and all the Odoo code required to perform the migrations. Leave "
|
|
||||||
"empty to use current directory (./).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-c",
|
|
||||||
"--config-file",
|
|
||||||
type=click.Path(
|
|
||||||
exists=True,
|
|
||||||
file_okay=True,
|
|
||||||
),
|
|
||||||
help=(
|
|
||||||
f"Configuration file to use. By default, a file named "
|
|
||||||
f'"{DEFAULT_CONFIG_FILE}" in the environment directory will be used.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--modules-file",
|
|
||||||
type=click.Path(
|
|
||||||
exists=True,
|
|
||||||
file_okay=True,
|
|
||||||
),
|
|
||||||
help=(
|
|
||||||
f"Modules file to use. By default, a file named "
|
|
||||||
f'"{DEFAULT_MODULES_FILE}" in the environment directory will be used.'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--filestore-folder",
|
|
||||||
type=click.Path(
|
|
||||||
exists=True, file_okay=False, writable=True, resolve_path=True
|
|
||||||
),
|
|
||||||
help="Directory that contains the Odoo filestore of the database(s) to "
|
|
||||||
"migrate. Leave empty to use the subdirectory 'filestore' of the "
|
|
||||||
"environment directory.",
|
|
||||||
)
|
|
||||||
@click.option("-l", "--log-level", type=LogLevel(), default=logging.INFO)
|
|
||||||
@click.pass_context
|
|
||||||
def main(
|
|
||||||
ctx, env_folder, config_file, modules_file, filestore_folder, log_level
|
|
||||||
):
|
|
||||||
"""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()
|
|
||||||
logger.remove()
|
|
||||||
logger.add(sys.stderr, level=log_level)
|
|
||||||
logger.debug(f"Beginning script '{ctx.invoked_subcommand}'...")
|
|
||||||
if not isinstance(ctx.obj, dict):
|
|
||||||
ctx.obj = {}
|
|
||||||
|
|
||||||
# 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/")
|
|
||||||
|
|
||||||
if config_file:
|
|
||||||
config_file_path = Path(config_file)
|
|
||||||
else:
|
|
||||||
config_file_path = env_folder_path / Path(DEFAULT_CONFIG_FILE)
|
|
||||||
|
|
||||||
if modules_file:
|
|
||||||
module_file_path = Path(modules_file)
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
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["config_file_path"] = config_file_path
|
|
||||||
ctx.obj["module_file_path"] = module_file_path
|
|
||||||
|
|
||||||
|
|
||||||
main.add_command(copydb)
|
|
||||||
main.add_command(restoredb)
|
|
||||||
main.add_command(docker_build)
|
|
||||||
main.add_command(dropdb)
|
|
||||||
main.add_command(dumpdb)
|
|
||||||
main.add_command(estimate_workload)
|
|
||||||
main.add_command(execute_script_python)
|
|
||||||
main.add_command(execute_script_sql)
|
|
||||||
main.add_command(generate_module_analysis)
|
|
||||||
main.add_command(guess_requirement)
|
|
||||||
main.add_command(get_code)
|
|
||||||
main.add_command(init)
|
|
||||||
main.add_command(install_from_csv)
|
|
||||||
main.add_command(psql)
|
|
||||||
main.add_command(pull_submodule)
|
|
||||||
main.add_command(run)
|
|
||||||
main.add_command(upgrade)
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools import (
|
|
||||||
tools_click_odoo_contrib as click_odoo_contrib,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"-s",
|
|
||||||
"--source",
|
|
||||||
type=str,
|
|
||||||
help="Name of the source database to copy.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-d",
|
|
||||||
"--dest",
|
|
||||||
type=str,
|
|
||||||
help="Name of the destination database to create.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def copydb(ctx, source, dest):
|
|
||||||
"""Create a new Odoo database by copying another.
|
|
||||||
|
|
||||||
This command duplicates both the PostgreSQL database and its associated
|
|
||||||
filestore.
|
|
||||||
|
|
||||||
"""
|
|
||||||
click_odoo_contrib.copydb(ctx, source, dest)
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
get_odoo_versions_from_options,
|
|
||||||
versions_options,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_docker import build_image, pull_image
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_docker_image_tag,
|
|
||||||
get_odoo_env_path,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import get_local_user_id
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@versions_options
|
|
||||||
@click.pass_context
|
|
||||||
def docker_build(ctx, versions):
|
|
||||||
"""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
|
|
||||||
logger.info(
|
|
||||||
"Pulling the PostgreSQL Docker image. This can take a while..."
|
|
||||||
)
|
|
||||||
pull_image(ctx.obj["config"]["postgres_image_name"])
|
|
||||||
|
|
||||||
# Build images for each odoo version
|
|
||||||
for odoo_version in get_odoo_versions_from_options(ctx, versions):
|
|
||||||
odoo_requirement_file_path = (
|
|
||||||
get_odoo_env_path(ctx, odoo_version) / "src/odoo/requirements.txt"
|
|
||||||
)
|
|
||||||
if not odoo_requirement_file_path.exists():
|
|
||||||
logger.error(
|
|
||||||
"Cannot build Odoo Docker image for version {odoo_version}, "
|
|
||||||
"because file {odoo_requirement_file_path} cannot be found. "
|
|
||||||
"Have you run the get-code command?",
|
|
||||||
odoo_version=odoo_version,
|
|
||||||
odoo_requirement_file_path=odoo_requirement_file_path,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Building Odoo Docker image for version '{odoo_version}'."
|
|
||||||
" This can take a while..."
|
|
||||||
)
|
|
||||||
image = build_image(
|
|
||||||
get_odoo_env_path(ctx, odoo_version),
|
|
||||||
get_docker_image_tag(ctx, odoo_version),
|
|
||||||
{"LOCAL_USER_ID": str(get_local_user_id())},
|
|
||||||
)
|
|
||||||
logger.info(f"Docker Image build. '{image[0].tags[0]}'")
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import database_option_required
|
|
||||||
from odoo_openupgrade_wizard.tools import (
|
|
||||||
tools_click_odoo_contrib as click_odoo_contrib,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@database_option_required
|
|
||||||
@click.pass_context
|
|
||||||
def dropdb(ctx, database):
|
|
||||||
"""Delete a database and its filestore."""
|
|
||||||
click_odoo_contrib.dropdb(ctx, database)
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import database_option_required
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
check_db_exist,
|
|
||||||
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="Destination path for the database dump relative to the project "
|
|
||||||
"directory.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--database-format",
|
|
||||||
type=click.Choice(("p", "c", "d", "t")),
|
|
||||||
default="c",
|
|
||||||
help="Database format (see pg_dump options): plain SQL (p), "
|
|
||||||
"custom format compressed (c), directory (d), or tar file (t).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--filestore-path",
|
|
||||||
type=click.Path(writable=True, resolve_path=True),
|
|
||||||
required=True,
|
|
||||||
help="Destination path for the filestore backup.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--filestore-format",
|
|
||||||
type=click.Choice(("d", "t", "tgz")),
|
|
||||||
default="tgz",
|
|
||||||
help="Filestore format: directory (d), tar file (t), "
|
|
||||||
"or tar file compressed with gzip (tgz)",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--force",
|
|
||||||
is_flag=True,
|
|
||||||
default=False,
|
|
||||||
help="Overwrite files if they already exist.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def dumpdb(
|
|
||||||
ctx,
|
|
||||||
database,
|
|
||||||
database_path,
|
|
||||||
database_format,
|
|
||||||
filestore_path,
|
|
||||||
filestore_format,
|
|
||||||
force,
|
|
||||||
):
|
|
||||||
"""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)
|
|
||||||
filestore_path = pathlib.Path(filestore_path)
|
|
||||||
|
|
||||||
# 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."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fail if dumps already exists and force argument not given
|
|
||||||
# Remove file if already exists and force is given
|
|
||||||
if not force and database_path.exists():
|
|
||||||
ctx.fail(f"{database_path} exists, use --force to overwrite it.")
|
|
||||||
elif force and database_path.exists():
|
|
||||||
if database_path.is_dir():
|
|
||||||
shutil.rmtree(database_path)
|
|
||||||
else:
|
|
||||||
database_path.unlink()
|
|
||||||
|
|
||||||
if not force and filestore_path.exists():
|
|
||||||
ctx.fail(f"{filestore_path} exists, use --force to overwrite it.")
|
|
||||||
elif force and filestore_path.exists():
|
|
||||||
if filestore_path.is_dir():
|
|
||||||
shutil.rmtree(filestore_path)
|
|
||||||
else:
|
|
||||||
filestore_path.unlink()
|
|
||||||
|
|
||||||
# Normalise database_path
|
|
||||||
database_path = absolute_database_path.relative_to(
|
|
||||||
absolute_env_folder_path
|
|
||||||
)
|
|
||||||
|
|
||||||
check_db_exist(ctx, database, raise_exception=True)
|
|
||||||
|
|
||||||
# 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,
|
|
||||||
)
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import get_odoo_modules_from_csv
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo_module import Analysis
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import (
|
|
||||||
ensure_file_exists_from_template,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--analysis-file-path",
|
|
||||||
type=click.Path(
|
|
||||||
dir_okay=False,
|
|
||||||
),
|
|
||||||
default="./analysis.html",
|
|
||||||
help="Path where the HTML analysis report will be saved. "
|
|
||||||
"Default is './analysis.html'",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--extra-modules",
|
|
||||||
"extra_modules_list",
|
|
||||||
# TODO, add a callback to check the quality of the argument
|
|
||||||
help="Comma-separated list of modules to analyze. If not set, "
|
|
||||||
"modules.csv will be used. Example: 'account,product,base'.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--time-unit",
|
|
||||||
type=click.Choice(["hour", "minute", "separator"]),
|
|
||||||
default="separator",
|
|
||||||
show_default=True,
|
|
||||||
help="Format to use for displaying time in the report. "
|
|
||||||
"*separator* display time as `HHH<sep>MM`, "
|
|
||||||
"*hour* display time as decimal hour, "
|
|
||||||
"*min* display time as minutes (rounded).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--time-separator",
|
|
||||||
default=":",
|
|
||||||
help="Character to use as a separator in time output. "
|
|
||||||
"Used only if --time-unit=separator. Default is ':' (e.g. HHH:MM).",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def estimate_workload(
|
|
||||||
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
|
|
||||||
analysis = Analysis(ctx)
|
|
||||||
|
|
||||||
def time_to_text(minutes):
|
|
||||||
"""Return a text representation for minutes"""
|
|
||||||
hours, mins = divmod(minutes, 60)
|
|
||||||
if time_unit == "hour":
|
|
||||||
result = str(hours)
|
|
||||||
elif time_unit == "minute":
|
|
||||||
result = str(minutes)
|
|
||||||
else:
|
|
||||||
result = "{}{}{:02d}".format(hours, time_separator, mins)
|
|
||||||
return result
|
|
||||||
|
|
||||||
if extra_modules_list:
|
|
||||||
module_list = extra_modules_list.split(",")
|
|
||||||
else:
|
|
||||||
module_list = get_odoo_modules_from_csv(ctx.obj["module_file_path"])
|
|
||||||
|
|
||||||
analysis.analyse_module_version(ctx, module_list)
|
|
||||||
analysis.analyse_missing_module()
|
|
||||||
analysis.analyse_openupgrade_state(ctx)
|
|
||||||
analysis.estimate_workload(ctx)
|
|
||||||
|
|
||||||
# Make some clean to display properly
|
|
||||||
analysis.modules = sorted(analysis.modules)
|
|
||||||
|
|
||||||
# Render html file
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
Path(analysis_file_path),
|
|
||||||
"analysis.html.j2",
|
|
||||||
ctx=ctx,
|
|
||||||
analysis=analysis,
|
|
||||||
current_date=datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
|
|
||||||
time_to_text=time_to_text,
|
|
||||||
)
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
get_migration_step_from_options,
|
|
||||||
step_option,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
execute_click_odoo_python_files,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@step_option
|
|
||||||
@database_option_required
|
|
||||||
@click.option(
|
|
||||||
"--script-file-path",
|
|
||||||
multiple=True,
|
|
||||||
type=click.Path(
|
|
||||||
exists=True,
|
|
||||||
dir_okay=False,
|
|
||||||
),
|
|
||||||
help="""List of Python files to execute, with either an absolute path
|
|
||||||
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
|
|
||||||
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)
|
|
||||||
|
|
||||||
execute_click_odoo_python_files(
|
|
||||||
ctx, database, migration_step, [Path(x) for x in script_file_path]
|
|
||||||
)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
get_migration_step_from_options,
|
|
||||||
step_option,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
execute_sql_files_pre_migration,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@step_option
|
|
||||||
@database_option_required
|
|
||||||
@click.option(
|
|
||||||
"--script-file-path",
|
|
||||||
multiple=True,
|
|
||||||
type=click.Path(
|
|
||||||
exists=True,
|
|
||||||
dir_okay=False,
|
|
||||||
),
|
|
||||||
help="List of SQL files to execute. Files will be executed in the order "
|
|
||||||
"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
|
|
||||||
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)
|
|
||||||
|
|
||||||
execute_sql_files_pre_migration(
|
|
||||||
ctx, database, migration_step, [Path(x) for x in script_file_path]
|
|
||||||
)
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
get_migration_steps_from_options,
|
|
||||||
step_option,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
|
||||||
generate_analysis_files,
|
|
||||||
generate_records,
|
|
||||||
get_installable_odoo_modules,
|
|
||||||
get_upgrade_analysis_module,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_odoo_env_path,
|
|
||||||
kill_odoo,
|
|
||||||
run_odoo,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo_instance import OdooInstance
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import ensure_folder_writable
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@step_option
|
|
||||||
@database_option_required
|
|
||||||
@click.option(
|
|
||||||
"-m",
|
|
||||||
"--modules",
|
|
||||||
type=str,
|
|
||||||
help="Comma-separated list of modules to analyse."
|
|
||||||
" Leave empty to analyse all the Odoo modules.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
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)
|
|
||||||
|
|
||||||
initial_step = migration_steps[0].copy()
|
|
||||||
final_step = migration_steps[1].copy()
|
|
||||||
|
|
||||||
alternative_xml_rpc_port = ctx.obj["config"]["odoo_host_xmlrpc_port"] + 10
|
|
||||||
|
|
||||||
if not database:
|
|
||||||
database = (
|
|
||||||
f"{ctx.obj['config']['project_name'].replace('-', '_')}"
|
|
||||||
"__analysis__"
|
|
||||||
)
|
|
||||||
|
|
||||||
initial_database = (
|
|
||||||
f"{database}_{str(initial_step['version']).replace('.', '')}"
|
|
||||||
)
|
|
||||||
final_database = (
|
|
||||||
f"{database}_{str(final_step['version']).replace('.', '')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
modules = modules and modules.split(",") or []
|
|
||||||
|
|
||||||
try:
|
|
||||||
# INITIAL : Run odoo and install analysis module
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
initial_step,
|
|
||||||
database=initial_database,
|
|
||||||
detached_container=False,
|
|
||||||
stop_after_init=True,
|
|
||||||
execution_context="openupgrade",
|
|
||||||
init=get_upgrade_analysis_module(initial_step),
|
|
||||||
)
|
|
||||||
|
|
||||||
# INITIAL : Run odoo for odoorpc
|
|
||||||
initial_container = run_odoo(
|
|
||||||
ctx,
|
|
||||||
initial_step,
|
|
||||||
database=initial_database,
|
|
||||||
execution_context="openupgrade",
|
|
||||||
detached_container=True,
|
|
||||||
publish_ports=True,
|
|
||||||
)
|
|
||||||
# INITIAL : install modules to analyse and generate records
|
|
||||||
initial_instance = OdooInstance(ctx, initial_database)
|
|
||||||
initial_modules = (
|
|
||||||
modules
|
|
||||||
and modules
|
|
||||||
or get_installable_odoo_modules(initial_instance, initial_step)
|
|
||||||
)
|
|
||||||
initial_instance.install_modules(initial_modules)
|
|
||||||
generate_records(initial_instance, initial_step)
|
|
||||||
|
|
||||||
# FINAL : Run odoo and install analysis module
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
final_step,
|
|
||||||
database=final_database,
|
|
||||||
detached_container=False,
|
|
||||||
stop_after_init=True,
|
|
||||||
init=get_upgrade_analysis_module(final_step),
|
|
||||||
execution_context="openupgrade",
|
|
||||||
)
|
|
||||||
|
|
||||||
# name of the first odoo instance inside the second odoo instance
|
|
||||||
odoo_initial_host_name = "odoo_initial_instance"
|
|
||||||
|
|
||||||
# FINAL : Run odoo for odoorpc and install modules to analyse
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
final_step,
|
|
||||||
database=final_database,
|
|
||||||
detached_container=True,
|
|
||||||
alternative_xml_rpc_port=alternative_xml_rpc_port,
|
|
||||||
execution_context="openupgrade",
|
|
||||||
links={initial_container.name: odoo_initial_host_name},
|
|
||||||
publish_ports=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# FINAL : install modules to analyse and generate records
|
|
||||||
final_instance = OdooInstance(
|
|
||||||
ctx,
|
|
||||||
final_database,
|
|
||||||
alternative_xml_rpc_port=alternative_xml_rpc_port,
|
|
||||||
)
|
|
||||||
final_modules = (
|
|
||||||
modules
|
|
||||||
and modules
|
|
||||||
or get_installable_odoo_modules(final_instance, final_step)
|
|
||||||
)
|
|
||||||
final_instance.install_modules(final_modules)
|
|
||||||
generate_records(final_instance, final_step)
|
|
||||||
|
|
||||||
# Make writable files and directories for "other"
|
|
||||||
# group to make possible to write analysis files
|
|
||||||
# for docker container user
|
|
||||||
ensure_folder_writable(
|
|
||||||
get_odoo_env_path(ctx, final_step["version"]) / "src"
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_analysis_files(
|
|
||||||
final_instance,
|
|
||||||
final_step,
|
|
||||||
odoo_initial_host_name,
|
|
||||||
initial_database,
|
|
||||||
)
|
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
logger.info("Received Keyboard Interrupt or System Exiting...")
|
|
||||||
finally:
|
|
||||||
kill_odoo(ctx, initial_database, initial_step)
|
|
||||||
kill_odoo(ctx, final_database, final_step)
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
get_odoo_versions_from_options,
|
|
||||||
versions_options,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_odoo_env_path,
|
|
||||||
get_repo_file_path,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import git_aggregate
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@versions_options
|
|
||||||
@click.option(
|
|
||||||
"-j",
|
|
||||||
"--jobs",
|
|
||||||
type=int,
|
|
||||||
default=10,
|
|
||||||
help="Jobs used to call the git-aggregate command."
|
|
||||||
" Reasonably set to 10 by default.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def get_code(ctx, versions, jobs):
|
|
||||||
"""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):
|
|
||||||
folder_path = get_odoo_env_path(ctx, odoo_version)
|
|
||||||
repo_file_path = get_repo_file_path(ctx, odoo_version)
|
|
||||||
git_aggregate(folder_path, repo_file_path, jobs)
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_odoo_env_path,
|
|
||||||
get_odoo_modules_from_csv,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo_module import Analysis
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import (
|
|
||||||
ensure_file_exists_from_template,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--extra-modules",
|
|
||||||
"extra_modules_list",
|
|
||||||
# TODO, add a callback to check the quality of the argument
|
|
||||||
help="Comma-separated list of modules to analyze. If not set, "
|
|
||||||
"modules.csv will be used. Example: 'account,product,base'.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
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
|
|
||||||
analysis = Analysis(ctx)
|
|
||||||
|
|
||||||
if extra_modules_list:
|
|
||||||
module_list = extra_modules_list.split(",")
|
|
||||||
else:
|
|
||||||
module_list = get_odoo_modules_from_csv(ctx.obj["module_file_path"])
|
|
||||||
|
|
||||||
analysis.analyse_module_version(ctx, module_list)
|
|
||||||
analysis.analyse_missing_module()
|
|
||||||
result = analysis.get_requirements(ctx)
|
|
||||||
|
|
||||||
for odoo_version in [x for x in ctx.obj["config"]["odoo_versions"]]:
|
|
||||||
path_version = get_odoo_env_path(ctx, odoo_version)
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("addons_python_requirements.txt"),
|
|
||||||
"odoo/addons_python_requirements.txt.j2",
|
|
||||||
dependencies=result[odoo_version]["python"],
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("addons_debian_requirements.txt"),
|
|
||||||
"odoo/addons_debian_requirements.txt.j2",
|
|
||||||
dependencies=result[odoo_version]["bin"],
|
|
||||||
)
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
|
||||||
get_odoo_versions,
|
|
||||||
get_version_options,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import get_odoo_env_path
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import (
|
|
||||||
ensure_file_exists_from_template,
|
|
||||||
ensure_folder_exists,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--project-name",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
type=str,
|
|
||||||
help="Name of your project without spaces, special"
|
|
||||||
" characters, or uppercases. Example: 'my-customer-9-12'."
|
|
||||||
" This will be used to tag the Odoo Docker images "
|
|
||||||
" with a friendly name.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--initial-version",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
type=click.Choice(get_version_options("initial")),
|
|
||||||
help="Initial Odoo version to migrate from.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--final-version",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
type=click.Choice(get_version_options("final")),
|
|
||||||
help="Target Odoo version to migrate to.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--postgresql-version",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
help="""The version of PostgreSQL that will be used
|
|
||||||
to create the PostgreSQL container. Example: '9.1', '16', etc.
|
|
||||||
The version should be available in Docker hub.
|
|
||||||
(https://hub.docker.com/_/postgres)
|
|
||||||
Avoid the 'latest' version if you want a deterministic installation.
|
|
||||||
Key Point: If your current production server uses PostgreSQL version A
|
|
||||||
and your future production server will use PostgreSQL version B,
|
|
||||||
you should select here a version X, with A <= X <= B.""",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--extra-repository",
|
|
||||||
"extra_repository_list",
|
|
||||||
# TODO, add a callback to check the quality of the argument
|
|
||||||
help="Comma-separated extra repositories to use in the Odoo environment."
|
|
||||||
"Example: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def init(
|
|
||||||
ctx,
|
|
||||||
project_name,
|
|
||||||
initial_version,
|
|
||||||
final_version,
|
|
||||||
postgresql_version,
|
|
||||||
extra_repository_list,
|
|
||||||
):
|
|
||||||
"""Initialize the OOW project environment.
|
|
||||||
|
|
||||||
This command sets up the project folder structure, configuration
|
|
||||||
files, and default templates needed to begin an Odoo migration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Handle arguments
|
|
||||||
if extra_repository_list:
|
|
||||||
extra_repositories = extra_repository_list.split(",")
|
|
||||||
else:
|
|
||||||
extra_repositories = []
|
|
||||||
|
|
||||||
orgs = {x: [] for x in set([x.split("/")[0] for x in extra_repositories])}
|
|
||||||
for extra_repository in extra_repositories:
|
|
||||||
org, repo = extra_repository.split("/")
|
|
||||||
orgs[org].append(repo)
|
|
||||||
|
|
||||||
# 1. Compute Odoo versions
|
|
||||||
odoo_versions = get_odoo_versions(
|
|
||||||
float(initial_version), float(final_version)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute Migration Steps
|
|
||||||
|
|
||||||
# Create initial Regular step
|
|
||||||
steps = [
|
|
||||||
{
|
|
||||||
"name": 1,
|
|
||||||
"execution_context": "regular",
|
|
||||||
"version": odoo_versions[0],
|
|
||||||
"complete_name": f"step_01__regular__{odoo_versions[0]}",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
# Add all Openupgrade steps
|
|
||||||
step_nbr = 2
|
|
||||||
for odoo_version in odoo_versions[1:]:
|
|
||||||
steps.append(
|
|
||||||
{
|
|
||||||
"name": step_nbr,
|
|
||||||
"execution_context": "openupgrade",
|
|
||||||
"version": odoo_version,
|
|
||||||
"complete_name": (
|
|
||||||
f"step_{step_nbr:>02}__openupgrade__{odoo_version}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
step_nbr += 1
|
|
||||||
|
|
||||||
# add final Regular step
|
|
||||||
if len(odoo_versions) > 1:
|
|
||||||
steps.append(
|
|
||||||
{
|
|
||||||
"name": step_nbr,
|
|
||||||
"execution_context": "regular",
|
|
||||||
"version": odoo_versions[-1],
|
|
||||||
"complete_name": (
|
|
||||||
f"step_{step_nbr:>02}__regular__{odoo_versions[-1]}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure src folder exists
|
|
||||||
ensure_folder_exists(ctx.obj["src_folder_path"])
|
|
||||||
|
|
||||||
# Ensure main configuration file exists
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
ctx.obj["config_file_path"],
|
|
||||||
"config.yml.j2",
|
|
||||||
project_name=project_name,
|
|
||||||
postgresql_version=postgresql_version,
|
|
||||||
steps=steps,
|
|
||||||
odoo_versions=odoo_versions,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure module list file exists
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
ctx.obj["module_file_path"],
|
|
||||||
"modules.csv.j2",
|
|
||||||
project_name=project_name,
|
|
||||||
steps=steps,
|
|
||||||
odoo_versions=odoo_versions,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create one folder per version and add files
|
|
||||||
for odoo_version in odoo_versions:
|
|
||||||
# Create main path for each version
|
|
||||||
path_version = get_odoo_env_path(ctx, odoo_version)
|
|
||||||
ensure_folder_exists(path_version)
|
|
||||||
|
|
||||||
# Create python requirements files
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("extra_python_requirements.txt"),
|
|
||||||
"odoo/extra_python_requirements.txt.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("addons_python_requirements.txt"),
|
|
||||||
"odoo/addons_python_requirements.txt.j2",
|
|
||||||
dependencies={},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create debian requirements files
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("extra_debian_requirements.txt"),
|
|
||||||
"odoo/extra_debian_requirements.txt.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("addons_debian_requirements.txt"),
|
|
||||||
"odoo/addons_debian_requirements.txt.j2",
|
|
||||||
dependencies={},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create odoo config file
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("odoo.conf"),
|
|
||||||
"odoo/odoo.conf.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create repos.yml file for gitaggregate tools
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("repos.yml"),
|
|
||||||
"odoo/repos.yml.j2",
|
|
||||||
odoo_version=odoo_version,
|
|
||||||
orgs=orgs,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create Dockerfile file
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
path_version / Path("Dockerfile"),
|
|
||||||
f"odoo/{odoo_version}/Dockerfile",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create 'src' folder that will contain all the odoo code
|
|
||||||
ensure_folder_exists(
|
|
||||||
path_version / Path("src"), git_ignore_content=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create one folder per step and add files
|
|
||||||
ensure_folder_exists(ctx.obj["script_folder_path"])
|
|
||||||
|
|
||||||
for step in steps:
|
|
||||||
step_path = ctx.obj["script_folder_path"] / step["complete_name"]
|
|
||||||
ensure_folder_exists(step_path)
|
|
||||||
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
step_path / Path("pre-migration.sql"),
|
|
||||||
"scripts/pre-migration.sql.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
step_path / Path("post-migration.py"),
|
|
||||||
"scripts/post-migration.py.j2",
|
|
||||||
)
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
demo_option,
|
|
||||||
get_migration_step_from_options,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_odoo_modules_from_csv,
|
|
||||||
kill_odoo,
|
|
||||||
run_odoo,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo_instance import OdooInstance
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@database_option_required
|
|
||||||
@demo_option
|
|
||||||
@click.pass_context
|
|
||||||
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)
|
|
||||||
ensure_database(ctx, database, state="present")
|
|
||||||
|
|
||||||
# Get modules list from the CSV file
|
|
||||||
module_names = get_odoo_modules_from_csv(ctx.obj["module_file_path"])
|
|
||||||
module_names.sort()
|
|
||||||
logger.info(f"Found {len(module_names)} modules.")
|
|
||||||
logger.debug(module_names)
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Install 'base' module on {database} database...")
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
database=database,
|
|
||||||
detached_container=True,
|
|
||||||
init="base",
|
|
||||||
demo=with_demo,
|
|
||||||
publish_ports=True,
|
|
||||||
)
|
|
||||||
odoo_instance = OdooInstance(ctx, database)
|
|
||||||
odoo_default_company = ctx.obj["config"].get(
|
|
||||||
"odoo_default_company", False
|
|
||||||
)
|
|
||||||
if odoo_default_company:
|
|
||||||
# Then, set correct country to the company of the current user
|
|
||||||
# Otherwise, due to poor design of Odoo, when installing account
|
|
||||||
# the US localization will be installed.
|
|
||||||
# (l10n_us + l10n_generic_coa)
|
|
||||||
|
|
||||||
countries = odoo_instance.browse_by_search(
|
|
||||||
"res.country",
|
|
||||||
[("code", "=", odoo_default_company["country_code"])],
|
|
||||||
)
|
|
||||||
if len(countries) != 1:
|
|
||||||
raise Exception(
|
|
||||||
f"Unable to find a country, based on the"
|
|
||||||
f" code {odoo_default_company['country_code']}."
|
|
||||||
f" Countries found:"
|
|
||||||
f" {', '.join([x.name for x in countries])}"
|
|
||||||
)
|
|
||||||
vals = {
|
|
||||||
"country_id": countries[0].id,
|
|
||||||
"currency_id": countries[0].currency_id.id,
|
|
||||||
"phone": odoo_default_company.get("phone"),
|
|
||||||
"email": odoo_default_company.get("email"),
|
|
||||||
}
|
|
||||||
logger.info(
|
|
||||||
f"Configuring main company with values {vals}"
|
|
||||||
f" (country {countries[0].name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
odoo_instance.env.user.company_id.write(vals)
|
|
||||||
|
|
||||||
# Install modules
|
|
||||||
odoo_instance.install_modules(module_names)
|
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
logger.info("Received Keyboard Interrupt or System Exiting...")
|
|
||||||
finally:
|
|
||||||
kill_odoo(ctx, database, migration_step)
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
|
|
||||||
def versions_options(function):
|
|
||||||
function = click.option(
|
|
||||||
"-v",
|
|
||||||
"--versions",
|
|
||||||
type=str,
|
|
||||||
help="Comma-separated Odoo versions to target. Leave empty to "
|
|
||||||
"perform the operation on all versions in the project",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def step_option(function):
|
|
||||||
function = click.option(
|
|
||||||
"-s",
|
|
||||||
"--step",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
type=int,
|
|
||||||
help="Migration step for which you want to perform the operation.",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def first_step_option(function):
|
|
||||||
function = click.option(
|
|
||||||
"--first-step",
|
|
||||||
type=int,
|
|
||||||
help="First step to include in the operation.",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def last_step_option(function):
|
|
||||||
function = click.option(
|
|
||||||
"--last-step",
|
|
||||||
type=int,
|
|
||||||
help="Last step to include in the operation.",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def demo_option(function):
|
|
||||||
function = click.option(
|
|
||||||
"--with-demo/--without-demo",
|
|
||||||
default=False,
|
|
||||||
help="Whether to include demo data when creating the database.",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def database_option_required(function):
|
|
||||||
function = click.option(
|
|
||||||
"-d",
|
|
||||||
"--database",
|
|
||||||
required=True,
|
|
||||||
prompt=True,
|
|
||||||
default="postgres",
|
|
||||||
type=str,
|
|
||||||
help="Name of the Odoo database to operate on. Default is 'postgres'.",
|
|
||||||
)(function)
|
|
||||||
return function
|
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_versions_from_options(ctx, versions_arg):
|
|
||||||
if not versions_arg:
|
|
||||||
return ctx.obj["config"]["odoo_versions"]
|
|
||||||
else:
|
|
||||||
odoo_versions = []
|
|
||||||
versions = [float(x) for x in versions_arg.split(",")]
|
|
||||||
for odoo_version in ctx.obj["config"]["odoo_versions"]:
|
|
||||||
if odoo_version in versions:
|
|
||||||
odoo_versions.append(odoo_version)
|
|
||||||
return odoo_versions
|
|
||||||
|
|
||||||
|
|
||||||
def get_migration_step_from_options(ctx, step_arg):
|
|
||||||
step = float(step_arg)
|
|
||||||
for migration_step in ctx.obj["config"]["migration_steps"]:
|
|
||||||
if migration_step["name"] == step:
|
|
||||||
return migration_step
|
|
||||||
raise ValueError(
|
|
||||||
f"No migration step found in configuration for step {step_arg}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_migration_steps_from_options(ctx, first_step_arg, last_step_arg):
|
|
||||||
result = []
|
|
||||||
if first_step_arg:
|
|
||||||
first_step = int(first_step_arg)
|
|
||||||
else:
|
|
||||||
first_step = ctx.obj["config"]["migration_steps"][0]["name"]
|
|
||||||
if last_step_arg:
|
|
||||||
last_step = int(last_step_arg)
|
|
||||||
else:
|
|
||||||
last_step = ctx.obj["config"]["migration_steps"][-1]["name"]
|
|
||||||
for migration_step in ctx.obj["config"]["migration_steps"]:
|
|
||||||
if migration_step["name"] in list(range(first_step, last_step + 1)):
|
|
||||||
result.append(migration_step.copy())
|
|
||||||
if result:
|
|
||||||
return result
|
|
||||||
|
|
||||||
raise ValueError(
|
|
||||||
"Unable to define steps in configuration from options."
|
|
||||||
f" (first step {first_step_arg} ; last step {last_step_arg})"
|
|
||||||
)
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import database_option_required
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import execute_psql_command
|
|
||||||
|
|
||||||
|
|
||||||
@click.command(context_settings={"ignore_unknown_options": True})
|
|
||||||
@database_option_required
|
|
||||||
@click.option(
|
|
||||||
"-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.pass_context
|
|
||||||
def psql(ctx, request, database, pager, psql_args):
|
|
||||||
"""Run a SQL command in the PostgreSQL container.
|
|
||||||
|
|
||||||
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)
|
|
||||||
if pager:
|
|
||||||
click.echo_via_pager(result)
|
|
||||||
else:
|
|
||||||
click.echo(result)
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
get_odoo_versions_from_options,
|
|
||||||
versions_options,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import get_odoo_env_path
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import execute_check_output
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@versions_options
|
|
||||||
@click.pass_context
|
|
||||||
def pull_submodule(ctx, versions):
|
|
||||||
"""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):
|
|
||||||
version_cfg = (
|
|
||||||
ctx.obj["config"]["odoo_version_settings"][odoo_version] or {}
|
|
||||||
)
|
|
||||||
if version_cfg.get("repo_url"):
|
|
||||||
logger.info(
|
|
||||||
f"Pull repos.yml from git repository"
|
|
||||||
f" for version {odoo_version}..."
|
|
||||||
)
|
|
||||||
submodule_path = (
|
|
||||||
get_odoo_env_path(ctx, odoo_version) / "repo_submodule"
|
|
||||||
)
|
|
||||||
if not submodule_path.exists():
|
|
||||||
execute_check_output(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"submodule",
|
|
||||||
"add",
|
|
||||||
"-b",
|
|
||||||
str(version_cfg["repo_branch"]),
|
|
||||||
version_cfg["repo_url"],
|
|
||||||
str(submodule_path),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
execute_check_output(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"pull",
|
|
||||||
"origin",
|
|
||||||
str(version_cfg["repo_branch"]),
|
|
||||||
"--rebase",
|
|
||||||
],
|
|
||||||
working_directory=submodule_path,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"No submodule configuration found"
|
|
||||||
f" for version {odoo_version}..."
|
|
||||||
)
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
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_restore
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import restore_filestore
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@database_option_required
|
|
||||||
@click.option(
|
|
||||||
"--database-path",
|
|
||||||
required=True,
|
|
||||||
type=click.Path(readable=True, resolve_path=True, exists=True),
|
|
||||||
help="Path to the database dump (inside the environment folder).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--database-format",
|
|
||||||
required=True,
|
|
||||||
type=click.Choice(("c", "d", "t", "p")),
|
|
||||||
default="c",
|
|
||||||
help="Format of the database dump: custom (c), directory (d), tar (t), "
|
|
||||||
"or plain SQL (p).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--filestore-path",
|
|
||||||
required=True,
|
|
||||||
type=click.Path(readable=True, resolve_path=True, exists=True),
|
|
||||||
help="Path to the filestore backup (inside the environment folder).",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--filestore-format",
|
|
||||||
required=True,
|
|
||||||
type=click.Choice(("d", "t", "tgz")),
|
|
||||||
default="tgz",
|
|
||||||
help="Format of the filestore: directory (d), tar (t), or gzip-compressed "
|
|
||||||
"tar (tgz).",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def restoredb(
|
|
||||||
ctx,
|
|
||||||
database,
|
|
||||||
database_path,
|
|
||||||
database_format,
|
|
||||||
filestore_path,
|
|
||||||
filestore_format,
|
|
||||||
):
|
|
||||||
"""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)
|
|
||||||
filestore_path = Path(filestore_path)
|
|
||||||
|
|
||||||
# Check that database_path is inside the env_folder_path
|
|
||||||
absolute_env_folder_path = ctx.obj["env_folder_path"].resolve().absolute()
|
|
||||||
if not str(database_path).startswith(str(absolute_env_folder_path)):
|
|
||||||
ctx.fail(
|
|
||||||
"database-path should be inside the project path to allow "
|
|
||||||
"PostgreSQL to read it."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Restore the database
|
|
||||||
output = execute_pg_restore(
|
|
||||||
ctx,
|
|
||||||
database_path.relative_to(absolute_env_folder_path),
|
|
||||||
database,
|
|
||||||
database_format,
|
|
||||||
)
|
|
||||||
if output:
|
|
||||||
click.echo(output)
|
|
||||||
|
|
||||||
# Restore the filestore
|
|
||||||
restore_filestore(
|
|
||||||
ctx,
|
|
||||||
database,
|
|
||||||
filestore_path,
|
|
||||||
filestore_format,
|
|
||||||
)
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
demo_option,
|
|
||||||
get_migration_step_from_options,
|
|
||||||
step_option,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import kill_odoo, run_odoo
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@step_option
|
|
||||||
@database_option_required
|
|
||||||
@demo_option
|
|
||||||
@click.option(
|
|
||||||
"--stop-after-init",
|
|
||||||
is_flag=True,
|
|
||||||
default=False,
|
|
||||||
help="Stop after init. Mainly used"
|
|
||||||
" for test purpose, for commands that are using input()"
|
|
||||||
" function to stop.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-i",
|
|
||||||
"--init-modules",
|
|
||||||
type=str,
|
|
||||||
help="List of modules to install. Equivalent to -i Odoo options.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-u",
|
|
||||||
"--update-modules",
|
|
||||||
type=str,
|
|
||||||
help="List of modules to update. Equivalent to -u Odoo options.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-e",
|
|
||||||
"--execution-context",
|
|
||||||
type=click.Choice(["regular", "openupgrade"]),
|
|
||||||
help="Force to use an openupgrade (OCA/openupgrade)"
|
|
||||||
" or a regular (odoo/odoo or OCA/OCB) base code when running odoo."
|
|
||||||
" Leave empty to use the default execution of the migration step.",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def run(
|
|
||||||
ctx,
|
|
||||||
step,
|
|
||||||
database,
|
|
||||||
with_demo,
|
|
||||||
stop_after_init,
|
|
||||||
init_modules,
|
|
||||||
update_modules,
|
|
||||||
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)
|
|
||||||
ensure_database(ctx, database, state="present")
|
|
||||||
try:
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
database=database,
|
|
||||||
detached_container=not stop_after_init,
|
|
||||||
init=init_modules,
|
|
||||||
update=update_modules,
|
|
||||||
stop_after_init=stop_after_init,
|
|
||||||
demo=with_demo,
|
|
||||||
execution_context=execution_context,
|
|
||||||
publish_ports=True,
|
|
||||||
)
|
|
||||||
if not stop_after_init:
|
|
||||||
logger.info(
|
|
||||||
"Odoo is available on your host at http://localhost:"
|
|
||||||
f"{ctx.obj['config']['odoo_host_xmlrpc_port']}"
|
|
||||||
)
|
|
||||||
input("Press 'Enter' to kill the odoo container and exit...")
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
logger.info("Received Keyboard Interrupt or System Exiting...")
|
|
||||||
finally:
|
|
||||||
kill_odoo(ctx, database, migration_step)
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
import click
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli_options import (
|
|
||||||
database_option_required,
|
|
||||||
demo_option,
|
|
||||||
first_step_option,
|
|
||||||
get_migration_steps_from_options,
|
|
||||||
last_step_option,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
execute_click_odoo_python_files,
|
|
||||||
kill_odoo,
|
|
||||||
run_odoo,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
execute_sql_files_pre_migration,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@first_step_option
|
|
||||||
@last_step_option
|
|
||||||
@database_option_required
|
|
||||||
@demo_option
|
|
||||||
@click.pass_context
|
|
||||||
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(
|
|
||||||
ctx, first_step, last_step
|
|
||||||
)
|
|
||||||
for migration_step in migration_steps:
|
|
||||||
execute_sql_files_pre_migration(ctx, database, migration_step)
|
|
||||||
if migration_step.get("update", True):
|
|
||||||
try:
|
|
||||||
run_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
database=database,
|
|
||||||
detached_container=False,
|
|
||||||
update="all",
|
|
||||||
stop_after_init=True,
|
|
||||||
demo=with_demo,
|
|
||||||
)
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
|
||||||
logger.info("Received Keyboard Interrupt or System Exiting...")
|
|
||||||
finally:
|
|
||||||
kill_odoo(ctx, database, migration_step)
|
|
||||||
elif migration_step.get("execution_context") == "openupgrade":
|
|
||||||
raise ValueError(
|
|
||||||
"Incorrect setting 'update: True'"
|
|
||||||
" and 'execution_context: openupgrade'"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info(
|
|
||||||
"Skip update=all for"
|
|
||||||
f" step {migration_step.get('complete_name')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
execute_click_odoo_python_files(ctx, database, migration_step)
|
|
||||||
32
odoo_openupgrade_wizard/cli_build.py
Normal file
32
odoo_openupgrade_wizard/cli_build.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import click
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
||||||
|
_get_repo_file,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.tools_system import (
|
||||||
|
create_virtualenv,
|
||||||
|
ensure_folder_exists,
|
||||||
|
git_aggregate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def build(ctx):
|
||||||
|
"""
|
||||||
|
Build OpenUpgrade Wizard Environment:
|
||||||
|
- gitaggregate all the repositories
|
||||||
|
- build virtualenv (TODO)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# distinct_versions = list(set(x["version"] for x in series))
|
||||||
|
|
||||||
|
for step in ctx.obj["config"]["migration_steps"]:
|
||||||
|
# 1. Create main folder for the odoo version
|
||||||
|
ensure_folder_exists(step["local_path"], mode="777")
|
||||||
|
|
||||||
|
# 2. Create virtual environment
|
||||||
|
create_virtualenv(step["local_path"], step["python"])
|
||||||
|
|
||||||
|
# 3. gitaggregate source code
|
||||||
|
git_aggregate(step["local_path"], _get_repo_file(ctx, step))
|
||||||
149
odoo_openupgrade_wizard/cli_init.py
Normal file
149
odoo_openupgrade_wizard/cli_init.py
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
||||||
|
_get_odoo_version_str_list,
|
||||||
|
_get_odoo_versions,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.templates import (
|
||||||
|
_CONFIG_YML_TEMPLATE,
|
||||||
|
_POST_MIGRATION_PY_TEMPLATE,
|
||||||
|
_PRE_MIGRATION_SQL_TEMPLATE,
|
||||||
|
_REPO_YML_TEMPLATE,
|
||||||
|
_REQUIREMENTS_TXT_TEMPLATE,
|
||||||
|
)
|
||||||
|
from odoo_openupgrade_wizard.tools_system import (
|
||||||
|
ensure_file_exists_from_template,
|
||||||
|
ensure_folder_exists,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option(
|
||||||
|
"-iv",
|
||||||
|
"--initial-version",
|
||||||
|
required=True,
|
||||||
|
prompt=True,
|
||||||
|
type=click.Choice(_get_odoo_version_str_list("initial")),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-fv",
|
||||||
|
"--final-version",
|
||||||
|
required=True,
|
||||||
|
prompt=True,
|
||||||
|
type=click.Choice(_get_odoo_version_str_list("final")),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-er",
|
||||||
|
"--extra-repository",
|
||||||
|
"extra_repository_list",
|
||||||
|
# TODO, add a callback to check the quality of the argument
|
||||||
|
help="Coma separated extra repositories to use in the odoo environment."
|
||||||
|
"Ex: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def init(ctx, initial_version, final_version, extra_repository_list):
|
||||||
|
"""
|
||||||
|
Initialize OpenUpgrade Wizard Environment based on the initial and
|
||||||
|
the final version of Odoo you want to migrate.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extra_repository_list:
|
||||||
|
extra_repositories = extra_repository_list.split(",")
|
||||||
|
else:
|
||||||
|
extra_repositories = []
|
||||||
|
|
||||||
|
# 1. create steps from series given as argument
|
||||||
|
series = _get_odoo_versions(float(initial_version), float(final_version))
|
||||||
|
distinct_versions = list(set(x["version"] for x in series))
|
||||||
|
|
||||||
|
# Create initial first step
|
||||||
|
steps = [series[0].copy()]
|
||||||
|
steps[0].update(
|
||||||
|
{
|
||||||
|
"name": "step_1",
|
||||||
|
"action": "update",
|
||||||
|
"complete_name": "step_1__update__%s" % (steps[0]["version"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add all upgrade steps
|
||||||
|
count = 1
|
||||||
|
for serie in series[1:]:
|
||||||
|
steps.append(serie.copy())
|
||||||
|
steps[count].update(
|
||||||
|
{
|
||||||
|
"name": "step_%d" % (count + 1),
|
||||||
|
"action": "upgrade",
|
||||||
|
"complete_name": "step_%d__upgrade__%s"
|
||||||
|
% (count + 1, serie["version"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# add final update step
|
||||||
|
steps.append(series[-1].copy())
|
||||||
|
steps[-1].update(
|
||||||
|
{
|
||||||
|
"name": "step_%d" % (count + 1),
|
||||||
|
"action": "update",
|
||||||
|
"complete_name": "step_%d__update__%s"
|
||||||
|
% (count + 1, steps[-1]["version"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. ensure src folder exists
|
||||||
|
ensure_folder_exists(ctx.obj["src_folder_path"], mode="777")
|
||||||
|
|
||||||
|
# 3. ensure filestore folder exists
|
||||||
|
ensure_folder_exists(ctx.obj["filestore_folder_path"], mode="777")
|
||||||
|
|
||||||
|
# 4. ensure main configuration file exists
|
||||||
|
ensure_file_exists_from_template(
|
||||||
|
ctx.obj["config_file_path"], _CONFIG_YML_TEMPLATE, steps=steps
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Create Repo folder and files
|
||||||
|
ensure_folder_exists(ctx.obj["repo_folder_path"])
|
||||||
|
|
||||||
|
orgs = {x: [] for x in set([x.split("/")[0] for x in extra_repositories])}
|
||||||
|
for extra_repository in extra_repositories:
|
||||||
|
org, repo = extra_repository.split("/")
|
||||||
|
orgs[org].append(repo)
|
||||||
|
|
||||||
|
for version in distinct_versions:
|
||||||
|
ensure_file_exists_from_template(
|
||||||
|
ctx.obj["repo_folder_path"] / Path("%s.yml" % (version)),
|
||||||
|
_REPO_YML_TEMPLATE,
|
||||||
|
version=version,
|
||||||
|
orgs=orgs,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. Create Requirements folder and files
|
||||||
|
ensure_folder_exists(ctx.obj["requirement_folder_path"])
|
||||||
|
|
||||||
|
for serie in series:
|
||||||
|
ensure_file_exists_from_template(
|
||||||
|
ctx.obj["requirement_folder_path"]
|
||||||
|
/ Path("%s_requirements.txt" % (serie["version"])),
|
||||||
|
_REQUIREMENTS_TXT_TEMPLATE,
|
||||||
|
python_libraries=serie["python_libraries"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6. Create Scripts folder and files
|
||||||
|
ensure_folder_exists(ctx.obj["script_folder_path"])
|
||||||
|
|
||||||
|
for step in steps:
|
||||||
|
step_path = ctx.obj["script_folder_path"] / step["complete_name"]
|
||||||
|
ensure_folder_exists(step_path)
|
||||||
|
|
||||||
|
ensure_file_exists_from_template(
|
||||||
|
step_path / Path("pre-migration.sql"),
|
||||||
|
_PRE_MIGRATION_SQL_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
ensure_file_exists_from_template(
|
||||||
|
step_path / Path("post-migration.py"),
|
||||||
|
_POST_MIGRATION_PY_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
@ -1,272 +1,83 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from loguru import logger
|
# List of the series of odoo
|
||||||
|
# python version is defined, based on the OCA CI.
|
||||||
FIRST_ODOO_VERSION_SUPPORTED = 8
|
# https://github.com/OCA/oca-addons-repo-template/blob/master/src/.github/workflows/%7B%25%20if%20ci%20%3D%3D%20'GitHub'%20%25%7Dtest.yml%7B%25%20endif%20%25%7D.jinja
|
||||||
LAST_ODOO_VERSION_SUPPORTED = 18
|
_ODOO_SERIES = [
|
||||||
|
{
|
||||||
_ALL_ODOO_VERSIONS = [
|
"version": 6.0,
|
||||||
float(x)
|
"python": "python2.7",
|
||||||
for x in range(
|
"python_libraries": ["openupgradelib"],
|
||||||
FIRST_ODOO_VERSION_SUPPORTED, LAST_ODOO_VERSION_SUPPORTED + 1
|
},
|
||||||
)
|
{
|
||||||
|
"version": 6.1,
|
||||||
|
"python": "python2.7",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 7.0,
|
||||||
|
"python": "python2.7",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 8.0,
|
||||||
|
"python": "python2.7",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 9.0,
|
||||||
|
"python": "python2.7",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 10.0,
|
||||||
|
"python": "python2.7",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 11.0,
|
||||||
|
"python": "python3.5",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 12.0,
|
||||||
|
"python": "python3.6",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 13.0,
|
||||||
|
"python": "python3.6",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 14.0,
|
||||||
|
"python": "python3.6",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": 15.0,
|
||||||
|
"python": "python3.8",
|
||||||
|
"python_libraries": ["openupgradelib"],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_version_options(mode: str) -> list:
|
def _get_odoo_version_str_list(mode):
|
||||||
"""Get options available for version click argument.
|
serie_list = [x["version"] for x in _ODOO_SERIES]
|
||||||
Arguments:
|
|
||||||
mode: Possible value 'initial', 'final'
|
|
||||||
Return:
|
|
||||||
list of string.
|
|
||||||
Exemple:
|
|
||||||
['9.0', '10.0', '11.0']
|
|
||||||
"""
|
|
||||||
version_options = [str(version) for version in _ALL_ODOO_VERSIONS]
|
|
||||||
if mode == "initial":
|
if mode == "initial":
|
||||||
version_options = version_options[:-1]
|
serie_list = serie_list[:-1]
|
||||||
if mode == "final":
|
if mode == "final":
|
||||||
version_options = version_options[1:]
|
serie_list = serie_list[1:]
|
||||||
return version_options
|
return [str(x) for x in serie_list]
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_versions(initial_version: float, final_version: float) -> list:
|
def _get_odoo_versions(initial, final):
|
||||||
"""Return a list of odoo versions from the initial version to the final
|
|
||||||
version
|
|
||||||
"""
|
|
||||||
result = []
|
result = []
|
||||||
for version in _ALL_ODOO_VERSIONS:
|
for serie in _ODOO_SERIES:
|
||||||
if version >= initial_version and version <= final_version:
|
if serie["version"] >= initial and serie["version"] <= final:
|
||||||
result.append(version)
|
result.append(serie)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_run_command(migration_step: dict) -> str:
|
def _get_repo_file(ctx, step):
|
||||||
"""Return the name of the command to execute, depending on the migration
|
return ctx.obj["repo_folder_path"] / Path("%s.yml" % (step["version"]))
|
||||||
step. (odoo-bin, odoo.py, etc...)"""
|
|
||||||
if migration_step["version"] >= 10.0:
|
|
||||||
return "odoo-bin"
|
|
||||||
|
|
||||||
return "odoo.py"
|
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_folder(
|
|
||||||
migration_step: dict, execution_context: str = None
|
|
||||||
) -> str:
|
|
||||||
"""return the main odoo folder, depending on the migration step.
|
|
||||||
(./src/odoo, ./src/openupgrade, ...)"""
|
|
||||||
|
|
||||||
if execution_context == "regular":
|
|
||||||
return "src/odoo"
|
|
||||||
elif execution_context == "openupgrade" and migration_step["version"] < 14:
|
|
||||||
return "src/openupgrade"
|
|
||||||
|
|
||||||
if migration_step["execution_context"] == "regular":
|
|
||||||
return "src/odoo"
|
|
||||||
|
|
||||||
if migration_step["version"] >= 14.0:
|
|
||||||
return "src/odoo"
|
|
||||||
|
|
||||||
return "src/openupgrade"
|
|
||||||
|
|
||||||
|
|
||||||
def get_base_module_folder(migration_step: dict) -> str:
|
|
||||||
"""return the name of the folder (odoo, openerp, etc...)
|
|
||||||
where the 'base' module is, depending on the migration_step"""
|
|
||||||
if migration_step["version"] >= 10.0:
|
|
||||||
return "odoo"
|
|
||||||
|
|
||||||
return "openerp"
|
|
||||||
|
|
||||||
|
|
||||||
def get_manifest_name(migration_step: dict) -> str:
|
|
||||||
"""return the name of the manifest file present in
|
|
||||||
each odoo module"""
|
|
||||||
if migration_step["version"] >= 10.0:
|
|
||||||
return "__manifest__.py"
|
|
||||||
|
|
||||||
return "__openerp__.py"
|
|
||||||
|
|
||||||
|
|
||||||
def skip_addon_path(migration_step: dict, path: Path) -> bool:
|
|
||||||
"""return a boolean to indicate if the addon_path should be
|
|
||||||
remove (during the generation of the addons_path).
|
|
||||||
Note : if repo.yml contains both odoo and openupgrade repo
|
|
||||||
we skip one of them (before the V14 refactoring)"""
|
|
||||||
return (
|
|
||||||
str(path).endswith("/src/odoo")
|
|
||||||
or str(path).endswith("src/openupgrade")
|
|
||||||
) and migration_step["version"] < 14.0
|
|
||||||
|
|
||||||
|
|
||||||
def get_server_wide_modules_upgrade(
|
|
||||||
migration_step: dict, execution_context: str = None
|
|
||||||
) -> list:
|
|
||||||
"""return a list of modules to load, depending on the migration step."""
|
|
||||||
if (
|
|
||||||
migration_step["version"] >= 14.0
|
|
||||||
and migration_step["execution_context"] == "openupgrade"
|
|
||||||
and execution_context != "regular"
|
|
||||||
):
|
|
||||||
return ["openupgrade_framework"]
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def get_upgrade_analysis_module(migration_step: dict) -> str:
|
|
||||||
"""return the upgrade_analysis module name"""
|
|
||||||
|
|
||||||
if migration_step["version"] >= 14.0:
|
|
||||||
# (Module in OCA/server-tools)
|
|
||||||
return "upgrade_analysis"
|
|
||||||
|
|
||||||
# (module in OCA/OpenUpgrade/odoo/addons/)
|
|
||||||
return "openupgrade_records"
|
|
||||||
|
|
||||||
|
|
||||||
def generate_records(odoo_instance, migration_step: dict):
|
|
||||||
logger.info(
|
|
||||||
"Generate Records in version %s..."
|
|
||||||
" (This may take a while)" % (migration_step["version"])
|
|
||||||
)
|
|
||||||
if migration_step["version"] < 14.0:
|
|
||||||
wizard = odoo_instance.browse_by_create(
|
|
||||||
"openupgrade.generate.records.wizard", {}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
wizard = odoo_instance.browse_by_create(
|
|
||||||
"upgrade.generate.record.wizard", {}
|
|
||||||
)
|
|
||||||
wizard.generate()
|
|
||||||
|
|
||||||
|
|
||||||
def get_installable_odoo_modules(odoo_instance, migraton_step):
|
|
||||||
if migraton_step["version"] < 14.0:
|
|
||||||
# TODO, improve that algorithm, if possible
|
|
||||||
modules = odoo_instance.browse_by_search(
|
|
||||||
"ir.module.module",
|
|
||||||
[
|
|
||||||
("state", "!=", "uninstallable"),
|
|
||||||
("website", "not ilike", "github/OCA"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# We use here a new feature implemented in the upgrade_analysis
|
|
||||||
# in a wizard to install odoo modules
|
|
||||||
wizard = odoo_instance.browse_by_create("upgrade.install.wizard", {})
|
|
||||||
wizard.select_odoo_modules()
|
|
||||||
modules = wizard.module_ids
|
|
||||||
|
|
||||||
return modules.mapped("name")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_analysis_files(
|
|
||||||
final_odoo_instance, final_step, initial_odoo_host, initial_database
|
|
||||||
):
|
|
||||||
logger.info(
|
|
||||||
"Generate analysis files for"
|
|
||||||
" the modules installed on %s..." % (initial_database)
|
|
||||||
)
|
|
||||||
proxy_vals = {
|
|
||||||
"name": "Proxy to Previous version",
|
|
||||||
"server": initial_odoo_host,
|
|
||||||
"port": "8069",
|
|
||||||
"database": initial_database,
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin",
|
|
||||||
}
|
|
||||||
if final_step["version"] < 14.0:
|
|
||||||
logger.info("> Create proxy...")
|
|
||||||
proxy = final_odoo_instance.browse_by_create(
|
|
||||||
"openupgrade.comparison.config", proxy_vals
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("> Create wizard...")
|
|
||||||
wizard = final_odoo_instance.browse_by_create(
|
|
||||||
"openupgrade.analysis.wizard",
|
|
||||||
{
|
|
||||||
"server_config": proxy.id,
|
|
||||||
"write_files": True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
logger.info("> Launch analysis. This can take a while...")
|
|
||||||
wizard.get_communication()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.info("> Create proxy...")
|
|
||||||
proxy = final_odoo_instance.browse_by_create(
|
|
||||||
"upgrade.comparison.config", proxy_vals
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("> Create wizard...")
|
|
||||||
analysis = final_odoo_instance.browse_by_create(
|
|
||||||
"upgrade.analysis",
|
|
||||||
{
|
|
||||||
"config_id": proxy.id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("> Launch analysis. This can take a while...")
|
|
||||||
analysis.analyze()
|
|
||||||
|
|
||||||
|
|
||||||
def get_apriori_file_relative_path(version: float) -> (str, Path):
|
|
||||||
"""Return the module name and the relative file path of
|
|
||||||
the apriori.py file that contains all the rename and
|
|
||||||
the merge information for a given upgrade."""
|
|
||||||
if version < 14.0:
|
|
||||||
return ("openupgrade_records", Path("lib/apriori.py"))
|
|
||||||
else:
|
|
||||||
return ("openupgrade_scripts", Path("apriori.py"))
|
|
||||||
|
|
||||||
|
|
||||||
def get_coverage_relative_path(version: float) -> (str, Path):
|
|
||||||
"""Return the path of the coverage file."""
|
|
||||||
if version < 10.0:
|
|
||||||
base_path = Path("src/openupgrade/openerp/openupgrade/doc/source")
|
|
||||||
elif version < 14.0:
|
|
||||||
base_path = Path("src/openupgrade/odoo/openupgrade/doc/source")
|
|
||||||
else:
|
|
||||||
base_path = Path("src/openupgrade/docsource")
|
|
||||||
|
|
||||||
previous_version = version - 1
|
|
||||||
return base_path / Path(
|
|
||||||
"modules%s-%s.rst"
|
|
||||||
% (
|
|
||||||
("%.1f" % previous_version).replace(".", ""),
|
|
||||||
("%.1f" % version).replace(".", ""),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_openupgrade_analysis_files(
|
|
||||||
odoo_env_path: Path, version: float
|
|
||||||
) -> dict:
|
|
||||||
"""return a dictionnary of module_name : path,
|
|
||||||
where module_name is the name of each module of a version
|
|
||||||
and and path is the path of the migration_analysis.txt file
|
|
||||||
of the module"""
|
|
||||||
result = {}
|
|
||||||
if version < 14.0:
|
|
||||||
base_name = "openupgrade_analysis.txt"
|
|
||||||
else:
|
|
||||||
base_name = "upgrade_analysis.txt"
|
|
||||||
|
|
||||||
files = [
|
|
||||||
x
|
|
||||||
for x in sorted(odoo_env_path.rglob("**/*.txt"))
|
|
||||||
if x.name == base_name
|
|
||||||
]
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
# this part doesn't depends only of the version
|
|
||||||
# 14+ module can have migrations folder.
|
|
||||||
if file.parent.parent.name == "migrations":
|
|
||||||
module_name = file.parent.parent.parent.name
|
|
||||||
else:
|
|
||||||
module_name = file.parent.parent.name
|
|
||||||
result[module_name] = file
|
|
||||||
logger.debug(
|
|
||||||
"Version %s: %d analysis files found." % (version, len(result))
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
67
odoo_openupgrade_wizard/templates.py
Normal file
67
odoo_openupgrade_wizard/templates.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
_CONFIG_YML_TEMPLATE = """migration_steps:
|
||||||
|
{% for step in steps %}
|
||||||
|
- name: {{ step['name'] }}
|
||||||
|
complete_name: {{ step['complete_name'] }}
|
||||||
|
version: {{ step['version'] }}
|
||||||
|
action: {{ step['action'] }}
|
||||||
|
python: {{ step['python'] }}
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_REPO_YML_TEMPLATE = """
|
||||||
|
##############################################################################
|
||||||
|
## Odoo Repository
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
./src/odoo:
|
||||||
|
defaults:
|
||||||
|
depth: 1
|
||||||
|
remotes:
|
||||||
|
odoo: https://github.com/odoo/odoo
|
||||||
|
target: odoo {{ version }}-target
|
||||||
|
merges:
|
||||||
|
- odoo {{ version }}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
## OpenUpgrade Repository
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
./src/openupgrade:
|
||||||
|
defaults:
|
||||||
|
depth: 1
|
||||||
|
remotes:
|
||||||
|
OCA: https://github.com/OCA/OpenUpgrade
|
||||||
|
target: OCA {{ version }}-target
|
||||||
|
merges:
|
||||||
|
- OCA {{ version }}
|
||||||
|
|
||||||
|
{% for org_name, repo_list in orgs.items() %}
|
||||||
|
##############################################################################
|
||||||
|
## {{ org_name }} Repositories
|
||||||
|
##############################################################################
|
||||||
|
{% for repo in repo_list %}
|
||||||
|
./src/{{ org_name }}/{{ repo }}:
|
||||||
|
defaults:
|
||||||
|
depth: 1
|
||||||
|
remotes:
|
||||||
|
{{ org_name }}: https://github.com/{{ org_name }}/{{ repo }}
|
||||||
|
target: {{ org_name }} {{ version }}-target
|
||||||
|
merges:
|
||||||
|
- {{ org_name }} {{ version }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
_REQUIREMENTS_TXT_TEMPLATE = """
|
||||||
|
{%- for python_librairy in python_libraries -%}
|
||||||
|
{{ python_librairy }}
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_PRE_MIGRATION_SQL_TEMPLATE = ""
|
||||||
|
|
||||||
|
_POST_MIGRATION_PY_TEMPLATE = """
|
||||||
|
def main(self, step):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script>
|
|
||||||
function ShowAll(){
|
|
||||||
var elements = document.getElementsByClassName('work_done');
|
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i ++) {
|
|
||||||
elements[i].style.display = 'table-row';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function HideWorkDone(){
|
|
||||||
var elements = document.getElementsByClassName('work_done');
|
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i ++) {
|
|
||||||
elements[i].style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Migration Analysis</h1>
|
|
||||||
<table border="1" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Initial Version</th>
|
|
||||||
<th>Final Version</th>
|
|
||||||
<th>Project Name</th>
|
|
||||||
<th>Analysis Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{{ ctx.obj['config']['odoo_versions'][0] }}</td>
|
|
||||||
<td>{{ ctx.obj['config']['odoo_versions'][-1] }}</td>
|
|
||||||
<td>{{ ctx.obj['config']['project_name'] }}</td>
|
|
||||||
<td>{{ current_date }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" style="text-align: center;">
|
|
||||||
<a href="#" onclick="ShowAll()">Show All Modules</a>
|
|
||||||
<a href="#" onclick="HideWorkDone()">Hide Done Modules</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2>Summary</h2>
|
|
||||||
<table border="1" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Module Type</th>
|
|
||||||
<th>Module Quantity</th>
|
|
||||||
<th>Remaining Hours</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Odoo</td>
|
|
||||||
<td>{{ analysis.get_module_qty('odoo') }}</td>
|
|
||||||
<td>{{ time_to_text(analysis.workload('odoo', False)) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OCA</td>
|
|
||||||
<td>{{ analysis.get_module_qty('oca') }}</td>
|
|
||||||
<td>{{ time_to_text(analysis.workload('oca', False)) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Custom</td>
|
|
||||||
<td>{{ analysis.get_module_qty('custom') }}</td>
|
|
||||||
<td>{{ time_to_text(analysis.workload('custom', False)) }}</td>
|
|
||||||
</tr>
|
|
||||||
{%- if analysis.get_module_qty('not_found') -%}
|
|
||||||
<tr>
|
|
||||||
<td>Not Found</td>
|
|
||||||
<td>{{ analysis.get_module_qty('not_found') }}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
{%- endif -%}
|
|
||||||
</tbody>
|
|
||||||
<tfood>
|
|
||||||
<tr>
|
|
||||||
<th>Total</th>
|
|
||||||
<td>{{ analysis.get_module_qty() }}</td>
|
|
||||||
<td>{{ time_to_text(analysis.workload(False, False)) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfood>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2>Details</h2>
|
|
||||||
<table border="1" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th> </th>
|
|
||||||
<th>Total</th>
|
|
||||||
{%- for odoo_version in ctx.obj['config']['odoo_versions'] %}
|
|
||||||
<th>{{ odoo_version }}</th>
|
|
||||||
{%- endfor %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{%- set ns = namespace(current_repository='', current_module_type='') %}
|
|
||||||
|
|
||||||
{%- for odoo_module in analysis.modules %}
|
|
||||||
{%- if (ns.current_module_type != odoo_module.module_type) %}
|
|
||||||
{%- set ns.current_module_type = odoo_module.module_type %}
|
|
||||||
|
|
||||||
<!-- ------------------------------------------------ -->
|
|
||||||
<!-- Handle New Module Type {{ns.current_module_type}}-->
|
|
||||||
<!-- ------------------------------------------------ -->
|
|
||||||
<tr>
|
|
||||||
<th colspan="{{2 + ctx.obj['config']['odoo_versions']|length}}">
|
|
||||||
<h3>{{ odoo_module.module_type}}</h3>
|
|
||||||
</th>
|
|
||||||
<tr>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{%- if ns.current_repository != odoo_module.repository %}
|
|
||||||
{%- set ns.current_repository = odoo_module.repository %}
|
|
||||||
{%- set repository_workload = analysis.workload(False, odoo_module.repository) %}
|
|
||||||
|
|
||||||
<!-- ---------------------------------------------- -->
|
|
||||||
<!-- Handle New Repository {{ns.current_repository}}-->
|
|
||||||
<!-- ---------------------------------------------- -->
|
|
||||||
|
|
||||||
{%- if ns.current_repository %}
|
|
||||||
<tr class="{{repository_workload == 0 and 'work_done' or ''}}">
|
|
||||||
<th colspan="{{2 + ctx.obj['config']['odoo_versions']|length}}">
|
|
||||||
<h4>{{ odoo_module.repository}}
|
|
||||||
{% if repository_workload %}
|
|
||||||
({{ time_to_text(repository_workload) }})
|
|
||||||
{% endif %}
|
|
||||||
</h4>
|
|
||||||
</th>
|
|
||||||
<tr>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
<tr class="{{odoo_module.workload == 0 and 'work_done' or ''}}">
|
|
||||||
<td>{{odoo_module.name}}
|
|
||||||
{%- if odoo_module.module_type == 'not_found' -%}
|
|
||||||
{%- set odoo_apps_url = odoo_module.get_odoo_apps_url() -%}
|
|
||||||
{%- if odoo_apps_url -%}
|
|
||||||
<a href="{{odoo_apps_url}}" target="_blank">AppsStore</a>
|
|
||||||
{%- else -%}
|
|
||||||
{%- set odoo_code_search_url = odoo_module.get_odoo_code_search_url() -%}
|
|
||||||
{%- if odoo_code_search_url -%}
|
|
||||||
<a href="{{odoo_code_search_url}}" target="_blank">OdooCodeSearch</a>
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endif -%}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{%- if odoo_module.workload -%}
|
|
||||||
{{time_to_text(odoo_module.workload)}}
|
|
||||||
{%- endif -%}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{%- for version in odoo_module.analyse.all_versions %}
|
|
||||||
{%- set module_version = odoo_module.get_module_version(version) %}
|
|
||||||
{%- if module_version %}
|
|
||||||
{%- set size_text = module_version.get_size_text() %}
|
|
||||||
{%- set analysis_text = module_version.get_analysis_text() %}
|
|
||||||
{%- set workload = module_version.workload %}
|
|
||||||
|
|
||||||
<td style="background-color:{{module_version.get_bg_color()}};">
|
|
||||||
{{module_version.get_text()}}
|
|
||||||
|
|
||||||
{%- if workload -%}
|
|
||||||
<span style="background-color:lightblue;">({{time_to_text(workload)}})</span>
|
|
||||||
{%- endif -%}
|
|
||||||
{%- if size_text -%}
|
|
||||||
<br/>
|
|
||||||
<span style="color:gray;font-size:11px;font-family:monospace;">({{ size_text}})</span>
|
|
||||||
{%- endif -%}
|
|
||||||
{%- if analysis_text -%}
|
|
||||||
<br/>
|
|
||||||
<span style="color:gray;font-size:11px;font-family:monospace;">
|
|
||||||
<a href="{{module_version.analysis_url()}}" target="_blank">({{ analysis_text}})</a>
|
|
||||||
</span>
|
|
||||||
{%- endif %}
|
|
||||||
</td>
|
|
||||||
{%- else %}
|
|
||||||
<td style="background-color:gray;"> </td>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
</tr>
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
project_name: {{ project_name }}
|
|
||||||
|
|
||||||
|
|
||||||
postgres_image_name: postgres:{{postgresql_version}}
|
|
||||||
postgres_container_name: {{project_name}}-container-postgres-{{postgresql_version}}
|
|
||||||
postgres_volume_name: {{project_name}}-volume-postgres-{{postgresql_version}}
|
|
||||||
postgres_extra_settings:
|
|
||||||
|
|
||||||
|
|
||||||
odoo_rpc_timeout: 3600
|
|
||||||
odoo_host_xmlrpc_port: 9069
|
|
||||||
odoo_default_company:
|
|
||||||
country_code: FR
|
|
||||||
|
|
||||||
|
|
||||||
odoo_versions:
|
|
||||||
{%- for odoo_version in odoo_versions %}
|
|
||||||
- {{ odoo_version }}
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
odoo_version_settings:
|
|
||||||
{%- for odoo_version in odoo_versions %}
|
|
||||||
{{odoo_version}}:
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
migration_steps:
|
|
||||||
{%- for step in steps %}
|
|
||||||
- name: {{ step['name'] }}
|
|
||||||
version: {{ step['version'] }}
|
|
||||||
execution_context: {{ step['execution_context'] }}
|
|
||||||
complete_name: {{ step['complete_name'] }}
|
|
||||||
{%- if step['execution_context'] == 'regular'%}
|
|
||||||
update: True
|
|
||||||
{%- endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
workload_settings:
|
|
||||||
|
|
||||||
# Ignored module list
|
|
||||||
ignored_module_list: []
|
|
||||||
|
|
||||||
# porting a module requires 45 minutes minimaly
|
|
||||||
port_minimal_time: 45
|
|
||||||
|
|
||||||
# a migration cost more for each version
|
|
||||||
port_per_version: 15
|
|
||||||
|
|
||||||
# Porting 120 lines of Python code costs 1 hour
|
|
||||||
port_per_python_line_time: 0.5
|
|
||||||
|
|
||||||
# Porting 120 lines of Javascript code costs 1 hour
|
|
||||||
port_per_javascript_line_time: 0.5
|
|
||||||
|
|
||||||
# Porting 10 lines of XML costs 1 minute
|
|
||||||
port_per_xml_line_time: 0.10
|
|
||||||
|
|
||||||
# Minimal time for Openupgrade PR
|
|
||||||
open_upgrade_minimal_time: 10
|
|
||||||
|
|
||||||
# time for a line of model in the openupgrade_analysis.txt
|
|
||||||
openupgrade_model_line_time: 10
|
|
||||||
|
|
||||||
# Time for a line of field in the openupgrade_analysis.txt
|
|
||||||
openupgrade_field_line_time: 5
|
|
||||||
|
|
||||||
# Time for a line of XML in the openupgrade_analysis.txt
|
|
||||||
openupgrade_xml_line_time: 0.1
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
base,Base
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/11.0/setup/package.dfsrc
|
|
||||||
FROM debian:stretch
|
|
||||||
|
|
||||||
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 sed -i -- 's/stretch-updates/stretch/g' /etc/apt/**.list
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
node-less \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
python3-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/12.0/setup/package.dfsrc
|
|
||||||
FROM debian:stretch
|
|
||||||
|
|
||||||
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 sed -i -- 's/stretch-updates/stretch/g' /etc/apt/**.list
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libsass0 \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
python3-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/13.0/setup/package.dfsrc
|
|
||||||
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 && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libsass1 \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
python3-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/14.0/setup/package.dfsrc
|
|
||||||
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 && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/15.0/setup/package.dfsrc
|
|
||||||
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 && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/16.0/setup/package.dfsrc
|
|
||||||
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 && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/17.0/setup/package.dfsrc
|
|
||||||
FROM debian:bookworm
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-venv \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt --break-system-packages
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
# <OOW> : Copy of https://github.com/odoo/odoo/blob/18.0/setup/package.dfsrc
|
|
||||||
FROM debian:bookworm
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y locales && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Reconfigure locales such that postgresql uses UTF-8 encoding
|
|
||||||
RUN dpkg-reconfigure locales && \
|
|
||||||
locale-gen C.UTF-8 && \
|
|
||||||
/usr/sbin/update-locale LANG=C.UTF-8
|
|
||||||
ENV LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get upgrade -qq -y && \
|
|
||||||
apt-get install \
|
|
||||||
postgresql \
|
|
||||||
postgresql-server-dev-all \
|
|
||||||
postgresql-client \
|
|
||||||
adduser \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3-venv \
|
|
||||||
python3-wheel \
|
|
||||||
build-essential \
|
|
||||||
python3 -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Debian packages
|
|
||||||
COPY extra_debian_requirements.txt /extra_debian_requirements.txt
|
|
||||||
COPY addons_debian_requirements.txt /addons_debian_requirements.txt
|
|
||||||
RUN apt-get update -qq \
|
|
||||||
&& apt-get install -y git \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' extra_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& grep -vE '^\s*#|^\s*$' addons_debian_requirements.txt | xargs apt-get install -y --no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# <OOW> Install Python librairies
|
|
||||||
COPY ./src/odoo/requirements.txt /odoo_python_requirements.txt
|
|
||||||
COPY addons_python_requirements.txt /addons_python_requirements.txt
|
|
||||||
COPY extra_python_requirements.txt /extra_python_requirements.txt
|
|
||||||
RUN pip3 install --upgrade pip --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir setuptools-scm --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /odoo_python_requirements.txt --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /extra_python_requirements.txt --break-system-packages \
|
|
||||||
&& python3 -m pip install --no-cache-dir -r /addons_python_requirements.txt --break-system-packages
|
|
||||||
|
|
||||||
# <OOW> Get local user id and set it to the odoo user
|
|
||||||
ARG LOCAL_USER_ID
|
|
||||||
|
|
||||||
RUN useradd --uid $LOCAL_USER_ID --non-unique odoo
|
|
||||||
|
|
||||||
USER odoo
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Do NOT edit manually. Changes here will be overwritten by the command 'guess-requirement'
|
|
||||||
|
|
||||||
{%- for bin_lib, module_list in dependencies.items() %}
|
|
||||||
|
|
||||||
# Required by the module(s): {{ ','.join(module_list) }}
|
|
||||||
{{ bin_lib }}
|
|
||||||
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Do NOT edit manually. Changes here will be overwritten by the command 'guess-requirement'
|
|
||||||
|
|
||||||
{%- for python_lib, module_list in dependencies.items() %}
|
|
||||||
|
|
||||||
# Required by the module(s): {{ ','.join(module_list) }}
|
|
||||||
{{ python_lib }}
|
|
||||||
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Mandatory library used in all odoo-openupgrade-wizard
|
|
||||||
# Note: As the openupgradelib is not allways up to date in pypi,
|
|
||||||
# we use the github master url.
|
|
||||||
git+https://github.com/OCA/openupgradelib@master#egg=openupgradelib
|
|
||||||
|
|
||||||
# Library used to run generate-module-analysis command
|
|
||||||
# dependencies of the module OCA/server-tools 'upgrade_analysis'
|
|
||||||
odoorpc
|
|
||||||
mako
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# This file can be left empty
|
|
||||||
# The following standard parameters are generated automatically and are
|
|
||||||
# not required to be found in this file:
|
|
||||||
# - addons_path
|
|
||||||
# - connection parameters to the database
|
|
||||||
# - database name
|
|
||||||
# - http or xml port
|
|
||||||
# - etc.
|
|
||||||
# Only non standard or special options should be specified here.
|
|
||||||
|
|
||||||
[options]
|
|
||||||
# Disable default memory limit of 2560 MiB
|
|
||||||
limit_memory_hard = 0
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
##############################################################################
|
|
||||||
## Odoo Repository
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
./src/odoo:
|
|
||||||
defaults:
|
|
||||||
depth: 1
|
|
||||||
remotes:
|
|
||||||
odoo: https://github.com/odoo/odoo
|
|
||||||
target: odoo {{ odoo_version }}-target
|
|
||||||
merges:
|
|
||||||
- odoo {{ odoo_version }}
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
## OpenUpgrade Repository
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
./src/openupgrade:
|
|
||||||
defaults:
|
|
||||||
depth: 1
|
|
||||||
remotes:
|
|
||||||
OCA: https://github.com/OCA/OpenUpgrade
|
|
||||||
target: OCA {{ odoo_version }}-target
|
|
||||||
merges:
|
|
||||||
- OCA {{ odoo_version }}
|
|
||||||
|
|
||||||
{% for org_name, repo_list in orgs.items() %}
|
|
||||||
##############################################################################
|
|
||||||
## {{ org_name }} Repositories
|
|
||||||
##############################################################################
|
|
||||||
{% for repo in repo_list %}
|
|
||||||
./src/{{ org_name }}/{{ repo }}:
|
|
||||||
defaults:
|
|
||||||
depth: 1
|
|
||||||
remotes:
|
|
||||||
{{ org_name }}: https://github.com/{{ org_name }}/{{ repo }}
|
|
||||||
target: {{ org_name }} {{ odoo_version }}-target
|
|
||||||
merges:
|
|
||||||
- {{ org_name }} {{ odoo_version }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
_logger.info("Executing post-migration.py script...")
|
|
||||||
|
|
||||||
env = env # noqa: F821
|
|
||||||
|
|
||||||
# Write custom script here
|
|
||||||
|
|
||||||
env.cr.commit()
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import shutil
|
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
check_db_exist,
|
|
||||||
ensure_database,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def copydb(ctx, source, dest):
|
|
||||||
# check if source exists
|
|
||||||
logger.info(f"Check if source database '{source}' exists...")
|
|
||||||
check_db_exist(ctx, source, raise_exception=True)
|
|
||||||
|
|
||||||
# drop database if exist
|
|
||||||
ensure_database(ctx, dest, state="absent")
|
|
||||||
|
|
||||||
# Copy database
|
|
||||||
ensure_database(ctx, dest, state="present", template=source)
|
|
||||||
|
|
||||||
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}' directory...")
|
|
||||||
shutil.copytree(source_path, dest_path)
|
|
||||||
|
|
||||||
|
|
||||||
def dropdb(ctx, database):
|
|
||||||
"""Drop a database and its filestore"""
|
|
||||||
# Check if database exists
|
|
||||||
logger.info(f"Check if database '{database}' exists...")
|
|
||||||
check_db_exist(ctx, database, raise_exception=True)
|
|
||||||
# Drop database
|
|
||||||
logger.info(f"Drop database '{database}'...")
|
|
||||||
ensure_database(ctx, database, state="absent")
|
|
||||||
# Drop filestore
|
|
||||||
root_filestore_path = ctx.obj["filestore_folder_path"] / "filestore"
|
|
||||||
filestore_path = root_filestore_path / database
|
|
||||||
logger.info(f"Remove filestore of '{database}' if exists...")
|
|
||||||
shutil.rmtree(filestore_path, ignore_errors=True)
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
import docker
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_client():
|
|
||||||
return docker.from_env()
|
|
||||||
|
|
||||||
|
|
||||||
def pull_image(image_name):
|
|
||||||
client = get_docker_client()
|
|
||||||
client.images.pull(image_name)
|
|
||||||
|
|
||||||
|
|
||||||
def build_image(path, tag, buildargs={}):
|
|
||||||
logger.debug(
|
|
||||||
f"Building image named based on {path}/Dockerfile."
|
|
||||||
" This can take a long time..."
|
|
||||||
)
|
|
||||||
debug_docker_command = f"docker build {path} --tag {tag}"
|
|
||||||
for arg_name, arg_value in buildargs.items():
|
|
||||||
debug_docker_command += f"\\\n --build-arg {arg_name}={arg_value}"
|
|
||||||
|
|
||||||
logger.debug(f"DOCKER COMMAND:\n\n{debug_docker_command}\n")
|
|
||||||
docker_client = get_docker_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
image = docker_client.images.build(
|
|
||||||
path=str(path),
|
|
||||||
tag=tag,
|
|
||||||
buildargs=buildargs,
|
|
||||||
rm=True,
|
|
||||||
)
|
|
||||||
logger.debug("Image build done.")
|
|
||||||
except docker.errors.BuildError as buildError:
|
|
||||||
logger.error("\n".join([str(log) for log in buildError.build_log]))
|
|
||||||
logger.error("Image build failed.")
|
|
||||||
raise buildError
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def run_container(
|
|
||||||
image_name,
|
|
||||||
container_name,
|
|
||||||
command=None,
|
|
||||||
ports={},
|
|
||||||
volumes={},
|
|
||||||
environments={},
|
|
||||||
links={},
|
|
||||||
detach=False,
|
|
||||||
auto_remove=False,
|
|
||||||
):
|
|
||||||
client = get_docker_client()
|
|
||||||
if not client.images.list(filters={"reference": image_name}):
|
|
||||||
raise Exception(
|
|
||||||
f"The image {image_name} is not available on your system."
|
|
||||||
" Did you run 'odoo-openupgrade-wizard docker-build' ?"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"Launching Docker container named {image_name}...")
|
|
||||||
debug_docker_command = f"docker run --name {container_name}\\\n"
|
|
||||||
|
|
||||||
for k, v in ports.items():
|
|
||||||
debug_docker_command += f" --publish {k}:{v}\\\n"
|
|
||||||
for k, v in volumes.items():
|
|
||||||
debug_docker_command += f" --volume {k}:{v}\\\n"
|
|
||||||
for k, v in environments.items():
|
|
||||||
debug_docker_command += f" --env {k}={v}\\\n"
|
|
||||||
for k, v in links.items():
|
|
||||||
debug_docker_command += f" --link {k}:{v}\\\n"
|
|
||||||
if auto_remove:
|
|
||||||
debug_docker_command += " --rm"
|
|
||||||
if detach:
|
|
||||||
debug_docker_command += " --detach"
|
|
||||||
debug_docker_command += f" {image_name}"
|
|
||||||
if command:
|
|
||||||
debug_docker_command += f" \\\n{command}"
|
|
||||||
logger.debug(f"DOCKER COMMAND:\n{debug_docker_command}")
|
|
||||||
|
|
||||||
container = client.containers.run(
|
|
||||||
image_name,
|
|
||||||
name=container_name,
|
|
||||||
command=command,
|
|
||||||
ports={x: y for y, x in ports.items()},
|
|
||||||
volumes=[str(k) + ":" + str(v) for k, v in volumes.items()],
|
|
||||||
environment=environments,
|
|
||||||
links=links,
|
|
||||||
detach=detach,
|
|
||||||
auto_remove=auto_remove,
|
|
||||||
)
|
|
||||||
if detach:
|
|
||||||
logger.debug(f"Container {image_name} launched.")
|
|
||||||
elif auto_remove:
|
|
||||||
logger.debug("Container closed.")
|
|
||||||
|
|
||||||
return container
|
|
||||||
|
|
||||||
|
|
||||||
def exec_container(container, command):
|
|
||||||
debug_docker_command = f"docker exec {container.name}"
|
|
||||||
debug_docker_command += f" \\\n{command}"
|
|
||||||
logger.debug(f"DOCKER COMMAND:\n{debug_docker_command}")
|
|
||||||
docker_result = container.exec_run(command)
|
|
||||||
if docker_result.exit_code != 0:
|
|
||||||
raise Exception(
|
|
||||||
f"The command failed in the container {container.name}.\n"
|
|
||||||
f"- Command: {command}\n"
|
|
||||||
f"- Exit Code: {docker_result.exit_code}\n"
|
|
||||||
f"- Output: {docker_result.output}"
|
|
||||||
)
|
|
||||||
return docker_result
|
|
||||||
|
|
||||||
|
|
||||||
def kill_container(container_name):
|
|
||||||
# In some situation, containers.list return
|
|
||||||
# containers with removal already in progress
|
|
||||||
# when we call container.remove(), it is raising an
|
|
||||||
# docker.errors.APIError
|
|
||||||
# "removal of container xx is already in progress".
|
|
||||||
# so, we retry a few seconds after
|
|
||||||
# and raise an exception after five failures.
|
|
||||||
for i in [1, 5, 10, 60, False]:
|
|
||||||
try:
|
|
||||||
_kill_container(container_name)
|
|
||||||
return
|
|
||||||
except docker.errors.APIError as e:
|
|
||||||
if not i:
|
|
||||||
logger.error(f"Fail to kill {container_name} after 5 retries")
|
|
||||||
raise e
|
|
||||||
logger.warning(
|
|
||||||
f"Fail to kill {container_name}. Retrying in {i} seconds"
|
|
||||||
)
|
|
||||||
time.sleep(i)
|
|
||||||
|
|
||||||
|
|
||||||
def _kill_container(container_name):
|
|
||||||
client = get_docker_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
containers = client.containers.list(
|
|
||||||
all=True,
|
|
||||||
filters={"name": container_name},
|
|
||||||
ignore_removed=True,
|
|
||||||
)
|
|
||||||
except docker.errors.NotFound as err:
|
|
||||||
logger.debug(f"Cannot kill container {container_name}: {err}")
|
|
||||||
containers = []
|
|
||||||
|
|
||||||
for container in containers:
|
|
||||||
if container.status != "exited":
|
|
||||||
logger.debug(
|
|
||||||
"Stop container %s, based on image '%s'."
|
|
||||||
% (container.name, ",".join(container.image.tags))
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
container.stop()
|
|
||||||
container.wait()
|
|
||||||
logger.debug(
|
|
||||||
"Container %s status is now '%s'."
|
|
||||||
% (container.name, container.status)
|
|
||||||
)
|
|
||||||
if container.status != "removed":
|
|
||||||
container.remove()
|
|
||||||
container.wait(condition="removed")
|
|
||||||
|
|
||||||
except docker.errors.NotFound as err:
|
|
||||||
logger.debug(f"Cannot kill container {container.name}: {err}")
|
|
||||||
|
|
@ -1,437 +0,0 @@
|
||||||
import configparser
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
|
||||||
get_base_module_folder,
|
|
||||||
get_manifest_name,
|
|
||||||
get_odoo_folder,
|
|
||||||
get_odoo_run_command,
|
|
||||||
get_server_wide_modules_upgrade,
|
|
||||||
skip_addon_path,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_docker import (
|
|
||||||
kill_container,
|
|
||||||
run_container,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import get_postgres_container
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import get_script_folder
|
|
||||||
|
|
||||||
DEFAULT_ODOO_HTTP_PORT = 8069
|
|
||||||
|
|
||||||
|
|
||||||
def get_repo_file_path(ctx, odoo_version: float) -> Path:
|
|
||||||
"""return the relative path of the repos.yml file
|
|
||||||
of a given odoo version"""
|
|
||||||
repo_file = False
|
|
||||||
# Check if submodule path exists
|
|
||||||
version_cfg = (
|
|
||||||
ctx.obj["config"]["odoo_version_settings"][odoo_version] or {}
|
|
||||||
)
|
|
||||||
submodule_path = get_odoo_env_path(ctx, odoo_version) / "repo_submodule"
|
|
||||||
|
|
||||||
if submodule_path.exists():
|
|
||||||
repo_file = submodule_path / version_cfg["repo_file_path"]
|
|
||||||
if repo_file.exists():
|
|
||||||
return repo_file
|
|
||||||
else:
|
|
||||||
logger.warning(f"Unable to find the repo file {repo_file}.")
|
|
||||||
repo_file = get_odoo_env_path(ctx, odoo_version) / Path("repos.yml")
|
|
||||||
if not repo_file.exists():
|
|
||||||
raise Exception(f"Unable to find the repo file {repo_file}.")
|
|
||||||
return repo_file
|
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_addons_path(
|
|
||||||
ctx,
|
|
||||||
odoo_env_path: Path,
|
|
||||||
migration_step: dict,
|
|
||||||
execution_context: str = None,
|
|
||||||
) -> str:
|
|
||||||
"""Return
|
|
||||||
- addons_path: a list of Path of that contains odoo module
|
|
||||||
for the current migration_step,
|
|
||||||
based on the analysis of the repos.yml file
|
|
||||||
- empty_addons_path: a list of Path of empty folders.
|
|
||||||
(without any odoo module)"""
|
|
||||||
repo_file = get_repo_file_path(ctx, migration_step["version"])
|
|
||||||
base_module_folder = get_base_module_folder(migration_step)
|
|
||||||
stream = open(repo_file, "r")
|
|
||||||
data = yaml.safe_load(stream)
|
|
||||||
data = data
|
|
||||||
|
|
||||||
addons_path = []
|
|
||||||
empty_addons_path = []
|
|
||||||
odoo_folder = get_odoo_folder(migration_step, execution_context)
|
|
||||||
for key in data.keys():
|
|
||||||
path = Path(key)
|
|
||||||
if str(path).endswith(odoo_folder):
|
|
||||||
# Add two folder for odoo folder
|
|
||||||
addons_path.append(path / Path("addons"))
|
|
||||||
addons_path.append(
|
|
||||||
path / Path(base_module_folder) / Path("addons")
|
|
||||||
)
|
|
||||||
elif skip_addon_path(migration_step, path):
|
|
||||||
pass
|
|
||||||
elif is_addons_path(ctx, odoo_env_path / path, migration_step):
|
|
||||||
addons_path.append(path)
|
|
||||||
else:
|
|
||||||
empty_addons_path.append(path)
|
|
||||||
|
|
||||||
return addons_path, empty_addons_path
|
|
||||||
|
|
||||||
|
|
||||||
def is_addons_path(ctx, path: Path, migration_step: dict):
|
|
||||||
logger.debug(f"Untersuche Pfad: {path}")
|
|
||||||
for folder in [x for x in path.iterdir() if x.is_dir()]:
|
|
||||||
logger.debug(f" Untersuche Pfad: {folder}")
|
|
||||||
if (folder / "__init__.py").exists() and (
|
|
||||||
folder / get_manifest_name(migration_step)
|
|
||||||
).exists():
|
|
||||||
logger.info(f" ✔️ Odoo-Modul gefunden in: {folder}")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
#def is_addons_path(ctx, path: Path, migration_step: dict):
|
|
||||||
# """Prüft, ob im Verzeichnis mindestens ein Odoo-Modul liegt."""
|
|
||||||
# logger.debug(f"Untersuche Pfad: {path}")
|
|
||||||
# for folder in path.iterdir():
|
|
||||||
# if folder.is_dir():
|
|
||||||
# init_exists = (folder / "__init__.py").exists()
|
|
||||||
# manifest_path = folder / get_manifest_name(migration_step)
|
|
||||||
# manifest_exists = manifest_path.exists()
|
|
||||||
# logger.debug(
|
|
||||||
# f" Untersuche Unterordner: {folder} | __init__.py: {init_exists}, Manifest: {manifest_path.name}: {manifest_exists}"
|
|
||||||
# )
|
|
||||||
# if init_exists and manifest_exists:
|
|
||||||
# logger.info(f" ✔️ Odoo-Modul gefunden in: {folder}")
|
|
||||||
# return True
|
|
||||||
# return False
|
|
||||||
|
|
||||||
def get_odoo_env_path(ctx, odoo_version: float) -> Path:
|
|
||||||
folder_name = "env_%s" % str(odoo_version).rjust(4, "0")
|
|
||||||
return ctx.obj["src_folder_path"] / folder_name
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_image_tag(ctx, odoo_version: float) -> str:
|
|
||||||
"""Return a docker image tag, based on project name and odoo version"""
|
|
||||||
return "odoo-openupgrade-wizard-image__%s__%s" % (
|
|
||||||
ctx.obj["config"]["project_name"],
|
|
||||||
str(odoo_version).rjust(4, "0"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_container_name(ctx, database: str, migration_step: dict) -> str:
|
|
||||||
"""Return a docker container name, based on project name, database name,
|
|
||||||
odoo version and migration step"""
|
|
||||||
return "oow-{project}-{database}-{version}-step-{step}".format(
|
|
||||||
project=ctx.obj["config"]["project_name"],
|
|
||||||
database=database,
|
|
||||||
# FIXME: version should be a string, but it is a float
|
|
||||||
version=str(migration_step["version"]).rjust(4, "0"),
|
|
||||||
step=str(migration_step["name"]).rjust(2, "0"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_odoo_command_options(
|
|
||||||
ctx,
|
|
||||||
migration_step: dict,
|
|
||||||
execution_context: str,
|
|
||||||
database: str,
|
|
||||||
demo: bool = False,
|
|
||||||
update: str = None,
|
|
||||||
init: str = None,
|
|
||||||
stop_after_init: bool = False,
|
|
||||||
) -> list:
|
|
||||||
"""
|
|
||||||
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"])
|
|
||||||
|
|
||||||
# Compute 'server_wide_modules'
|
|
||||||
custom_odoo_config_file = odoo_env_path / "odoo.conf"
|
|
||||||
parser = configparser.RawConfigParser()
|
|
||||||
parser.read(custom_odoo_config_file)
|
|
||||||
server_wide_modules = parser.get(
|
|
||||||
"options", "server_wide_modules", fallback=[]
|
|
||||||
)
|
|
||||||
server_wide_modules += get_server_wide_modules_upgrade(
|
|
||||||
migration_step, execution_context
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute 'addons_path'
|
|
||||||
addons_path_list, empty_addons_path_list = get_odoo_addons_path(
|
|
||||||
ctx, odoo_env_path, migration_step, execution_context
|
|
||||||
)
|
|
||||||
|
|
||||||
# custom_addons from odoo_version_settings[version]
|
|
||||||
version = migration_step["version"]
|
|
||||||
#TODO: check if custom_addons is in ctx.obj['config']
|
|
||||||
custom_addons = ctx.obj.get("config", {}).get("odoo_version_settings", {}).get(version, {}).get("custom_addons", [])
|
|
||||||
|
|
||||||
for path in custom_addons:
|
|
||||||
logger.info(f"📁 custom_addons: adding path '{path}'")
|
|
||||||
addons_path_list.append(path)
|
|
||||||
|
|
||||||
|
|
||||||
# Normalize addons path (relative to /odoo_env in container)
|
|
||||||
addons_path = ",".join(
|
|
||||||
[str(Path("/odoo_env") / x) for x in addons_path_list]
|
|
||||||
)
|
|
||||||
for empty_addons_path in empty_addons_path_list:
|
|
||||||
logger.info(
|
|
||||||
"Skipping addons path"
|
|
||||||
f" '{(odoo_env_path / empty_addons_path).resolve()}'"
|
|
||||||
" because it doesn't contain any odoo module."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute 'log_file'
|
|
||||||
log_file_name = (
|
|
||||||
f"{ctx.obj['log_prefix']}____{migration_step['complete_name']}.log"
|
|
||||||
)
|
|
||||||
log_file_docker_path = f"/env/log/{log_file_name}"
|
|
||||||
|
|
||||||
# Build options string
|
|
||||||
options = [
|
|
||||||
"--config=/odoo_env/odoo.conf",
|
|
||||||
"--data-dir=/env/filestore/",
|
|
||||||
f"--addons-path={addons_path}",
|
|
||||||
f"--logfile={log_file_docker_path}",
|
|
||||||
"--db_host=db",
|
|
||||||
"--db_port=5432",
|
|
||||||
"--db_user=odoo",
|
|
||||||
"--db_password=odoo",
|
|
||||||
"--workers=0",
|
|
||||||
f"{'--without-demo=all' if not demo else ''}",
|
|
||||||
f"{'--load ' + ','.join(server_wide_modules) if server_wide_modules else ''}", # noqa
|
|
||||||
f"{'--database=' + database if database else ''}",
|
|
||||||
f"{'--update ' + update if update else ''}",
|
|
||||||
f"{'--init ' + init if init else ''}",
|
|
||||||
f"{'--stop-after-init' if stop_after_init else ''}",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Log resolved addons_path
|
|
||||||
logger.info(f"📦 Final --addons-path: {addons_path}")
|
|
||||||
|
|
||||||
# Remove empty strings
|
|
||||||
return [x for x in options if x]
|
|
||||||
|
|
||||||
|
|
||||||
def generate_odoo_command(
|
|
||||||
ctx,
|
|
||||||
migration_step: dict,
|
|
||||||
execution_context: str,
|
|
||||||
database: str,
|
|
||||||
demo: bool = False,
|
|
||||||
update: str = None,
|
|
||||||
init: str = None,
|
|
||||||
stop_after_init: bool = False,
|
|
||||||
shell: bool = False,
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Generate the full Odoo command using options from
|
|
||||||
generate_odoo_command_options.
|
|
||||||
"""
|
|
||||||
options = generate_odoo_command_options(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
execution_context,
|
|
||||||
database,
|
|
||||||
demo,
|
|
||||||
update,
|
|
||||||
init,
|
|
||||||
stop_after_init,
|
|
||||||
)
|
|
||||||
|
|
||||||
base_command = (
|
|
||||||
Path("/odoo_env")
|
|
||||||
/ Path(get_odoo_folder(migration_step, execution_context))
|
|
||||||
/ Path(get_odoo_run_command(migration_step))
|
|
||||||
)
|
|
||||||
|
|
||||||
options_as_string = " ".join(options)
|
|
||||||
if shell:
|
|
||||||
return f"{base_command} shell {options_as_string}"
|
|
||||||
else:
|
|
||||||
return f"{base_command} {options_as_string}"
|
|
||||||
|
|
||||||
|
|
||||||
def run_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step: dict,
|
|
||||||
detached_container: bool = False,
|
|
||||||
database: str = None,
|
|
||||||
update: str = None,
|
|
||||||
init: str = None,
|
|
||||||
stop_after_init: bool = False,
|
|
||||||
shell: bool = False,
|
|
||||||
demo: bool = False,
|
|
||||||
execution_context: str = None,
|
|
||||||
alternative_xml_rpc_port: int = False,
|
|
||||||
links: dict = {},
|
|
||||||
publish_ports: bool = False,
|
|
||||||
):
|
|
||||||
# Ensure that Postgres container exist
|
|
||||||
get_postgres_container(ctx)
|
|
||||||
logger.info(
|
|
||||||
"Launching Odoo Container (Version {version}) for {db_text}"
|
|
||||||
" in {execution_context} mode. Demo Data is {demo_text}"
|
|
||||||
" {stop_text} {init_text} {update_text}".format(
|
|
||||||
version=migration_step["version"],
|
|
||||||
db_text=database and "database '%s'" % database or "any databases",
|
|
||||||
execution_context=execution_context
|
|
||||||
or migration_step["execution_context"],
|
|
||||||
demo_text=demo and "enabled" or "disabled",
|
|
||||||
stop_text=stop_after_init and " (stop-after-init)" or "",
|
|
||||||
init_text=init and " (Init: %s)" % init or "",
|
|
||||||
update_text=update and " (Update: %s)" % update or "",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
command = generate_odoo_command(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
execution_context,
|
|
||||||
database,
|
|
||||||
demo=demo,
|
|
||||||
update=update,
|
|
||||||
init=init,
|
|
||||||
stop_after_init=stop_after_init,
|
|
||||||
shell=shell,
|
|
||||||
)
|
|
||||||
|
|
||||||
return run_container_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
command,
|
|
||||||
detached_container=detached_container,
|
|
||||||
database=database,
|
|
||||||
execution_context=execution_context,
|
|
||||||
alternative_xml_rpc_port=alternative_xml_rpc_port,
|
|
||||||
links=links,
|
|
||||||
publish_ports=publish_ports,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_container_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step: dict,
|
|
||||||
command: str,
|
|
||||||
detached_container: bool = False,
|
|
||||||
database: str = None,
|
|
||||||
alternative_xml_rpc_port: int = False,
|
|
||||||
execution_context: str = None,
|
|
||||||
links: dict = {},
|
|
||||||
publish_ports: bool = False,
|
|
||||||
):
|
|
||||||
env_path = ctx.obj["env_folder_path"]
|
|
||||||
odoo_env_path = get_odoo_env_path(ctx, migration_step["version"])
|
|
||||||
|
|
||||||
host_xmlrpc_port = (
|
|
||||||
alternative_xml_rpc_port
|
|
||||||
and alternative_xml_rpc_port
|
|
||||||
or ctx.obj["config"]["odoo_host_xmlrpc_port"]
|
|
||||||
)
|
|
||||||
|
|
||||||
links.update({ctx.obj["config"]["postgres_container_name"]: "db"})
|
|
||||||
|
|
||||||
if publish_ports:
|
|
||||||
ports = {host_xmlrpc_port: DEFAULT_ODOO_HTTP_PORT}
|
|
||||||
else:
|
|
||||||
ports = {}
|
|
||||||
|
|
||||||
return run_container(
|
|
||||||
get_docker_image_tag(ctx, migration_step["version"]),
|
|
||||||
get_docker_container_name(ctx, database, migration_step),
|
|
||||||
command=command,
|
|
||||||
ports=ports,
|
|
||||||
volumes={
|
|
||||||
env_path: "/env/",
|
|
||||||
odoo_env_path: "/odoo_env/",
|
|
||||||
},
|
|
||||||
links=links,
|
|
||||||
detach=detached_container,
|
|
||||||
auto_remove=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def kill_odoo(ctx, database, migration_step: dict):
|
|
||||||
kill_container(get_docker_container_name(ctx, database, migration_step))
|
|
||||||
|
|
||||||
|
|
||||||
def execute_click_odoo_python_files(
|
|
||||||
ctx,
|
|
||||||
database: str,
|
|
||||||
migration_step: dict,
|
|
||||||
python_files: list = [],
|
|
||||||
execution_context: str = None,
|
|
||||||
):
|
|
||||||
if not python_files:
|
|
||||||
# Get post-migration python scripts to execute
|
|
||||||
script_folder = get_script_folder(ctx, migration_step)
|
|
||||||
python_files = [
|
|
||||||
Path("scripts") / Path(migration_step["complete_name"]) / Path(f)
|
|
||||||
for f in os.listdir(script_folder)
|
|
||||||
if os.path.isfile(os.path.join(script_folder, f))
|
|
||||||
and f[-3:] == ".py"
|
|
||||||
]
|
|
||||||
python_files = sorted(python_files)
|
|
||||||
|
|
||||||
base_command = generate_odoo_command(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
execution_context,
|
|
||||||
database,
|
|
||||||
shell=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for python_file in python_files:
|
|
||||||
command = f"/bin/bash -c 'cat /env/{python_file} | {base_command}'"
|
|
||||||
try:
|
|
||||||
logger.info(
|
|
||||||
f"Step {migration_step['complete_name']}."
|
|
||||||
f" Executing script {python_file}..."
|
|
||||||
)
|
|
||||||
run_container_odoo(
|
|
||||||
ctx,
|
|
||||||
migration_step,
|
|
||||||
command,
|
|
||||||
detached_container=False,
|
|
||||||
database=database,
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
logger.error(
|
|
||||||
"An error occured. Exiting. %s\n%s"
|
|
||||||
% (e, traceback.print_exception(*sys.exc_info()))
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
finally:
|
|
||||||
kill_odoo(ctx, database, migration_step)
|
|
||||||
|
|
||||||
|
|
||||||
def get_odoo_modules_from_csv(module_file_path: Path) -> list:
|
|
||||||
logger.debug(f"Reading '{module_file_path}' file...")
|
|
||||||
module_names = []
|
|
||||||
csvfile = open(module_file_path, "r")
|
|
||||||
spamreader = csv.reader(csvfile, delimiter=",", quotechar='"')
|
|
||||||
for row in spamreader:
|
|
||||||
# Try to guess that a line is not correct
|
|
||||||
if not row:
|
|
||||||
continue
|
|
||||||
if not row[0]:
|
|
||||||
continue
|
|
||||||
if " " in row[0]:
|
|
||||||
continue
|
|
||||||
if any([x.isupper() for x in row[0]]):
|
|
||||||
continue
|
|
||||||
module_names.append(row[0])
|
|
||||||
return module_names
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
import odoorpc
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
# Wait for the launch of odoo instance 60 seconds
|
|
||||||
_ODOO_RPC_MAX_TRY = 60
|
|
||||||
_ODOO_RPC_URL = "0.0.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
class OdooInstance:
|
|
||||||
env = False
|
|
||||||
version = False
|
|
||||||
|
|
||||||
def __init__(self, ctx, database, alternative_xml_rpc_port=False):
|
|
||||||
port = (
|
|
||||||
alternative_xml_rpc_port
|
|
||||||
and alternative_xml_rpc_port
|
|
||||||
or ctx.obj["config"]["odoo_host_xmlrpc_port"]
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Connect to database {database} via odoorpc (Port {port})..."
|
|
||||||
)
|
|
||||||
|
|
||||||
for x in range(1, _ODOO_RPC_MAX_TRY + 1):
|
|
||||||
# Connection
|
|
||||||
try:
|
|
||||||
rpc_connexion = odoorpc.ODOO(
|
|
||||||
_ODOO_RPC_URL,
|
|
||||||
"jsonrpc",
|
|
||||||
port=port,
|
|
||||||
timeout=ctx.obj["config"]["odoo_rpc_timeout"],
|
|
||||||
)
|
|
||||||
# connexion is OK
|
|
||||||
break
|
|
||||||
except (socket.gaierror, socket.error) as e:
|
|
||||||
if x < _ODOO_RPC_MAX_TRY:
|
|
||||||
logger.debug(
|
|
||||||
f"{x}/{_ODOO_RPC_MAX_TRY}"
|
|
||||||
" Unable to connect to the server."
|
|
||||||
" Retrying in 1 second..."
|
|
||||||
)
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
logger.critical(
|
|
||||||
f"{x}/{_ODOO_RPC_MAX_TRY}"
|
|
||||||
" Unable to connect to the server."
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
# Login
|
|
||||||
try:
|
|
||||||
rpc_connexion.login(database, "admin", "admin")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Unable to connect to http://localhost:{port}"
|
|
||||||
" with login 'admin' and password 'admin."
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
self.env = rpc_connexion.env
|
|
||||||
self.version = rpc_connexion.version
|
|
||||||
|
|
||||||
def browse_by_search(
|
|
||||||
self, model_name, domain=False, order=False, limit=False
|
|
||||||
):
|
|
||||||
domain = domain or []
|
|
||||||
model = self.env[model_name]
|
|
||||||
return model.browse(model.search(domain, order=order, limit=limit))
|
|
||||||
|
|
||||||
def browse_by_create(self, model_name, vals):
|
|
||||||
model = self.env[model_name]
|
|
||||||
return model.browse(model.create(vals))
|
|
||||||
|
|
||||||
def install_modules(self, module_names):
|
|
||||||
if type(module_names) is str:
|
|
||||||
module_names = [module_names]
|
|
||||||
installed_modules = []
|
|
||||||
i = 0
|
|
||||||
for module_name in module_names:
|
|
||||||
i += 1
|
|
||||||
log_prefix = f"{i}/{len(module_names)} - Module '{module_name}': "
|
|
||||||
modules = self.browse_by_search(
|
|
||||||
"ir.module.module", [("name", "=", module_name)]
|
|
||||||
)
|
|
||||||
if not len(modules):
|
|
||||||
logger.error(f"{log_prefix}': Not found.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
module = modules[0]
|
|
||||||
if module.state == "installed":
|
|
||||||
logger.info(f"{log_prefix}': still installed. Skipped.")
|
|
||||||
elif module.state == "uninstalled":
|
|
||||||
try_qty = 0
|
|
||||||
installed = False
|
|
||||||
while installed is False:
|
|
||||||
try_qty += 1
|
|
||||||
try_qty_text = f" (try #{try_qty})" if try_qty != 1 else ""
|
|
||||||
logger.info(f"{log_prefix}': Installing... {try_qty_text}")
|
|
||||||
try:
|
|
||||||
module.button_immediate_install()
|
|
||||||
installed = True
|
|
||||||
installed_modules.append(module_name)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
if try_qty <= 5:
|
|
||||||
sleeping_time = 2 * try_qty * 60
|
|
||||||
logger.warning(
|
|
||||||
f"Error. Retrying in {sleeping_time} seconds."
|
|
||||||
f"\n{e}"
|
|
||||||
)
|
|
||||||
time.sleep(sleeping_time)
|
|
||||||
else:
|
|
||||||
logger.critical(
|
|
||||||
f"Error after {try_qty} try. Exiting." f"\n{e}"
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"{log_prefix}': In the {module.state} state."
|
|
||||||
" (Unable to install)"
|
|
||||||
)
|
|
||||||
return installed_modules
|
|
||||||
|
|
@ -1,857 +0,0 @@
|
||||||
import ast
|
|
||||||
import importlib
|
|
||||||
import os
|
|
||||||
from functools import total_ordering
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from git import Repo
|
|
||||||
from git.exc import InvalidGitRepositoryError
|
|
||||||
from loguru import logger
|
|
||||||
from pygount import SourceAnalysis
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.configuration_version_dependant import (
|
|
||||||
get_apriori_file_relative_path,
|
|
||||||
get_coverage_relative_path,
|
|
||||||
get_openupgrade_analysis_files,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import (
|
|
||||||
get_odoo_addons_path,
|
|
||||||
get_odoo_env_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Analysis(object):
|
|
||||||
def __init__(self, ctx):
|
|
||||||
self.modules = []
|
|
||||||
self.initial_version = ctx.obj["config"]["odoo_versions"][0]
|
|
||||||
self.final_version = ctx.obj["config"]["odoo_versions"][-1]
|
|
||||||
self.all_versions = [x for x in ctx.obj["config"]["odoo_versions"]]
|
|
||||||
|
|
||||||
def analyse_module_version(self, ctx, module_list):
|
|
||||||
self._generate_module_version_first_version(ctx, module_list)
|
|
||||||
|
|
||||||
for count in range(len(self.all_versions) - 1):
|
|
||||||
previous_version = self.all_versions[count]
|
|
||||||
current_version = self.all_versions[count + 1]
|
|
||||||
self._generate_module_version_next_version(
|
|
||||||
ctx, previous_version, current_version
|
|
||||||
)
|
|
||||||
|
|
||||||
def analyse_openupgrade_state(self, ctx):
|
|
||||||
logger.info("Parsing openupgrade module coverage for each migration.")
|
|
||||||
coverage_analysis = {}
|
|
||||||
for version in self.all_versions[1:]:
|
|
||||||
coverage_analysis[version] = {}
|
|
||||||
relative_path = get_coverage_relative_path(version)
|
|
||||||
env_folder_path = get_odoo_env_path(ctx, version)
|
|
||||||
coverage_path = env_folder_path / relative_path
|
|
||||||
with open(coverage_path) as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
for line in [x for x in lines if "|" in x]:
|
|
||||||
clean_line = (
|
|
||||||
line.replace("\n", "")
|
|
||||||
.replace("|del|", "")
|
|
||||||
.replace("|new|", "")
|
|
||||||
)
|
|
||||||
splited_line = [x.strip() for x in clean_line.split("|") if x]
|
|
||||||
if len(splited_line) == 2:
|
|
||||||
coverage_analysis[version][splited_line[0]] = splited_line[
|
|
||||||
1
|
|
||||||
]
|
|
||||||
if len(splited_line) == 3:
|
|
||||||
coverage_analysis[version][splited_line[0]] = (
|
|
||||||
splited_line[1] + " " + splited_line[2]
|
|
||||||
).strip()
|
|
||||||
elif len(splited_line) > 3:
|
|
||||||
raise ValueError(
|
|
||||||
"Incorrect value in openupgrade analysis"
|
|
||||||
f" file {coverage_path} for line {line}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for odoo_module in filter(
|
|
||||||
lambda x: x.module_type == "odoo", self.modules
|
|
||||||
):
|
|
||||||
for module_version in list(odoo_module.module_versions.values()):
|
|
||||||
module_version.analyse_openupgrade_state(coverage_analysis)
|
|
||||||
|
|
||||||
for version in self.all_versions[1:]:
|
|
||||||
odoo_env_path = get_odoo_env_path(ctx, version)
|
|
||||||
openupgrade_analysis_files = get_openupgrade_analysis_files(
|
|
||||||
odoo_env_path, version
|
|
||||||
)
|
|
||||||
openupgrade_analysis_files = openupgrade_analysis_files
|
|
||||||
for odoo_module in filter(
|
|
||||||
lambda x: x.module_type == "odoo", self.modules
|
|
||||||
):
|
|
||||||
module_version = odoo_module.get_module_version(version)
|
|
||||||
if module_version:
|
|
||||||
module_version.analyse_openupgrade_work(
|
|
||||||
openupgrade_analysis_files
|
|
||||||
)
|
|
||||||
|
|
||||||
def analyse_missing_module(self):
|
|
||||||
for odoo_module in filter(
|
|
||||||
lambda x: x.module_type != "odoo", self.modules
|
|
||||||
):
|
|
||||||
last_module_version = odoo_module.module_versions.get(
|
|
||||||
self.final_version, False
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
not last_module_version.addon_path
|
|
||||||
and last_module_version.state
|
|
||||||
not in ["renamed", "merged", "normal_loss"]
|
|
||||||
):
|
|
||||||
last_module_version.analyse_missing_module()
|
|
||||||
|
|
||||||
def estimate_workload(self, ctx):
|
|
||||||
logger.info("Estimate workload...")
|
|
||||||
for odoo_module in self.modules:
|
|
||||||
for module_version in odoo_module.module_versions.values():
|
|
||||||
module_version.estimate_workload(ctx)
|
|
||||||
|
|
||||||
def get_requirements(self, ctx):
|
|
||||||
logger.info("Get requirements...")
|
|
||||||
result = {x: {"python": {}, "bin": {}} for x in self.all_versions}
|
|
||||||
|
|
||||||
for odoo_module in self.modules:
|
|
||||||
for module_version in odoo_module.module_versions.values():
|
|
||||||
module_result = module_version.get_requirements(ctx)
|
|
||||||
python_result = result[module_result["version"]]["python"]
|
|
||||||
module_name = module_result["module_name"]
|
|
||||||
for python_lib in module_result["python"]:
|
|
||||||
if python_lib not in python_result:
|
|
||||||
python_result[python_lib] = [module_name]
|
|
||||||
else:
|
|
||||||
python_result[python_lib].append(module_name)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _generate_module_version_first_version(self, ctx, module_list):
|
|
||||||
logger.info(f"Analyse version {self.initial_version}. (First version)")
|
|
||||||
|
|
||||||
# Instanciate a new odoo_module
|
|
||||||
for module_name in module_list:
|
|
||||||
addon_path = OdooModule.get_addon_path(
|
|
||||||
ctx, module_name, self.initial_version
|
|
||||||
)
|
|
||||||
if addon_path:
|
|
||||||
repository_name = OdooModule.get_repository_name(addon_path)
|
|
||||||
if f"{repository_name}.{module_name}" not in self.modules:
|
|
||||||
logger.debug(
|
|
||||||
f"Discovering module '{module_name}'"
|
|
||||||
f" in {repository_name}"
|
|
||||||
f" for version {self.initial_version}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
repository_name = False
|
|
||||||
logger.error(
|
|
||||||
f"Module {module_name} not found"
|
|
||||||
f" for version {self.initial_version}."
|
|
||||||
)
|
|
||||||
new_odoo_module = OdooModule(
|
|
||||||
ctx, self, module_name, repository_name
|
|
||||||
)
|
|
||||||
new_module_version = OdooModuleVersion(
|
|
||||||
self.initial_version, new_odoo_module, addon_path
|
|
||||||
)
|
|
||||||
new_odoo_module.module_versions.update(
|
|
||||||
{self.initial_version: new_module_version}
|
|
||||||
)
|
|
||||||
self.modules.append(new_odoo_module)
|
|
||||||
|
|
||||||
def _generate_module_version_next_version(
|
|
||||||
self, ctx, previous_version, current_version
|
|
||||||
):
|
|
||||||
logger.info(
|
|
||||||
f"Analyse change between {previous_version} and {current_version}"
|
|
||||||
)
|
|
||||||
# Get changes between the two versions
|
|
||||||
(
|
|
||||||
apriori_module_name,
|
|
||||||
apriori_relative_path,
|
|
||||||
) = get_apriori_file_relative_path(current_version)
|
|
||||||
apriori_module_path = OdooModule.get_addon_path(
|
|
||||||
ctx, apriori_module_name, current_version
|
|
||||||
)
|
|
||||||
if not apriori_module_path:
|
|
||||||
raise ValueError(
|
|
||||||
f"Unable to find the path of the module {apriori_module_name}"
|
|
||||||
f" for the version {current_version}."
|
|
||||||
)
|
|
||||||
apriori_absolute_path = (
|
|
||||||
apriori_module_path
|
|
||||||
/ Path(apriori_module_name)
|
|
||||||
/ apriori_relative_path
|
|
||||||
)
|
|
||||||
|
|
||||||
module_spec = importlib.util.spec_from_file_location(
|
|
||||||
"package", str(apriori_absolute_path)
|
|
||||||
)
|
|
||||||
module = importlib.util.module_from_spec(module_spec)
|
|
||||||
module_spec.loader.exec_module(module)
|
|
||||||
|
|
||||||
renamed_modules = module.renamed_modules
|
|
||||||
merged_modules = module.merged_modules
|
|
||||||
|
|
||||||
for odoo_module in self.modules:
|
|
||||||
state = False
|
|
||||||
new_module_name = False
|
|
||||||
if odoo_module.name in renamed_modules:
|
|
||||||
state = "renamed"
|
|
||||||
new_module_name = renamed_modules[odoo_module.name]
|
|
||||||
logger.debug(
|
|
||||||
f"{previous_version} -> {current_version}:"
|
|
||||||
f" {odoo_module.name} renamed into {new_module_name}"
|
|
||||||
)
|
|
||||||
elif odoo_module.name in merged_modules:
|
|
||||||
state = "merged"
|
|
||||||
new_module_name = merged_modules[odoo_module.name]
|
|
||||||
logger.debug(
|
|
||||||
f"{previous_version} -> {current_version}:"
|
|
||||||
f" {odoo_module.name} merged into {new_module_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Handle new module
|
|
||||||
if state and new_module_name != odoo_module.name:
|
|
||||||
# Ensure that the module exists in self.modules
|
|
||||||
new_addon_path = OdooModule.get_addon_path(
|
|
||||||
ctx, new_module_name, current_version
|
|
||||||
)
|
|
||||||
if not new_addon_path:
|
|
||||||
raise ValueError(
|
|
||||||
f"The module {new_module_name} has not been found"
|
|
||||||
f" in the version {current_version}."
|
|
||||||
" Analyse can not be done."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
new_repository_name = OdooModule.get_repository_name(
|
|
||||||
new_addon_path
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
"%s.%s" % (new_repository_name, new_module_name)
|
|
||||||
not in self.modules
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Discovering module '{new_module_name}'"
|
|
||||||
f" in {new_repository_name}"
|
|
||||||
f" for version {current_version}"
|
|
||||||
)
|
|
||||||
new_odoo_module = OdooModule(
|
|
||||||
ctx, self, new_module_name, new_repository_name
|
|
||||||
)
|
|
||||||
self.modules.append(new_odoo_module)
|
|
||||||
new_odoo_module.module_versions.update(
|
|
||||||
{
|
|
||||||
current_version: OdooModuleVersion(
|
|
||||||
current_version,
|
|
||||||
new_odoo_module,
|
|
||||||
new_addon_path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the previous version of the module
|
|
||||||
previous_module_version = odoo_module.get_module_version(
|
|
||||||
previous_version
|
|
||||||
)
|
|
||||||
# if the previous version has been renamed or merged
|
|
||||||
# the loss is normal
|
|
||||||
if previous_module_version and previous_module_version.state in [
|
|
||||||
"merged",
|
|
||||||
"renamed",
|
|
||||||
"normal_loss",
|
|
||||||
]:
|
|
||||||
state = "normal_loss"
|
|
||||||
|
|
||||||
new_addon_path = OdooModule.get_addon_path(
|
|
||||||
ctx, odoo_module.name, current_version
|
|
||||||
)
|
|
||||||
odoo_module.module_versions.update(
|
|
||||||
{
|
|
||||||
current_version: OdooModuleVersion(
|
|
||||||
current_version,
|
|
||||||
odoo_module,
|
|
||||||
new_addon_path,
|
|
||||||
state=state,
|
|
||||||
target_module=new_module_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_module_qty(self, module_type=False):
|
|
||||||
if module_type:
|
|
||||||
odoo_modules = [
|
|
||||||
x
|
|
||||||
for x in filter(
|
|
||||||
lambda x: x.module_type == module_type, self.modules
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
odoo_modules = self.modules
|
|
||||||
return len(odoo_modules)
|
|
||||||
|
|
||||||
def workload(self, module_type, repository):
|
|
||||||
odoo_modules = self.modules
|
|
||||||
if module_type:
|
|
||||||
odoo_modules = [
|
|
||||||
x
|
|
||||||
for x in filter(
|
|
||||||
lambda x: x.module_type == module_type, odoo_modules
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if repository:
|
|
||||||
odoo_modules = [
|
|
||||||
x
|
|
||||||
for x in filter(
|
|
||||||
lambda x: x.repository == repository, odoo_modules
|
|
||||||
)
|
|
||||||
]
|
|
||||||
total = 0
|
|
||||||
for odoo_module in odoo_modules:
|
|
||||||
for module_version in list(odoo_module.module_versions.values()):
|
|
||||||
total += module_version.workload
|
|
||||||
return total
|
|
||||||
|
|
||||||
|
|
||||||
@total_ordering
|
|
||||||
class OdooModule(object):
|
|
||||||
def __init__(self, ctx, analyse, module_name, repository_name):
|
|
||||||
self.analyse = analyse
|
|
||||||
self.name = module_name
|
|
||||||
self.repository = repository_name
|
|
||||||
self.unique_name = f"{repository_name}.{module_name}"
|
|
||||||
self.ignored = self.is_ignored(ctx, module_name)
|
|
||||||
self.module_versions = {}
|
|
||||||
if not repository_name:
|
|
||||||
self.module_type = "not_found"
|
|
||||||
elif repository_name == "odoo/odoo":
|
|
||||||
self.module_type = "odoo"
|
|
||||||
elif repository_name.startswith("oca"):
|
|
||||||
self.module_type = "oca"
|
|
||||||
else:
|
|
||||||
self.module_type = "custom"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def workload(self):
|
|
||||||
return sum(
|
|
||||||
round(module_version.workload)
|
|
||||||
for _, module_version in self.module_versions.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_ignored(self, ctx, module_name):
|
|
||||||
"""Return true if module should be ignored"""
|
|
||||||
settings = ctx.obj["config"]["workload_settings"]
|
|
||||||
return module_name in settings["ignored_module_list"]
|
|
||||||
|
|
||||||
def get_module_version(self, current_version):
|
|
||||||
res = self.module_versions.get(current_version, False)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_odoo_apps_url(self):
|
|
||||||
logger.info(f"Searching {self.name} in the Odoo appstore...")
|
|
||||||
url = (
|
|
||||||
f"https://apps.odoo.com/apps/modules/"
|
|
||||||
f"{self.analyse.initial_version}/{self.name}/"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
except requests.exceptions.RequestException as err:
|
|
||||||
logger.warning(f"Error when trying to get {url}: {err}")
|
|
||||||
return False
|
|
||||||
if response.status_code == 200:
|
|
||||||
return url
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_odoo_code_search_url(self):
|
|
||||||
logger.info(f"Searching {self.name} in Odoo-Code-Search...")
|
|
||||||
url = (
|
|
||||||
f"https://odoo-code-search.com/ocs/search?"
|
|
||||||
f"q=name%3A%3D{self.name}+version%3A{self.analyse.initial_version}"
|
|
||||||
)
|
|
||||||
result = requests.get(url)
|
|
||||||
if '<td class="code">404</td>' in result.text:
|
|
||||||
return False
|
|
||||||
return url
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_addon_path(cls, ctx, module_name, current_version):
|
|
||||||
"""Search the module in all the addons path of a given version
|
|
||||||
and return the addon path of the module, or False if not found.
|
|
||||||
For exemple find_repository(ctx, 'web_responsive', 12.0)
|
|
||||||
'/PATH_TO_LOCAL_ENV/src/oca/web'
|
|
||||||
"""
|
|
||||||
# Try to find the repository that contains the module
|
|
||||||
main_path = get_odoo_env_path(ctx, current_version)
|
|
||||||
addons_path, _ = get_odoo_addons_path(
|
|
||||||
ctx,
|
|
||||||
main_path,
|
|
||||||
{"version": current_version, "execution_context": "openupgrade"},
|
|
||||||
)
|
|
||||||
for addon_path in addons_path:
|
|
||||||
if (main_path / addon_path / module_name).exists():
|
|
||||||
return main_path / addon_path
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_repository_name(cls, addon_path):
|
|
||||||
"""Given an addons path that contains odoo modules in a folder
|
|
||||||
that has been checkouted via git, return a repository name with the
|
|
||||||
following format org_name/repo_name.
|
|
||||||
For exemple 'oca/web' or 'odoo/odoo'
|
|
||||||
"""
|
|
||||||
# TODO, make the code cleaner and more resiliant
|
|
||||||
# for the time being, the code will fail for
|
|
||||||
# - github url set with git+http...
|
|
||||||
# - gitlab url
|
|
||||||
# - if odoo code is not in a odoo folder in the repos.yml file...
|
|
||||||
odoo_odoo_addons = (
|
|
||||||
"odoo/odoo/addons",
|
|
||||||
"odoo/openerp/addons",
|
|
||||||
"openupgrade/odoo/addons",
|
|
||||||
"openupgrade/openerp/addons",
|
|
||||||
)
|
|
||||||
odoo_addons = (
|
|
||||||
"odoo/addons",
|
|
||||||
"openupgrade/addons",
|
|
||||||
)
|
|
||||||
if str(addon_path).endswith(odoo_odoo_addons):
|
|
||||||
path = addon_path.parent.parent
|
|
||||||
elif str(addon_path).endswith(odoo_addons):
|
|
||||||
path = addon_path.parent
|
|
||||||
else:
|
|
||||||
path = addon_path
|
|
||||||
try:
|
|
||||||
repo = Repo(str(path))
|
|
||||||
except InvalidGitRepositoryError as err:
|
|
||||||
logger.critical(f"{path} is not a Git Repository.")
|
|
||||||
raise err
|
|
||||||
github_url_prefixes = (
|
|
||||||
"https://github.com/",
|
|
||||||
"git@github.com:",
|
|
||||||
)
|
|
||||||
repository_names = []
|
|
||||||
for remote in repo.remotes:
|
|
||||||
# Standardize all repository_name to lower case
|
|
||||||
repository_name = remote.url.lower()
|
|
||||||
for github_url_prefix in github_url_prefixes:
|
|
||||||
repository_name = repository_name.replace(
|
|
||||||
github_url_prefix, ""
|
|
||||||
)
|
|
||||||
if repository_name.endswith(".git"):
|
|
||||||
repository_name = repository_name[: -len(".git")]
|
|
||||||
repository_names.append(repository_name)
|
|
||||||
# find main repository_name
|
|
||||||
main_repository_name = next(
|
|
||||||
(
|
|
||||||
repo_name
|
|
||||||
for repo_name in repository_names
|
|
||||||
if repo_name.startswith("oca")
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if not main_repository_name:
|
|
||||||
main_repository_name = repository_names[0]
|
|
||||||
if main_repository_name == "oca/openupgrade":
|
|
||||||
return "odoo/odoo"
|
|
||||||
else:
|
|
||||||
return main_repository_name
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, str):
|
|
||||||
return self.unique_name == other
|
|
||||||
elif isinstance(other, OdooModule):
|
|
||||||
return self.unique_name == other.unique_name
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
if self.module_type != other.module_type:
|
|
||||||
if self.module_type == "odoo":
|
|
||||||
return True
|
|
||||||
elif self.module_type == "oca" and other.module_type in [
|
|
||||||
"custom",
|
|
||||||
"not_found",
|
|
||||||
]:
|
|
||||||
return True
|
|
||||||
elif (
|
|
||||||
self.module_type == "custom"
|
|
||||||
and other.module_type == "not_found"
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
elif self.repository != other.repository:
|
|
||||||
return self.repository < other.repository
|
|
||||||
else:
|
|
||||||
return self.name < other.name
|
|
||||||
|
|
||||||
|
|
||||||
class OdooModuleVersion(object):
|
|
||||||
_exclude_directories = [
|
|
||||||
"lib",
|
|
||||||
"demo",
|
|
||||||
"test",
|
|
||||||
"tests",
|
|
||||||
"doc",
|
|
||||||
"description",
|
|
||||||
]
|
|
||||||
_manifest_files = ["__openerp__.py", "__manifest__.py"]
|
|
||||||
|
|
||||||
_file_extensions = [".py", ".xml", ".js"]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
version,
|
|
||||||
odoo_module,
|
|
||||||
addon_path,
|
|
||||||
state=False,
|
|
||||||
target_module=False,
|
|
||||||
):
|
|
||||||
self.version = version
|
|
||||||
self.odoo_module = odoo_module
|
|
||||||
self.addon_path = addon_path
|
|
||||||
self.state = "ignored" if odoo_module.ignored else state
|
|
||||||
self.target_module = target_module
|
|
||||||
self.openupgrade_state = ""
|
|
||||||
self.python_code = 0
|
|
||||||
self.xml_code = 0
|
|
||||||
self.javascript_code = 0
|
|
||||||
self._workload = 0
|
|
||||||
self.analysis_file = False
|
|
||||||
self.openupgrade_model_lines = 0
|
|
||||||
self.openupgrade_field_lines = 0
|
|
||||||
self.openupgrade_xml_lines = 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def workload(self):
|
|
||||||
return int(round(self._workload))
|
|
||||||
|
|
||||||
@workload.setter
|
|
||||||
def workload(self, value):
|
|
||||||
self._workload = int(round(value))
|
|
||||||
|
|
||||||
def get_last_existing_version(self):
|
|
||||||
if self.odoo_module.module_type != "not_found":
|
|
||||||
versions = list(self.odoo_module.module_versions.values())
|
|
||||||
return [x for x in filter(lambda x: x.addon_path, versions)][-1]
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_requirements(self, ctx):
|
|
||||||
result = {
|
|
||||||
"python": [],
|
|
||||||
"bin": [],
|
|
||||||
"module_name": self.odoo_module.name,
|
|
||||||
"version": self.version,
|
|
||||||
}
|
|
||||||
manifest_path = False
|
|
||||||
for manifest_name in self._manifest_files:
|
|
||||||
if not self.odoo_module.name or not self.addon_path:
|
|
||||||
continue
|
|
||||||
manifest_path = (
|
|
||||||
self.addon_path / self.odoo_module.name / manifest_name
|
|
||||||
)
|
|
||||||
if manifest_path.exists():
|
|
||||||
break
|
|
||||||
if not manifest_path or not manifest_path.exists():
|
|
||||||
return result
|
|
||||||
|
|
||||||
with manifest_path.open(mode="r", encoding="utf-8") as f_manifest:
|
|
||||||
manifest = ast.literal_eval(f_manifest.read())
|
|
||||||
python_libs = manifest.get("external_dependencies", {}).get(
|
|
||||||
"python", []
|
|
||||||
)
|
|
||||||
bin_libs = manifest.get("external_dependencies", {}).get("bin", [])
|
|
||||||
result["bin"] = bin_libs
|
|
||||||
|
|
||||||
# Handle specific replacement in the setup folder
|
|
||||||
setup_path = (
|
|
||||||
self.addon_path / "setup" / self.odoo_module.name / "setup.py"
|
|
||||||
)
|
|
||||||
if setup_path.exists():
|
|
||||||
with setup_path.open(mode="r", encoding="utf-8") as f_setup:
|
|
||||||
tree = ast.parse(source=f_setup.read())
|
|
||||||
|
|
||||||
for node in ast.walk(tree):
|
|
||||||
if (
|
|
||||||
node.__class__.__name__ == "Dict"
|
|
||||||
and "external_dependencies_override"
|
|
||||||
in [k.value for k in node.keys]
|
|
||||||
):
|
|
||||||
python_replacements = ast.literal_eval(
|
|
||||||
ast.unparse(node)
|
|
||||||
)["external_dependencies_override"]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
python_replacements = {}
|
|
||||||
|
|
||||||
for k, v in python_replacements.items():
|
|
||||||
if k in python_libs:
|
|
||||||
python_libs.remove(k)
|
|
||||||
result["python"].append(v)
|
|
||||||
|
|
||||||
result["python"] += python_libs
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def estimate_workload(self, ctx):
|
|
||||||
settings = ctx.obj["config"]["workload_settings"]
|
|
||||||
port_minimal_time = settings["port_minimal_time"]
|
|
||||||
port_per_version = settings["port_per_version"]
|
|
||||||
port_per_python_line_time = settings["port_per_python_line_time"]
|
|
||||||
port_per_javascript_line_time = settings[
|
|
||||||
"port_per_javascript_line_time"
|
|
||||||
]
|
|
||||||
port_per_xml_line_time = settings["port_per_xml_line_time"]
|
|
||||||
open_upgrade_minimal_time = settings["open_upgrade_minimal_time"]
|
|
||||||
openupgrade_model_line_time = settings["openupgrade_model_line_time"]
|
|
||||||
openupgrade_field_line_time = settings["openupgrade_field_line_time"]
|
|
||||||
openupgrade_xml_line_time = settings["openupgrade_xml_line_time"]
|
|
||||||
|
|
||||||
if self.state in ["merged", "renamed", "normal_loss", "ignored"]:
|
|
||||||
# The module has been moved, nothing to do
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.odoo_module.module_type == "odoo":
|
|
||||||
if self.version == self.odoo_module.analyse.initial_version:
|
|
||||||
# No work to do for the initial version
|
|
||||||
return
|
|
||||||
if self.openupgrade_state and (
|
|
||||||
self.openupgrade_state.lower().startswith("done")
|
|
||||||
or self.openupgrade_state.lower().startswith("nothing to do")
|
|
||||||
or self.openupgrade_state.lower().startswith(
|
|
||||||
"no db layout changes"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.workload = (
|
|
||||||
# Minimal openupgrade time
|
|
||||||
open_upgrade_minimal_time
|
|
||||||
# Add model time
|
|
||||||
+ (
|
|
||||||
openupgrade_model_line_time
|
|
||||||
* self.openupgrade_model_lines
|
|
||||||
)
|
|
||||||
# Add field Time
|
|
||||||
+ (
|
|
||||||
openupgrade_field_line_time
|
|
||||||
* self.openupgrade_field_lines
|
|
||||||
)
|
|
||||||
# Add XML Time
|
|
||||||
+ (openupgrade_xml_line_time * self.openupgrade_xml_lines)
|
|
||||||
)
|
|
||||||
|
|
||||||
# OCA / Custom Module
|
|
||||||
if self.version != self.odoo_module.analyse.final_version:
|
|
||||||
# No need to work for intermediate version (in theory ;-))
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.addon_path:
|
|
||||||
# The module has been ported, nothing to do
|
|
||||||
return
|
|
||||||
|
|
||||||
previous_module_version = self.get_last_existing_version()
|
|
||||||
if not previous_module_version:
|
|
||||||
return
|
|
||||||
self.workload = (
|
|
||||||
# Minimal port time
|
|
||||||
port_minimal_time
|
|
||||||
# Add time per version
|
|
||||||
+ (self.version - previous_module_version.version)
|
|
||||||
* port_per_version
|
|
||||||
# Add python time
|
|
||||||
+ (port_per_python_line_time * previous_module_version.python_code)
|
|
||||||
# Add XML Time
|
|
||||||
+ (port_per_xml_line_time * previous_module_version.xml_code)
|
|
||||||
# Add Javascript Time
|
|
||||||
+ (
|
|
||||||
port_per_javascript_line_time
|
|
||||||
* previous_module_version.javascript_code
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def analyse_size(self):
|
|
||||||
self.python_code = 0
|
|
||||||
self.xml_code = 0
|
|
||||||
self.javascript_code = 0
|
|
||||||
# compute file list to analyse
|
|
||||||
file_list = []
|
|
||||||
for root, dirs, files in os.walk(
|
|
||||||
self.addon_path / Path(self.odoo_module.name), followlinks=True
|
|
||||||
):
|
|
||||||
relative_path = os.path.relpath(Path(root), self.addon_path)
|
|
||||||
if set(Path(relative_path).parts) & set(self._exclude_directories):
|
|
||||||
continue
|
|
||||||
for name in files:
|
|
||||||
if name in self._manifest_files:
|
|
||||||
continue
|
|
||||||
filename, file_extension = os.path.splitext(name)
|
|
||||||
if file_extension in self._file_extensions:
|
|
||||||
file_list.append(
|
|
||||||
(os.path.join(root, name), file_extension)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Analyse files
|
|
||||||
for file_path, file_ext in file_list:
|
|
||||||
file_res = SourceAnalysis.from_file(
|
|
||||||
file_path, "", encoding="utf-8"
|
|
||||||
)
|
|
||||||
if file_ext == ".py":
|
|
||||||
self.python_code += file_res.code
|
|
||||||
elif file_ext == ".xml":
|
|
||||||
self.xml_code += file_res.code
|
|
||||||
elif file_ext == ".js":
|
|
||||||
self.javascript_code += file_res.code
|
|
||||||
|
|
||||||
def analyse_openupgrade_state(self, coverage_analysis):
|
|
||||||
if self.version == self.odoo_module.analyse.initial_version:
|
|
||||||
return
|
|
||||||
self.openupgrade_state = coverage_analysis[self.version].get(
|
|
||||||
self.odoo_module.name, False
|
|
||||||
)
|
|
||||||
|
|
||||||
def analyse_openupgrade_work(self, analysis_files):
|
|
||||||
if self.version == self.odoo_module.analyse.initial_version:
|
|
||||||
return
|
|
||||||
analysis_file = analysis_files.get(self.odoo_module.name, False)
|
|
||||||
|
|
||||||
if not analysis_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.analysis_file = analysis_file
|
|
||||||
with open(analysis_file, "r") as input_file:
|
|
||||||
line_type = False
|
|
||||||
for line in input_file.readlines():
|
|
||||||
if line.startswith("---Models in module"):
|
|
||||||
line_type = "model"
|
|
||||||
continue
|
|
||||||
elif line.startswith("---Fields in module"):
|
|
||||||
line_type = "field"
|
|
||||||
continue
|
|
||||||
elif line.startswith("---XML records in module"):
|
|
||||||
line_type = "xml"
|
|
||||||
continue
|
|
||||||
elif line.startswith("---nothing has changed in this module"):
|
|
||||||
continue
|
|
||||||
elif line.startswith("---"):
|
|
||||||
raise Exception(f"comment {line} not understood")
|
|
||||||
|
|
||||||
if line_type == "model":
|
|
||||||
self.openupgrade_model_lines += 1
|
|
||||||
elif line_type == "field":
|
|
||||||
self.openupgrade_field_lines += 1
|
|
||||||
elif line_type == "xml":
|
|
||||||
self.openupgrade_xml_lines += 1
|
|
||||||
|
|
||||||
def workload_hour_text(self):
|
|
||||||
if not self.workload:
|
|
||||||
return ""
|
|
||||||
hour = int(self.workload // 60)
|
|
||||||
minute = round(self.workload % 60)
|
|
||||||
return f"{hour}h{minute:02}"
|
|
||||||
|
|
||||||
def get_size_text(self):
|
|
||||||
data = {
|
|
||||||
"Python": self.python_code,
|
|
||||||
"XML": self.xml_code,
|
|
||||||
"JavaScript": self.javascript_code,
|
|
||||||
}
|
|
||||||
# Remove empty values
|
|
||||||
data = {k: v for k, v in data.items() if v}
|
|
||||||
if not data:
|
|
||||||
return ""
|
|
||||||
else:
|
|
||||||
return ", ".join(["%s: %s" % (a, b) for a, b in data.items()])
|
|
||||||
|
|
||||||
def get_analysis_text(self):
|
|
||||||
data = {
|
|
||||||
"Model": self.openupgrade_model_lines,
|
|
||||||
"Field": self.openupgrade_field_lines,
|
|
||||||
"XML": self.openupgrade_xml_lines,
|
|
||||||
}
|
|
||||||
# Remove empty values
|
|
||||||
data = {k: v for k, v in data.items() if v}
|
|
||||||
if not data:
|
|
||||||
return ""
|
|
||||||
else:
|
|
||||||
return ", ".join(["%s: %s" % (a, b) for a, b in data.items()])
|
|
||||||
|
|
||||||
def analysis_url(self):
|
|
||||||
return os.path.relpath(self.analysis_file, Path(os.getcwd()))
|
|
||||||
|
|
||||||
def analyse_missing_module(self):
|
|
||||||
last_existing_version = self.get_last_existing_version()
|
|
||||||
if not last_existing_version:
|
|
||||||
return
|
|
||||||
last_existing_version.analyse_size()
|
|
||||||
|
|
||||||
def get_bg_color(self):
|
|
||||||
if self.addon_path:
|
|
||||||
if (
|
|
||||||
self.odoo_module.module_type == "odoo"
|
|
||||||
and self.version != self.odoo_module.analyse.initial_version
|
|
||||||
):
|
|
||||||
if self.openupgrade_state and (
|
|
||||||
self.openupgrade_state.lower().startswith("done")
|
|
||||||
or self.openupgrade_state.lower().startswith(
|
|
||||||
"nothing to do"
|
|
||||||
)
|
|
||||||
or self.openupgrade_state.lower().startswith(
|
|
||||||
"no db layout changes"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return "lightgreen"
|
|
||||||
else:
|
|
||||||
return "orange"
|
|
||||||
return "lightgreen"
|
|
||||||
else:
|
|
||||||
# The module doesn't exist in the current version
|
|
||||||
if self.state in ["merged", "renamed", "normal_loss", "ignored"]:
|
|
||||||
# Normal case, the previous version has been renamed
|
|
||||||
# or merged
|
|
||||||
return "lightgray"
|
|
||||||
|
|
||||||
if self.odoo_module.module_type == "odoo":
|
|
||||||
# A core module disappeared and has not been merged
|
|
||||||
# or renamed
|
|
||||||
return "red"
|
|
||||||
elif self.version != self.odoo_module.analyse.final_version:
|
|
||||||
return "lightgray"
|
|
||||||
elif self.odoo_module.module_type == "not_found":
|
|
||||||
return "lightgray"
|
|
||||||
else:
|
|
||||||
return "orange"
|
|
||||||
|
|
||||||
def get_text(self):
|
|
||||||
if self.addon_path:
|
|
||||||
if (
|
|
||||||
self.odoo_module.module_type == "odoo"
|
|
||||||
and self.version != self.odoo_module.analyse.initial_version
|
|
||||||
):
|
|
||||||
if self.openupgrade_state:
|
|
||||||
return self.openupgrade_state
|
|
||||||
else:
|
|
||||||
return "To analyse"
|
|
||||||
else:
|
|
||||||
if self.state == "merged":
|
|
||||||
return f"Merged into {self.target_module}"
|
|
||||||
elif self.state == "renamed":
|
|
||||||
return f"Renamed into {self.target_module}"
|
|
||||||
elif self.state == "ignored":
|
|
||||||
return "Ignored"
|
|
||||||
elif self.state == "normal_loss":
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if self.odoo_module.module_type == "odoo":
|
|
||||||
# A core module disappeared and has not been merged
|
|
||||||
# or renamed
|
|
||||||
return "Module lost"
|
|
||||||
else:
|
|
||||||
last_existing_version = self.get_last_existing_version()
|
|
||||||
if not last_existing_version:
|
|
||||||
return "Unknown"
|
|
||||||
elif self.version != self.odoo_module.analyse.final_version:
|
|
||||||
return "Unported"
|
|
||||||
else:
|
|
||||||
return f"To port from {last_existing_version.version}"
|
|
||||||
|
|
@ -1,317 +0,0 @@
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import docker
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_docker import (
|
|
||||||
exec_container,
|
|
||||||
get_docker_client,
|
|
||||||
run_container,
|
|
||||||
)
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_system import get_script_folder
|
|
||||||
|
|
||||||
|
|
||||||
def get_postgres_container(ctx):
|
|
||||||
client = get_docker_client()
|
|
||||||
image_name = ctx.obj["config"]["postgres_image_name"]
|
|
||||||
container_name = ctx.obj["config"]["postgres_container_name"]
|
|
||||||
volume_name = ctx.obj["config"]["postgres_volume_name"]
|
|
||||||
|
|
||||||
# Check if container exists
|
|
||||||
containers = client.containers.list(
|
|
||||||
all=True,
|
|
||||||
filters={"name": container_name},
|
|
||||||
ignore_removed=True,
|
|
||||||
)
|
|
||||||
if containers:
|
|
||||||
container = containers[0]
|
|
||||||
if container.status == "exited":
|
|
||||||
logger.warning(
|
|
||||||
f"Found container {container_name} in a exited status."
|
|
||||||
" Removing it..."
|
|
||||||
)
|
|
||||||
if container.status != "removed":
|
|
||||||
container.remove()
|
|
||||||
container.wait(condition="removed")
|
|
||||||
else:
|
|
||||||
return container
|
|
||||||
|
|
||||||
# Check if volume exists
|
|
||||||
try:
|
|
||||||
client.volumes.get(volume_name)
|
|
||||||
logger.debug(f"Recovering existing PostgreSQL volume: {volume_name}")
|
|
||||||
except docker.errors.NotFound:
|
|
||||||
logger.info(f"Creating Postgres volume: {volume_name}")
|
|
||||||
client.volumes.create(volume_name)
|
|
||||||
|
|
||||||
command = "postgres"
|
|
||||||
postgres_extra_settings = ctx.obj["config"].get("postgres_extra_settings")
|
|
||||||
if postgres_extra_settings:
|
|
||||||
for key, value in postgres_extra_settings.items():
|
|
||||||
command += f" -c {key}={value}"
|
|
||||||
|
|
||||||
logger.info(f"Launching PostgreSQL Container. (Image {image_name})")
|
|
||||||
|
|
||||||
# base environement variables
|
|
||||||
environments = {
|
|
||||||
"POSTGRES_USER": "odoo",
|
|
||||||
"POSTGRES_PASSWORD": "odoo",
|
|
||||||
"POSTGRES_DB": "postgres",
|
|
||||||
"PGDATA": "/var/lib/postgresql/data/pgdata",
|
|
||||||
}
|
|
||||||
|
|
||||||
# if postgresql version is >= 14 and odoo <= 12 we need to use md5 for auth
|
|
||||||
# method and password encryption
|
|
||||||
try:
|
|
||||||
postgres_version = float(image_name.split(":")[1])
|
|
||||||
except ValueError:
|
|
||||||
raise Exception(
|
|
||||||
"Unable to extract PostgreSQL version "
|
|
||||||
f"from image name {image_name}. "
|
|
||||||
"Define version in the image name is mandatory."
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
odoo_start_version = float(ctx.obj["config"]["odoo_versions"][0])
|
|
||||||
except ValueError:
|
|
||||||
raise Exception(
|
|
||||||
"Unable to extract start odoo version from odoo_versions "
|
|
||||||
f"{ctx.obj['config']['odoo_versions']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if odoo_start_version <= 12 and postgres_version >= 14:
|
|
||||||
environments |= {
|
|
||||||
"POSTGRES_HOST_AUTH_METHOD": "md5",
|
|
||||||
"POSTGRES_INITDB_ARGS": "--auth-host=md5",
|
|
||||||
}
|
|
||||||
|
|
||||||
command += " -c password_encryption=md5"
|
|
||||||
|
|
||||||
container = run_container(
|
|
||||||
image_name,
|
|
||||||
container_name,
|
|
||||||
command=command,
|
|
||||||
environments=environments,
|
|
||||||
volumes={
|
|
||||||
# Data volume
|
|
||||||
volume_name: "/var/lib/postgresql/data/pgdata/",
|
|
||||||
# main folder path (to pass files)
|
|
||||||
ctx.obj["env_folder_path"].absolute(): "/env/",
|
|
||||||
},
|
|
||||||
detach=True,
|
|
||||||
# ports={"5432/tcp": 5432}, # <--- Port-Mapping hinzugefügt
|
|
||||||
)
|
|
||||||
# TODO, improve me.
|
|
||||||
# Postgres container doesn't seems available immediately.
|
|
||||||
# check in odoo container, i remember that there is
|
|
||||||
# some script to do the job
|
|
||||||
time.sleep(5)
|
|
||||||
return container
|
|
||||||
|
|
||||||
|
|
||||||
def execute_sql_file(ctx, database, sql_file):
|
|
||||||
container = get_postgres_container(ctx)
|
|
||||||
|
|
||||||
# Recreate relative path to make posible to
|
|
||||||
# call psql in the container
|
|
||||||
if str(ctx.obj["env_folder_path"]) not in str(sql_file):
|
|
||||||
raise Exception(
|
|
||||||
f"The SQL file {sql_file} is not in the"
|
|
||||||
f" main directory {ctx.obj['env_folder_path']} available"
|
|
||||||
" in the PostgreSQL container."
|
|
||||||
)
|
|
||||||
relative_path = Path(
|
|
||||||
str(sql_file).replace(str(ctx.obj["env_folder_path"]), ".")
|
|
||||||
)
|
|
||||||
|
|
||||||
container_path = Path("/env/") / relative_path
|
|
||||||
command = (
|
|
||||||
"psql --username=odoo --dbname={database} --file {file_path}"
|
|
||||||
).format(database=database, file_path=container_path)
|
|
||||||
logger.info(
|
|
||||||
f"Executing the script '{relative_path}' in PostgreSQL container"
|
|
||||||
f" on database {database}"
|
|
||||||
)
|
|
||||||
exec_container(container, command)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_sql_request(ctx, request, database="postgres"):
|
|
||||||
psql_args = ("--tuples-only",)
|
|
||||||
output = execute_psql_command(ctx, request, database, psql_args)
|
|
||||||
lines = output.split("\n")
|
|
||||||
result = []
|
|
||||||
for line in lines:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
result.append([x.strip() for x in line.split("|")])
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def execute_psql_command(
|
|
||||||
ctx, request: str, database: str = None, psql_args: tuple = ()
|
|
||||||
):
|
|
||||||
"""Execute psql request in postgres container with psql_args on database"""
|
|
||||||
container = get_postgres_container(ctx)
|
|
||||||
command = (
|
|
||||||
"psql"
|
|
||||||
" --username=odoo"
|
|
||||||
f" --dbname={database or 'postgres'}"
|
|
||||||
f" --command {shlex.quote(request)}"
|
|
||||||
f" {' '.join(psql_args)}"
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"Executing the following command in PostgreSQL container\n{command}"
|
|
||||||
)
|
|
||||||
docker_result = exec_container(container, command)
|
|
||||||
return docker_result.output.decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def check_db_exist(ctx, database: str, raise_exception=False):
|
|
||||||
"""
|
|
||||||
- Connect to postgres container.
|
|
||||||
- Check if the database exist.
|
|
||||||
- Return True if exists, False otherwise.
|
|
||||||
- raise_exception paramater used for source database checking
|
|
||||||
"""
|
|
||||||
request = "select datname FROM pg_database WHERE datistemplate = false;"
|
|
||||||
result = execute_sql_request(ctx, request)
|
|
||||||
if [database] in result:
|
|
||||||
return True
|
|
||||||
if raise_exception:
|
|
||||||
raise Exception(f"Database '{database}' not found.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_database(ctx, database: str, state="present", template: str = ""):
|
|
||||||
"""
|
|
||||||
- Connect to postgres container.
|
|
||||||
- Check if the database exist.
|
|
||||||
- if doesn't exists and state == 'present', create it.
|
|
||||||
- if exists and state == 'absent', drop it.
|
|
||||||
"""
|
|
||||||
if state == "present":
|
|
||||||
if check_db_exist(ctx, database):
|
|
||||||
return
|
|
||||||
|
|
||||||
if template:
|
|
||||||
logger.info(f'Copy database "{template}" into "{database}"...')
|
|
||||||
request = (
|
|
||||||
f'CREATE DATABASE "{database}" WITH TEMPLATE "{template}";'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info(f"Create database '{database}'...")
|
|
||||||
request = f'CREATE DATABASE "{database}" OWNER odoo;'
|
|
||||||
execute_psql_command(ctx, request)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not check_db_exist(ctx, database):
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(f'Drop database "{database}"...')
|
|
||||||
request = f'DROP DATABASE "{database}";'
|
|
||||||
execute_psql_command(ctx, request)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_sql_files_pre_migration(
|
|
||||||
ctx, database: str, migration_step: dict, sql_files: list = []
|
|
||||||
):
|
|
||||||
ensure_database(ctx, database, state="present")
|
|
||||||
if not sql_files:
|
|
||||||
script_folder = get_script_folder(ctx, migration_step)
|
|
||||||
sql_files = [
|
|
||||||
script_folder / Path(f)
|
|
||||||
for f in os.listdir(script_folder)
|
|
||||||
if os.path.isfile(os.path.join(script_folder, f))
|
|
||||||
and f[-4:] == ".sql"
|
|
||||||
]
|
|
||||||
sql_files = sorted(sql_files)
|
|
||||||
|
|
||||||
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(
|
|
||||||
f"Executing the following command in PostgreSQL container:\n{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(
|
|
||||||
f"Executing the following command in PostgreSQL container:\n{command}"
|
|
||||||
)
|
|
||||||
pg_dump_result = exec_container(container, command)
|
|
||||||
|
|
||||||
chown_to_local_user(ctx, filepath)
|
|
||||||
return pg_dump_result.output.decode("utf8")
|
|
||||||
|
|
||||||
|
|
||||||
def execute_pg_restore(
|
|
||||||
ctx,
|
|
||||||
filepath: Path,
|
|
||||||
database: str,
|
|
||||||
database_format: str,
|
|
||||||
):
|
|
||||||
"""Execute pg_restore command on the postgres container"""
|
|
||||||
container = get_postgres_container(ctx)
|
|
||||||
ensure_database(ctx, database, "absent")
|
|
||||||
ensure_database(ctx, database, "present")
|
|
||||||
if database_format == "p":
|
|
||||||
command = (
|
|
||||||
"psql"
|
|
||||||
f" --file='{Path('/env') / filepath}'"
|
|
||||||
" --username odoo"
|
|
||||||
f" --dbname={database}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
command = (
|
|
||||||
"pg_restore"
|
|
||||||
f" {Path('/env') / filepath}"
|
|
||||||
f" --dbname={database}"
|
|
||||||
" --schema=public"
|
|
||||||
" --username=odoo"
|
|
||||||
" --no-owner"
|
|
||||||
f" --format {database_format}"
|
|
||||||
)
|
|
||||||
logger.info(f"Restoring database '{database}'...")
|
|
||||||
pg_dump_result = exec_container(container, command)
|
|
||||||
return pg_dump_result.output.decode("utf8")
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import importlib_resources
|
|
||||||
from git_aggregator import main as gitaggregate_cmd
|
|
||||||
from git_aggregator.utils import working_directory_keeper
|
|
||||||
from jinja2 import Template
|
|
||||||
from loguru import logger
|
|
||||||
from plumbum.cmd import chmod, mkdir
|
|
||||||
from plumbum.commands.processes import ProcessExecutionError
|
|
||||||
|
|
||||||
|
|
||||||
def get_script_folder(ctx, migration_step: dict) -> Path:
|
|
||||||
return ctx.obj["script_folder_path"] / migration_step["complete_name"]
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_folder_writable(folder_path: Path):
|
|
||||||
logger.info(f"Make writable the directory '{folder_path}'")
|
|
||||||
try:
|
|
||||||
chmod(["--silent", "--recursive", "o+w", str(folder_path)])
|
|
||||||
except ProcessExecutionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_folder_exists(
|
|
||||||
folder_path: Path, mode: str = "755", git_ignore_content: bool = False
|
|
||||||
):
|
|
||||||
"""Create a local folder.
|
|
||||||
- directory is created if it doesn't exist.
|
|
||||||
- mode is applied if defined.
|
|
||||||
- a log is done at INFO level.
|
|
||||||
"""
|
|
||||||
if not folder_path.exists():
|
|
||||||
cmd = ["-p", folder_path]
|
|
||||||
cmd = ["-m", mode] + cmd
|
|
||||||
logger.info(f"Creating directory '{folder_path}'...")
|
|
||||||
mkdir(cmd)
|
|
||||||
|
|
||||||
if git_ignore_content:
|
|
||||||
ensure_file_exists_from_template(
|
|
||||||
folder_path / Path(".gitignore"),
|
|
||||||
".gitignore.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_file_exists_from_template(
|
|
||||||
file_path: Path, template_name: str, **args
|
|
||||||
):
|
|
||||||
template_folder = (
|
|
||||||
importlib_resources.files("odoo_openupgrade_wizard") / "templates"
|
|
||||||
)
|
|
||||||
template_path = template_folder / template_name
|
|
||||||
if not template_path.exists():
|
|
||||||
logger.warning(
|
|
||||||
f"Unable to generate {file_path},"
|
|
||||||
f" the template {template_name} has not been found."
|
|
||||||
f" If it's a Dockerfile,"
|
|
||||||
f" you should maybe contribute to that project ;-)"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
text = template_path.read_text()
|
|
||||||
template = Template(text)
|
|
||||||
output = template.render(args)
|
|
||||||
|
|
||||||
if file_path.exists():
|
|
||||||
# Check if content is different
|
|
||||||
with open(file_path, "r") as file:
|
|
||||||
data = file.read()
|
|
||||||
file.close()
|
|
||||||
if data == output:
|
|
||||||
return
|
|
||||||
|
|
||||||
log_text = f"Updating file '{file_path}' from template..."
|
|
||||||
else:
|
|
||||||
log_text = f"Creating file '{file_path}' from template..."
|
|
||||||
|
|
||||||
with open(file_path, "w") as f:
|
|
||||||
logger.info(log_text)
|
|
||||||
print(output, file=f)
|
|
||||||
|
|
||||||
|
|
||||||
def git_aggregate(folder_path: Path, config_path: Path, jobs: int, env_file: str = ".env"):
|
|
||||||
# Convert relative env_file path to absolute path before changing directory
|
|
||||||
if not os.path.isabs(env_file):
|
|
||||||
env_file = os.path.abspath(env_file)
|
|
||||||
|
|
||||||
args = argparse.Namespace(
|
|
||||||
command="aggregate",
|
|
||||||
config=str(config_path),
|
|
||||||
jobs=jobs,
|
|
||||||
dirmatch=None,
|
|
||||||
do_push=False,
|
|
||||||
expand_env=True,
|
|
||||||
env_file=env_file,
|
|
||||||
force=True,
|
|
||||||
)
|
|
||||||
with working_directory_keeper:
|
|
||||||
os.chdir(folder_path)
|
|
||||||
logger.info(
|
|
||||||
f"Gitaggregate source code for {config_path}."
|
|
||||||
" This can take a while..."
|
|
||||||
)
|
|
||||||
gitaggregate_cmd.run(args)
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_user_id():
|
|
||||||
return os.getuid()
|
|
||||||
|
|
||||||
|
|
||||||
def execute_check_output(args_list, working_directory=None):
|
|
||||||
logger.debug(f"Execute {' '.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")
|
|
||||||
|
|
||||||
|
|
||||||
def restore_filestore(
|
|
||||||
ctx,
|
|
||||||
database: str,
|
|
||||||
src_path: Path,
|
|
||||||
file_format: str = "d",
|
|
||||||
):
|
|
||||||
"""Restore filestore of database from src_path using file_format.
|
|
||||||
file_format can be :
|
|
||||||
'd' for 'directory': a normal copy,
|
|
||||||
't' / 'tgz' for 'tar': an extraction from a tar achive
|
|
||||||
"""
|
|
||||||
valid_format = ("d", "t", "tgz")
|
|
||||||
if file_format not in valid_format:
|
|
||||||
raise ValueError(
|
|
||||||
f"file_format should be one of the following {valid_format}"
|
|
||||||
)
|
|
||||||
|
|
||||||
filestore_path = (
|
|
||||||
ctx.obj["env_folder_path"] / "filestore/filestore" / database
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Restoring filestore of '{database}'...")
|
|
||||||
if file_format == "d":
|
|
||||||
shutil.copytree(src_path, filestore_path)
|
|
||||||
|
|
||||||
else: # works for "t" and "tgz"
|
|
||||||
tar = tarfile.open(src_path)
|
|
||||||
tar.extractall(path=filestore_path)
|
|
||||||
|
|
||||||
# If a filestore/filestore/database/filestore directory exists,
|
|
||||||
# it means that the filestore was in a "filestore" subdirectory
|
|
||||||
# and we need to move this content to the parent directory.
|
|
||||||
filestore_subfolder = filestore_path / "filestore"
|
|
||||||
if filestore_subfolder.exists():
|
|
||||||
for file in filestore_subfolder.iterdir():
|
|
||||||
shutil.move(file, filestore_path)
|
|
||||||
shutil.rmtree(filestore_subfolder)
|
|
||||||
81
odoo_openupgrade_wizard/tools_system.py
Normal file
81
odoo_openupgrade_wizard/tools_system.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
from git_aggregator import main as gitaggregate_cmd
|
||||||
|
from git_aggregator.utils import working_directory_keeper
|
||||||
|
from jinja2 import Template
|
||||||
|
from loguru import logger
|
||||||
|
from plumbum import local
|
||||||
|
from plumbum.cmd import mkdir
|
||||||
|
from virtualenv.run import cli_run as virtualenv_cmd
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_folder_exists(folder_path, mode=False):
|
||||||
|
"""Create a local folder.
|
||||||
|
- directory is created if it doesn't exist.
|
||||||
|
- mode is applied if defined.
|
||||||
|
- a log is done at INFO level.
|
||||||
|
"""
|
||||||
|
if not folder_path.exists():
|
||||||
|
cmd = ["--parents", folder_path]
|
||||||
|
if mode:
|
||||||
|
cmd = ["--mode", "777"] + cmd
|
||||||
|
logger.info("Creating folder '%s' ..." % (folder_path))
|
||||||
|
mkdir(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_file_exists_from_template(file_path, template_name, **args):
|
||||||
|
|
||||||
|
template = Template(template_name)
|
||||||
|
output = template.render(args)
|
||||||
|
|
||||||
|
if file_path.exists():
|
||||||
|
# Check if content is different
|
||||||
|
with open(file_path, "r") as file:
|
||||||
|
data = file.read()
|
||||||
|
file.close()
|
||||||
|
if data == output:
|
||||||
|
return
|
||||||
|
|
||||||
|
log_text = "Updating file '%s' from template ..." % (file_path)
|
||||||
|
else:
|
||||||
|
log_text = "Creating file '%s' from template ..." % (file_path)
|
||||||
|
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
logger.info(log_text)
|
||||||
|
f.write(output)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def git_aggregate(folder_path, config_path):
|
||||||
|
args = argparse.Namespace(
|
||||||
|
command="aggregate",
|
||||||
|
config=str(config_path),
|
||||||
|
jobs=1,
|
||||||
|
dirmatch=None,
|
||||||
|
do_push=False,
|
||||||
|
expand_env=False,
|
||||||
|
env_file=None,
|
||||||
|
force=False,
|
||||||
|
)
|
||||||
|
with working_directory_keeper:
|
||||||
|
os.chdir(folder_path)
|
||||||
|
logger.info(
|
||||||
|
"Gitaggregate source code for %s. This can take a while ..."
|
||||||
|
% config_path
|
||||||
|
)
|
||||||
|
gitaggregate_cmd.run(args)
|
||||||
|
|
||||||
|
|
||||||
|
def create_virtualenv(folder_path, python_version):
|
||||||
|
"""
|
||||||
|
Create a virtual env named ``env`` in the ``folder_path`` folder
|
||||||
|
with the given ``python_version``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with local.cwd(folder_path):
|
||||||
|
logger.info(
|
||||||
|
"Create Virtual Env in %s with version %s"
|
||||||
|
% (folder_path, python_version)
|
||||||
|
)
|
||||||
|
virtualenv_cmd(["env", "--python", python_version])
|
||||||
1848
poetry.lock
generated
1848
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
101
pyproject.toml
101
pyproject.toml
|
|
@ -1,63 +1,44 @@
|
||||||
[project]
|
[tool.poetry]
|
||||||
name = "odoo-openupgrade-wizard"
|
name = "odoo-openupgrade-wizard"
|
||||||
version = "1.3.1.1"
|
version = "0.0.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"},
|
"GRAP, Groupement Régional Alimentaire de Proximité",
|
||||||
{name = "Rémy TAYMANS", email = "remy@coopiteasy.be"},
|
"Coop IT Easy SCRLfs",
|
||||||
{name = "Simon MAILLARD", email = "simon@ogesta.fr"},
|
|
||||||
]
|
]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
{name = "Sylvain LE GAL", email = "sylvain.legal@grap.coop"},
|
"Sylvain LE GAL",
|
||||||
{name = "Rémy TAYMANS", email = "remy@coopiteasy.be"},
|
|
||||||
]
|
]
|
||||||
|
license = "AGPLv3+"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://gitlab.com/odoo-openupgrade-wizard/odoo-openupgrade-wizard"
|
repository = "https://gitlab.com/odoo-openupgrade-wizard/odoo-openupgrade-wizard"
|
||||||
keywords = ["cli", "odoo", "openupgrade"]
|
keywords = ["cli", "odoo", "openupgrade"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 2 - Pre-Alpha",
|
||||||
"Operating System :: Unix",
|
"Operating System :: Unix",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
|
||||||
"Programming Language :: Python :: 3.12",
|
|
||||||
# Currently poetry 2.0.0 failed to find the following classifier
|
|
||||||
#"Programming Language :: Python :: 3.13",
|
|
||||||
"Framework :: Odoo",
|
"Framework :: Odoo",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9,<4.0"
|
|
||||||
dependencies = [
|
|
||||||
"click (>=7.0,<8.0)",
|
|
||||||
"click-loglevel (>=0.4)",
|
|
||||||
"docker (>=7.0,<8.0)",
|
|
||||||
"importlib-resources (>=5.4,<6.0)",
|
|
||||||
"git-aggregator (>=2.1,<3.0)",
|
|
||||||
"GitPython (>=3.1,<4.0)",
|
|
||||||
"jinja2 (>=3.0,<4.0)",
|
|
||||||
"loguru (>=0.6)",
|
|
||||||
"odoorpc (>=0.10.1)",
|
|
||||||
"plumbum (>=1.7,<2.0)",
|
|
||||||
"pygount (>=1.4,<2.0)",
|
|
||||||
"pyyaml (>=6.0.0,<7.0.0)",
|
|
||||||
"single-source (>=0.3)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.urls]
|
[tool.poetry.scripts]
|
||||||
Repository = "https://gitlab.com/odoo-openupgrade-wizard/odoo-openupgrade-wizard"
|
odoo-openupgrade-wizard = "odoo_openupgrade_wizard.cli:main"
|
||||||
|
|
||||||
[project.scripts]
|
[tool.poetry.dependencies]
|
||||||
oow = "odoo_openupgrade_wizard.cli.cli:main"
|
python = "^3.6"
|
||||||
odoo-openupgrade-wizard = "odoo_openupgrade_wizard.cli.cli:main"
|
click = "^7.0"
|
||||||
|
loguru = "^0.6"
|
||||||
|
plumbum = "^1.7"
|
||||||
|
single-source = "^0.3"
|
||||||
|
virtualenv = "^20.14"
|
||||||
|
git-aggregator = "^2.1"
|
||||||
|
pyyaml = "5.4.1"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
pytest = [
|
pytest = [
|
||||||
{version = "<=6.1.2", python = "<3.10"},
|
{version = "<=6.1.2", python = "<3.10"},
|
||||||
{version = ">=6.2.5", python = ">=3.10"}
|
{version = ">=6.2.5", python = ">=3.10"}
|
||||||
]
|
]
|
||||||
pytest-mock = "3.6.1"
|
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
safety = "*"
|
safety = "*"
|
||||||
pylint = "*"
|
pylint = "*"
|
||||||
|
|
@ -68,42 +49,18 @@ towncrier = "*"
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.towncrier]
|
||||||
|
package = "ociedoo"
|
||||||
|
package_dir = "ociedoo"
|
||||||
|
filename = "CHANGES.rst"
|
||||||
|
directory = "newsfragments"
|
||||||
|
title_format = "{version} ({project_date})"
|
||||||
|
wrap = 72
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 79
|
line-length = 79
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
line_length = 79
|
line_length = 79
|
||||||
|
|
||||||
[tool.towncrier]
|
|
||||||
name = "odoo-openupgrade-wizard"
|
|
||||||
package = "odoo_openupgrade_wizard"
|
|
||||||
package_dir = "."
|
|
||||||
filename = "CHANGES.rst"
|
|
||||||
directory = "newsfragments"
|
|
||||||
wrap = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
directory = "feature"
|
|
||||||
name = "Features"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
directory = "bugfix"
|
|
||||||
name = "Bugfixes"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
directory = "removal"
|
|
||||||
name = "Removals"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
directory = "doc"
|
|
||||||
name = "Documentation"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
directory = "misc"
|
|
||||||
name = "Misc"
|
|
||||||
showcontent = true
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
[pytest]
|
[pytest]
|
||||||
norecursedirs=tests/data/*
|
norecursedirs = output/*
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from click.testing import CliRunner
|
|
||||||
from plumbum.cmd import mkdir
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.cli.cli import main
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import execute_sql_request
|
|
||||||
|
|
||||||
_logger = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def assert_result_cli_invoke(result):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def move_to_test_folder():
|
|
||||||
"""function to call at the beginning at the tests
|
|
||||||
to change the current working directory.
|
|
||||||
Note : this function is idempotens, to avoid to generate errors
|
|
||||||
if many tests scripts are executed.
|
|
||||||
"""
|
|
||||||
if os.getcwd().endswith("tests/data/output"):
|
|
||||||
return
|
|
||||||
test_folder_path = Path("tests/data/output")
|
|
||||||
mkdir(["-p", test_folder_path])
|
|
||||||
os.chdir(test_folder_path)
|
|
||||||
|
|
||||||
|
|
||||||
def cli_runner_invoke(cmd, expect_success=True):
|
|
||||||
if not cmd:
|
|
||||||
cmd = []
|
|
||||||
cmd = ["--log-level=DEBUG"] + cmd
|
|
||||||
try:
|
|
||||||
result = CliRunner().invoke(
|
|
||||||
main,
|
|
||||||
args=cmd,
|
|
||||||
catch_exceptions=False,
|
|
||||||
)
|
|
||||||
if expect_success:
|
|
||||||
if not result.exit_code == 0:
|
|
||||||
_logger.error("exit_code: %s" % result.exit_code)
|
|
||||||
_logger.error("output: %s" % result.output)
|
|
||||||
assert result.exit_code == 0
|
|
||||||
else:
|
|
||||||
assert result.exit_code != 0
|
|
||||||
except Exception as exception:
|
|
||||||
if Path("log").exists():
|
|
||||||
log_files = [
|
|
||||||
Path("log") / Path(f)
|
|
||||||
for f in os.listdir(Path("log"))
|
|
||||||
if f[-4:] == ".log"
|
|
||||||
]
|
|
||||||
for log_file in log_files:
|
|
||||||
print("============================")
|
|
||||||
print(log_file)
|
|
||||||
print("============================")
|
|
||||||
_f = open(log_file)
|
|
||||||
print(_f.read())
|
|
||||||
_f.close()
|
|
||||||
print("============================")
|
|
||||||
raise exception
|
|
||||||
|
|
||||||
|
|
||||||
def build_ctx_from_config_file() -> dict:
|
|
||||||
env_folder_path = Path(".")
|
|
||||||
|
|
||||||
class context:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ctx = context()
|
|
||||||
setattr(ctx, "obj", {})
|
|
||||||
config_file_path = env_folder_path / "config.yml"
|
|
||||||
if not config_file_path.exists():
|
|
||||||
raise Exception(
|
|
||||||
"Configuration file not found %s" % config_file_path.absolute()
|
|
||||||
)
|
|
||||||
with open(config_file_path) as file:
|
|
||||||
config = yaml.safe_load(file)
|
|
||||||
ctx.obj["config"] = config
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
ctx.obj["env_folder_path"] = env_folder_path
|
|
||||||
ctx.obj["src_folder_path"] = env_folder_path / Path("src")
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
def mock_odoo_rpc_url(mocker):
|
|
||||||
"""Mock the _ODOO_RPC_URL for testing purpose"""
|
|
||||||
odoo_rpc_url = os.environ.get("ODOO_RPC_URL")
|
|
||||||
if odoo_rpc_url:
|
|
||||||
mocker.patch(
|
|
||||||
"odoo_openupgrade_wizard.tools.tools_odoo_instance._ODOO_RPC_URL",
|
|
||||||
odoo_rpc_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def assert_database(ctx, db_name, state):
|
|
||||||
request = "select datname FROM pg_database WHERE datistemplate = false;"
|
|
||||||
results = execute_sql_request(ctx, request)
|
|
||||||
if state == "present":
|
|
||||||
assert [db_name] in results
|
|
||||||
else:
|
|
||||||
assert [db_name] not in results
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import filecmp
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from . import cli_runner_invoke, move_to_test_folder
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_init():
|
|
||||||
move_to_test_folder()
|
|
||||||
|
|
||||||
expected_folder_path = Path("../output_expected").absolute()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"init",
|
|
||||||
"--project-name=test-cli",
|
|
||||||
"--initial-version=14.0",
|
|
||||||
"--final-version=15.0",
|
|
||||||
"--postgresql-version=13",
|
|
||||||
"--extra-repository="
|
|
||||||
"OCA/web,OCA/server-tools,OCA/bank-statement-import",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert filecmp.cmp(
|
|
||||||
Path("config.yml"),
|
|
||||||
expected_folder_path / Path("config.yml"),
|
|
||||||
)
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from . import cli_runner_invoke, move_to_test_folder
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_get_code():
|
|
||||||
move_to_test_folder()
|
|
||||||
|
|
||||||
cli_runner_invoke(["get-code"])
|
|
||||||
|
|
||||||
# Check V14
|
|
||||||
web_path = Path("./src/env_14.0/src/OCA/web")
|
|
||||||
assert web_path.exists()
|
|
||||||
|
|
||||||
# check V15
|
|
||||||
openupgrade_path = Path("./src/env_15.0/src/openupgrade")
|
|
||||||
|
|
||||||
assert openupgrade_path.exists()
|
|
||||||
|
|
||||||
assert (openupgrade_path / Path("openupgrade_framework")).exists()
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
from odoo_openupgrade_wizard.tools.tools_docker import (
|
|
||||||
get_docker_client,
|
|
||||||
kill_container,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_docker_build():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
# Drop postgresql container if exist
|
|
||||||
# (we ensure that the postgres container is removed to
|
|
||||||
# be sure that the call (environment, etc...) is correct now.)
|
|
||||||
kill_container(ctx.obj["config"]["postgres_container_name"])
|
|
||||||
|
|
||||||
cli_runner_invoke(["docker-build", "--versions=14.0,15.0"])
|
|
||||||
|
|
||||||
docker_client = get_docker_client()
|
|
||||||
|
|
||||||
assert docker_client.images.get(
|
|
||||||
"odoo-openupgrade-wizard-image__test-cli__14.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert docker_client.images.get(
|
|
||||||
"odoo-openupgrade-wizard-image__test-cli__15.0"
|
|
||||||
)
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_docker import get_docker_client
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_sql_request,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_run():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
db_name = "database_test_cli___run"
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"run",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--init-modules=base",
|
|
||||||
"--stop-after-init",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that a subfolder filestore/DB_NAME has been created
|
|
||||||
db_filestore_path = Path("./filestore/filestore") / db_name
|
|
||||||
assert db_filestore_path.exists()
|
|
||||||
|
|
||||||
# Ensure that 'base' module is installed
|
|
||||||
request = (
|
|
||||||
"SELECT id"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name='base';"
|
|
||||||
)
|
|
||||||
assert execute_sql_request(ctx, request, database=db_name)
|
|
||||||
|
|
||||||
# Ensure that 'point_of_sale' module is not installed
|
|
||||||
request = (
|
|
||||||
"SELECT id"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name='point_of_sale';"
|
|
||||||
)
|
|
||||||
assert not execute_sql_request(ctx, request, database=db_name)
|
|
||||||
|
|
||||||
# Ensure that all the containers are removed
|
|
||||||
docker_client = get_docker_client()
|
|
||||||
assert not docker_client.containers.list(
|
|
||||||
all=True, filters={"name": "odoo-openupgrade-wizard"}
|
|
||||||
)
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from plumbum.cmd import cp
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_sql_request,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_execute_script_python():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
extra_script_path = Path(
|
|
||||||
"../extra_script/01-post-migration-custom_test.py"
|
|
||||||
).absolute()
|
|
||||||
cp(
|
|
||||||
extra_script_path,
|
|
||||||
Path("01-post-migration-custom_test.py"),
|
|
||||||
)
|
|
||||||
|
|
||||||
db_name = "database_test_cli___execute_script_python"
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
# Install Odoo on V14 with base installed
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"run",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--init-modules=base",
|
|
||||||
"--stop-after-init",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute partners quantity
|
|
||||||
request = "SELECT count(*) FROM res_partner;"
|
|
||||||
partner_quantity_before = int(
|
|
||||||
execute_sql_request(ctx, request, database=db_name)[0][0]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Execute Custom Python Script
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"execute-script-python",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--script-file-path=01-post-migration-custom_test.py",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
partner_quantity_after = int(
|
|
||||||
execute_sql_request(ctx, request, database=db_name)[0][0]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that partners have been created by click_odoo_test.py
|
|
||||||
assert partner_quantity_after == (partner_quantity_before + 10)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from plumbum.cmd import cp
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_sql_request,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_execute_script_sql():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
extra_script_path = Path(
|
|
||||||
"../extra_script/pre-migration-custom_test.sql"
|
|
||||||
).absolute()
|
|
||||||
|
|
||||||
# Deploy SQL Script
|
|
||||||
destination_path = Path("scripts/step_01__regular__14.0")
|
|
||||||
cp([extra_script_path, destination_path])
|
|
||||||
|
|
||||||
# Reset database
|
|
||||||
db_name = "database_test_cli___execute_script_sql"
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
ensure_database(ctx, db_name, state="present")
|
|
||||||
|
|
||||||
# TODO call with script-file-path
|
|
||||||
# to avoid to copy file in scripts/step_xxx folder
|
|
||||||
cli_runner_invoke(
|
|
||||||
["execute-script-sql", "--step=1", f"--database={db_name}"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that the request has been done correctly
|
|
||||||
request = "SELECT name from city order by id;"
|
|
||||||
result = execute_sql_request(ctx, request, database=db_name)
|
|
||||||
|
|
||||||
assert result == [["Chicago"], ["Cavalaire Sur Mer"]]
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
from shutil import copy
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_sql_request,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_upgrade():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
for n in ["01", "02"]:
|
|
||||||
copy(
|
|
||||||
Path(
|
|
||||||
f"../extra_script/{n}-post-migration-custom_test.py"
|
|
||||||
).absolute(),
|
|
||||||
Path(
|
|
||||||
"scripts/step_01__regular__14.0/"
|
|
||||||
f"{n}-post-migration-custom_test.py"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
db_name = "database_test_cli___upgrade"
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"run",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--init-modules=base",
|
|
||||||
"--stop-after-init",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that 'base' module is installed at 14.0
|
|
||||||
request = (
|
|
||||||
"SELECT latest_version"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name='base';"
|
|
||||||
)
|
|
||||||
latest_version = execute_sql_request(ctx, request, database=db_name)
|
|
||||||
|
|
||||||
assert latest_version[0][0].startswith("14.")
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"upgrade",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--first-step=1",
|
|
||||||
"--last-step=3",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that 'base' module is installed at 15.0
|
|
||||||
request = (
|
|
||||||
"SELECT latest_version"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name='base';"
|
|
||||||
)
|
|
||||||
latest_version = execute_sql_request(ctx, request, database=db_name)
|
|
||||||
|
|
||||||
assert latest_version[0][0].startswith("15.")
|
|
||||||
|
|
||||||
# ensure the first post-migration-custom scripts have been executed
|
|
||||||
request = (
|
|
||||||
"SELECT name"
|
|
||||||
" FROM res_partner"
|
|
||||||
" WHERE name like 'Post Script 1 - Partner #%';"
|
|
||||||
)
|
|
||||||
|
|
||||||
result = execute_sql_request(ctx, request, database=db_name)
|
|
||||||
assert len(result) == 10
|
|
||||||
|
|
||||||
# ensure the second post-migration-custom scripts have been executed
|
|
||||||
request = (
|
|
||||||
"SELECT name"
|
|
||||||
" FROM res_partner"
|
|
||||||
" WHERE name = 'Post Script 2 - Partner #1';"
|
|
||||||
)
|
|
||||||
|
|
||||||
result = execute_sql_request(ctx, request, database=db_name)
|
|
||||||
assert len(result) == 1
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from . import cli_runner_invoke, move_to_test_folder
|
|
||||||
|
|
||||||
|
|
||||||
class TestCliEstimateWorkload(unittest.TestCase):
|
|
||||||
def test_cli_estimate_workload(self):
|
|
||||||
move_to_test_folder()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"estimate-workload",
|
|
||||||
"--extra-modules="
|
|
||||||
# Done Module
|
|
||||||
"base"
|
|
||||||
# Deleted module (because merged)
|
|
||||||
",account_analytic_default"
|
|
||||||
# Deleted module (because lost merge)
|
|
||||||
",account_edi_extended"
|
|
||||||
# Some modules that are not ported (for the time being)
|
|
||||||
",l10n_be_invoice_bba,l10n_ch_qriban,l10n_latam_base"
|
|
||||||
# OCA Ported Modules
|
|
||||||
",web_responsive"
|
|
||||||
# OCA Unported modules
|
|
||||||
",web_boolean_button"
|
|
||||||
",web_editor_background_color"
|
|
||||||
",web_pivot_computed_measure"
|
|
||||||
",web_view_calendar_list"
|
|
||||||
",web_widget_child_selector"
|
|
||||||
",web_widget_one2many_tree_line_duplicate"
|
|
||||||
",web_widget_dropdown_dynamic_example"
|
|
||||||
",my_module_that_doesnt_exist",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# We check file has been created
|
|
||||||
# parsing this file is a mess, so we don't do it ;-)
|
|
||||||
assert Path("./analysis.html").exists()
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import filecmp
|
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from . import cli_runner_invoke, move_to_test_folder
|
|
||||||
|
|
||||||
|
|
||||||
class TestCliGuessRequirement(unittest.TestCase):
|
|
||||||
def test_cli_guess_requirement(self):
|
|
||||||
move_to_test_folder()
|
|
||||||
|
|
||||||
expected_folder_path = Path("../output_expected").absolute()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
["guess-requirement", "--extra-modules=sentry"],
|
|
||||||
)
|
|
||||||
|
|
||||||
relative_path = Path("src/env_14.0/addons_python_requirements.txt")
|
|
||||||
|
|
||||||
assert filecmp.cmp(
|
|
||||||
relative_path,
|
|
||||||
expected_folder_path / relative_path,
|
|
||||||
)
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_sql_request,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_install_from_csv(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
db_name = "database_test_cli___install_from_csv"
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
cli_runner_invoke(["install-from-csv", f"--database={db_name}"])
|
|
||||||
|
|
||||||
# Ensure that 'base' is installed
|
|
||||||
request = (
|
|
||||||
"SELECT count(*)"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name in ('base');"
|
|
||||||
)
|
|
||||||
module_qty = int(execute_sql_request(ctx, request, database=db_name)[0][0])
|
|
||||||
|
|
||||||
assert module_qty == 1
|
|
||||||
|
|
||||||
# Ensure that 'account' is not installed
|
|
||||||
request = (
|
|
||||||
"SELECT count(*)"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name in ('account');"
|
|
||||||
)
|
|
||||||
module_qty = int(execute_sql_request(ctx, request, database=db_name)[0][0])
|
|
||||||
|
|
||||||
assert module_qty == 0
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_odoo import get_odoo_env_path
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_generate_module_analysis(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
db_name = "database_test_cli___generate_module_analysis"
|
|
||||||
|
|
||||||
# identify main analysis file of openupgrade
|
|
||||||
analysis_file_path = get_odoo_env_path(ctx, 15.0) / Path(
|
|
||||||
"src/openupgrade/openupgrade_scripts/scripts"
|
|
||||||
"/base/15.0.1.3/upgrade_general_log.txt"
|
|
||||||
)
|
|
||||||
|
|
||||||
# We remove this file and run the analysis
|
|
||||||
try:
|
|
||||||
analysis_file_path.unlink()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
analysis_file_path
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"generate-module-analysis",
|
|
||||||
"--step=2",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--modules=base",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# The file should has been recreated by the analysis command
|
|
||||||
assert analysis_file_path.exists()
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_dumpdb(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
db_name = "database_test_cli___dumpdb"
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
cli_runner_invoke(["install-from-csv", f"--database={db_name}"])
|
|
||||||
|
|
||||||
# Dump database and filestore
|
|
||||||
formatlist = [("p", "d"), ("c", "tgz"), ("t", "t"), ("d", "d")]
|
|
||||||
for formats in formatlist:
|
|
||||||
database_path = pathlib.Path("database_test_cli___dumpdb")
|
|
||||||
filestore_path = pathlib.Path("database_test_clie___dumpdb.filestore")
|
|
||||||
|
|
||||||
assert not database_path.exists()
|
|
||||||
assert not filestore_path.exists()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert database_path.exists()
|
|
||||||
assert filestore_path.exists()
|
|
||||||
|
|
||||||
# Cleanup files
|
|
||||||
if database_path.is_dir():
|
|
||||||
shutil.rmtree(database_path)
|
|
||||||
else:
|
|
||||||
database_path.unlink()
|
|
||||||
|
|
||||||
if filestore_path.is_dir():
|
|
||||||
shutil.rmtree(filestore_path)
|
|
||||||
else:
|
|
||||||
filestore_path.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_dumpdb_failure(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
db_name = "database_test_cli___dumpdb"
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"install-from-csv",
|
|
||||||
f"--database={db_name}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# First dump
|
|
||||||
formats = ("d", "d")
|
|
||||||
database_path = pathlib.Path("database_test_cli___dumpdb")
|
|
||||||
filestore_path = pathlib.Path("database_test_clie___dumpdb.filestore")
|
|
||||||
|
|
||||||
assert not database_path.exists()
|
|
||||||
assert not filestore_path.exists()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert database_path.exists()
|
|
||||||
assert filestore_path.exists()
|
|
||||||
|
|
||||||
# With same name
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
],
|
|
||||||
expect_success=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# With --force
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
"--force",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# With name outside of project path
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path=/{database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path=/{filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
],
|
|
||||||
expect_success=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# With a non-existing database
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"--log-level=DEBUG",
|
|
||||||
"dumpdb",
|
|
||||||
"--database=database_test_cli___dumpdb_non_existing",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
f"--database-format={formats[0]}",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
f"--filestore-format={formats[1]}",
|
|
||||||
],
|
|
||||||
expect_success=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cleanup files
|
|
||||||
if database_path.is_dir():
|
|
||||||
shutil.rmtree(database_path)
|
|
||||||
else:
|
|
||||||
database_path.unlink()
|
|
||||||
|
|
||||||
if filestore_path.is_dir():
|
|
||||||
shutil.rmtree(filestore_path)
|
|
||||||
else:
|
|
||||||
filestore_path.unlink()
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
assert_database,
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_restoredb(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
db_name = "database_test_cli___restoredb"
|
|
||||||
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_name}")
|
|
||||||
shutil.rmtree(dest_filestore_path, ignore_errors=True)
|
|
||||||
|
|
||||||
# Copy database and filestore data in a accessible folder
|
|
||||||
database_path = pathlib.Path("./restoredb.dump")
|
|
||||||
filestore_path = pathlib.Path("./restoredb.tar.gz")
|
|
||||||
|
|
||||||
shutil.copyfile(pathlib.Path("../restoredb/test.dump"), database_path)
|
|
||||||
shutil.copyfile(pathlib.Path("../restoredb/test.tar.gz"), filestore_path)
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"restoredb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
"--database-format=c",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
"--filestore-format=tgz",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# check filestore exists
|
|
||||||
assert dest_filestore_path.exists()
|
|
||||||
|
|
||||||
# check the filestore content is at the right place
|
|
||||||
assert (dest_filestore_path / "01").exists()
|
|
||||||
|
|
||||||
# Check database exists
|
|
||||||
assert_database(ctx, db_name, "present")
|
|
||||||
|
|
||||||
# Delete filestore and database
|
|
||||||
shutil.rmtree(dest_filestore_path)
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
shutil.copyfile(
|
|
||||||
pathlib.Path("../restoredb/test_with_nested_filestore_dir.tar.gz"),
|
|
||||||
filestore_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"restoredb",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f"--database-path={database_path}",
|
|
||||||
"--database-format=c",
|
|
||||||
f"--filestore-path={filestore_path}",
|
|
||||||
"--filestore-format=tgz",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# check filestore exists
|
|
||||||
assert dest_filestore_path.exists()
|
|
||||||
|
|
||||||
# check the filestore content is at the right place
|
|
||||||
assert (dest_filestore_path / "01").exists()
|
|
||||||
|
|
||||||
# Check database exists
|
|
||||||
assert_database(ctx, db_name, "present")
|
|
||||||
|
|
||||||
# Delete filestore and database
|
|
||||||
shutil.rmtree(dest_filestore_path)
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import (
|
|
||||||
ensure_database,
|
|
||||||
execute_psql_command,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_psql():
|
|
||||||
move_to_test_folder()
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
db_name = "database_test_cli___psql"
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
|
|
||||||
# initialize database
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"run",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--init-modules=base",
|
|
||||||
"--stop-after-init",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test requests from lib
|
|
||||||
request = (
|
|
||||||
"SELECT name"
|
|
||||||
" FROM ir_module_module"
|
|
||||||
" WHERE state ='installed'"
|
|
||||||
" AND name='base';"
|
|
||||||
)
|
|
||||||
output = execute_psql_command(
|
|
||||||
ctx,
|
|
||||||
request,
|
|
||||||
database=db_name,
|
|
||||||
psql_args=("--tuples-only",),
|
|
||||||
)
|
|
||||||
assert output.strip() == "base"
|
|
||||||
|
|
||||||
# test via cli ok
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"psql",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f'--command "{request}"',
|
|
||||||
"--no-pager",
|
|
||||||
"--tuples-only",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# test that cli fails with wrong parameters
|
|
||||||
with raises(Exception):
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"psql",
|
|
||||||
f"--database={db_name}",
|
|
||||||
f'--command "{request}"',
|
|
||||||
"--no-pager",
|
|
||||||
"--tuples-only",
|
|
||||||
"---unkwon-argument",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
assert_database,
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_copydb(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
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(["install-from-csv", f"--database={db_name}"])
|
|
||||||
|
|
||||||
# Copy database
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"copydb",
|
|
||||||
f"--source={db_name}",
|
|
||||||
f"--dest={db_dest_name}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# check filestore exists
|
|
||||||
assert dest_filestore_path.exists()
|
|
||||||
|
|
||||||
# Check database exists
|
|
||||||
assert_database(ctx, db_dest_name, "present")
|
|
||||||
|
|
||||||
# Delete filestore and database
|
|
||||||
shutil.rmtree(dest_filestore_path)
|
|
||||||
ensure_database(ctx, db_dest_name, state="absent")
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from odoo_openupgrade_wizard.tools.tools_postgres import ensure_database
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
assert_database,
|
|
||||||
build_ctx_from_config_file,
|
|
||||||
cli_runner_invoke,
|
|
||||||
mock_odoo_rpc_url,
|
|
||||||
move_to_test_folder,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_dropdb(mocker):
|
|
||||||
move_to_test_folder()
|
|
||||||
mock_odoo_rpc_url(mocker)
|
|
||||||
|
|
||||||
db_name = "database_test_cli___dropdb"
|
|
||||||
ctx = build_ctx_from_config_file()
|
|
||||||
|
|
||||||
# Ensure environment is clean
|
|
||||||
ensure_database(ctx, db_name, state="absent")
|
|
||||||
filestore_path = pathlib.Path(f"./filestore/filestore/{db_name}")
|
|
||||||
shutil.rmtree(filestore_path, ignore_errors=True)
|
|
||||||
|
|
||||||
# Initialize database
|
|
||||||
cli_runner_invoke(["install-from-csv", f"--database={db_name}"])
|
|
||||||
|
|
||||||
# Drop database
|
|
||||||
cli_runner_invoke(["dropdb", f"--database={db_name}"])
|
|
||||||
|
|
||||||
# Check database does not exists
|
|
||||||
assert_database(ctx, db_name, "absent")
|
|
||||||
|
|
||||||
# Check filestore does not exists
|
|
||||||
assert not filestore_path.exists()
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
from . import cli_runner_invoke, move_to_test_folder
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_downgrade_pg_auth_method_for_old_versions():
|
|
||||||
move_to_test_folder()
|
|
||||||
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"init",
|
|
||||||
"--project-name=test-cli-downgrade-auth-method",
|
|
||||||
"--initial-version=12.0",
|
|
||||||
"--final-version=13.0",
|
|
||||||
"--postgresql-version=14",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
move_to_test_folder()
|
|
||||||
cli_runner_invoke(["get-code"])
|
|
||||||
cli_runner_invoke(["docker-build", "--versions=12.0"])
|
|
||||||
|
|
||||||
db_name = "database_test_cli-downgrade-auth-method__run"
|
|
||||||
cli_runner_invoke(
|
|
||||||
[
|
|
||||||
"run",
|
|
||||||
"--step=1",
|
|
||||||
f"--database={db_name}",
|
|
||||||
"--init-modules=base",
|
|
||||||
"--stop-after-init",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
39
tests/cli_build_test.py
Normal file
39
tests/cli_build_test.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
from plumbum.cmd import mkdir
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.cli import main
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_build():
|
||||||
|
output_folder_path = Path("./tests/output")
|
||||||
|
mkdir([output_folder_path, "--parents"])
|
||||||
|
|
||||||
|
# We initialize an env with only one version to avoid to git clone
|
||||||
|
# large data
|
||||||
|
CliRunner().invoke(
|
||||||
|
main,
|
||||||
|
[
|
||||||
|
"--env-folder=%s" % output_folder_path,
|
||||||
|
"init",
|
||||||
|
"--initial-version=14.0",
|
||||||
|
"--final-version=14.0",
|
||||||
|
"--extra-repository=OCA/web",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
main,
|
||||||
|
[
|
||||||
|
"--env-folder=%s" % output_folder_path,
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
openupgrade_path = Path("./tests/output/src/env_14.0/src/openupgrade")
|
||||||
|
|
||||||
|
assert openupgrade_path.exists()
|
||||||
|
|
||||||
|
assert (openupgrade_path / Path("openupgrade_framework")).exists()
|
||||||
30
tests/cli_init_test.py
Normal file
30
tests/cli_init_test.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import filecmp
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
from plumbum.cmd import mkdir
|
||||||
|
|
||||||
|
from odoo_openupgrade_wizard.cli import main
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_init():
|
||||||
|
output_folder_path = Path("./tests/output")
|
||||||
|
expected_folder_path = Path("./tests/output_expected")
|
||||||
|
mkdir([output_folder_path, "--parents"])
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
main,
|
||||||
|
[
|
||||||
|
"--env-folder=%s" % output_folder_path,
|
||||||
|
"init",
|
||||||
|
"--initial-version=9.0",
|
||||||
|
"--final-version=12.0",
|
||||||
|
"--extra-repository="
|
||||||
|
"OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert filecmp.cmp(
|
||||||
|
output_folder_path / Path("config.yml"),
|
||||||
|
expected_folder_path / Path("config.yml"),
|
||||||
|
)
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
_logger.info("01-post-migration-custom_test.py : Begin of script ...")
|
|
||||||
|
|
||||||
env = env # noqa: F821
|
|
||||||
|
|
||||||
for i in range(0, 10):
|
|
||||||
partner_name = "Post Script 1 - Partner #%d" % (i)
|
|
||||||
_logger.info("Create Partner %s" % partner_name)
|
|
||||||
env["res.partner"].create({"name": partner_name})
|
|
||||||
|
|
||||||
_logger.info("01-post-migration-custom_test.py : End of script.")
|
|
||||||
|
|
||||||
env.cr.commit()
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
_logger.info("02-post-migration-custom_test.py : Begin of script ...")
|
|
||||||
|
|
||||||
env = env # noqa: F821
|
|
||||||
|
|
||||||
env["res.partner"].create({"name": "Post Script 2 - Partner #1"})
|
|
||||||
|
|
||||||
_logger.info("02-post-migration-custom_test.py : End of script.")
|
|
||||||
|
|
||||||
env.cr.commit()
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
CREATE TABLE city (
|
|
||||||
id int,
|
|
||||||
name varchar
|
|
||||||
);
|
|
||||||
|
|
||||||
insert INTO city (id, name) values (1, 'Chicago');
|
|
||||||
insert INTO city (id, name) values (2, 'Cavalaire Sur Mer');
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
project_name: test-cli
|
|
||||||
|
|
||||||
|
|
||||||
postgres_image_name: postgres:13
|
|
||||||
postgres_container_name: test-cli-container-postgres-13
|
|
||||||
postgres_volume_name: test-cli-volume-postgres-13
|
|
||||||
postgres_extra_settings:
|
|
||||||
|
|
||||||
|
|
||||||
odoo_rpc_timeout: 3600
|
|
||||||
odoo_host_xmlrpc_port: 9069
|
|
||||||
odoo_default_company:
|
|
||||||
country_code: FR
|
|
||||||
|
|
||||||
|
|
||||||
odoo_versions:
|
|
||||||
- 14.0
|
|
||||||
- 15.0
|
|
||||||
|
|
||||||
|
|
||||||
odoo_version_settings:
|
|
||||||
14.0:
|
|
||||||
15.0:
|
|
||||||
|
|
||||||
|
|
||||||
migration_steps:
|
|
||||||
- name: 1
|
|
||||||
version: 14.0
|
|
||||||
execution_context: regular
|
|
||||||
complete_name: step_01__regular__14.0
|
|
||||||
update: True
|
|
||||||
|
|
||||||
- name: 2
|
|
||||||
version: 15.0
|
|
||||||
execution_context: openupgrade
|
|
||||||
complete_name: step_02__openupgrade__15.0
|
|
||||||
|
|
||||||
- name: 3
|
|
||||||
version: 15.0
|
|
||||||
execution_context: regular
|
|
||||||
complete_name: step_03__regular__15.0
|
|
||||||
update: True
|
|
||||||
|
|
||||||
|
|
||||||
workload_settings:
|
|
||||||
|
|
||||||
# Ignored module list
|
|
||||||
ignored_module_list: []
|
|
||||||
|
|
||||||
# porting a module requires 45 minutes minimaly
|
|
||||||
port_minimal_time: 45
|
|
||||||
|
|
||||||
# a migration cost more for each version
|
|
||||||
port_per_version: 15
|
|
||||||
|
|
||||||
# Porting 120 lines of Python code costs 1 hour
|
|
||||||
port_per_python_line_time: 0.5
|
|
||||||
|
|
||||||
# Porting 120 lines of Javascript code costs 1 hour
|
|
||||||
port_per_javascript_line_time: 0.5
|
|
||||||
|
|
||||||
# Porting 10 lines of XML costs 1 minute
|
|
||||||
port_per_xml_line_time: 0.10
|
|
||||||
|
|
||||||
# Minimal time for Openupgrade PR
|
|
||||||
open_upgrade_minimal_time: 10
|
|
||||||
|
|
||||||
# time for a line of model in the openupgrade_analysis.txt
|
|
||||||
openupgrade_model_line_time: 10
|
|
||||||
|
|
||||||
# Time for a line of field in the openupgrade_analysis.txt
|
|
||||||
openupgrade_field_line_time: 5
|
|
||||||
|
|
||||||
# Time for a line of XML in the openupgrade_analysis.txt
|
|
||||||
openupgrade_xml_line_time: 0.1
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# Do NOT edit manually. Changes here will be overwritten by the command 'guess-requirement'
|
|
||||||
|
|
||||||
# Required by the module(s): sentry
|
|
||||||
sentry_sdk<=1.9.0
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
31
tests/output_expected/config.yml
Normal file
31
tests/output_expected/config.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
migration_steps:
|
||||||
|
|
||||||
|
- name: step_1
|
||||||
|
complete_name: step_1__update__9.0
|
||||||
|
version: 9.0
|
||||||
|
action: update
|
||||||
|
python: python2.7
|
||||||
|
|
||||||
|
- name: step_2
|
||||||
|
complete_name: step_2__upgrade__10.0
|
||||||
|
version: 10.0
|
||||||
|
action: upgrade
|
||||||
|
python: python2.7
|
||||||
|
|
||||||
|
- name: step_3
|
||||||
|
complete_name: step_3__upgrade__11.0
|
||||||
|
version: 11.0
|
||||||
|
action: upgrade
|
||||||
|
python: python3.5
|
||||||
|
|
||||||
|
- name: step_4
|
||||||
|
complete_name: step_4__upgrade__12.0
|
||||||
|
version: 12.0
|
||||||
|
action: upgrade
|
||||||
|
python: python3.6
|
||||||
|
|
||||||
|
- name: step_5
|
||||||
|
complete_name: step_5__update__12.0
|
||||||
|
version: 12.0
|
||||||
|
action: update
|
||||||
|
python: python3.6
|
||||||
Loading…
Reference in New Issue
Block a user