🔧 Configuration du code en fonction de l’environnement

Lorsqu’on exĂ©cute un programme, il est souvent nĂ©cessaire de le configurer, de le paramĂ©trer ou de lui associer des Ă©lĂ©ments spĂ©cifiques Ă  l’environnement depuis lequel il est lancĂ©. Par exemple, il peut s’agir de travailler avec des donnĂ©es stockĂ©es dans un rĂ©pertoire particulier ou d’injecter des paramĂštres de connexion Ă  une base de donnĂ©es que l’on souhaite garder confidentiels et non versionnĂ©s.

Heureusement, il est possible d’injecter dans un programme des variables externes, qui serviront Ă  configurer son comportement tout en restant indĂ©pendantes du code source.

Pourquoi externaliser la configuration d’un programme ?

Pour garantir la flexibilitĂ© et la sĂ©curitĂ© d’une application, il est souvent judicieux d’externaliser certaines configurations. Voici quelques exemples courants :

  • ÉlĂ©ments dĂ©pendants de l’environnement cible : Par exemple, l’URL d’une base de donnĂ©es, un lien vers un jeu de donnĂ©es dans un datalake, le chemin d’un fichier externe, ou encore une rĂšgle mĂ©tier configurable comme un seuil d’erreur.
  • AccĂšs sĂ©curisĂ© Ă  des ressources : Les mots de passe ou clĂ©s d’authentification tel que les clĂ©s d’API, ne doivent pas ĂȘtre versionnĂ©s dans le code source pour Ă©viter tout risque de fuite.

La configuration d’une application regroupe tout ce qui est susceptible de varier entre diffĂ©rents environnements (dĂ©veloppement, validation, production, etc.). Une bonne pratique consiste Ă  sĂ©parer strictement la configuration du code. Tandis que le code reste inchangĂ© Ă  travers les dĂ©ploiements, la configuration peut varier considĂ©rablement.

Un bon test pour s’assurer de cette sĂ©paration consiste Ă  se demander si l’application pourrait ĂȘtre rendue open source Ă  tout moment, sans risquer de compromettre des identifiants ou d’autres donnĂ©es sensibles.

Cela peut ĂȘtre mis en Ɠuvre de plusieurs façons :

  • Par la lecture de variables d’environnement.
  • Par l’injection de paramĂštres lors du lancement du programme.
  • Par l’ajout de fichiers de configuration situĂ©s Ă  un emplacement connu et attendu par le programme.

Ces trois techniques sont diffĂ©rentes interfaces pour fournir des donnĂ©es au programme, mais elles servent toutes le mĂȘme objectif : injecter des informations externes dans le code.

