NOM

RackMan::Manual - Fonctionnement et extension de RackMan

DESCRIPTION

Ce document décrit le fonctionnement de RackMan et les mécanismes pour étendre le logiciel par l'ajout de nouveaux formats et rôles.

RackMan est un ensemble de modules destinés à utiliser les données provenant d'une base RackTackes, afin de générer les fichiers de configuraton correspond pour différents logiciels. Sa principale interface est la commande rack(1).

CONFIGURATION

La configuration de RackMan même est gérée au travers du module RackMan::Config, qui permet la surcharge de chaque paramètre du fichier général rack.conf par la valeur définie dans un éventuel fichier rack.local.conf propre à chaque équipement. De plus certaines valeurs (par exemple %name%) sont interprétées à la volée pour être remplacées par le nom de l'équipement en cours.

Exemples

Pour récupérer le paramètre [general]/timezone défini ainsi dans le fichier de configuration :

    [general]
    timezone = Europe/Paris

il suffit du code suivant

    my $config = RackMan::Config->new(-file => "rack.conf");
    my $timezone = $config->val("general", "timezone");

Dans le cas d'un paramètre qui doit typiquement être surchargé pour chaque équipement, il est attendu que le chemin vers les configurations personnalisées doit défini avec le marque %name%, ainsi, dans rack.conf :

    [general]
    path = configs/%name%

et dans configs/squeak.infra/rack.local.conf :

    [device:server:hp_proliant]
    ilo_password   = squeeee
    admin_password = en4bl3d
    license_key    = 482173267218579

Il faut associer l'objet RackMan::Device correspondant à l'équipement en cours de traitement à l'objet RackMan::Config afin qu'il puisse résoudre le nom :

    my $rackdev = RackMan->device("squeak.infra");
    $config->set_current_rackobject($rackdev);
    my $ilo_pwd = $config->val("device:server:hp_proliant", "ilo_password");

ARCHITECTURE

Le but de RackMan étant de générer des fichiers de configuration, il est centré autour de la notion d'équipement au sens générique du terme, représenté par la classe RackMan::Device. Celle-ci récupère à la demande les informations à propos de l'équipement depuis la base RackTables, au travers de l'ORM DBIx::Class, instancié sous la forme des modules RackTables::Schema.

Lors de l'instanciation de l'object, on compose le rôle correspondant au type de l'équipement (PDU, Switch, Server) afin d'une part de définir par la méthode formats() les formats de configuration associés à cet équipement (Bacula, Cacti, DHCP, Kickstart, LDAP, Nagios), et d'autre part de spécialiser l'objet par la composition d'un second rôle sachant dialoguer avec le matériel pour le configurer.

API POUR UN FORMAT

Un format est une classe qui doit simplement fournir une méthode de classe write() qui est en charge de réaliser l'opération de configuration, quelle qu'elle soit. Cette méthode recevra en argument un hashref avec les clés suivantes :

  • rackman - l'object RackMan

  • rackdev - l'object RackMan::Device correspondant à l'équipement en cours de traitement

  • verbose - un booléen indiquant s'il faut être verbeux ou pas

Exemple : squelette d'un format

    package RackMan::Format::Whatever;

    use strict;
    use RackMan::File;

    use constant {
        CONFIG_SECTION  => "format:whatever",
        DEFAULT_PATH    => "/etc/whatever/hosts",
    };

    #
    # write()
    # -----
    sub write {
        my ($class, $args) = @_;

        # récupération des arguments
        my $rackdev = $args->{rackdev};
        my $rackman = $args->{rackman};

        # objet RackMan::File pour gérer le fichier à créer
        my $file    = RackMan::File->new;

        # récupération du nom de l'équipement
        my $name    = $rackdev->object_name;

        # génération du contenu du fichier proprement dit
        $file->add_content(...);

        # positionnement du nom et du chemin du fichier
        $file->name("$name.conf");
        $file->path($rackman->config->val(CONFIG_SECTION, "path", DEFAULT_PATH));

        # écriture du fichier sur disque, en utilisant un SCM pour
        # le versionnement
        my $scm = $rackman->get_scm({ path => $file->path });
        $scm->update;
        $file->write;
        $scm->add($file->name);
        $scm->commit($file->name, "generated by $class");
    }

API POUR UN RÔLE D'ÉQUIPEMENT

Un rôle d'équipement est un rôle (Moose::Role) qui est composé sur une instance de RackMan::Device depuis le rôle de type d'équipement (voir la méthode specialise()). Il doit fournir les méthodes write_config(), diff_config() et push_config() qui sont les implémentations respectives des actions write, diff et push. Ces méthodes d'objet recevront en argument un hashref avec les clés suivantes :

  • rackman - l'object RackMan

  • verbose - un booléen indiquant s'il faut être verbeux ou pas

Il peut fournir une méthode tmpl_params() qui renvoie un hash avec des paramètres additionnels pour RackMan::Template.

