[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:
Mathieu Benoit 2026-08-27 16:29:00 -04:00
parent 7ba3c6504c
commit b7d68cd199
3 changed files with 172 additions and 0 deletions

View file

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