Enviando correos con Perl

Regularmente los administradores de sistemas requieren notificar, vía correo electrónico, a sus usuarios de ciertos cambios o nuevos servicios disponibles. La experiencia me ha indicado que el usuario aprecia más un correo personalizado que uno general. Sin embargo, lograr lo primero de manera manual es bastante tedioso e ineficaz. Por lo tanto, es lógico pensar en la posibilidad de automatizar el proceso de envío de correos electrónicos personalizados, en este artículo, explicaré una de las tantas maneras de lograrlo haciendo uso del lenguaje de programación Perl.

En CPAN podrá encontrar muchas alternativas, recuerde el principio TIMTOWTDI. Sin embargo, la opción que más me atrajo fue MIME::Lite:TT, básicamente este módulo en Perl es un wrapper de MIME::Lite que le permite el uso de plantillas, vía Template::Toolkit, para el cuerpo del mensaje del correo electrónico. También puede encontrar MIME::Lite::TT::HTML que le permitirá enviar correos tanto en texto sin formato (MIME::Lite::TT) como en formato HTML. Sin embargo, estoy en contra de enviar correos en formato HTML, lo dejo a su criterio.

Una de las ventajas de utilizar Template::Toolkit para el cuerpo del mensaje es separar en capas nuestra script, si se observa desde una versión muy simplificada del patrón MVC, el control de la lógica de programación reside en el script en Perl, la plantilla basada en Template Toolkit ofrecería la vista de los datos, de modo tal que podríamos garantizar que la presentación está separada de los datos, los cuales pueden encontrarse desde una base de datos o un simple fichero CSV. Otra ventaja evidente es el posible reuso de componentes posteriormente.

Un primer ejemplo del uso de MIME::Lite:TT puede ser el siguiente:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

my %params;
$params{first_name} = "Milton";
$params{last_name}  = "Mazzarri";
$params{username}   = "milmazz";
$params{groups}     = "sysadmin";

my $msg = MIME::Lite::TT->new(
    From        => '[email protected]',
    To          => '[email protected]',
    Charset     => 'utf8',
    TimeZone    => 'America/Caracas',
    Subject     => 'Example',
    Template    => 'example.txt.tt',
    TmplOptions => \%options,
    TmplParams  => \%params,
);

$msg->send();

Y el cuerpo del correo electrónico, lo que en realidad es una plantilla basada en Template::Toolkit, vendría definido en el fichero example.txt.tt de la siguiente manera:

Hola [% last_name %], [% first_name %].

Tu nombre de usuario es [% username %].

Un saludo, feliz día.

Su querido BOFH de siempre.

En el script en Perl mostrado previamente podemos percatarnos que los datos del destinario se encuentran inmersos en la lógica. Por lo tanto, el siguiente paso sería desacoplar esta parte de la siguiente manera:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Class::CSV;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => '[email protected]',
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    $msg->send();
}

Ahora los datos de los destinarios los extraemos de un fichero en formato CSV, en este ejemplo, el fichero en formato CSV lo hemos denominado example.csv.

Cabe aclarar que $msg->send() realiza el envío por medio de Net::SMTP y podrá usar las opciones que se describen en dicho módulo. Sin embargo, si necesita establecer una conexión SSL con el servidor SMTP es oportuno recurrir a Net::SMTP::SSL:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Net::SMTP::SSL;
use Class::CSV;

my $from = '[email protected]';
my $host = 'mail.example.com';
my $user = 'jdoe';
my $pass = 'example';

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => $from,
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    my $smtp = Net::SMTP::SSL->new( $host, Port => 465 )
      or die "No pude conectarme";
    $smtp->auth( $user, $pass )
      or die "No pude autenticarme:" . $smtp->message();
    $smtp->mail($from)                 or die "Error:" . $smtp->message();
    $smtp->to( $line->email() )        or die "Error:" . $smtp->message();
    $smtp->data()                      or die "Error:" . $smtp->message();
    $smtp->datasend( $msg->as_string ) or die "Error:" . $smtp->message();
    $smtp->dataend()                   or die "Error:" . $smtp->message();
    $smtp->quit()                      or die "Error:" . $smtp->message();
}

