Gérer ses dotfiles sur plusieurs environnements

Un gestionnaire de dotfiles devient légitime dès lors que l’on jongle entre différents environnements.
Chezmoi est un gestionnaire de dotfiles puissant et simple d’accès.
Son usage est décuplé dès lors que les conteneurs entrent dans l’équation.

Chezmoi, le besoin

En ces temps de confinement au domicile, le temps pour soi redevient possible.
On se voit à reprendre notre TODO list personnelle pour y dépiler les tâches.  
En ce qui me concerne, autour de la 121ème place se trouvait le besoin d’industrialiser la gestion de mes dotfiles.
Ces fichiers tel le .bashrc, le .gitconfig et autres .vimrc qui permettent de piloter la manière dont nous interagissons avec notre environnement et les outils qu’il embarque.

Mais quelle est la pertinence d’industrialiser la gestion de tels fichiers ?Effectivement, ce besoin est artificiel si vous avez l’habitude de travailler sur une unique machine.
Cette situation a été la mienne durant ces 5 dernières années.
Pour cette raison je ne m’étais jamais réellement penché sur le sujet.

Maintenant prenons une situation plus réaliste dans laquelle nous avons une machine professionnelle et une personnelle.Ma machine personnelle est configurée aux petits oignons. Mais arrivé sur le terminal de mon poste de travail, entrer alias_de_la_mort_qui_tue me renvoie command not found. Pénible.

Il nous faut de la cohérence entre les dotfiles de ces deux machines pour être efficace et se sentir “comme à la maison” quel que soit l’environnement.

Je vais amplifier ce besoin avec la remarque suivante :
Nous parlons de deux machines, mais aujourd’hui la réalité se rapproche plus d’une infinité de machines, ou plus précisément, une infinité d’environnements.
Est ce que la technologie des conteneurs vous parle ?

Assez récemment, en tant que développeur cloud native, je n’avais qu’un usage direct limité de cette technologie.
Après tout, charge à des services tel K8S, lambda function ou un dyno Heroku de gérer les conteneurs.
Il m’était beaucoup plus facile de gérer le multi environnement avec des outils type virtualenv (dans le cas de Python) plutôt que de les gérer via Docker que je voyais comme un outils Ops.
C’était sans compter cette merveilleuse extension VSCode qui simplifie grandement l’usage de Docker pour un développeur, concurrençant sérieusement virtualenv, bundler et consort.
Comment donc assurer la cohérence et la gestion de ces environnements?

La duplication par copier-coller fonctionne… d’accord. Mais comment allez-vous gérer la divergence des dotfiles sur ces différentes machines?
Le problème est sans fin et clairement ne passe pas à l’échelle.

Git arrive à la rescousse. Cette solution simple se base sur un dépôt Git en tant que point de vérité unique vis à vis du contenu de ces dotfiles.
Il s’agirait donc de renseigner ces dotfiles dans un dépôt à cloner sur chacun des environnements et de créer des liens symboliques au bon endroit via un script (power)shell.
Le clonage du dépôt peut être envisagé de manière automatique si l’environnement est éphémère.

Il existe cependant deux objections à cette approche :

  • Le contenu des dotfiles diffère selon les machines.
    Par exemple, je ne commit pas en tant que “pikachu_du_75” depuis mon poste de travail.
  • Le contenu de certains fichiers ne doit pas quitter l’environnement associé.
    Je ne vais pas pousser sur un dépôt Github une API key, un Keyfile ou autres clés privées.
    Le stockage des secrets est à déléguer aux outils dédiés.

Le premier point peut trouver un élément de réponse à travers le système de branches Git.
Nous créerions alors une branche par environnement pour ensuite tirer la branche voulue depuis un environnement.
Methode simple… mais non efficace.

Comment dès lors gérer simplement les nombreuses branches potentielles dont le nombre peut égaler celui des environnements cibles ?
Comment automatiquement déterminer si un environnement doit tirer telle ou telle branche ?

Ce couplage fort entre environnement et branche git est un problème. Un système intéressant serait de “faire dire” à l'environnement quelle version du fichier de configuration l’intéresse. Pour ceux connaissant les applications 12 factors, le factor III appelle un environnement à porter la configuration d’un applicatif et non pas l’applicatif lui-même.

Par extension, en remplaçant “applicatif” par “source de vérité”, il faudrait que cette dernière soit la même sur tous les environnements mais que son “expression” diffère en fonction de l'environnement hôte.
Soit par son type, le contenu d’un fichier spécifique, la présence de variables d’environnements, d’un nom de host, etc
De cette manière nous aurions un dépôt avec une branche unique dans laquelle nos dotfiles seraient des templates dont la sortie dépendra de l’environnement dans lequel on le clone.


Le second point doit être délégué à un outil spécialisé tel que pass, lastpass ou encore Vault.
En d’autres termes, le gestionnaire de dotfiles doit “comprendre” ces outils.

Si l’on résume, notre outil de management de .dotfiles aurait dans l’idéal :

  • Un système de templating pour en générer des dotfiles dont le contenu dépend de l’environnement de déploiement.
  • La possibilité de s’intégrer avec des gestionnaires de secrets.

