[FIX] odoo_bin : lire le config.conf du dépôt, pas le ~/.odoorc
« odoo_bin.sh db --drop » échoue par AccessDenied à chaque palier de migration, et le clone bute ensuite sur « database already exists ». db_restore.py lit ./config.conf, y voit « admin_passwd = admin » et n'envoie donc aucun mot de passe ; odoo_bin.sh ne passait pas de « -c », donc Odoo lisait ~/.odoorc, qui porte un mot de passe haché. ODOO_RC ferme la couture en un point plutôt qu'à vingt sites d'appel : les versions 12 à 18 le lisent après « -c » et avant ~/.odoorc, donc un choix explicite l'emporte. Vérifié : le drop passe, la précédence est testée. --- EN --- "odoo_bin.sh db --drop" fails with AccessDenied at every migration step, and the clone then hits "database already exists". db_restore.py reads ./config.conf, sees "admin_passwd = admin" and so sends no master password; odoo_bin.sh passed no "-c", so Odoo read ~/.odoorc, which carries a hashed password. ODOO_RC closes the seam in one place instead of twenty call sites: versions 12 to 18 read it after "-c" and before ~/.odoorc, so an explicit choice wins. Verified: the drop succeeds, and a test covers precedence. Assisted-by: Claude Opus 5 (cherry picked from commit 9503d80982b725034513b610b527f29d49025381)
This commit is contained in:
parent
7ba3c6504c
commit
b7d68cd199
3 changed files with 172 additions and 0 deletions
|
|
@ -2,6 +2,11 @@
|
|||
# This is required to change environment for the running Odoo
|
||||
source ./.venv.$(< .erplibre-version)/bin/activate
|
||||
|
||||
# Le config.conf du DÉPÔT, pas le ~/.odoorc de qui lance. Un « -c »
|
||||
# explicite l'emporte toujours : Odoo lit opt.config avant ODOO_RC.
|
||||
source ./script/lib_odoo_rc.sh
|
||||
odoo_rc_resolve "$(pwd)"
|
||||
|
||||
ODOO_PATH="$(pwd)/odoo$(< .odoo-version)"
|
||||
#export PATH=$ODOO_PATH:$PATH
|
||||
#echo $PATH
|
||||
|
|
|
|||
38
script/lib_odoo_rc.sh
Executable file
38
script/lib_odoo_rc.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
#
|
||||
# Quel fichier de configuration Odoo doit lire, quand personne ne le dit.
|
||||
#
|
||||
# Sans cela, Odoo retombe sur ~/.odoorc — un fichier PERSONNEL, hors du
|
||||
# dépôt, que rien ne synchronise avec `config.conf`. Mesuré : un ~/.odoorc
|
||||
# portant un mot de passe maître haché faisait échouer
|
||||
# « odoo_bin.sh db --drop » par AccessDenied, alors que db_restore.py
|
||||
# venait de lire `admin_passwd = admin` dans config.conf et d'en conclure
|
||||
# qu'aucun mot de passe n'était nécessaire. Les deux avaient raison : ils
|
||||
# ne parlaient pas du même fichier.
|
||||
#
|
||||
# La précédence d'Odoo est la même en 12 et en 18 (tools/config.py) :
|
||||
#
|
||||
# self.config_file or opt.config or ODOO_RC or OPENERP_SERVER or ~/.odoorc
|
||||
#
|
||||
# Poser ODOO_RC ne retire donc rien à personne : un « -c » explicite
|
||||
# l'emporte toujours, et un ODOO_RC déjà posé n'est pas écrasé.
|
||||
#
|
||||
# L'ordre des candidats est celui de db_restore.py, pour que la
|
||||
# vérification qu'il fait porte sur le fichier qu'Odoo lira vraiment.
|
||||
|
||||
odoo_rc_resolve() {
|
||||
if [[ -n "${ODOO_RC:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local racine="${1:-$(pwd)}"
|
||||
local candidat
|
||||
for candidat in "${racine}/config.conf" /etc/odoo/odoo.conf; do
|
||||
if [[ -f "${candidat}" ]]; then
|
||||
export ODOO_RC="${candidat}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
129
test/test_odoo_rc.py
Normal file
129
test/test_odoo_rc.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
"""Quel fichier de configuration Odoo lit, quand personne ne le dit.
|
||||
|
||||
Sans ODOO_RC, Odoo retombe sur `~/.odoorc` — un fichier PERSONNEL que
|
||||
rien ne synchronise avec le `config.conf` du dépôt. Mesuré sur une vraie
|
||||
migration : un `~/.odoorc` portant un mot de passe maître haché faisait
|
||||
échouer « odoo_bin.sh db --drop » par AccessDenied, alors que
|
||||
`db_restore.py` venait de lire « admin_passwd = admin » dans config.conf
|
||||
et d'en conclure qu'aucun mot de passe n'était nécessaire.
|
||||
|
||||
Les deux avaient raison. Ils ne parlaient pas du même fichier — et rien,
|
||||
nulle part, ne les confrontait. Le drop échouait, le clone butait ensuite
|
||||
sur « database already exists », et la boucle de reprise faisait passer
|
||||
les deux pour un accident. Huit fois par migration.
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
LIB = os.path.join(REPO, "script", "lib_odoo_rc.sh")
|
||||
|
||||
|
||||
class Base(unittest.TestCase):
|
||||
def resoudre(self, racine, environ=None):
|
||||
"""Ce que la résolution pose dans ODOO_RC, pour cette racine."""
|
||||
env = dict(os.environ)
|
||||
env.pop("ODOO_RC", None)
|
||||
env.update(environ or {})
|
||||
done = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
f'source {LIB!r}; odoo_rc_resolve "$1"; echo "${{ODOO_RC:-}}"',
|
||||
"bash",
|
||||
racine,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
)
|
||||
self.assertEqual(0, done.returncode, done.stderr)
|
||||
return done.stdout.strip()
|
||||
|
||||
|
||||
class TestWhichFileIsChosen(Base):
|
||||
def setUp(self):
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
self.dossier = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.dossier)
|
||||
|
||||
def config(self):
|
||||
chemin = os.path.join(self.dossier, "config.conf")
|
||||
with io.open(chemin, "w", encoding="utf-8") as handle:
|
||||
handle.write("[options]\nadmin_passwd = admin\n")
|
||||
return chemin
|
||||
|
||||
def test_the_repository_config_wins_over_the_personal_one(self):
|
||||
# C'est tout le propos : ~/.odoorc n'est plus consulté.
|
||||
self.assertEqual(self.config(), self.resoudre(self.dossier))
|
||||
|
||||
def test_an_explicit_choice_is_never_overridden(self):
|
||||
# Odoo lit ODOO_RC avant ~/.odoorc, et « -c » avant ODOO_RC :
|
||||
# écraser une valeur posée exprès retirerait ce choix.
|
||||
self.config()
|
||||
self.assertEqual(
|
||||
"/ailleurs/odoo.conf",
|
||||
self.resoudre(self.dossier, {"ODOO_RC": "/ailleurs/odoo.conf"}),
|
||||
)
|
||||
|
||||
def test_without_a_repository_config_nothing_is_forced(self):
|
||||
# Sur une machine sans config.conf ni /etc/odoo/odoo.conf, on
|
||||
# laisse Odoo faire ce qu'il a toujours fait.
|
||||
vide = self.resoudre(self.dossier)
|
||||
self.assertIn(vide, ("", "/etc/odoo/odoo.conf"))
|
||||
|
||||
def test_it_never_fails_the_command_that_sourced_it(self):
|
||||
# Elle est appelée au tout début d'odoo_bin.sh : y échouer
|
||||
# empêcherait de lancer Odoo, pour une question de configuration.
|
||||
done = subprocess.run(
|
||||
["bash", "-c", f"source {LIB!r}; odoo_rc_resolve /inexistant"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={k: v for k, v in os.environ.items() if k != "ODOO_RC"},
|
||||
)
|
||||
self.assertEqual(0, done.returncode, done.stderr)
|
||||
|
||||
|
||||
class TestTheEntryPointUsesIt(unittest.TestCase):
|
||||
def source(self, nom):
|
||||
with io.open(os.path.join(REPO, nom), encoding="utf-8") as handle:
|
||||
return handle.read()
|
||||
|
||||
def test_odoo_bin_resolves_before_running_odoo(self):
|
||||
source = self.source("odoo_bin.sh")
|
||||
self.assertIn("odoo_rc_resolve", source)
|
||||
self.assertLess(
|
||||
source.index("odoo_rc_resolve"), source.index("odoo-bin")
|
||||
)
|
||||
|
||||
def test_the_order_matches_what_db_restore_checks(self):
|
||||
# `db_restore.py` lit ./config.conf puis /etc/odoo/odoo.conf pour
|
||||
# décider s'il faut un mot de passe maître. Si Odoo en lisait un
|
||||
# autre, cette vérification ne vérifierait rien.
|
||||
lib = self.source(os.path.join("script", "lib_odoo_rc.sh"))
|
||||
restore = self.source(
|
||||
os.path.join("script", "database", "db_restore.py")
|
||||
)
|
||||
for candidat in ("config.conf", "/etc/odoo/odoo.conf"):
|
||||
self.assertIn(candidat, lib, candidat)
|
||||
self.assertIn(candidat, restore, candidat)
|
||||
self.assertLess(
|
||||
lib.index("config.conf"), lib.index("/etc/odoo/odoo.conf")
|
||||
)
|
||||
self.assertLess(
|
||||
restore.index("config.conf"),
|
||||
restore.index("/etc/odoo/odoo.conf"),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue