Accès aux exemples
Les exemples présentés sont accessibles directement sur le dépôt git associé : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/configuration
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 = noDans 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 : hgFeuilleter 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.propertiesouapplication.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.
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=valeurRemarque : Les noms des variables d’environnement suivent la notation
UPPER_SNAKE_CASE.
Librairie python-dotenv#
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=localVoici 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)L’outil système uv#
L’outil uv permet également de charger des variables d’environnement à partir d’un fichier dotenv lors de l’exécution d’un programme, grâce à la commande uv run.
Contrairement à python-dotenv, uv ne charge pas les variables d’environnement via du code Python. Celles-ci sont injectées directement au moment de l’exécution du programme.
Par défaut, uv recherche les fichiers dans le répertoire courant.
Exemple#
Supposons que vous disposiez d’un fichier .env contenant les variables suivantes :
ENVIRONNEMENT=local
CLE_API=macledapiEt d’un fichier Python nommé load_fichier_dotenv_uv.py :
import os
for key, value in os.environ.items():
print(f"{key}={value}")Vous pouvez alors exécuter ce script avec la commande suivante :
uv run --env-file .env python3 load_fichier_dotenv_uv.pyOù, si votre fichier .env se trouve dans un autre répertoire, en précisant explicitement son chemin :
uv run --env-file path/.env python3 load_fichier_dotenv_uv.pyDans cet exemple, path/ correspond au chemin vers le dossier contenant le fichier .env, relatif à votre répertoire courant.
L’exécution affichera l’ensemble des variables d’environnement disponibles pour le programme, notamment ENVIRONNEMENT et CLE_API.
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.
Créez un fichier
.env.localcontenant :- 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.1si vous n’avez pas encore commencé votre projet).
Créez un fichier
.env.templatequi 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.locallocalement.Ajoutez le fichier
.env.localau.gitignorepour éviter qu’il soit versionné.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.localest présent).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, oumotdepasse.
Pour ces variables, affichez des****à la place de leur valeur.
Créez une nouvelle version de votre code (en veillant à ce que le fichier
.env.localne soit pas inclus dans la version). Envoyez ensuite cette version à votre dépôt distant.