Enfin, j’ajouterais la facilité d’installation avec un minimum de requis pour le système hôte.
Exit les obligations de runtime ou autres JVM. Un exécutable binaire par exemple.

Visite guidée de Chezmoi

Après avoir testé plusieurs solutions mon choix s’est arrêté sur Chezmoi.
Cet outil a le bon goût de répondre élégamment aux trois critères précédemment énoncés et va même au-delà.
En définissant un “état source” (le point de vérité) à travers un répertoire contenant des dotfiles templatisés, Chezmoi va être capable de définir un état cible dans un environnement, cet état dépendant d’un fichier de configuration propre à l’environnement.
Chezmoi offre des facilités pour initialiser ce fichier à partir d’un template.

Rq : Nous pouvons comparer ce fichier de configuration Chezmoi au fichier .env de dotenv.

Voici ce que l’on peut retenir de Chezmoi :

  1. est une CLI qui s’installe simplement avec un curl et un shell
  2. se repose par défaut sur Git pour versionner l’état source. Mercurial est possible.
  3. définit un protocole d’ajout et de modification (proche de celui de Git) de fichiers de dotfiles à l’état source via un ensemble de commandes.
    Les ajouts de répertoires sont possibles.
  4. se base sur un langage de templating Go.
  5. s’intègre avec les principaux gestionnaires de secrets.

Les capacités de l’outil ne s’arrêtent pas là et je vous invite à lire cette section du repository Github.

Note : Dans l’absolu, Chezmoi peut servir de point de vérité pour l’état de n’importe quel fichier/répertoire. Ce n’est néanmoins pas l’esprit de l’outil qui déléguera ce genre de responsabilité à des outils plus riches tels Ansible, Puppet, ...

Les deux schémas suivant résument les principes de Chezmoi :

Sont entourés en vert les deux répertoires qui se répondent par l’intermédiaire de Chezmoi et présents sur chaque environnement.

Note 1 : Env1 et Env2 résident en pratique sur deux machines différentes.

Note 2 : Notez la ressemblance du flow git (push + pull) avec celui de Chezmoi (apply + add).

Enfin, la meilleure solution n’est rien sans communauté ni documentation.
Chezmoi est utilisé par une communauté assez large et dispose d’une documentation claire et précise.
Pour information, l’auteur de la solution a suivi les recommandations de ce billet de blog.

Dans la pratique

Nous avons précédemment parlé de VSCode ainsi que sa remote Containers extension.
En quoi Chezmoi va améliorer cette association?

Prenons donc la situation suivante.
En tant que développeur, nous avons un side project en Go, un projet professionnel en Java et projet associatif Php.
Ces différents projets sont portés par trois conteneurs Docker différents sur lesquels nous développons grâce à l’extension VSCode. Chacun portant ses dépendances propres.
Enfin, n’oublions pas le système hôte qui aura aussi besoin de nos dotfiles.

Nous souhaitons avoir un état cible de nos dotfiles, différents pour chaque environnement.
C’est là que Chezmoi intervient.

En supposant que c’est la première fois que nous utilisons Chezmoi, voici une proposition d’étapes à suivre pour y arriver.
Nous commençons par créer notre état source (le dépôt Git distant) sur notre environnement hôte.

  1. Installation de Chezmoi
  2. Création de la source de vérité
    Il s’agit de créer un repo dotfiles compatible avec Chezmoi. En voici un exemple. Chezmoi offre une commande d’initialisation pour, soit créer un dépôt local à pousser vers le distant, soit cloner une source existante.
  3. Ajout de nouveaux “attributes” au répertoire source
    Chezmoi add [target] ajoutera un dotfile représenté par la target au répertoire source en tant qu’attribute Chezmoi.
    Un attribute n’est rien d’autre qu’un fichier avec une nomenclature propre à Chezmoi et dont le contenu est une copie de la target avec des éléments de templating.
  4. Création d’un fichier de configuration
    Le langage de templating peut faire appel à des variables qu’on listera avec Chezmoi data.
    On enrichira avec ces valeurs avec le fichier chezmoiconfig.toml à créer dans ~/.config/chezmoi.
    Note : Chezmoi add contient l’option -T --autotemplate, pour que l’attribute généré soit templatisé en fonction du contenu du fichier de configuration.
    Si mon dotfile contient : id=”foo@bar.com”Mon fichier de config : [data]email=”foo@bar.com”Alors le template généré par chezmoi add -T --autotemplate dotfile contiendra :id={{ .email }}Le matching étant fait par la valeur et non la clé.
  5. Modification selon les besoins des templates créés (optionnel)
    Les valeurs des variables créées dans le fichier de configuration permettent de conditionner le contenu des dotfiles. Sur un environnement donné, je définis les variables suivantes dans le fichier de configuration :
    [data]category=”professional”
    Alors cette partie d’un dotfile sera prise en compte lors d’un chezmoi apply:
    {{- if ne .category “professional” }}
    Anything you want
    {{ else }}
    Other thing you want
    {{ end }}
    
    apply est la commande permettant de transformer le contenu du répertoire source en contenu pour le répertoire cible.
    Chezmoi apply échoue si ne serait-ce qu’une variable présente dans les templates n’est pas définie.
    Rq : Une astuce consiste à templatiser le fichier .chezmoiignore du répertoire source pour que la commande Chezmoi apply génère ou non les attributes présents dans la cible.
    6. Pousser le contenu du répertoire source sur un dépôt distant
    Github, Gitlab ou autre

