La version courte
- Restaurer un dump ne neutralise rien. La neutralisation est une étape distincte, et elle est désactivée par défaut dans chaque point d'entrée qu'Odoo fournit
- La neutralisation est une option par module. Pour chaque module installé, Odoo recherche <module>/data/neutralize.sql et l'exécute en tant que SQL brut. Un module sans ce fichier ne contribue rien, silencieusement.
- La couverture du cœur est bonne : serveurs de messagerie, crons, webhooks, fournisseurs de paiement, synchronisation bancaire, transporteurs de livraison, jetons IAP et plus :
- Vos modules personnalisés ne sont pas couverts à moins que vous n'ayez écrit le fichier. La plupart des modules tiers et OCA ne le sont pas non plus.
- Les actions automatisées de type code restent armées, et les déclencheurs on-create/on-write n'ont pas besoin d'un cron pour se déclencher.
- Exécutez ceci avant de faire confiance à quoi que ce soit : odoo neutralize -d staging_db
Ce qu'est réellement la neutralisation
Le mécanisme entier est un court module. C'est ça, en substance :
def get_neutralization_queries(modules):
for module in modules:
filename = f'{module}/data/neutralize.sql'
with suppress(FileNotFoundError):
with file_open(filename) as file:
yield file.read().strip()
Un module qui n'a rien à dire et un module qui n'a jamais été demandé produisent une sortie identique : rien. Pas d'avertissement, pas de ligne de journal, pas de liste de modules qui n'ont contribué à aucune requête. Lorsque l'exécution se termine, il enregistre "Neutralisation terminée" peu importe s'il a neutralisé quarante modules ou quatre.
Cela a trois conséquences, toutes des lectures directes du code ci-dessus.
C'est sur option, par module. La couverture est une liste maintenue par quiconque a écrit chaque module. Odoo en maintient une bonne pour le sien. Personne n'en maintient une pour le vôtre.
Il ne voit que les modules installés. L'état doit être installé, pour mettre à niveau ou pour supprimer. Raisonnable, et bon à savoir.
C'est du SQL brut sur un curseur, pas l'ORM. Pas de Python, pas de hooks de création/écriture, pas de champs calculés recalculés. Tout ce qui doit être désactivé doit être exprimable en SQL contre des tables dans cette base de données.
Un détail de plus qui n'est pas évident à partir du code mais en découle : get_installed_modules exécute un SELECT sans ORDER BY, donc l'ordre dans lequel les fichiers de module s'exécutent est non spécifié. N'écrivez jamais un neutralize.sql qui dépend de l'exécution préalable d'un autre module.
Ce que le core désactive réellement
Le fichier de Base est assez court pour être lu en entier, et c'est la meilleure preuve dans ce post :
-- désactiver les serveurs de messagerie
UPDATE ir_mail_server
SET active = false;
-- insérer un serveur de messagerie fictif pour éviter d'utiliser des serveurs de secours spécifiés via la ligne de commande
INSERT INTO ir_mail_server(name, smtp_port, smtp_host, smtp_encryption, active, smtp_authentication)
VALUES ('neutralisation - désactiver les e-mails', 1025, 'invalid', 'none', true, 'login');
-- désactiver les crons
UPDATE ir_cron SET active = false
WHERE id NOT IN (
SELECT res_id FROM ir_model_data
WHERE model = 'ir.cron' AND name = 'autovacuum_job' AND module = 'base');
-- drapeau de neutralisation pour la base de données
INSERT INTO ir_config_parameter (key, value)
VALUES ('database.is_neutralized', true)
ON CONFLICT (key) DO UPDATE SET value = true;
-- désactiver les webhooks
UPDATE ir_act_server
SET webhook_url = 'neutralisation - désactiver le webhook'
WHERE state = 'webhook';
Le serveur de messagerie fictif mérite de l'attention, car c'est un travail minutieux. Il est inséré actif, pointant vers un hôte invalide sur le port 1025. Il est là pour qu'un serveur SMTP passé en ligne de commande ne puisse pas être pris comme solution de secours une fois que les vrais serveurs sont désactivés. Les envois échouent bruyamment au lieu de s'échapper discrètement. C'est le bon échange, et c'est la raison pour laquelle une base de données neutralisée est réellement sûre o, le chemin du mail.
Notez également la clause cron : seul base.autovacuum_job survit. Tout le reste programmé est désactivé.
Il y a 76 fichiers neutralize.sql dans Community, et d'autres encore dans Enterprise. Ce sont ceux d'Enterprise qui couvrent les intégrations coûteuses. Un échantillon, là encore lu directement depuis les sources : account_online_synchronization définit account_online_link.client_id = 'duplicate', ce qui correspond à la synchronisation bancaire ; whatsapp remplace le token du compte par 'dummy_token' ; social_facebook met à NULL le token d'accès Facebook ; delivery_ups_rest passe les identifiants UPS à 'dummy' ; voip bascule le fournisseur en mode démo ; sale_amazon remplace la clé vendeur et le refresh token ; l10n_be_hr_payroll remplace res_company.onss_expeditor_number, c'est-à-dire le numéro sous lequel sont soumises les déclarations Dimona belges.
La couverture est meilleure que ce que la plupart des gens supposent, et cela mérite d'être dit clairement. Le problème n'est pas qu'Odoo le fasse mal. Le problème, c'est qu'une couverture n'est jamais qu'une liste et que votre liste est plus longue que la leur.
Les défauts sont le bord tranchant
La neutralisation est une étape distincte de la restauration, et elle est désactivée à moins que vous ne l'activiez.
Ainsi, la restauration via le gestionnaire de bases de données, celle via la CLI et le chemin de duplication produisent tous une base de production pleinement armée, sauf si quelqu'un a explicitement demandé le contraire. Et un simple pg_restore, ou un createdb suivi d'une restauration, ne neutralise strictement rien, puisqu'il ne passe jamais par le code d'Odoo. Il n'existe aucune version de « restaurer un dump » qui soit sûre en elle-même.
Deux conséquences en découlent. Premièrement, si vous restaurez à la main, la neutralisation est une étape qu'il vous revient de ne pas oublier, à chaque fois — y compris lors de la restauration faite à 22 h 40 parce qu'il y avait urgence. Deuxièmement, si elle échoue, elle le signale :
"An error occurred during the neutralization. THE DATABASE IS NOT NEUTRALIZED!"
La CLI journalise cela et sort avec le code 1. Ce qui signifie aussi que l'échec du fichier d'un seul module interrompt toute l'exécution, y compris le vôtre, si vous en livrez un avec une faute de frappe.
Ce qu'il ne couvre pas
Les actions automatisées restent armées, et c'est l'élément le plus aigu de la liste. addons/base_automation/ n'expédie aucune donnée/neutralize.sql, son répertoire data/ ne contient que base_automation_data.xml et digest_data.xml. Base désactive les actions serveur où l'état = 'webhook', ce qui couvre exactement le type webhook et rien d'autre. Une action serveur de type code, celle qui appelle l'API d'un partenaire, publie sur un canal Slack, notifie un fournisseur de fulfillment, pousse un enregistrement vers un système d'entrepôt, reste intacte.
Les automatisations basées sur le temps sont, en effet, arrêtées, car leur déclencheur fonctionne à partir de base_automation.ir_cron_data_base_automation_check, et le cron cull le désactive. Mais les déclencheurs on-create et on-write n'ont pas besoin d'un cron. Ils se déclenchent au moment où quelqu'un édite un enregistrement. C'est la raison même pour laquelle vous avez construit une base de données de staging.
Les modules personnalisés sont neutralisés uniquement si vous avez écrit le fichier. Rien de générique ne les couvre. Si votre module détient une clé API, ouvre une connexion sortante, ou publie sur un webhook, get_neutralization_queries cherche your_module/data/neutralize.sql, ne le trouve pas, et passe à autre chose sans un mot.
Modules tiers et OCA : le même. Certains en expédient un. La plupart non. Vérifiez ceux qui communiquent avec le monde extérieur, plutôt que de supposer.
Les données personnelles restent intactes. Rien dans aucun neutralize.sql n'anonymise un nom de client, une adresse e-mail, un numéro de téléphone, un salaire ou un compte bancaire. Plus à ce sujet ci-dessous.
Le filestore ne fait pas partie de cela. La neutralisation est SQL contre la base de données. Les pièces jointes arrivent par une route séparée et ne sont pas affectées par cela.
La correction en deux lignes
La bonne nouvelle est vraiment bonne : la solution pour vos propres modules est un fichier, et une fois qu'il est dans le dépôt, il s'applique à chaque futur clone, pour toujours, sans que personne n'ait à se souvenir de quoi que ce soit.
Créez your_module/data/neutralize.sql. Il ne va pas dans __manifest__.py, get_neutralization_queries construit le chemin à partir du nom du module et l'ouvre directement, donc le fichier existant à ce chemin est l'étape d'enregistrement entière.
Ce qui y va est du SQL brut, exécuté sur un curseur. Cela signifie :
- Plusieurs instructions dans un fichier sont acceptables. Base en a cinq.
- UPDATE, INSERT, DELETE et TRUNCATE sont tous utilisés dans le cœur.Le folklore au niveau du plan selon lequel neutralize.sql est uniquement UPDATE est faux.
- Pas de Python, pas d'ORM. Pas de remplacements de création/écriture, pas de méthodes de calcul, pas de @api.constrains, pas d'environnement.
- Rien en dehors de la base de données. Il ne peut pas faire tourner une clé chez votre fournisseur, supprimer un fichier du filestore, ou désenregistrer un webhook à l'autre bout. Il peut seulement faire en sorte que cette base de données ne puisse plus les utiliser.
- Il ne doit pas lever d'exception. Une erreur interrompt l'exécution pour chaque module, pas seulement le vôtre.
Un fichier réaliste pour un module avec une intégration sortante :
-- your_module/data/neutralize.sql
-- 1. pointez le connecteur vers quelque chose qui ne peut pas se résoudre, et désactivez-le
UPDATE your_module_connector
SET endpoint_url = 'https://neutralized.invalid',
api_key = 'neutralized',
active = false;
-- 2. videz la file d'attente de tout ce qui attend d'être envoyé
DELETE FROM your_module_outbound_queue;
-- 3. désactiver les actions du serveur de code que ce module fournit
METTRE À JOUR ir_act_server
DEFINIR code = 'pass # neutralisé'
OÙ état = 'code'
ET id DANS (SÉLECTIONNER res_id
DE ir_model_data
OÙ modèle = 'ir.actions.server'
ET module = 'your_module');
Adaptez les noms de table et de colonne à votre schéma, et vérifiez-les par rapport à la version que vous exécutez avant de vous fier à la troisième déclaration. Le modèle est ce qui est transféré : désactiver l'interrupteur, invalider les informations d'identification et vider tout ce qui est déjà en file d'attente. L'invalidation des informations d'identification est aussi importante que la désactivation de l'interrupteur, car l'interrupteur est exactement ce qu'un développeur remet en marche pour tester quelque chose.
Testez-le en exécutant odoo neutralize -d <copy> --stdout et en confirmant que votre fichier apparaît dans la sortie. Ensuite, exécutez-le réellement contre une copie jetable et vérifiez les lignes.
Pré-vol : avant que quiconque n'ouvre la base de données de staging
Exécutez ceci dans l'ordre. Cela prend dix minutes et c'est les mêmes dix minutes à chaque fois.
- Confirmez que la neutralisation a bien été effectuée. Vérifiez database.is_neutralized dans ir_config_parameter. S'il est absent, rien n'a été exécuté, peu importe ce dont quiconque se souvient.
- Cherchez la bannière. web active web.neutralize_banner ; le site active website.neutralize_ribbon. Une base de données neutralisée devrait être visiblement marquée dans l'interface. Si ce n'est pas le cas, considérez cela comme la réponse.
- Lisez le SQL qui a été exécuté. odoo neutraliser -d <db> --stdout. Comparez les modules représentés dans la sortie avec votre liste de modules installés. La différence entre ces deux listes est votre exposition, et c'est le seul endroit dans cette procédure où vous apprenez quelque chose que vous ne soupçonniez pas déjà.
- Mail. Toutes les lignes dans ir_mail_server inactives sauf celle fictive sur l'hôte invalide. mail_template.mail_server_id null. fetchmail_server inactif, donc rien ne tire le mail non plus.
- Crons. Seul base.autovacuum_job actif dans ir_cron.
- Actions du serveur. Chaque ligne dans ir_act_server avec l'état = 'webhook' doit porter le placeholder de neutralisation dans webhook_url. Ensuite, listez ceux avec l'état = 'code' et lisez-les, car base ne les a pas touchés. Faites attention à ceux liés aux déclencheurs on-create et on-write.
- Fournisseurs et transporteurs. états payment_provider, lignes delivery_carrier, tokens iap_account, et, si vous exécutez Enterprise, synchronisation bancaire, WhatsApp, VoIP et les connecteurs sociaux. Tous ceux-ci ont une couverture de base lorsque le module est installé. Confirmez plutôt que de supposer, car ils ne sont couverts que si le module qui les possède a été installé à ce moment-là.
- Votre propre domaine. Parcourez la liste des modules installés pour tout ce qui est personnalisé, OCA ou payant qui communique avec le monde extérieur, et vérifiez chacun pour un data/neutralize.sql. C'est la liste qui grandit chaque trimestre et ne diminue jamais.
- Le filestore. Il est arrivé avec de véritables pièces jointes : contrats signés, documents d'identité, PDF de paie. Rien dans la neutralisation ne s'applique à cela.
- Accès. Décidez qui peut se connecter à cette base de données avant de dire à quiconque qu'elle existe.
La partie neutralisation ne peut pas aider avec
La neutralisation arrête la base de données d'atteindre des personnes. Elle ne l'empêche pas de contenir elles.
Chaque nom de client, adresse e-mail, numéro de téléphone, adresse de livraison, ligne de facture, dossier d'employé, salaire et détail bancaire en production est maintenant également en staging, inchangé. Cette copie est un deuxième ensemble complet de données personnelles, elle se trouve dans votre dossier de traitement, et elle est soumise à la même obligation de sécurité que la production.
Cela devient généralement moins. La production a un accès restreint, une piste d'audit et un processus de changement. La staging a un identifiant partagé, un large groupe de développeurs et une URL que quelqu'un a collée dans un chat il y a deux mois, parce que c'est juste du staging. Les données ne le savent pas.
Rien ici n'est une raison de ne pas restaurer les données de production, des données réalistes sont la raison pour laquelle l'environnement est utile, et ce n'est pas un conseil juridique. C'est une raison de donner a la staging le même contrôle d'accès et la même discipline de conservation que la production, et de supprimer les copies que vous avez cessé d'utiliser.
Ce que c'est réellement
La neutralisation est un bon mécanisme faisant exactement ce qu'il dit. L'écart est entre « la neutralisation a fonctionné » et « tout ce qui est dangereux est éteint », et il reste ouvert car les deux états semblent identiques de l'extérieur : même ligne de journal, même bannière, même absence d'erreurs. Rien dans votre instance ne va vous dire que le connecteur qu'un de vos développeurs a écrit en 2023 n'a jamais été sur la liste.
C'est la différence entre Odoo en cours d'exécution et Odoo fonctionnant correctement, encore une fois. Ce n'est que rarement dramatique. C'est un fichier qui n'a jamais été créé, et il reste ainsi jusqu'à ce que quelqu'un aille chercher.
La staging qui arrive neutralisé, à chaque fois
La version fiable de ceci n'est pas une meilleure liste de contrôle. Ce n'est pas avoir à se souvenir.
Sur l'hébergement Skysize Odoo, odoo neutralise s'exécute sur la branche de staging automatiquement sur chaque chemin où de nouvelles données de production arrivent : une restauration dans un conteneur existant, une restauration dans un nouveau, une restauration blue-green, et un clone de staging. Cela s'exécute à l'intérieur du conteneur, contre la base de données qui vient d'arriver, avant que vous n'y accédiez.
Cela ne fait délibérément pas pas de réexécution sur un push de code ordinaire. C'est le détail qui vaut plus que n'importe quel adjectif : si vous avez réactivé un fournisseur de paiement de test ou pointé un connecteur vers un sandbox après le clone, le prochain commit ne défait pas votre travail. La neutralisation se produit lorsque les données de production arrivent, et seulement alors. Des sauvegardes peuvent également être prises pré-neutralisées pour téléchargement, neutralisées par une copie jetable afin que la source ne soit jamais modifiée.
Ce qu'il ne fait pas, et nous préférerions le dire : il ne crée pas neutralize.sql pour vos modules personnalisés. La couverture de Core est la couverture de core. Ce fichier est à vous d'expédier, cela prend un après-midi, et c'est la chose de la plus haute valeur que vous puissiez faire avec ce que vous venez de lire.
Construit par d'anciens ingénieurs d'Odoo. Essayez gratuitement aujourd'hui.