Note en este último ejemplo que la representación en cadena de caracteres del cuerpo del correo electrónico viene dado por $msg->as_string.

Para finalizar, es importante mencionar que también podrá adjuntar ficheros de cualquier tipo a sus correos electrónicos, solo debe prestar especial atención en el tipo MIME de los ficheros que adjunta, es decir, si enviará un fichero adjunto PDF debe utilizar el tipo application/pdf, si envía una imagen en el formato GIF, debe usar el tipo image/gif. El método a seguir para adjuntar uno o más ficheros lo dejo para su investigación ;)

4 min read

Construyendo de manera efectiva y rápida imágenes ISO de Debian con jigdo

Si usted desea el conjunto de CD o DVD para instalar Debian, tiene muchas posibilidades, desde la compra de los mismos, muchos de los vendedores contribuyen con Debian. También puede realizar descargas vía HTTP/FTP, vía torrent o rsync. Pero en este artículo se discutirá sobre un método para construir las imágenes ISO de Debian de manera eficiente, sobretodo si cuenta con un repositorio local de paquetes, dicho método se conoce de manera abreviada como jigdo o Jigsaw Download.

Las ventajas que ofrece jigdo están bien claras en el portal de Debian, cito:

¿Por qué jigdo es mejor que una descarga directa?

¡Porque es más rápido! Por varias razones, hay muchas menos réplicas para imágenes de CDs que para el archivo «normal» de Debian. Consecuentemente, si descarga desde una réplica de imágenes de CD, esa réplica no sólo estará más lejos de su ubicación, además estará sobrecargada, especialmente justo después de una publicación.

Además, algunos tipos de imágenes no están disponibles para descarga completa como .iso porque no hay suficiente espacio en nuestros servidores para alojarlas.

Considero que la pregunta pertinente ahora es: ¿Cómo descargo la imagen con jigdo?.

En primer lugar, instalamos el paquete jigdo-file.

# aptitude install jigdo-file

Mi objetivo era generar los 2 primeros CD para Debian Lenny, para la fecha de publicación de este artículo la versión más reciente es la 5.0.7. La lista de imágenes oficiales para jigdo las puede encontrar acá.

[email protected] /tmp $ cat files
http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.jigdo
http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.template
http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-2.jigdo
http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-2.template
[email protected] /tmp $ wget -c -i files
--2010-12-02 12:39:52--  http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.jigdo
Resolving cdimage.debian.org... 130.239.18.163, 130.239.18.173, 2001:6b0:e:2018::173, ...
Connecting to cdimage.debian.org|130.239.18.163|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 31737 (31K) [text/plain]
Saving to: `debian-507-i386-CD-1.jigdo'

100%[===================================================================================================================>] 31.737      44,7K/s   in 0,7s

...

FINISHED --2010-12-02 12:50:15--
Downloaded: 4 files, 27M in 10m 21s (44,7 KB/s)
[email protected] /tmp $ ls
debian-507-i386-CD-1.jigdo  debian-507-i386-CD-1.template  debian-507-i386-CD-2.jigdo  debian-507-i386-CD-2.template files

Una vez descargados los ficheros necesarios, es hora de ejecutar el comando jigdo-lite, siga las instrucciones del asistente.

[email protected] ~ $ jigdo-lite debian-507-i386-CD-2.jigdo

Jigsaw Download "lite"
Copyright (C) 2001-2005  |  [email protected]
Richard Atterer          |  atterer.net
Loading settings from `/home/milmazz/.jigdo-lite'

-----------------------------------------------------------------
Images offered by `debian-507-i386-CD-2.jigdo':
1: 'Debian GNU/Linux 5.0.7 "Lenny" - Official i386 CD Binary-2 20101127-16:55 (20101127)' (debian-507-i386-CD-2.iso)

Further information about `debian-507-i386-CD-2.iso':
Generated on Sat, 27 Nov 2010 17:02:14 +0000

