"Module principal du cadriciel Scrippy"
Project description
scrippy_core
scrippy_core
est le module Python3 principal du cadriciel Scrippy
permettant la normalisation de l'écriture de scripts Python.
Ce module apporte l'ensemble des fonctionnalités de base telles que la gestion des fichiers de configuration, de logs, d'historiques d'exécution, le contrôle d'accès aux scripts, la gestion des exécution concurrentielles, etc.
Prérequis
Système
Debian et dérivées
- python3
- python3-pip
- python-dev
- build-essential
Modules Python
Liste des modules nécessaires
Les modules listés ci-dessous seront automatiquement installés.
- prettytable
- coloredlogs
- argcomplete
- filelock
Installation
Manuelle
git clone https://codeberg.org/scrippy/scrippy-core.git
cd scrippy-core
sudo python3 -m pip install -r requirements.txt
sudo python3 ./setup.py build install
Avec pip
sudo pip install scrippy-core
Configuration de l'environnement
- Le fichier de configuration de Scrippy
/etc/scrippy/scrippy.yml
doit définir un certain nombres de répertoires qui seront utiles aux scripts reposant sur Scrippy.
Clef | Utilité | Variable associée |
---|---|---|
env::logdir |
Répertoire des journaux d'exécution des scripts basés sur Scrippy | scrippy_core.SCRIPPY_LOGDIR |
env::histdir |
Répertoire des fichiers d'historisation des exécutions | scrippy_core.SCRIPPY_HISTDIR |
env::reportdir |
Répertoire des fichiers de rapports | scrippy_core.SCRIPPY_REPORTDIR |
env::tmpdir |
Répertoire temporaire des scripts basés sur Scrippy | scrippy_core.SCRIPPY_TMPDIR |
env::templatedirdir |
Répertoire des fichiers modèles | scrippy_core.SCRIPPY_TEMPLATEDIR |
env::confdir |
Répertoire des fichiers de configuration des scripts basés sur Scrippy | scrippy_core.SCRIPPY_CONFDIR |
env::datadir |
répertoire des données utilisées par les scripts basés sur Scrippy | scrippy_core.SCRIPPY_DATADIR |
Modèle de fichier de configuration de l'environnement d'exécution Scrippy:
env:
logdir: /var/log/scrippy
histdir: /var/lib/scrippy/hist
reportdir: /var/lib/scrippy/reports
tmpdir: /var/tmp/scrippy
datadir: /var/lib/scrippy/data
templatedir: /var/lib/scrippy/templates
confdir: /etc/scrippy/conf
- Création des répertoires définis dans le fichier de configuration
/etc/scrippy/scrippy.yml
par l'utilisateur root (ou viasudo
)
Script python de création des répertoires nécessaires :
import os
import yaml
conf_file = "/etc/scrippy/scrippy.yml"
with open(conf_file, "r") as conf:
scrippy_conf = yaml.load(conf, Loader=yaml.FullLoader)
for rep in scrippy_conf["env"]:
os.makedirs(scrippy_conf["env"][rep], 0o0775)
Activation de l'auto-complétion (facultatif)
Si votre shell dispose de la commande whence
, le parser des arguments (argparse) pourra être utilisé pour alimenter l'auto-complétion. Voir la documentation d'argcomplete
.
Pour l'activer lancer la commande suivante (installée avec le module python argcomplete
) :
sudo activate-global-python-argcomplete
Rafraîchissez votre environnement Bash.
source /etc/profile
Formalisme
Les scripts utilisant le module scrippy_core
doivent répondre à un certain formalisme afin de garantir une harmonisation de leur format tout en facilitant la prise en charge de certaines fonctionnalités avancées telles que le contrôle de la validité de la configuration ou la gestion des paramètres optionnels.
Ainsi chaque script doit comporter dans sa doc string un cartouche déclaratif et un ensemble de sections prédéfinies.
Une fonction main()
doit impérativement:
- Être définie dans la section
Définition des fonctions et classes
- Être appelée dans la section
Point d'entrée
- Directement encadrée son contenu par
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
qui gère et active l'ensemble des fonctionnalités des scripts écrit à partir du modulescrippy_core
.
def main():
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
# Code principal
if __name__ == '__main__':
# gestion des arguments si il y en a
main()
Modèle de base
Le modèle de base ci-dessous peut servir de base à un extrait de code (snippet):
#!/bin/env python3
"""
--------------------------------------------------------------------------------
@author : <Auteur>
@date : <Date de la version actuelle du script>
@version : <X.Y.Z>
@description : <Brève description (une ligne) de l'utilité du script>
--------------------------------------------------------------------------------
Mise a jour :
<X.Y.Z> <Date> - <Auteur> - <Raison>: <Description de la mise à jour>
--------------------------------------------------------------------------------
Liste des utilisateurs ou groupe autorisés:
@user:<nom d'utilisateur>
@group:<nom du groupe>
--------------------------------------------------------------------------------
Exécutions concurrentes :
@max_instance: <INT>
@timeout: <INT>
@exit_on_wait: <BOOL>
@exit_on_timeout: <BOOL>
--------------------------------------------------------------------------------
Liste des paramètres de configuration obligatoires:
@conf:<section>|<clé>|<type>|<secret>
--------------------------------------------------------------------------------
Liste des paramètres des options et arguments d'exécution:
@args:<nom>|<type>|<aide>|<nombre arguments>|<valeur par défaut>|<conflit>|<implique>|<requis>|<secret>
--------------------------------------------------------------------------------
Fonctionnement:
---------------
<Description détaillée du script>
...
"""
#-------------------------------------------------------------------------------
# Initialisation de l’environnement
#-------------------------------------------------------------------------------
import logging
import scrippy_core
#-------------------------------------------------------------------------------
# Définition des fonctions et classes
#-------------------------------------------------------------------------------
class <Class>(<object>):
def __init__(self, [<param>, ...]):
[...]
def <fonction>([<param>, ...]):
[...]
#-------------------------------------------------------------------------------
# Traitement principal
#-------------------------------------------------------------------------------
def main(args):
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
# recupération de la config si necessaire
config = _context.config
[...]
#-------------------------------------------------------------------------------
# Point d'entrée
#-------------------------------------------------------------------------------
if __name__ == '__main__':
# gestion des arguments si il y en a. Les arguments sont accessibles avec la variable 'scrippy_core.args'
main(scrippy_core.args)
Éléments du cartouche
Les éléments @author
, @date
, @version
, @description
sont obligatoires et seront automatiquement affichés par l'option --help
.
Version:
Le numéro de version d'un script est au format X.Y.Z avec:
X
, l’identifiant de version majeureY
est l’identifiant de version mineureZ
, l’identifiant de version de correction
Version majeure X: Il vaut "0" lors du développement, le script est considéré non valide et ne devrait ni être appelé par d’autres scripts ni être utilisé en production.
Une fois le script validé la version doit être 1.0.0 (première version stable).
X
doit être incrémenté si des changements dans le code n’assure plus la rétro-compatibilité.
Les identifiants de version mineure Y
et de correction Z
doivent être remis à zéro lorsque l’identifiant de version majeure X
est incrémenté.
Version mineure Y: Doit être incrémenté lors de l’ajout de nouvelles fonctionnalités ou d’amélioration du code qui n’ont pas d’impact sur la rétro-compatibilité.
L’identifiant de version de correction Z
doit être remis à zéro lorsque l’identifiant de version mineure est incrémenté.
Version de correction Z: Doit être incrémenté si seules des corrections rétro-compatibles sont introduites.
Une correction est définie comme un changement interne qui corrige un comportement incorrect (Bug).
Z
peut être incrémenté lors de correction typographique ou grammaticale.
Mise à jour:
En plus du numéro de version, de la date de modification et de l'auteur de la modification chaque ligne de l'historique du script doit indiquer la raison de la modification.
<Raison>
peut prendre l'une des valeurs suivantes:
cre
: Création du scriptevo
: Évolution du script (ajout de fonctionnalité, amélioration du code, etc)bugfix
: Correction de comportement inattendu (bug)typo
: Correction de faute de frappe, ajout de commentaires et toute action n'apportant aucune modification au code.
Utilisateurs et groupes autorisés
Le module scrippy_core
ajoute une couche de vérification quant à l'exécution du script permettant de s'assurer qu'un script est exécuté par un utilisateur spécifique ou un utilisateur appartenant à un groupe spécifique.
Placée dans le cartouche, une ligne telle que @user:harry.fink
empêchera l’exécution par tout utilisateur autre que harry.fink
.
Il est possible de définir plusieurs utilisateurs autorisés en multipliant les déclarations:
@user:harry.fink
@user:luiggi.vercotti
Il est également possible d'autoriser des groupes d'utilisateurs avec une ligne telle que @group:monty
qui assurera que seul un utilisateur du groupe monty
exécute le script.
De la même manière que pour les utilisateurs il est possible de multiplier les déclarations de groupe:
@group:monty
@group:python
En cas d'absence de ligne déclarative @user
et @group
les permissions sur le fichier font foi et dans tous les cas les permissions sur les fichiers sont prévalentes.
Attention: Si un groupe et un utilisateur sont déclarés, les deux conditions doivent être remplies pour que l'utilisateur soit autorisé à exécuter le script.
Gestion des exécutions concurrentes
Les déclarations optionnelles @max_instance
, @timeout
, @exit_on_wait
et @exit_on_timeout
permettent de définir le nombre d'exécution concurrentes d'un même script ainsi que le comportement du script le cas échéant:
Déclaration | Type | Utilité | Valeur par défaut |
---|---|---|---|
@max_instance |
Nombre entier | Nombre maximum d'exécutions parallèles | 0 (infini) |
@timeout |
Nombre entier | Délai d'attente maximum exprimé en secondes si @exit_on_timeout est positionnée à vrai |
0 (infini) |
@exit_on_timeout |
booléen (true , 1 , on ) |
Fait sortir le script en erreur lorsque le délai d'attente est atteint | False |
@exit_on_wait |
booléen (true , 1 , on ) |
Fait immédiatement sortir le script en erreur en cas d'attente y compris si le délai d'attente n'est pas atteint | False |
Les occurrences mises en attente d'exécution sont exécutées séquentiellement dans l'ordre de leur inscription dans la file d'exécution.
Dans l'exemple suivant, deux occurrences du script sont permises. Une troisième exécution sera mise en attente 10 secondes, délai au delà duquel le script sortira en erreur s'il n'a pas peu obtenir un créneau d'exécution.
@max_instance: 2
@timeout: 10
@exit_on_wait: false
@exit_on_timeout: true
Gestion et vérification des paramètres de configuration obligatoires
Un fichier de configuration est un fichier simple fichier ini découpé en autant de sections que nécessaire:
Pour être chargé un tel fichier de configuration doit simplement se trouver dans le répertoire défini par la constante scrippy_core.SCRIPPY_CONFDIR
et avoir le même nom que le script qui doit le charger débarrassé de son extension et suffixé de l'extension .conf
.
De cette manière le script exp_test_logs.py
chargera automatiquement le fichier de configuration exp_test_logs.conf
.
[log]
level = ERROR
[database]
host = srv.flying.circus
port = 5432
user = arthur.pewtey
base = ministry_of_silly_walks
password = parrot
# La section comporte des espaces
[ma section]
# un commentaire indenté
ma variable = ma valeur
Dans un tel fichier:
- L'indentation est possible
- Une ligne commençant par
#
ou;
est considérée comme un commentaire, y compris si elle est indentée. - Toutes les valeurs sont des chaînes de caractères:
- Il appartient au développeur de convertir la valeur des variables dans le type désiré lors du traitement (voir Récupération d’une valeur d’un type particulier).
- Les espaces sont acceptés que se soit dans le nom des section, dans le nom d'une clé ou dans une valeur.
Contrôle de la validité du fichier de configuration
Afin de valider le fichier de configuration le script doit comporter dans sa docstring un ensemble de lignes commençant par @conf
et décrivant le fichier de configuration tel qu'il est attendu.
Les lignes de déclarations du format de configuration doivent respecter le formalisme suivant:
@conf:<section>|<clé>|<type_valeur>[|<secret>]
<type_valeur>
doit être l'un des types reconnus suivants:
str
(chaîne de caractères)int
(entier)float
(nombre à virgule flottante)bool
(booléen)
secret
est optionnel et s'il est défini doit prendre la valeur true
ou false
.
Si secret
est défini et a pour valeur true
, la valeur de paramètre de configuration sera considérée comme un secret et apparaîtra masquée dans les fichiers de journalisations.
Exemple:
À partir de la déclaration suivante:
@conf:log|level|str
@conf:database|port|int
@conf:sql|verbose|boolean
@conf:sql|database|str|false
@conf:sql|password|str|true
Le fichier de configuration suivant sera vérifié:
[log]
level = error
[database]
port = 5432
[sql]
verbose = True
database = monty
password = d34dp4rr0t
Toutes les occurrences du mot de passe d34dp4rr0t
seront remplacé par *******
dans les fichiers de journalisations comme sur la sortie standard.
Aucun contrôle des valeurs des paramètres n'est effectué, il n'est donc pas utile de les indiquer. Seule la structure de la configuration et le type des clés sont vérifiés.
Lors du contrôle de la configuration et si le niveau de journalisation est positionné à debug
, la configuration chargée sera affichée sur la sortie standard et dans le journal (attention à la présence de mots de passe dans la configuration lors de l'utilisation du niveau de journalisation debug
).
"""
@conf:database|port|int
@conf:database|base|str
@conf:database|host|str
@conf:database|password|str
@conf:database|user|str
@conf:local|dir|str
@conf:src|port|int
@conf:src|host|str
@conf:src|dir|str
@conf:src|user|str
@conf:dst|port|int
@conf:dst|host|str
@conf:dst|dir|str
@conf:dst|user|str
"""
import scrippy_core
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
# recupération de la configuration
config = _context.config
Si l'une des sections ou l'une des clés décrites par @conf
est absente du fichier de configuration ou que le type décrit pour une clé ne correspond pas au type trouvé dans le fichier de configuration pour cette clé alors une erreur critique est levée et le script sort immédiatement en erreur.
Les sections ou clés surnuméraires trouvées dans le fichier de configuration et non déclarées seront simplement ignorées lors de la vérification mais resteront utilisables par le script.
Ainsi dans l'exemple ci-dessus la section [mail]
n'étant pas définie dans @conf
ni sa présence ni sa validité ne seront contrôlées.
Récupération de la valeur d'un paramètre de configuration
La récupération de la valeur d'un paramètre du fichier de configuration se fait par l'intermédiaire de la méthode _context.config.get()
.
"""
@conf:database|port|int
@conf:database|base|str
@conf:database|host|str
@conf:database|password|str
@conf:database|user|str
"""
import logging
import scrippy_core
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
config = _context.config
logging.info(config.get('database', 'host'))
Dans l'exemple ci-dessus, la valeur de la clé host
de la section database
sera affiché à l'écran.
Si la section ou la clé n'existe pas, une erreur est levée et le script lèvera immédiatement une erreur critique.
Récupération d'une valeur d'un type particulier
À moins que le paramètre 'param_type' soit positionné à l'une des valeurs autorisées (str
(défaut), int
, float
ou bool
), le type renvoyé est systématiquement une chaîne de caractère.
Appeler la méthode Config.get()
avec le mauvais type lèvera une erreur et le script lèvera immédiatement une erreur critique.
"""
@conf:log|level|str
@conf:database|port|int
@conf:database|base|str
@conf:database|host|str
@conf:database|password|str
@conf:database|user|str
"""
import logging
import scrippy_core
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
config = _context.config
logging.info(config.get('database', 'port', 'int'))
Sections et clés réservées:
Certaines sections et clés sont automatiquement lues et interprétées lors de l'import du module scrippy_core
.
Ces clés de configuration sont facultatives, tout comme le fichier de configuration.
- Niveau de journalisation, lu et appliqué automatiquement
[log]
level = <str>
- Activation de la journalisation et d'historisation (True par défaut)
[log]
file = <bool>
Plus de détails dans la section Gestion des journaux d'exécution
Exemples
Tous les exemples de cette documentation se basent sur tout ou partie du fichier de configuration suivant:
[log]
level = info
file = true
[database]
host = srv.flying.circus
port = 5432
user = arthur.pewtey
base = ministry_of_silly_walks
password = dead parrot
[local]
dir = /tmp/transfert
[src]
host = srv.source.circus
port = 22
user = harry.fink
dir = /home/harry.fink/data
[dst]
host = srv.destination.circus
port = 22
user = luigi.vercotti
dir = /home/luigi.vercotti/received
[mail]
host = srv.mail.circus
port = 25
from = Luiggi Vercotti
from_addr = luiggi.vercotti@circus.com
to = Harry Fink
to_addr = harry.fink@circus.com
subject = Compte rendu d'exécution
Gestion des options d'exécution
La gestion des options d'un script se fait au moyen de déclarations dans sa docstring.
La déclaration d'une option est composée de 8 champs dont certains sont obligatoires:
@args:<nom>|<type>|<aide>|<nombre arguments>|<valeur par défaut>|<conflit>|<implique>|<requis>|<secret>
avec:
- nom: Le nom de l'option (obligatoire)
- type: Une des valeurs suivantes:
str
,int
,float
,choice
,bool
(défaut: str). Si le type estchoice
, la liste des choix possibles doit être saisie dans le champs valeur par défaut sous forme de liste de mots séparés par des virgules. - aide: Une chaîne de caractères résumant l'objectif de cette option (obligatoire).
- nombre arguments: Le nombre d'arguments que prend l'option. Ce champs est obligatoire pour tous les types sauf
bool
ou le nombre d'arguments déclarés est ignoré. Sa valeur est généralement un nombre entier mais peut prendre la valeur+
lorsque le nombre d'arguments est supérieur à 1 mais n'est pas connu à l'avance ou?
lorsque le nombre d'argument peut être égal à zéro. - valeur par défaut: La valeur par défaut de l'option (optionnel). Les option de type
bool
prennenttrue
comme valeur par défaut. - conflit: La liste des options qui entrent en conflit avec l'option courante (optionnel, liste d'options séparés par des virgules).
- implique: La liste des options induites par l'option courante (optionnel, liste d'options séparés par des virgules).
- requis: Un booléen (
true
oufalse
) indiquant si l'option est obligatoire ou non. - secret: Un booléen (
true
oufalse
) indiquant si la valeur de l'option doit être gardée secrète.
La valeur d'une option dont l'attribut secret est positionné à true
sera automatiquement masquée dans les fichiers de journalisations comme sur la sortie standard.
La déclaration des options générera automatiquement l'aide du script accessible avec l'option --help
.
Les options suivantes seront également automatiquement générées:
--version
: Affiche le numéro de version du script à partir des informations contenues dans le cartouche.--source-code
: Affiche le code source du script.--hist [NB_EXECUTION (default:10)]
: Affiche l'historique des exécutions du script.--log [SESSION]
: Affiche le contenu du fichier log correspondant à la session dont l'identifiant est passé en argument. Par défaut la dernière session est affichée.--debug
: Force le niveau de journalisation à DEBUG (Les changements de niveau de log au cours de l'exécution sont alors ignorés).--nolog
: Désactive totalement la journalisation à l'exception des niveaux de journalisationerror
etcritical
.--no-log-file
: Empêche la création des fichiers de journalisation et d'historisation.
Les options --help
, --version
, --hist
, --log
, --debug
et --no-log-file
ne doivent donc pas être déclarées.
Exemples
Le script suivant pourra être appelé avec les options et arguments suivants:
--env
: Obligatoire. Accepte l'une des valeurs suivantes: qualif, preprod ou prod--appname
: Optionnelle. Une chaîne de caractères libre ayant pour valeur par défaut "aviva"--now
: Option booléenne. Si utilisée sa valeur sera true (false par défaut) et l'option--date
ne pourra pas être utilisée.--date
: Une suite de 3 entiers à partir de laquelle une date sera créée (ie. 24 02 2019). Cette option entre en conflit avec l'option--now
.--email
: Une chaîne de caractère libre. Cette option est obligatoire si l'option--now
est utilisée.
#!/bin/env python3
"""
--------------------------------------------------------------------------------
@author : Florent Chevalier
@date : 27-07-2019
@version : 1.0.0
@description : test des options
--------------------------------------------------------------------------------
Mise a jour :
1.0.0 27/07/2019 - Florent Chevalier - Cre : Mise en production
--------------------------------------------------------------------------------
Liste des utilisateurs ou groupe autorisés:
@user:asr
@group:asr
--------------------------------------------------------------------------------
Liste des paramètres des options et arguments d'exécution:
nom|type|help|num_args|defaut|conflit|implique|requis
@args:appname|str|Nom de l'application|1|aviva|||false
@args:date|int|Date de plannification (jour, mois, annee)|3||now|email|false
@args:email|str|Email de notification|1||||false
@args:now|bool|Appliquer immediatement||false|date||false
@args:env|choice|Environnement||qualif,preprod,prod|||true
--------------------------------------------------------------------------------
"""
import logging
import datetime
import scrippy_core
def print_contact(email):
logging.info(" - Contact: {}".format(email))
def print_appname(appname):
logging.info(" - Application: {}".format(appname))
def print_env(env):
logging.info(" - Environnement: {}".format(env))
def print_date(date):
logging.info(" - Date: {}".format(date))
def main(args):
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
if args.date:
date = "{}/{}/{}".format(args.date[0], args.date[1], args.date[2])
if args.now:
date = datetime.datetime.now().strftime('%d/%m/%Y')
logging.info("[+] Rapport:")
print_date(date)
print_appname(args.appname)
print_env(args.env)
print_contact(args.email)
if __name__ == '__main__':
main(scrippy_core.args)
Gestion des journaux d'exécution
La journalisation d'exécution s'effectue à partir du module logging
de la bibliothèque standard.
Deux types de journaux sont simultanément disponibles:
- La sortie standard: Affichage en couleurs vers
sys.stdout
- Le fichier journal: Un fichier situé dans
scrippy_core.SCRIPPY_LOGDIR
dont le nom est extrapolé du nom du script de la manière suivante:<nom_du_script>_<timestamp>_<pid>.log
.
Plusieurs niveaux de journalisation sont disponibles (Voir la documentation) et le niveau de log par défaut est INFO
.
Si un fichier de configuration existe pour le script et qu'il contient une section [log]
indiquant un niveau de log avec la clef level
alors le niveau de journalisation indiqué est automatiquement appliqué.
Si le fichier de configuration contient une section [log]
ayant une clef file
dont la valeur est false
alors aucun fichier de journalisation ne sera créé et seule la sortie standard recevra le journal.
Définition du niveau de journalisation par fichier configuration:
[log]
level = warning
La valeur du niveau de journalisation dans le fichier de configuration est insensible à la casse.
Les niveaux de log disponibles sont, du moins verbeux au plus verbeux, les niveaux de journalisation du module standard logging
critical
error
warning
info
debug
À noter que le niveau de journalisation DEBUG
affiche l'intégralité du fichier de configuration ainsi que d'autres détails qui pourraient s'avérer être une source de fuite d'information. Il est déconseillé de l'utiliser en production.
Tous les scripts écrits à partir du module scrippy_core
dans le règles de l'art disposent des options de journalisation suivantes:
--no-log-file
: Lorsque cette option est utilisée, aucun journal d'exécution n'est enregistré sur le disque. Cette option n’empêche pas l'affichage à l'écran.--debug
: Lorsque cette option est utilisée, le niveau de journalisation est forcé àDEBUG
. Dans ce cas le script ne tient pas compte d'un éventuel paramètres de configuration indiquant le contraire.
Ces deux options peuvent être cumulées.
Changer le niveau de log:
Le niveau de log peut être modifié en cours d'exécution du script à l'aide de la méthode logging.getLogger().setLevel(<LEVEL>)
Exemple
En plus d'accepter naturellement un paramètre de configuration définissant le niveau de journalisation, le script suivant dispose d'une option --loglevel
permettant de surcharger le niveau de journalisation à l'exécution.
Si l'option --debug
est utilisée, l'option --loglevel
n'aura aucun effet.
"""
@args:loglevel|str|Le niveau de log|1||||false
"""
import logging
import scrippy_core
def change_loglevel(level):
"""
Passe le niveau de journalisation à <level>
"""
try:
logging.getLogger().setLevel(level.upper())
except ValueError:
logging.error("Niveau de log inconnu: {}".format(level.upper()))
def main(args):
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
# recupération de la configuration du script
config = _context.config
if args.loglevel:
# L'option --loglevel a reçu un argument
change_loglevel(args.loglevel)
logging.debug("Nobody expects the Spanish Inquisition!")
logging.info("And now for something completely different...")
logging.error("It’s not pinin’! It’s passed on! This parrot is no more!")
if __name__ == '__main__':
main(scrippy_core.args)
Gestion des erreurs
Le wrapper with scrippy_core.ScriptContext(__file__) as _context:
intercepte les exceptions pour n'afficher que le type et le message sans la stack trace.
En cas d'exception interceptée, le socle déclenche un sys.exit(1)
.
Pour afficher la stack trace il faut que le log level soit à DEBUG
Gestion de l'historisation des exécutions
Le fichier d'historisation des exécutions situé dans scrippy_core.SCRIPPY_HISTDIR
sera créé et nommé <nom_du_script>.db
à la première exécution du script.
Ce fichier est une base de données sqlite3 qui recense l'ensemble des exécutions d'un script et pour chacune des exécution les informations suivantes:
- identifiant de session
- Date de début de l'exécution
- Date de fin de l'exécution
- Durée de l'exécution
- Utilisateur à l'origine de l'exécution
- Utilisateur ayant effectivement exécuté le script (cas de sudo)
- Code de retour de l'exécution (0 si Ok, autre valeur si KO)
- Ensemble des paramètres passés en arguments au script
- Nom de l'erreur si l'exécution ne s'est pas terminée correctement (0 dans le cas contraire)
Si le fichier d'historisation est préexistant à l'exécution il sera mis à jour avec les paramètres de la nouvelle exécution.
L'historisation est activée automatiquement par l'encadrement de l'appel de la fonction main
avec la déclaration with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
main()
À chaque exécution d'un script est attribuée une session permettant d'identifier chaque exécution de manière unique.
Cette session est composée:
- d'un timestamp représentant l'heure d'exécution
- de l'identifiant du processus (PID)
1568975414.6954327_10580
Cet identifiant de session est reporté dans la colonne Session
de l'historique et permet de retrouver le log correspondant (Voir l'option --log
dans Gestion des options d'exécution).
Rétention
Le nombre d'exécutions conservées dans le fichier d'historisation est de 50 par défaut.
Il est possible de surcharger cette valeur en précisant le nombre de rétention souhaité à l'aide de la déclaration with scrippy_core.ScriptContext(__file__, retention=100) as _context:
with scrippy_core.ScriptContext(__file__, workspace=True, retention=100) as _context:
main()
Affichage de l'historique d'exécution
Tous les scripts basé sur le module scrippy_core
disposent automatiquement d'une option --hist
permettant l'affichage des dernières exécutions.
exp_test_script.py --hist
Le nombre d'exécutions à afficher peut être précisé par le passage d'un paramètre (int) à l'option --hist
exp_test_script.py --hist 2
Pour chacune des exécutions d'un script l'historique d'exécution enregistre les informations suivantes:
- La date d'exécution
- L'utilisateur d'origine
- L'utilisateur effectif (sudo)
- L'identifiant unique de session
- Le code de sortie (0 par défaut)
- La liste des options et arguments passés au script
Espace de travail temporaire
La déclaration with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
crée automatiquement un espace de travail temporaire dont le chemin est récupérable à l'aide de l'attribut workspace_path
du contexte d'exécution _context
.
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
workspace_path = _context.workspace_path
...
Dans l'exemple précédent la variable workspace_path
contiendra le chemin vers le répertoire temporaire de travail dont le nom sera construit de la manière suivante: scrippy_core.SCRIPPY_TMPDIR/<NOM DU SCRIPT>_<SESSION ID>
Ex:
/var/tmp/scrippy/exp_transfert_ftp_1574391960.6696503_102257
Cet espace de travail temporaire, qui sera automatiquement détruit avec son contenu à la fin du script, est un répertoire qui pourra être utilisé pour y créer des fichiers.
#!/bin/env python3
import logging
import scrippy_core
def create_file(workspace_path):
tmp_file = "fichier.tmp"
logging.info("[+] Création du fichier temporaire: {}".format(os.path.join(workspace_path, tmp_file)))
with open(os.path.join(workspace_path, tmp_file), 'a') as tmpfile:
logging.info("[+] Écriture dans le fichier temporaire")
tmpfile.write("Nobody expects the Spanish inquisition !")
def main(args):
with scrippy_core.ScriptContext(__file__, workspace=True) as _context:
config = _context.config
create_file(_context.workspace_path)
if __name__ == '__main__':
main(scrippy_core.args)
Conseils et lignes directrices
- Un log n'est jamais trop verbeux
- Utiliser plusieurs niveaux de log afin de séparer ce qui est utile à l'exploitation de ce qui est utile au déverminage
- Privilégier la lisibilité et la maintenabilité plutôt que la compacité et la technicité
- Décomposer le code en petites fonctions
- Chaque fonction doit logger son point d'entrée et lorsque cela est possible les paramètres qu'elle reçoit
- Variabiliser au maximum et déporter le maximum de variables dans le fichier de configuration ou les options d’exécution.
- Simplifier l'algorithme du programme principal à sa plus simple expression
- Gérer les erreurs le plus finement possible et au plus près possible
- Limiter autant que possible les variables globales
- Minimiser la fuite d'information dans les fichiers de journalisation en utilisant les attributs
secret
des options et arguments dont les valeurs sont des informations sensibles (login, mot de passe, serveur de connexion, etc) - Créer impérativement une fonction
main()
qui contiendra l'algorithme principal du script - Créer l'environnement (objets et variables utilisées au niveau global) dans la section
Point d'entrée
.
Modules complémentaires
Le cadriciel Scrippy dont scrippy-core
est le noyau dispose de modules facilitant l'écriture de scripts Python évolués dans le respect des principes de base de Scrippy.
Module | Utilité |
---|---|
scrippy-template |
Gestion de fichier modèles (basé sur Jinja2) |
scrippy-remote |
Implémentation des protocoles SSH/SFTP et FTP |
scrippy-mail |
Implémentation des protocoles SMTP, POP et Spamassassin |
scrippy-git |
Gestion de dépôts Git |
scrippy-db |
Gestion de base de données (PostgreSQL et Oracle) |
scrippy-api |
Utilisation d'API ReST (basé sur resquets) |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file scrippy-core-1.3.9.tar.gz
.
File metadata
- Download URL: scrippy-core-1.3.9.tar.gz
- Upload date:
- Size: 46.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c6ce2f6141b871f1dda397db5eda2f18f9ce77744409a0b464dc8574b367e36f |
|
MD5 | c5299a811e01a07d5e7e0187ecfc06aa |
|
BLAKE2b-256 | 143769fc51e5663183f95f5f3bee348da8c92700030236a2f78dd215fc870f1c |
File details
Details for the file scrippy_core-1.3.9-py3-none-any.whl
.
File metadata
- Download URL: scrippy_core-1.3.9-py3-none-any.whl
- Upload date:
- Size: 30.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 23ab3a5e15fd8b36a0e32c832be8e2bcf35b33462ad3be8d313a26bb81f74d29 |
|
MD5 | 38a70f7425728132f27c57a16d0c426e |
|
BLAKE2b-256 | 22cbb8529c4ca2a8e7fa6c51fcecc8fe4bc06d701f1e36ec5b27c8bee5999f1a |