=pod

=encoding utf-8

=head1 NOM

RackMan::Manual - Fonctionnement et extension de RackMan

=head1 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 B<rack(1)>.


=head1 CONFIGURATION

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

=head2 Exemples

Pour récupérer le paramètre C<[general]/timezone> défini ainsi dans
le fichier de S<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 C<%name%>, ainsi, dans 
S<F<rack.conf> :>

    [general]
    path = configs/%name%

et dans S<F<configs/squeak.infra/rack.local.conf> :>

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

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

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


=head1 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 C<RackMan::Device>. Celle-ci récupère à la demande
les informations à propos de l'équipement depuis la base RackTables, au
travers de l'ORM C<DBIx::Class>, instancié sous la forme des modules
C<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 C<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.


=head1 API POUR UN FORMAT

Un format est une classe qui doit simplement fournir une méthode de
classe C<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 S<suivantes :>

=over

=item *

C<rackman> - l'object C<RackMan>

=item *

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

=item *

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

=back


=head2 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");
    }


=head1 API POUR UN RÔLE D'ÉQUIPEMENT

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

=over

=item *

C<rackman> - l'object C<RackMan>

=item *

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

=back

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


=head2 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
    }


=head1 MODULES UTILITAIRES

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

=head2 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;


=head2 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 C<get_scm()> d'un objet C<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 S<C<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);


=head2 RackMan::Template

Ce module est un petit système de I<templating> s'appuyant sur
C<HTML::Template> et C<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 F<dhcp.tmpl> comme suit

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


=head2 RackMan::Utils

Ce module contient des fonctions utilitaires, pour le moment seulement
la fonction C<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);


=head1 AUTEUR

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

=cut