-----------------------------------------------------------------
If you already have a previous version of the CD you are
downloading, jigdo can re-use files on the old CD that are also
present in the new image, and you do not need to download them
again. Mount the old CD ROM and enter the path it is mounted under
(e.g. `/mnt/cdrom').
Alternatively, just press enter if you want to start downloading
the remaining files.
Files to scan:

El comando despliega información acerca de la imagen ISO que generará, en este caso particular, debian-507-i386-CD-2.iso. Además, jigdo-lite puede reutilizar ficheros que se encuentren en CD viejos y así no tener que descargarlos de nuevo. Sin embargo, este no era mi caso así que presione la tecla ENTER.

-----------------------------------------------------------------
The jigdo file refers to files stored on Debian mirrors. Please
choose a Debian mirror as follows: Either enter a complete URL
pointing to a mirror (in the form
`ftp://ftp.debian.org/debian/'), or enter any regular expression
for searching through the list of mirrors: Try a two-letter
country code such as `de', or a country name like `United
States', or a server name like `sunsite'.
Debian mirror [http://debian.example.com/debian/]:

En esta fase jigdo-lite solicita la dirección URL completa de un repositorio, aproveche la oportunidad de utilizar su repositorio local si es que cuenta con uno. Luego de presionar la tecla ENTER es tiempo de relajarse y esperar que jigdo descargue todos y cada uno de los ficheros que componen la imagen ISO.

Luego de descargar los paquetes y realizar las operaciones necesarias para la construcción de la imagen ISO jigdo le informará los resultados.

FINISHED --2010-12-01 14:43:50--
Downloaded: 6 files, 2,5M in 1,8s (1,39 MB/s)
Found 6 of the 6 files required by the template
Successfully created `debian-507-i386-CD-2.iso'

-----------------------------------------------------------------
Finished!
The fact that you got this far is a strong indication that `debian-507-i386-CD-2.iso'
was generated correctly. I will perform an additional, final check,
which you can interrupt safely with Ctrl-C if you do not want to wait.

OK: Checksums match, image is good!

Ahora bien, haciendo uso de un repositorio local, es bueno preguntarse en cuanto tiempo aproximadamente puedes construir tu imagen ISO, en mi caso el tiempo de construcción de debian-507-i386-CD-2.iso fue de:

[email protected] ~ $ time jigdo-lite debian-507-i386-CD-2.jigdo

...

real	8m35.704s
user	0m13.101s
sys	0m16.569s

Nada mal, ¿no les parece?.

Ahora bien, haciendo uso de un repositorio local, es bueno preguntarse en cuanto tiempo aproximadamente puedes construir tu imagen ISO, en mi caso el tiempo de construcción de debian-507-i386-CD-2.iso fue de:

Referencias

4 min read

Generar reporte en formato CSV de tickets en Trac desde Perl

El día de hoy recibí una llamada telefónica de un compañero de labores en donde me solicitaba con cierta preocupación un “pequeño” reporte del estado de un listado de tickets que recién me había enviado vía correo electrónico puesto que no contaba con conexión a la intranet, al analizar un par de tickets me dije que no iba a ser fácil realizar la consulta desde el asistente que brinda el mismo Trac. Así que inmediatamente puse las manos sobre un pequeño script en Perl que hiciera el trabajo sucio por mí.

Es de hacer notar que total de tickets a revisar era el siguiente:

$ wc -l tickets
126 tickets

Tomando en cuenta el resultado previo, era inaceptable hacer dicha labor de manera manual. Por lo tanto, confirmaba que realizar un script era la vía correcta y a la final iba a ser más divertido.

Tomando en cuenta que el formato de entrada era el siguiente:

#3460
#3493
...

El formato de la salida que esperaba era similar a la siguiente:

3460,"No expira la sesión...",closed,user

Básicamente el formato implica el id, sumario, estado y responsable asociado al ticket.

Net::Trac le ofrece una manera sencilla de interactuar con una instancia remota de Trac, desde el manejo de credenciales, consultas, revisión de tickets, entre otros. A la vez, se hace uso del módulo Class::CSV el cual le ofrece análisis y escritura de documentos en formato CSV.

#!/usr/bin/perl

use warnings;
use strict;

use Net::Trac;
use Class::CSV;

# Estableciendo la conexion a la instancia remota de Trac
my $trac = Net::Trac::Connection->new(
    url      => 'http://trac.example.com/project',
    user     => 'user',
    password => 'password'
);

# Construccion del objecto CSV y definicion de opciones
my $csv = Class::CSV->new(
    fields         => [qw/ticket sumario estado responsable/],
    line_separator => "\r\n",
    csv_xs_options => { binary => 1, }    # Manejo de caracteres non-ASCII
);

# Nos aseguramos que el inicio de sesion haya sido exitoso
if ( $trac->ensure_logged_in ) {
    my $ticket = Net::Trac::Ticket->new( connection => $trac );

    # Consultamos cada uno de los tickets indicados en el fichero de entrada
    while ( my $line = <> ) {
        chomp($line);
        if ( $line =~ m/^#\d+$/ ) {
            $line =~ s/^#(\d+)$/$1/;
            $ticket->load($line);

            $csv->add_line(
                {
                    ticket      => $ticket->id,
                    sumario     => $ticket->summary,
                    estado      => $ticket->status,
                    responsable => $ticket->owner
                }
            );
        }
        else {
            print "[INFO] La linea no cumple el formato requerido: $line\n";
        }
    }
    $csv->print();
}
else {
    print "No se pudieron asegurar las credenciales";
}

La manera de ejecutar el script es la siguiente:

$ perl trac_query.pl tickets

En donde trac_query.pl es el nombre del script y tickets es el fichero de entrada.

Debo aclarar que el script carece de comentarios, mea culpa. Además, el manejo de opciones vía linea de comandos es inexistente, si desea mejorarlo puede hacer uso de Getopt::Long.

Cualquier comentario, sugerencia o corrección es bienvenida.

2 min read

Instalando dependencias no-libres de JAVA en ambientes pbuilder

El día de hoy asumí la construcción de unos paquetes internos compatibles con Debian 5.0 (a.k.a. Lenny) que anteriormente eran responsabilidad de ex-compañeros de labores. El paquete en cuestión posee una dependencia no-libre, sun-java6-jre. En este artículo se describirá como lograr adecuar su configuración de pbuilder para la correcta construcción del paquete.

Asumiendo que tiene un configuración similar a la siguiente:

$ cat /etc/pbuilderrc
MIRRORSITE=http://example.com/debian
DEBEMAIL="Maintainer Name <[email protected]>"
DISTRIBUTION=lenny
DEBOOTSTRAP="cdebootstrap"
COMPONENTS="main contrib non-free"

Para mayor información sobre estas opciones sírvase leer:

$ man 5 pbuilderrc

Mientras intenta compilar su paquete en el ambiente proporcionado por pbuilder el proceso fallará ya que no se mostró la ventana para aceptar la licencia de JAVA. Podrá observar en el registro de la construcción del build un mensaje similar al siguiente:

Unpacking sun-java6-jre (from .../sun-java6-jre_6-20-0lenny1_all.deb) ...

sun-dlj-v1-1 license could not be presented
try 'dpkg-reconfigure debconf' to select a frontend other than noninteractive

dpkg: error processing /var/cache/apt/archives/sun-java6-jre_6-20-0lenny1_all.deb (--unpack):
subprocess pre-installation script returned error exit status 2

Para evitar esto altere la configuración del fichero pbuilderrc de la siguiente manera:

$ cat /etc/pbuilderrc
MIRRORSITE=http://example.com/debian
DEBEMAIL="Maintainer Name <[email protected]>"
DISTRIBUTION=lenny
DEBOOTSTRAP="cdebootstrap"
COMPONENTS="main contrib non-free"
export DEBIAN_FRONTEND="readline"

Una vez alterada la configuración podrá interactuar con las opciones que le ofrece debconf.

Ahora bien, si usted constantemente tiene que construir paquetes con dependencias no-libres como las de JAVA, es probable que le interese lo que se menciona a continuación.

Si lee detenidamente la página del manual de pbuilder en su sección 8 podrá encontrar lo siguiente:

$ man 8 pbuilder
...
--save-after-login
--save-after-exec
Save the chroot image after exiting from the chroot instead of deleting changes.  Effective for login and execute session.
...

Por lo tanto, usaremos esta funcionalidad que ofrece pbuilder para insertar valores por omisión en la base de datos de debconf para que no se nos pregunte si deseamos aceptar la licencia de JAVA:

# pbuilder login --save-after-login
I: Building the build Environment
I: extracting base tarball [/var/cache/pbuilder/base.tgz]
I: creating local configuration
I: copying local configuration
I: mounting /proc filesystem
I: mounting /dev/pts filesystem
I: Mounting /var/cache/pbuilder/ccache
I: policy-rc.d already exists
I: Obtaining the cached apt archive contents
I: entering the shell
File extracted to: /var/cache/pbuilder/build//27657

pbuilder:/# cat > java-license << EOF
> sun-java6-bin shared/accepted-sun-dlj-v1-1 boolean true
> sun-java6-jdk shared/accepted-sun-dlj-v1-1 boolean true
> sun-java6-jre shared/accepted-sun-dlj-v1-1 boolean true
> EOF
pbuilder:/# debconf-set-selections < java-license
pbuilder:/# exit
logout
I: Copying back the cached apt archive contents
I: Saving the results, modifications to this session will persist
I: unmounting /var/cache/pbuilder/ccache filesystem
I: unmounting dev/pts filesystem
I: unmounting proc filesystem
I: creating base tarball [/var/cache/pbuilder/base.tgz]
I: cleaning the build env
I: removing directory /var/cache/pbuilder/build//27657 and its subdirectories
2 min read

Presentaciones

Desde hace algunos meses he decidido recopilar y organizar algunas de las presentaciones que he dado hasta ahora en eventos de Software Libre, Universidades y empresas privadas.

El software que regularmente utilizo para realizar mis presentaciones es Beamer, una clase LaTeX que facilita enormente la producción de presentaciones de alta calidad, este software trabaja de la mano con pdflatex, también con dvips.

La lista de presentaciones que he recopilado hasta la fecha son las siguientes:

  • Análisis estático del código fuente en Python: Describe el concepto del análisis estático del código, se indica los pasos a seguir para la detección de errores mediante la herramienta Pylint, se exponen sus funcionalidades, reportes y se muestran ejemplos para corregir los errores encontrados por la herramienta.
  • Desarrollo colectivo en Turpial: Describe la visión del cliente para Twitter Turpial, sus funcionalidades actuales, el uso de herramientas como Transifex, PyBabel, Distutils, Sphinx, dichas herramientas facilitan y mejoran la calidad del software que se desarrolla.
  • Canaima GNU/Linux: Una introducción, se describe la historia, definición del proyecto Canaima, principales características, procesos para colaborar, enlaces de interés, entre otros.
  • Novela gráfica creada con el motor Ren’Py: Relata la experiencia del desarrollo de una novela gráfica para niños de 5to. grado de educación, de acuerdo a currículo impartido en las escuelas venezolanas.
  • Trac: Herramientas libres para el apoyo en el proceso de desarrollo de software, se discute las características y funcionalidades que ofrece el software. Además del proceso de personalización por medio de complementos o plugins.
  • GnuPG, GNU Privacy Guard: Importancia del cifrado de la información, diferencias entre llaves simétricas y asimétricas, criptografía, fiestas de firmado de llaves, beneficios. Instalación y suo práctico de GnuPG.
  • Uso de dbconfig-common: Presentación que es parte de la serie mejores prácticas para el empaquetamiento de aplicaciones en Debian, se describe el uso de la herramienta y su respectiva integración con el asistente debhelper
  • Conociendo el framework web Django: Introducción, historia, características, primeros pasos, instalación y demostración de desarrollo de una aplicación sencilla bajo este excelente framework basado en el lenguaje de Programación Python

Las fuentes en LaTeX de las presentaciones, así como su licencia de uso y proceso de conversión al formato PDF se describe en el proyecto Presentations que he creado en github.

Agradezco enormemente cualquier comentario que pueda hacer respecto a los temas presentados puesto que en el próximo mes trataré de actualizar el contenido, así como incluir nuevas presentaciones. ¿Desearía poder conocer más sobre un tema en particular?, ¿cuál sería ese tema?.

Nota final: Si encuentra algún error por favor notificarlo vía issues del proyecto Presentations.

2 min read