A la fin de ces étapes, nous sommes donc avec un dépôt Github qui peut ressembler à celui-ci ou celui-là.

Nous voulons désormais démarrer des environnements dockerisés via VSCode avec des dotfiles adaptés.
L’idée va être de s’appuyer d’une part sur l’état source que nous venons de créer, et d'automatiser l’installation de Chezmoi et la génération des dotfiles dans un conteneur d’autre part.

Le code source de cette approche se trouve ici.
Nous nous appuyons sur son contenu pour illustrer les étapes.

  1. Configuration automatique du répertoire source dans chaque conteneur
    VSCode a le bon goût de “comprendre” les dotfiles et il est possible de configurer un dépôt Git qui sera cloné dans un répertoire cible du conteneur.
    En particulier le chemin du répertoire source par défaut de Chezmoi.
    Cette configuration est globale à VSCode et ne doit être faite qu’une seule fois.

    Astuce 1:
    Nous utilisons un Docker-compose file sur un service unique (en plus du Dockerfile représentant ce service) afin de simplifier la déclaration de volumes, faciliter la surcharge spécifique à notre contexte et s’abstraire au maximum de fichiers (ex: .devcontainer.json) spécifiques à VSCode et de rester le plus standard possible.
    En particulier, nous montons en bind, le répertoire .ssh du système hôte sur notre service unique à /tmp.

    Astuce 2 :  
    Nous surchargeons le docker-compose file de base avec une commande pour copier le contenu /tmp/.ssh avec le mask 600 dans /root/.ssh. Ceci est nécessaire dans le cas d’un système hôte windows qui, par défaut, set un mask 755 sur un bind .ssh mettant en défaut Git qui refusera d’utiliser une clé SSH “trop” ouverte.
    L’étape 1 échouera autrement.
  2. Ajout automatique du fichier de configuration Chezmoi spécifique au conteneur
    Dans le répertoire de configuration locale de l’extension VSCode, .devcontainer, nous créons le fichier de configuration de Chezmoi que nous bindons sur /root/.config/chezmoi/chezmoi.toml i.e son chemin par défaut.
    De cette manière chezmoi apply dans ce conteneur se basera sur son contenu pour interpréter les templates.
  3. Installation automatique de Chezmoi suivi d’un Chezmoi apply
    Il n’est pas question ici de modifier un quelconque Dockerfile ou image auxquels nous n’avons pas nécessairement accès.
    VSCode permet de définir, par conteneur, une commande postCreateCommand qui s'exécute dans le conteneur après sa création.
    Dans notre cas nous pointons vers un script shell en charge de ces deux actions.

Et voilà!
Vous pouvez désormais oublier ces étapes. Chaque redémarrage du conteneur vous ouvrira un environnement avec les dotfiles adaptés.
Il suffit de reproduire ces étapes sur d’autres conteneurs pour atteindre la cible que nous nous étions fixée.

Pour aller plus loin, nous pouvons remarquer que le contenu de la source de vérité peut être “proxifié” à travers un volume Docker.
Il suffit de copier le contenu d’un répertoire source existant dans un volume créé pour l’occasion (par exemple avec un docker cp du repo git local vers ce volume monté sur une busybox).
Dès lors, chaque conteneur sur lequel est attaché ce volume bénéficiera en temps réel des changements et ajouts apportés par tel ou tel environnement bénéficiant de l’installation précédente.
Le tout sans avoir besoin d'une connexion à internet.  

Rq : Le volume est  à monter sur le chemin du répertoire source par défaut : ~/.local/share/chezmoi

Il est à noter qu’il n’est plus nécessaire de cloner un répertoire source au niveau du conteneur. Le volume mount défini dans le docker-compose file remplace cette étape.
Nous créons alors la nouvelle notion de volume source et pourquoi pas de bucket source avec les bons drivers.

Le schéma ci-dessous illustre l’idée qui dans les faits fonctionne très bien :


En conclusion

Chezmoi est un outil simple et puissant.
Les quelques commandes et cas d’usage mis en avant ne font que gratter la surface de ce que permet Chezmoi.
Chezmoi.io illustre de belle manière les différents cas d’usage.
Au vu de l’intégration que nous en faisons avec les conteneurs, il apparaît qu’une évolution intéressante serait de définir les variables de template directement par les variables d’environnement plutôt qu’un fichier de configuration. Variables dont les valeurs seraient portées par le Docker-compose file.
La suggestion a été soumise, approuvée et implémentée dans le dépôt officiel de Chezmoi rappelant qu'il est facile d'y contribuer.

Sur ce, je vous souhaite la bienvenue Chezmoi.