Exemple : squelette d'un rôle d'équipement

    package RackMan::Device::HardwareType::HardwareModel;

    use Moose::Role;
    use RackMan::File;
    use RackMan::Utils;
    use namespace::autoclean;

    use constant {
        CONFIG_SECTION  => "device:hw_type:hw_model",
        CONFIG_FILENAME => "",
    };

    #
    # write_config()
    # ------------
    sub write_config {
        my ($self, $args) = @_;

        # récupération des arguments
        my $rackman = $args->{rackman};

        # génération du fichier de configuration depuis la base de données
        my $config  = $self->fetch_from_database($args);

        # positionnement du nom et du chemin du fichier
        $config->name(CONFIG_FILENAME);
        $config->path($rackman->config->val(general => "path"));

        # écriture du fichier sur disque, en utilisant un SCM pour
        # le versionnement
        my $scm = $rackman->get_scm({ path => $config->path });
        $scm->update;
        $config->write;
        $scm->add($config->name);
        $scm->commit($config->name, "generated by ".__PACKAGE__);
    }

    #
    # diff_config()
    # -----------
    sub diff_config {
        my ($self, $args) = @_;

        # récupération des arguments
        my $rackman = $args->{rackman};

        # récupération de la configuration courante de l'équipement
        my $current  = $self->fetch_from_device($args);

        # génération du fichier de configuration depuis la base de données
        my $expected = $self->fetch_from_database($args);

        # calcul des différences entre les deux fichiers
        my @cfg_current   = split $/, $current->content;
        my @cfg_expected  = split $/, $expected->content;
        my @diff = diff_lines(\@cfg_current, \@cfg_expected);

        # affichage du résultat
        print @diff;

        # positionnement du statut du processus
        RackMan->set_status(1) if @diff;
    }

    #
    # push_config()
    # -----------
    sub push_config {
        my ($self, $args) = @_;

        # récupération des arguments
        my $rackman = $args->{rackman};
        my $path    = $rackman->config->val(general => "path");
        my $config  = RackMan::File->new;

        # lecture du fichier de configuration tel qu'il a été
        # écrit sur disque par write_config()
        $config->name(CONFIG_FILENAME);
        $config->path($path);
        $config->read;

        # transfert de la configuration à l'équipement
        $self->store_to_device($args, $config);
    }


    #
    # fetch_from_database()
    # -------------------
    sub fetch_from_database {
        my ($self, $args) = @_;

        my $rackman = $args->{rackman};
        my $config  = RackMan::File->new;

        # génération du fichier de configuration à partir des
        # informations de l'équipement dans RackTables.
        # le fichier peut être construit morceau par morceau à
        # coups d'appels à $config->add_content(...)

        return $config
    }


    #
    # fetch_from_device()
    # -----------------
    sub fetch_from_device {
        my ($self, $args) = @_;

        my $rackman = $args->{rackman};
        my $config  = RackMan::File->new;

        # discussion avec le périphérique pour récupérer sa
        # configuration, et la stocker dans l'objet $config

        return $config
    }


    #
    # store_to_device()
    # ---------------
    sub store_to_device {
        my ($self, $args) = @_;

        my $rackman = $args->{rackman};
        my $config  = RackMan::File->new;

        # discussion avec le périphérique pour récupérer sa
        # configuration, et la stocker dans l'objet $config

        return $config
    }

MODULES UTILITAIRES

RackMan est fourni avec des modules offrant des abstractions de haut niveau pour gérer certaines tâches.

RackMan::File

Ce module offre une abstraction de fichier, afin de gérer facilement le contenu et l'emplacement d'un fichier.

    use RackMan::File;

    my $file = RackMan::File->new(name => "lipsum.txt");
    $file->add_content("Lorem ipsum dolor sit amet");
    $file->write;

RackMan::SCM

Ce module offre une abstraction de SCM, pour effectuer les opérations basiques quel que soit l'outil sous-jacent. Il est conseillé d'utiliser la méthode get_scm() d'un objet RackMan pour avoir le SCM attendu par l'utilisateur.

    my $scm = $rackman->get_scm();
    $scm->add($path);
    $scm->commit($path, "added $path");

Combiné avec un object RackMan::File :

    my $file = RackMan::File->new({ name => ..., path => ... });
    $file->add_content(...);

    my $scm = $rackman->get_scm({ path => $file->path });
    $scm->update;
    $file->write;
    $scm->add($file->name);
    $scm->commit($file->name, "added ".$file->name);

RackMan::Template

Ce module est un petit système de templating s'appuyant sur HTML::Template et HTML::Template::Filter::TT2. Rien de bien sophistiqué, mais c'est néanmoins utile.

    use RackMan::Template;

    my $tmpl = RackMan::Template->new(filename => "dhcp.tmpl");
    $tmpl->param(
        host_name => "zeruel", host_ipaddr => "192.168.0.43",
        host_macaddr => "78:2B:CB:B3:E7:F4", gateway => "192.168.0.254",
    );
    print $tmpl->output;

avec un fichier dhcp.tmpl comme suit

    host [% host_name %] {
        option routers [% gateway %];
        hardware ethernet [% host_macaddr %];
        fixed-address [% host_ipaddr %];
    }

RackMan::Utils

Ce module contient des fonctions utilitaires, pour le moment seulement la fonction diff_lines(), qui sert à déterminer et à fournir un diff au format unifié entre deux sources de données.

    use File::Slurp;
    use RackMan::Utils;

    my @old_conf = read_file(...);
    my @new_conf = read_file(...);

    my @diff = diff_lines(\@old_conf, \@new_conf);

AUTEUR

Sébastien Aperghis-Tramoni (sebastien@aperghis.net)