Le templating dans les scripts shell

Les modèles

En programmation, on est souvent amené à produire un résultat ayant une forme prédéfinie, mais du contenu variable. C'est particulièrement vrai pour les sites web et blogs, dont les pages et les articles doivent respecter une mise en page uniforme et des éléments en commun, comme le menu de navigation, la charte graphique, les pieds de pages... C'est aussi vrai pour générer des fichiers de configuration, par exemple grâce à Ansible, dont on est sûr qu'ils respectent tous la même syntaxe, ce qui permet de mettre de côté le facteur humain, connu pour sa nature erratique.

La forme prédéfinie utilisée par ces techniques est appelée un template (ou modèle en français). On utilise alors un moteur de templating à qui on fournit du contenu d'un côté, un template de l'autre, et en sortie on trouvera le résultat mis en forme, notre fameuse page HTML ou fichier de configuration par exemple. On dit alors qu'on fait un rendu d'un template.

Les pythonistes utilisent principalement le moteur Jinja2 (c'est le cas pour Ansible et le moteur par défaut de Django), les PHPistes se tournent plutôt vers twig, qui a commencé en s'inspirant de Jinja2. Tous les deux ont beaucoup de fonctionnalités et disposent d'un méta-langage rappelant les langages de programmation : boucles, conditions, fonctions...

Utilisation basique

Des fonctionnalités qui sont inutiles dans certains cas d'usages. Parfois, on a uniquement envie de placer le contenu de variables à des endroits donnés, et basta. On a pas besoin de boucler dans des structures complexes ou d'utiliser des conditions. Pour ces usages basiques, vous avez probablement déjà l'outil installé sur votre distribution, il s'agit de l'outil GNU envsubst, présent dans les outils gettext (paquet gettext-base sous Debian).

J'ai utilisé envsubst lorsque j'ai développé pour mon entreprise un ensemble de scripts permettant de gérer des accès au VPN Wireguard permettant d'accéder à distance aux ressources nécessaires pour travailler. Ces scripts permettent de mettre à disposition des utilisateurs des fichiers de configuration qu'ils doivent fournir à leur client.

Je ne m'étendrai pas sur le fonctionnement de ces scripts (ni sur les considérations de sécurité nécessaires), ce sera peut-être l'objet d'un autre article. Je parlerai uniquement du templating qui y est opéré pour permettre à l'utilisateur de récupérer sa configuration, lorque la paire de clefs a été générée et l'IP interne attribuée.

À ce stade j'ai besoin de quelques variables :

  • $client_privkey contenant la clef privée de l'utilisateur.
  • $client_ip contenant son adresse IP.
  • $server_pubkey contenant la clef publique du serveur.
  • $server_endpoint contenant la clef publique du serveur.
  • $allowed_ips contenant les intervalles d'IP prises en charge par le tunnel.
  • $dns contenant l'IP du serveur DNS interne.

Mon template envsubst est le suivant (fichier userconf.tpl) :

[Interface]
PrivateKey = ${client_privkey}
Address = ${client_ip}
DNS = ${dns}

[Peer]
PublicKey = ${server_pubkey}
Endpoint = ${server_endpoint}
AllowedIPs = ${allowed_ips}
PersistentKeepalive = 25

Remarquons que seules les variables $client_privkey et $client_ip varient selon l'utilisateur, et que j'aurais très bien pu directement inscrire la valeur des autres variables directement dans le fichier. Néanmoins ce code n'aurait pas été très propre ni portable.

Dans mon script, pour effectuer le rendu de ce template, j'ai les instructions suivantes :

export client_privkey client_ip server_pubkey server_endpoint allowed_ips dns
cat template/userconf.tpl | envsubst > generated_conf/${username}.conf

Ainsi, si mon nom est raspbeguy, le fichier raspbeguy.conf contiendra mon fichier de configuration personnel, il ne me reste plus qu'à le télécharger (et à le supprimer du serveur pour des raisons de sécurité).

Si ça ne suffit pas

envsubst est vraiment très pratique car il est très simple et permet de ne pas dépendre de tout un tas de bibliothèques lourdes.

Cependant vous pouvez être amené a avoir réellement besoin d'un moteur de templating complet comme Jinja2, ne serait-ce que pour itérer des des listes d'objets par exemple. Il n'y a pas vraiment d'outil intermédiaire qui vaille le coup à ma connaissance.

Il existe un outil, présent dans les dépôts Debian, permettant de faire des rendus de templates Jinja2 directement en ligne de commande.

apt install j2cli

Dépôt Github de j2cli

Ce paquet fournit un exécutable j2 qui accepte d'être fourni en variable suivant plusieurs méthodes, notament en piochant directement dans les variables d'environnement (comme envsubst) ou bien via des syntaxes répendues comme les format JSON, INI ou YAML. Notament, si vous voulez passez des listes ou des dictionnaires à cet outil, vous serez obligé de passer par ces syntaxes.

Cet outil vous offre toutes les fonctionnalités du moteur Jinja2.

Conclusion

Je pense qu'il est toujours préférable de se tourner en priorité vers des outils simples. Un bon projet est un projet qui ne demande pas tout l'univers en dépendances.