Ce qui reste le plus prĂ©conisĂ©, c’est l’utilisation de variable d’environnement dans l’applicatif (cf https://12factor.net)

Toutefois, les diffĂ©rentes mĂ©thodes d’injection existent et sont plus ou moins maintenues selon le language.

Configuration par fichier de configuration externe

Dans de nombreux langages, il existe des formats spécifiques pour gérer la configuration via des fichiers.

En Python, les fichiers de configuration sont couramment utilisĂ©s, souvent dans des formats standardisĂ©s, parmi lesquels le format TOML (Tom’s Obvious, Minimal Language) et le format INI.

Exemple de fichier INI

# config.ini Ă  la racine du projet
[section_name]
key1 = value1
key2 = value2

[another_section]
keyA = valueA
keyB = valueB

# Exemple avec des paramÚtres par défaut
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes

[forge.example]
User = hg

[topsecret.server.example]
Port = 50022
ForwardX11 = no

Dans la bibliothÚque standard de Python, on trouve le module configparser, qui permet de gérer des fichiers de configuration au format INI. Ces fichiers sont habituellement nommés avec une extension descriptive comme .ini, et ce module permet également de les lire facilement :

import configparser

# Création d'un objet ConfigParser
config = configparser.ConfigParser()

# Lecture du fichier config.ini
config.read('config.ini')

# Exemple d’accĂšs aux donnĂ©es
server_interval = config['DEFAULT']['ServerAliveInterval']
user = config['forge.example']['User']
print(server_interval)  # Affiche : 45
print(user)             # Affiche : hg
Pour aller plus loin
Feuilleter la documentation https://docs.python.org/3/library/configparser.html

Note :
Ce type de configuration est présent dans pratiquement tous les langages, bien que chaque langage ait ses conventions. Par exemple :

  • En Java : application.properties ou application.yml
  • En Node.js : config.json
  • En Python : .ini (ou plus rĂ©cemment, TOML)

Configuration par arguments en ligne de commande

L’utilisation de la ligne de commande est une mĂ©thode historique pour gĂ©rer la configuration des programmes. Elle permet de crĂ©er des variables de CLI (command-line interface), facilitant ainsi l’exĂ©cution de programmes directement via le terminal.

De nombreux programmes informatiques, qu’ils soient conçus pour effectuer des tĂąches rapidement (comme des scripts ou des outils en ligne de commande) ou pour fonctionner en arriĂšre-plan en tant que serveurs (comme les serveurs web ou les bases de donnĂ©es), peuvent recevoir des paramĂštres (variables) directement via la ligne de commande lorsque vous les exĂ©cutez. En Python, il est possible de rĂ©cupĂ©rer ces paramĂštres Ă  l’aide du module sys.

Prenons, par exemple, le fichier main.py que nous avons construit précédemment. En ajoutant le code suivant :

import sys
import logging

arguments = sys.argv
logging.basicConfig(filename=arguments[1], encoding='utf-8', level=logging.DEBUG)

et en lançant la commande :
python main.py toto.log

nous pouvons écrire les logs dans le fichier toto.log.

Cependant, un inconvĂ©nient de cette approche est qu’avec un nombre accru d’arguments, il devient plus facile de commettre des erreurs d’ordonnancement ou d’oublier un paramĂštre.

Pour aller plus loin
Clique est une bibliothÚque pour Python qui facilite la création d'interfaces en ligne de command https://click.palletsprojects.com/en/stable/

À noter que cette mĂ©thode ne sera pas approfondie ici, car nous nous concentrerons principalement sur la configuration par variables d’environnement.

Configuration par variable d’environnement

Les variables d’environnement sont des variables disponibles dynamiquement pour votre programme pendant l’exĂ©cution. Contrairement Ă  des valeurs codĂ©es en dur, elles sont adaptables Ă  l’environnement dans lequel l’application s’exĂ©cute. Cela permet une plus grande flexibilitĂ© et facilite les Ă©ventuelles modifications de configuration.

L’injection par variable d’environnement est une solution optimale, notamment pour des personnes extĂ©rieures au projet. Cette approche permet de surcharger facilement la configuration en renseignant des paramĂštres sous la forme :

CLE=valeur

Remarque : Les noms des variables d’environnement suivent la notation UPPER_SNAKE_CASE.

En Python, la gestion des variables d’environnement peut ĂȘtre facilitĂ©e avec la librairie python-dotenv. Celle-ci permet de lire un fichier .env et d’exporter son contenu dans l’environnement d’exĂ©cution.

Exemple d’un fichier .env

# Configuration par défaut
CHEMIN_FICHIER_LOG=
ENVIRONNEMENT=local

Voici comment importer les variables depuis un fichier .env :

from dotenv import load_dotenv

# Charge toutes les variables d'environnement depuis le fichier .env
load_dotenv()

Une fois chargées, les variables sont accessibles via la librairie os :

import os

chemin_fichier_log = os.getenv("CHEMIN_FICHIER_LOG")
environnement = os.getenv("ENVIRONNEMENT")

Il est également possible de définir plusieurs fichiers pour organiser les configurations et éviter de versionner des données sensibles. Par exemple, vous pouvez charger un fichier local supplémentaire :

from dotenv import load_dotenv
import os

# Charge le fichier principal
load_dotenv()

# Charge un fichier local si présent
local_env_path = ".env.local"
if os.path.exists(local_env_path):
    load_dotenv(dotenv_path=local_env_path, override=True)
Pour aller plus loin
Variables d'environnement : https://kinsta.com/knowledgebase/what-is-an-environment-variable/

Travaux Pratiques

OBJECTIF DU TP : CrĂ©ez un fichier main.py qui, au lancement de l’application, affiche dans les logs toutes les valeurs des variables d’environnement, Ă  l’exception des mots de passe.

  1. Créez un fichier .env.local contenant :

    • Une fausse URL de base de donnĂ©es
    • Un faux mot de passe de base de donnĂ©es
    • La version actuelle de votre application (par dĂ©faut, utilisez 0.1 si vous n’avez pas encore commencĂ© votre projet).
  2. Créez un fichier .env.template qui liste toutes les variables présentes dans le fichier .env.local. Ce fichier servira de modÚle pour permettre aux développeurs de créer leur propre fichier .env localement.

  3. Ajoutez le fichier .env.local au .gitignore pour Ă©viter qu’il soit versionnĂ©.

  4. Dans le fichier main.py : ImplĂ©mentez une mĂ©thode qui charge les variables d’environnement globales ainsi que celles du fichier local (si un fichier .env.local est prĂ©sent).

  5. Ajoutez une mĂ©thode qui affiche toutes les variables d’environnement (globales et locales), en masquant les valeurs des variables dont le nom contient les mots-clĂ©s suivants :

    • password, pwd, jeton, token, secret, key, cle, mdp, ou motdepasse.
      Pour ces variables, affichez des **** Ă  la place de leur valeur.
  6. Créez une nouvelle version de votre code (en veillant à ce que le fichier .env.local ne soit pas inclus dans la version). Envoyez ensuite cette version à votre dépÎt distant.