Puppet : outillage et plugins



  • Avoir un serveur Puppet scalable, à jour et qui tourne comme une horloge c’est parfait.

    Ça facilite la vie de beaucoup de monde dès que l’on connaît un peu son fonctionnement. Cependant, il y a des moments où des utilisateurs ne connaissent pas mais doivent intervenir sur les machines (oui … la vie est dure).

    Et bien qu’au début cela peut être amusant d’observer quelqu’un modifier son fichier 10 fois avant de se demander pourquoi ses modifications ne sont pas conservées, à la longue ça l’est beaucoup moins. Du coup, une problématique se pose : Comment une personne ne parlant pas Puppet couramment peut savoir ce qui est géré par puppet ?

    La réponse simple : il modifie son fichier et lance un run … si ça change c’est que c’est géré par puppet. Étonnamment ça ne séduit pas les masses.

    La réponse dont on va parler : mettre à disposition des utilisateurs une commande permettant de lister les ressources gérées par Puppet et accessoirement ajouter 2-3 infos utiles.

    Pour ne pas ajouter un script en plus ce qui serait bien serait de pouvoir ajouter des actions à la commande ‘puppet’.

    Quelque chose du type : puppet list file

    Cela lève donc une autre question (oui … encore … ) : Comment ajouter des commandes à puppet ?

    En fouillant un peu sur le site de puppetlabs, une page semble intéressante : http://puppetlabs.com/faces/comment-page-1

    Les faces … ok… Je n’ai pas forcément trouvé beaucoup de docs (bon ok je n’ai pas cherché très longtemps non plus…), j’ai préféré regarder un peu comment sont fait les sous commandes existantes.

    Le point positif: cela peut prendre la forme d’un module et de ce fait, ne nécessite pas de modifier du code de puppet, ou encore d’ajouter des fichiers qui seraient supprimés à la prochaine mise à jour.

    Création d’une application…

    Allez, créons notre sous commande list.

    On va donc commencer par déclarer une sous commande. Pour cela il faut créer un fichier mon_module/lib/puppet/application/list.rb.

    require 'puppet/application/face_base'
    
    class Puppet::Application::List < Puppet::Application::FaceBase end
    

    Bon … rien de bien complexe … il suffit de mettre le nom de l’application où il faut … le reste ne bouge pas. Voyons ce que ça donne :

    root@debtest2:~# puppet help
    Usage: puppet <subcommand> [options] <action> [options]
    
    Available subcommands:
    
      agent             The puppet agent daemon
      apply             Apply Puppet manifests locally
      [...]
      kick              Remotely control puppet agent
      ! list            ! Subcommand unavailable due to error. Check error logs.
      man               Display Puppet manual pages.
      [...]
    

    Donc il y a du progrès on voit bien notre commande. Mais qui est en erreur : normal nous n’avons pas défini de face.

    Faisons le maintenant.

    … et des commandes qui vont avec

    Pour cela rien de sorcier : on va créer un fichier dans mon_module/lib/puppet/face/ qui se nomme comme la commande list.rb.

    require 'puppet/face'
    
    Puppet::Face.define(:list, '0.0.1') do
    
      summary "View resources managed by puppet."
      description <<-'EOT'
    This subcommand provides a command line interface to list resources
    (File, Services, Package) managed by puppet and its associated manifest.
    It also list changed resources during the last run.
    EOT
    
    end
    

    À ce stade nous avons … fait de la doc. On lie notre face à l’application via la méthode define, on donne un résumé de ce que fait la commande et une description plus longue. Ces informations sont accessibles via puppet help ou via le man généré de la commande puppet man list.

    Par contre, si on lance la commande ou si on souhaite avoir des informations sur les actions possibles, Puppet nous insulte car nous n’avons rien défini… C’est de bonne guerre.

    Définissons alors une action avec quelques options (alors oui … je vais remettre tout le code car je n’aime pas les posts avec des extraits de code que l’on ne sait pas où mettre … ) :

    require 'puppet/face'
    require 'puppet/util/terminal'
    
    Puppet::Face.define(:list, '0.0.1') do
      extend Puppet::Util::Colors
    
      summary "View resources managed by puppet."
      description <<-'EOT'
    This subcommand provides a command line interface to list resources
    (File, Services, Package) managed by puppet and its associated manifest.
    It also list changed resources during the last run.
    EOT
      action :all do
        summary 'List all type of resources'
        arguments "[<file>]"
        description <<-EOT
    Do not limit output for a specific type of resource
    EOT
    
        option "--changed" do
          summary "Only show changed resources"
          default_to { false }
        end
        option "--[no-]prefix" do
          summary "Do not show resource type at the begining of the line"
          default_to { true }
        end
        output = []
    
        when_invoked do |*args|
          options = args.pop
          options[:path] = args[0]
        end
    
        when_rendering :console do |output|
          render_output(output)
        end
      end
    
    end
    

    Encore une fois, c’est relativement explicite, on fait un bloc action avec le nom que l’on souhaite lui donner (ici all), un résumé, une description complète, les arguments si nécessaire et les options si besoin. Dans cette action on va déclarer un block when_invoked qui va faire le traitement voulu et un bloc when_rendering pour l’affichage (ici pour la console). Et voilà… nous avons une nouvelle sous commande, certes elle ne renvoie rien mais le bon côté c’est qu’elle ne renvoie pas d’erreur non plus…

    Détails utiles

    Concernant les arguments et les options, pour définir un argument optionnel on le met entre crochets comme c’est le cas ici, sinon il sera obligatoire.

    Pour les options c’est un peu pareil : entre crochets pour les arguments optionnels, rien pour les arguments obligatoires. On notera aussi la syntaxe --[no-]prefix qui permet de définir l’option --prefix et --no-prefix (et qui sera un booléen).

    Avec ce squelette, il ne reste plus qu’à faire le traitement et nous avons notre commande pour nos utilisateurs profanes.

    Dans le traitement (que je ne détaillerai pas ici), nous allons parser le catalogue, la liste des ressources et également le rapport du dernier run.

    Concernant l’affichage, il semble nécessaire de retourner une chaîne de caractères pour obtenir un affichage correct.

    La méthode render_output est faite main pour retourner les informations sous forme de chaîne depuis un hash. Ce n’est pas une méthode de puppet.

    Résultat:

    Voici notre résultat :

    [root] >> puppet list all
    File:/etc/apt/apt.conf.d/02periodic              common::updates.pp
    File:/etc/apt/apt.conf.d/50unattended-upgrades   common::updates.pp
    [...]
    Service:mcollective                              mcollective::service.pp
    Service:postfix                                  services::postfix.pp
    Service:puppet                                   services::puppetclient.pp
    Service:rsyslog                                  services::rsyslog.pp
    [...]
    Package:debian-archive-keyring                   common::packages.pp
    Package:debsecan                                 common::security_debian.pp
    Package:dhcp_client                              common::packages.pp
    

    en filtrant :

    [root] >> puppet list all mcollective
    File:/etc/default/mcollective                    mcollective::agent_debian.pp
    File:/etc/mcollective/facts.yaml                 mcollective::agent_debian.pp
    File:/etc/mcollective/server.cfg                 mcollective::agent_debian.pp
    [...]
    Service:mcollective                              mcollective::service.pp
    Package:mcollective                              mcollective::agent_debian.pp
    

    Lister les changements du dernier run :

    [root] >> puppet list all --changed
    Service:mcollective (changed)                    mcollective::service.pp
    

    L’aide :

    [root] >> puppet help list
    USAGE: puppet list <action>
    
    This subcommand provides a command line interface to list resources
    (File, Services, Package) managed by puppet and its associated manifest.
    It also list changed resources during the last run.
    
    OPTIONS:
      --render-as FORMAT             - The rendering format to use.
      --verbose                      - Whether to log verbosely.
      --debug                        - Whether to log debug information.
    
    ACTIONS:
      all        List all type of resources
      file       List all resources of type File
      package    List all resources of type package
      service    List all resources of type Service
    
    See 'puppet man list' or 'man puppet-list' for full help.
    
    [root] >> puppet help list all
    USAGE: puppet list all [--changed] [--[no-]prefix] [<file>]
    
    Do not limit output for a specific type of resource
    
    OPTIONS:
      --render-as FORMAT             - The rendering format to use.
      --verbose                      - Whether to log verbosely.
      --debug                        - Whether to log debug information.
      --changed                      - Only show changed resources
      --[no-]prefix                  - Do not show resource type at the begining of
                                       the line
    
    See 'puppet man list' or 'man puppet-list' for full help.
    

    Cela ressemble beaucoup au script puppet-ls mais ce dernier utilise un catalogue au format yaml, et chez nous c’est du json.

    Vers l’infini et au-delà…

    Cette commande permet également de faire un alias pour lister les fichiers gérer par puppet dans le répertoire courant (nommé pls chez nous) :

    (!)[root] [~] >> pls
    File:/root/.ssh                                         common::ssh.pp
    File:/root/.ssh/authorized_keys                        common::ssh.pp
    (!)[root] [~] >> alias
    [...]
    alias pls='puppet list file $(pwd) | grep "File:$(pwd)"'
    

    Et au passage un prompt indiquant si le dossier courant ou un fichier de ce dossier est géré par puppet en affichant un (!).

    Et voilà, nos utilisateurs sont contents, du coup ils nous laissent tranquille, ce qui nous laisse plus de temps pour tenter de conquérir le monde.

    Ah au passage une implémentation de ce dont il est question ici est disponible sur github. N’étant pas développeur pour deux sous, il est sûrement possible de l’améliorer mais pour autant ça fonctionne (et en prime il y a le prompt dans les files).

    Voilà voilà … amusez-vous bien.