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        => 'jdoe@example.com',
    To          => 'milmazz@example.com',
    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        => 'jdoe@example.com',
        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 = 'jdoe@example.com';
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 ;)

Introducción a pruebas de rendimiento sobre aplicaciones Web con Multi-Mechanize (Parte I)

Corey Goldberg, autor de la herramienta para pruebas de rendimiento Pylot, anunciaba a mediados del mes de febrero de este año que estaba trabajando en un nuevo framework para este mismo tipo de pruebas, al que denominó Multi-Mechanize. Pero parece lógico preguntarse: ¿por qué otra herramienta?, Corey argumentaba que el diseño de Pylot está bastante limitado debido al hecho que los casos de pruebas deben establecerse en forma declarativa, vía XML, llegando a la conclusión que era necesario un lenguaje de scripting para la definición de los casos de pruebas, además, la mejora en la concurrencia de las mismas.

Multi-Mechanize es un framework orientado a pruebas de rendimiento y carga en sitios Web. Este framework permite la ejecución simultánea de scripts en Python por medio de un motor multi-proceso, multi-hilo para reproducir secuencias de comandos y generar usuarios virtuales de manera concurrente, todo esto con el fin de establecer carga en contra de un sitio o servicio Web.

Una de las grandes ventajas de Multi-Mechanize es que ofrece la posibilidad de incluir en los scripts el poderoso módulo mechanize (derivado de WWW::Mechanize en Perl) junto con el lenguaje de programación Python. Permitiendo crear scripts de pruebas que simulan la actividad de usuarios virtuales de manera rápida y sencilla, estos scripts generarán peticiones HTTP para navegar o enviar solicitudes de forma inteligente un sitio o servicio Web.

Los resultados se almacenan en formato CSV o en una base de datos, junto con el informe en formato HTML que contiene las estadísticas y gráficos.

Un ejemplo de la capacidad de reportes que ofrece Multi-Mechanize puede verla en http://code.google.com/p/multi-mechanize/

Requisitos

Multi-Mechanize requiere Python 2.x (2.6 o 2.7), si desea generar gráficos a partir de las pruebas, debe instalar Matplotib y sus dependencias, mayor información vea la página de Preguntas de Uso Frecuente

De manera adicional necesitará el módulo mechanize.

La instalación de los requisitos en Debian GNU/Linux y derivados es tal como se describe a continuación:

# aptitude install python-mechanize python-matplotlib

Almacenamiento de datos de prueba y resultados en Bases de Datos

Opcionalmente, los resultados pueden ser almacenados en una base de datos. Para ello debe agregar la opción results_database en el fichero de configuración config.cfg, el cual define la cadena de conexión a la base de datos.

El almacenamiento en base de datos requiere tener instalado previamente SQLAlchemy

Algunos ejemplos de conexión a bases de datos son los siguientes:

SQLite sqlite:///dbname
MySQL mysql://user:password@localhost/dbname
PostgreSQL postgresql://user:password@host:port/dbname
MS SQL Server mssql://mydsn

Tenga en cuenta que el soporte de SQLite es nativo en Python. Por lo tanto, no es necesario su instalación y configuración.

Para mayor información sobre el soporte a base de datos vea Database Storage

Instalando Multi-Mechanize

Puede descargar el proyecto Multi-Mechanize desde http://code.google.com/p/multi-mechanize/downloads/list

Una vez descargado el proyecto Multi-Mechanize, descomprima y vaya a la raíz del proyecto y ejecute desde la línea de comandos:

$ python multi-mechanize.py default_project

Este comando ejecutará el proyecto de prueba incluido en la distribución de Multi-Mechanize, el proyecto de prueba generará datos aleatorios.

NOTA: Se le recomienda analizar los scripts incluidos en projects/default_project/test_scripts/

Comenzando con Multi-Mechanize

Para iniciar, usted puede usar el directorio default_project que incluye la distribución de Multi-Mechanize. Si usted necesita manejar múltiples proyectos, solo cree un directorio al mismo nivel de default_project para cada uno de los proyectos, se le recomienda mantener la misma estructura del proyecto por omisión. Recuerde que se necesita especificar el proyecto que ejecutará desde la línea de comandos.

Cada proyecto debe contener lo siguiente:

config.cfg
Fichero de configuración, establezca sus opciones de prueba.
test_scripts
Directorio de almacenamiento de scripts de sus usuarios virtuales.
results
Directorio para el almacenamiento de los resultados. Acá encontrará subdirectorios cuyos nombres son una estampa de tiempo y son creados por cada ejecución de las pruebas y contiene datos en formato CSV, un sumario en formato HTML e imágenes en formato PNG.

Formato del fichero de configuración

Cada proyecto contiene un fichero config.cfg donde debe definir la configuración para una prueba.

A continuación se le muestra un fichero de configuración que muestra todas las opciones posibles de configuración:

[global]
run_time: 300
rampup: 300
console_logging: off
results_ts_interval: 30
results_database: sqlite:///projects/default_project/results.db
post_run_script: python projects/default_project/foo.py

[user_group-1]
threads: 30
script: example_mock.py

[user_group-2]
threads: 30
script: example_mock.py

Opciones globales

run_time
Duración de la prueba en segundos
rampup
Duración en segundos de la rampa de usuarios (hilos)
console_logging
Activar/Desactivar el registro a la salida estándar
results_ts_interval
Intervalos de las series de tiempo para el análisis de los resultados expresadas en segundos.
results_database
Cadena de conexión a base de datos (opcional)
post_run_script
script que será invocado después de la culminación de la prueba (opcional)

Grupos de usuarios

threads
Número de hilos/usuarios virtuales
script
Script del usuario virtual a ejecutar.

Para mayor información sobre el formato del fichero de configuración consulte: Config File.

En la segunda parte de este artículo mostraré algunos ejemplos con casos de pruebas que se ejecutarán sobre una instalación de Trac.

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";
}

view raw trac_query.pl This Gist brought to you by GitHub.

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.

Mejoras en el comportamiento a la hora de eliminar un ForeignKey

Logo de Django Cuando un objeto referenciado por una clave foránea (ForeignKey) es eliminado, Django por omisión emula el comportamiento de la sentencia SQL ON DELETE CASCADE y también se elimina el objeto que contiene el ForeignKey.

A partir de la versión 1.3 de Django el comportamiento descrito en el párrafo anterior puede ser sobreescrito al especificar el argumento on_delete. Por ejemplo, si usted permite que una clave foránea pueda ser nula y usted desea que sea establecida a NULL cuando un objeto referenciado sea eliminado:

user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)

Los posibles valores para el argumento on_delete pueden encontrarse en django.db.models:

CASCADE
Eliminación en cascada, el comportamiento por omisión.
PROTECT
Prevee la eliminación del objeto referenciado al lanzar una excepción del tipo: django.db-IntegrityError.
SET_NULL
Establece la clave foránea a NULL, esto solo es posible si el argumento null es True.
SET_DEFAULT
Establece la clave foránea a su valor por omisión, tenga en cuenta que un valor por omisión debe ser establecido.
SET()
Establece el valor del ForeignKey indicado en SET(), si una función es invocada, el resultado de dicha función será el valor establecido.
DO_NOTHING
No tomar acciones. Si el gestor de base de datos requiere integridad referencial, esto causará una excepción del tipo IntegrityError.

A continuación un par de ejemplos de esta nueva funcionalidad:

# models.py
from django.db import models

class Author(models.Model):
    nickname = models.CharField(max_length=32)

    def __unicode__(self):
        return self.nickname

class Post(models.Model):
    author = models.ForeignKey(Author, blank=True, null=True, on_delete=models.SET_NULL)
    title = models.CharField(max_length=128)

    def __unicode__(self):
        return self.title

Nuestra sesión interactiva con el API sería similar a la siguiente:

$ python manage.py shell
>>> from ondelete.models import Author, Post

>>> Author.objects.all()
[]
# Creamos el autor
>>> author = Author(nickname='milmazz')
# Guardamos el objeto en la base de datos al usar de manera explícita el método save()
>>> author.save()

# Obtenemos el autor en base a su id
>>> Author.objects.get(pk=1)
<Author: milmazz>

# Creamos par de artículos
>>> article1 = Post(author=author, title="Article 1")
>>> article1.save()

>>> article2 = Post(author=author, title="Article 2")
>>> article2.save()

>>> for article in Post.objects.all():
     print("%s by %s" % (article.title, article.author))
Article 1 by milmazz
Article 2 by milmazz

# Eliminamos el autor
>>> author.delete()

>>> for article in Post.objects.all():
    print("%s by %s" % (article.title, article.author))
Article 1 by None
Article 2 by None

Un segundo ejemplo, ahora haciendo uso del valor SET() en el argumento on_delete:

# models.py
from django.db import models
from django.contrib.auth.models import User

def get_superuser():
    return User.objects.get(pk=1)

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET(get_superuser))
    title = models.CharField(max_length=128)

    def __unicode__(self):
        return self.title

Nuestra sesión interactiva con el API sería similar a la siguiente:

$ python manage.py shell
>>> from ondelete.models import Post
>>> from django.contrib.auth.models import User

>>> User.objects.all()
[<User: milmazz>]
# Creamos un nuevo usuario
>>> author = User(username='milton')
# Guardamos el objeto en la base de datos,
# de manera explícita al invocar el método save()
>>> author.save()
# Vista de los usuarios registrados en la base de datos
>>> User.objects.all()
[<User: milmazz>, <User: milton>]

# Creamos par de artículos
>>> article1 = Post(user=author, title="Article 1")
>>> article1.save()

>>> article2 = Post(user=author, title="Article 2")
>>> article2.save()

>>> for article in Post.objects.all():
     print("%s by %s" % (article.title, article.user))
Article 1 by milton
Article 2 by milton

# Eliminamos el usuario 'milton'
>>> author.delete()

>>> for article in Post.objects.all():
    print("%s by %s" % (article.title, article.user))
Article 1 by milmazz
Article 2 by milmazz

Subversion: Notificaciones vía correo electrónico

Al darse un proceso de desarrollo colectivo es recomendable mantener una o varias listas de notificación acerca de los cambios hechos (commits) en el repositorio de código fuente. Para este tipo de actividades es muy útil emplear SVN::Notify.

SVN::Notify le ofrece un número considerable de opciones, a continuación resumo algunas de ellas.

  • Obtiene información relevante acerca de los cambios ocurridos en el repositorio Subversion.
  • Realiza análisis sobre la información recolectada y brinda la posibilidad de reconocer distintos formatos vía filtros (Ej. Textile, Markdown, Trac).
  • Puede obtener la salida tanto en texto sin formato como en XHTML.
  • Le brinda la posibilidad de construir correos electrónicos en base a la salida obtenida.
  • Permite el envío de correo, ya sea por el comando sendmail o SMTP.
  • Es posible indicar el método de autenticación ante el servidor SMTP.

Para instalar el SVN::Notify en sistemas Debian o derivados proceda de la siguiente manera:

# aptitude install libsvn-notify-perl

Una vez instalado SVN::Notify, es hora de definir su comportamiento. Aunque es posible hacerlo vía comando svnnotify y empotrarlo en un script escrito en Bash he preferido hacerlo en Perl, es más natural y legible hacerlo de este modo.

#!/usr/bin/perl -w

use strict;
use SVN::Notify;

my $path = $ARGV[0];
my $rev  = $ARGV[1];

my %params = (
    repos_path      =&gt; $path,
    revision        =&gt; $rev,
    handler         =&gt; 'HTML::ColorDiff',
    trac_url        =&gt; 'http://trac.example.com/project',
    filters         =&gt; ['Trac'],
    with_diff       =&gt; 1,
    diff_switches   =&gt; '--no-diff-added --no-diff-deleted',
    subject_cx      =&gt; 1,
    strip_cx_regex  =&gt; [ '^trunk/', '^branches/', '^tags/' ],
    footer          =&gt; 'Administrado por: BOFH ',
    max_sub_length  =&gt; 72,
    max_diff_length =&gt; 1024,
    from            =&gt; 'noreply@example.com',
    subject_prefix  =&gt; '[project]',
    smtp            =&gt; 'smtp.example.com',
    smtp_user       =&gt; 'example',
    smtp_pass       =&gt; 'example',
);

my $developers = 'dev@example.com';
my $admins     = 'admins@example.com';

$params{to_regex_map} = {
    $developers =&gt; '^trunk|^branches',
    $admins     =&gt; '^tags|^trunk'
};

my $notifier = SVN::Notify-&gt;new(%params);
$notifier-&gt;prepare;
$notifier-&gt;execute;

Si seguimos con el ejemplo indicado en el artículo anterior, Instalación básica de Trac y Subversion, este hook lo vamos a colocar en /srv/svn/project/hooks/post-commit, dicho fichero deberá tener permisos de ejecución para el Servidor Web Apache.

Con este sencillo script en Perl se ha logrado lo siguiente:

  • La salida se generará en XHTML.
  • Las diferencias de código serán resaltadas o coloreadas, esto es posible por el handler SVN::Notify::ColorDiff
  • El sistema de notificación está integrado a la sintaxis de enlaces de Trac. Por lo tanto, los commits que posean este tipo de enlaces serán interpretados correctamente. Ej. #123 changeset:234 r234 [/source]

Muestra del coloreado que ofrece SVN::Notify

Aunque el código es sucinto y claro, trataré de resumir cada uno de los parámetros utilizados.

repos_path
Define la ruta al repositorio Subversion, la cual es obtenida a partir del primer argumento que pasa Subversion al ejecutar el hook post-commit.
revision
El número de la revisión del commit actual. El número de la revisión se obtiene a partir del segundo argumento que pasa Subversion al ejecutar el hook post-commit
handler
Especifica una subclase de SVN::Notify que será utilizada para el manejo de las notificaciones. En el ejemplo se hace uso de HTML::ColorDiff, el cual permite colorear o resaltar la sintaxis del comando svnlook diff
trac_url
Este parámetro será usado para generar enlaces al Trac para los números de revisiones y similares en el mensaje de notificación.
filters
Especifica la carga de más módulos que terminan de difinir la salida de la notificación. En el ejemplo, se hace uso del filtro Trac, filtro que convierte el log del commit que cumple con el formato de Trac a HTML.
with_diff
Valor lógico que especifica si será o no incluida la salida del comando svnlook diff en la notificación vía correo electrónico.
diff_switches
Permite el pase de opciones al comando svnlook diff, en particular recomiendo utilizar --no-diff-deleted y --no-diff-added para evitar ver las diferencias para los archivos borrados y añadidos respectivamente.
subject_cx
Valor lógico que indica si incluir o nó el contexto del commit en la línea de asunto del correo electrónico de notificación.
strip_cx_regex
Acá se indican las expresiones regulares que serán utilizadas para eliminar información del contexto de la línea de asunto del correo electrónico de notificación.
footer
Agrega la cadena definida al final del cuerpo del correo electrónico de notificación
max_sub_length
Indica la longitud máxima de la línea de asunto del correo electrónico de notificación.
max_diff_length
Máxima longitud del diff (esté adjunto o en el cuerpo de la notificación).
from
Define la dirección de correo que será usada en la línea From. Si no se especifica será utilizado el nombre de usuario definido en el commit, esta información se obtiene vía el comando svnlook.
subject_prefix
Define una cadena de texto que será el prefijo de la línea correspondiente al asunto del correo electrónico de notificación.
smtp
Indica la dirección para el servidor SMTP que el cual se enviarán las notificaciones de correo electrónico. Si no se utiliza este parámetro, SVN::Notify utilizará el comando sendmail para el envío del mensaje.
smtp_user
El nombre de usuario para la autenticación SMTP.
smtp_pass
Contraseña para la autenticación SMTP
to_regex_map
Este parámetro contiene un hash que mantiene referencias de direcciones de correo electrónico contra expresiones regulares. La idea es enviar las notificaciones si y solo si el nombre de uno o más directorios son afectados por un commit y dicha ruta coincide con las expresiones regulares definidas. Este parámetro resulta muy útil en proyectos de desarrollo de software grandes y donde es posible disponer de varias listas de correo para informar a los desarrolladores interesados en secciones específicas.

Para mayor detalle de las opciones mencionadas previamente vea SVN::Notify, acá también encontrará más opciones de configuración.

Observación

Hasta ahora he encontrado que el coloreado o resaltado de la sintaxis no funciona en algunos sistemas Webmail, como por ejemplo Gmail, SquirrelMail. Sin embargo, en otros sistemas Webmail como RoundCube si funciona. Este comportamiento se presenta porque en sistemas como Gmail las hojas de estilos en cascada (CSS) internas no son aplicadas en la interfaz. Es por ello que en estos casos es necesario recurrir a la definición de estilos en línea.

Sistema de manejo y seguimiento de proyectos: Trac

Trac logoTrac es un sistema multiplataforma desarrollado y mantenido por Edgewall Software, el cual está orientado al seguimiento y manejo de proyectos de desarrollo de software haciendo uso de un enfoque minimalista basado en la Web, su misión es ayudar a los desarrolladores a escribir software de excelente calidad mientras busca no interferir con el proceso y políticas de desarrollo. Incluye un sistema wiki que es adecuado para el manejo de la base de conocimiento del proyecto, fácil integración con sistemas de control de versiones1. Además incluye una interfaz para el seguimiento de tareas, mejoras y reporte de errores por medio de un completo y totalmente personalizable sistema de tickets, todo esto con el fin de ofrecer una interfaz integrada y consistente para acceder a toda información referente al proyecto de desarrollo de software. Además, todas estas capacidades son extensibles por medio de plugins o complementos desarrollados específicamente para Trac.

Breve historia de Trac

El origen de Trac no es una idea original, algunos de sus objetivos se basan en los diversos sistemas de manejo y seguimiento de errores que existen en la actualidad, particularmente del sistema CVSTrac y sus autores.

Trac comenzó como la reimplementación del sistema CVSTrac en el lenguaje de programación Python y como ejercicio de entretenimiento, además de utilizar la base de datos embebida SQLite2. En un corto lapso de tiempo, el alcance de estos esfuerzos iniciales crecieron en gran medida, se establecieron metas y en el presente Trac presenta un curso de desarrollo propio.

Los desarrolladores de Edgewall Software esperan que Trac sea una plataforma viable para explorar y expandir el cómo y qué puede hacerse con sistemas de manejo de proyectos de desarrollo de software basados en sistemas wiki.

Características de Trac

A continuación se presenta una descripción breve de las distintas características de Trac.

Herramienta de código abierto para el manejo de proyectos

Trac es una herramienta ligera para el manejo de proyectos basada en la Web, desarrollada en el lenguaje de programación Python. Está enfocada en el manejo de proyectos de desarrollo de software, aunque es lo suficientemente flexible para usarla en muchos tipos de proyectos. Al ser una herramienta de código abierto, si Trac no llena completamente sus necesidades, puede aplicar los cambios necesarios usted mismo, escribir complementos o plugins, o contratar a alguien calificado que lo haga por usted.

Sistema de tickets
Vista de tickets en Trac

Tickets activos para el hito 0.12 de Trac, ordenados por última fecha de modificación

El sistema de tickets le permite hacer seguimiento del progreso en la resolución de problemas de programación particulares, requerimientos de nuevas características, problemas e ideas, cada una de ellas en su propio ticket, los cuales son enumerados de manera ascendente. Puede resolver o reconciliar aquellos tickets que buscan un mismo objetivo o donde más de una persona reporta el mismo requerimiento. Permite hacer búsquedas o filtrar tickets por severidad, componente del proyecto, versión, responsable de atender el ticket, entre otros.

Para mejorar el seguimiento de los tickets Trac ofrece la posibilidad de activar notificaciones vía correo electrónico, de este modo se mantiene informado a los desarrolladores de los avances en la resolución de las actividades planificadas.

Vista de progreso

Existen varias maneras convenientes de estar al día con los acontecimientos y cambios que ocurren dentro de un proyecto. Puede establecer hitos y ver un mapa del progreso (así como los logros históricos) de manera resumida. Además, puede visualizar desde una interfaz centralizada los cambios ocurridos cronológicamente en el wiki, hitos, tickets y repositorios de código fuente, comenzando con los eventos más recientes, toda esta información es accesible vía Web o de manera alternativa Trac le ofrece la posibilidad de exportar esta información a otros formatos como el RSS, permitiendo que los usuarios puedan observar esos cambios fuera de la interfaz centralizada de Trac, así como la notificación por correo electrónico.

vista de progreso del proyecto

Vista del avance del proyecto para un hito particular

Vista del repositorio en línea

Una de las características de mayor uso en Trac es el navegador o visor del repositorio en línea, se le ofrece una interfaz bastante amigable para el sistema de control de versiones que esté usando3. Este visualizador en línea le ofrece una manera clara y elegante de observar el código fuente resaltado, así como también la comparación de ficheros, apreciando fácilmente las diferencias entre ellos.

Visor de código fuente en Trac

Cambios entre revisiones en trunk/setup.py

Manejo de usuarios

Trac ofrece un sistema de permisología para controlar cuales usuarios pueden acceder o no a determinadas secciones del sistema de manejo y seguimiento del proyecto, esto se logra a través de la interfaz administrativa. Además, esta interfaz es posible integrarla con la definición de permisos de lectura y escritura de los usuarios en el sistema de control de versiones, de ese modo se logra una
administración centralizada de usuarios.

Wiki

El sistema wiki es ideal para mantener la base de conocimientos del proyecto, la cual puede ser usada por los desarrolladores o como medio para ofrecerles recursos a los usuarios. Tal como funcionan otros sistemas wiki, puede permitirse la edición compartida. La sintaxis del sistema wiki es bastante sencilla, si esto no es suficiente, es posible integrar en Trac un editor WYSIWYG (lo que se ve es lo que se obtiene, por sus siglas en inglés) que facilita la edición de los documentos.

Características adicionales

Al ser Trac un sistema modular puede ampliarse su funcionalidad por medio de complementos o plugins, desde sistemas anti-spam hasta diagramas de Gantt o sistemas de seguimiento de tiempo.

  1. Por defecto Trac se integra con subversion[regresar]
  2. Hoy día también se le da soporte a PostgreSQL, mayor detalle en Database Backend[regresar]
  3. Por defecto Trac se integra con subversion, la integración con otros sistemas es posible gracias a plugins o complementos.[regresar]

Pylint: Análisis estático del código en Python

Básicamente el análisis estático del código se refiere al proceso de evaluación del código fuente sin ejecutarlo, es en base a este análisis que se obtendrá información que nos permita mejorar la línea base de nuestro proyecto, sin alterar la semántica original de la aplicación.

Pylint es una herramienta que todo programador en Python debe considerar en su proceso de integración continua (Ej. Bitten), básicamente su misión es analizar código en Python en busca de errores o síntomas de mala calidad en el código fuente. Cabe destacar que por omisión, la guía de estilo a la que se trata de apegar Pylint es la descrita en el PEP-8.

Es importante resaltar que Pylint no sustituye las labores de revisión continua de alto nivel en el Proyecto, con esto quiero decir, su estructura, arquitectura, comunicación con elementos externos como bibliotecas, diseño, entre otros.

Tome en cuenta que Pylint puede arrojar falsos positivos, esto puede entenderse al recibir una alerta por parte de Pylint de alguna cuestión que usted realizó conscientemente. Ciertamente algunas de las advertencias encontradas por Pylint pueden ser peligrosas en algunos contextos, pero puede que en otros no lo sea, o simplemente Pylint haga revisiones de cosas que a usted realmente no le importan. Puede ajustar la configuración de Pylint para no ser informado acerca de ciertos tipos de advertencias o errores.

Entre la serie de revisiones que hace Pylint al código fuente se encuentran las siguientes:

  • Revisiones básicas:
    • Presencia de cadenas de documentación (docstring).
    • Nombres de módulos, clases, funciones, métodos, argumentos, variables.
    • Número de argumentos, variables locales, retornos y sentencias en funciones y métodos.
    • Atributos requeridos para módulos.
    • Valores por omisión no recomendados como argumentos.
    • Redefinición de funciones, métodos, clases.
    • Uso de declaraciones globales.
  • Revisión de variables:
    • Determina si una variable o import no está siendo usado.
    • Variables indefinidas.
    • Redefinición de variables proveniente de módulos builtins o de ámbito externo.
    • Uso de una variable antes de asignación de valor.
  • Revisión de clases:
    • Métodos sin self como primer argumento.
    • Acceso único a miembros existentes vía self
    • Atributos no definidos en el método __init__
    • Código inalcanzable.
  • Revisión de diseño:
    • Número de métodos, atributos, variables locales, entre otros.
    • Tamaño, complejidad de funciones, métodos, entre otros.
  • Revisión de imports:
    • Dependencias externas.
    • imports relativos o importe de todos los métodos, variables vía * (wildcard).
    • Uso de imports cíclicos.
    • Uso de módulos obsoletos.
  • Conflictos entre viejo/nuevo estilo:
    • Uso de property, __slots__, super.
    • Uso de super.
  • Revisiones de formato:
    • Construcciones no autorizadas.
    • Sangrado estricto del código.
    • Longitud de la línea.
    • Uso de <> en vez de !=.
  • Otras revisiones:
    • Notas de alerta en el código como FIXME, XXX.
    • Código fuente con caracteres non-ASCII sin tener una declaración de encoding. PEP-263
    • Búsqueda por similitudes o duplicación en el código fuente.
    • Revisión de excepciones.
    • Revisiones de tipo haciendo uso de inferencia de tipos.

Mientras Pylint hace el análisis estático del código muestra una serie de mensajes así como también algunas estadísticas acerca del número de advertencias y errores encontrados en los diferentes ficheros. Estos mensajes son clasificados bajo ciertas categorías, entre las cuales se encuentran:

Refactorización
Asociado a una violación en alguna buena práctica.
Convención
Asociada a una violación al estándar de codificación.
Advertencia
Asociadas a problemas de estilo o errores de programación menores.
Error
Asociados a errores de programación importantes, es probable que se trate de un bug.
Fatal
Asociados a errores que no permiten a Pylint avanzar en su análisis.

Un ejemplo de este reporte lo puede ver a continuación:

Messages by category
--------------------

+-----------+-------+---------+-----------+
|type       |number |previous |difference |
+===========+=======+=========+===========+
|convention |969    |1721     |-752.00    |
+-----------+-------+---------+-----------+
|refactor   |267    |182      |+85.00     |
+-----------+-------+---------+-----------+
|warning    |763    |826      |-63.00     |
+-----------+-------+---------+-----------+
|error      |78     |291      |-213.00    |
+-----------+-------+---------+-----------+

Cabe resaltar que el formato de salida del ejemplo utilizado está en modo texto, usted puede elegir entre: coloreado, texto, msvs (Visual Estudio) y HTML.

Pylint le ofrece una sección dedicada a los reportes, esta se encuentra inmediatamente después de la sección de mensajes de análisis, cada uno de estos reportes se enfocan en un aspecto particular del proyecto, como el número de mensajes por categorías (mostrado arriba), dependencias internas y externas de los módulos, número de módulos procesados, el porcentaje de errores y advertencias encontradas por módulo, el total de errores y advertencias encontradas, el porcentaje de clases, funciones y módulos con docstrings y su respectiva comparación con un análisis previo (si existe). El porcentaje de clases, funciones y módulos con nombres correctos (de acuerdo al estándar de codificación), entre otros.

Al final del reporte arrojado por Pylint podrá observar una puntuación general por su código, basado en el número y la severidad de los errores y advertencias encontradas a lo largo del código fuente. Estos resultados suelen motivar a los desarrolladores a mejorar cada día más la calidad de su código fuente.

Si usted ejecuta Pylint varias veces sobre el mismo código, podrá ver el puntaje de la corrida previa junto al resultado de la corrida actual, de esta manera puede saber si ha mejorado la calidad de su código o no.

Global evaluation
-----------------
Your code has been rated at 7.74/10 (previous run: 4.64/10)
If you commit now, people should not be making nasty comments about you on c.l.py

Una de las grandes ventajas de Pylint es que es totalmente configurable, además, se pueden escribir complementos o plugins para agregar una funcionalidad que nos pueda ser útil.

Si usted quiere probar desde ya Pylint le recomiendo seguir la guía introductoria descrita en Pylint tutorial. Si desea una mayor ayuda respecto a los códigos indicados por Pylint, puede intentar el comando pylint --help-msg=CODIGO, si eso no es suficiente, le recomiendo visitar el wiki PyLint Messages para obtener una rápida referencia de los mensajes arrojados por Pylint, organizados de varias maneras para hacer más fácil y agradable la búsqueda. Además de encontrar explicaciones de cada mensaje, especialmente útiles para aquellos programadores que recién comienzan en Python.

Referencias

Consideraciones para el envío de cambios en Subversion

Hoy día pareciese que los sistemas de control de versiones centralizados están pasando de moda ante la aparición de sistemas de control de versiones descentralizados poderosos como Git, del cual espero poder escribir en próximos artículos. Sin embargo, puede que la adopción de estos sistemas descentralizados tarden en controlar el mundo de los SCM, al menos por ahora las métricas de ohloh.net indican que Subversion sigue siendo bastante empleado, mayor detalle en el artículo Subversion – As Strong As Ever.

En este artículo se expondrán algunas políticas que suelen definirse para la sana convivencia entre colaboradores de un proyecto de desarrollo de software, estas reglas no solo aplican a Subversion en particular, son de uso común en otros sistemas de control de versiones centralizados.

Analice bien los cambios hechos antes de enviarlos

Cualquier cambio en la línea base de su proyecto puede traer consecuencias, tome en cuenta que los demás desarrolladores obtendrán sus cambios una vez que estén en el repositorio centralizado. Si usted no se preocupó por validar que todo funcionara correctamente antes de enviar sus cambios, es probable que algún compañero le recrimine por su irresponsabilidad, esto afecta de cierta forma el anillo de confianza entre los colaboradores del proyecto.

Nunca envíe cambios que hagan fallar la construcción del proyecto

Siempre verifique que su proyecto compile, si el proyecto presenta errores debido a los cambios hechos por usted atiendalos inmediatamente, evite los errores de sintaxis, tenga siempre presente respetar las políticas de estilo de código definidas para su proyecto.

Si ha ingresado nuevos ficheros o directorios al proyecto recuerde ejecutar el comando svn add para programar la adición en Subversion, si omite este paso es posible que su copia de trabajo funcione o compile, pero la del resto de sus compañeros no, evite de nuevo ser recriminado por los demás colaboradores del proyecto.

Si su proyecto pretende ser multiplataforma, trate de imaginar las consecuencias de sus cambios bajo otro sistema operativo o arquitectura. Por ejemplo, he visto más de una vez este tipo de error:

path = "dir/subdir/fichero.txt" # Malo
path = os.path.join(os.path.dirname(__file__),
                    'dir', 'subdir', 'fichero.txt') # Correcto

Pruebe los cambios antes de hacer el envío

Antes de realizar un envío al repositorio centralizado actualice su copia de trabajo (svn up), verifique que las pruebas unitarias, regresión de su proyecto arrojan resultados positivos, de igual manera haga pruebas funcionales del sistema.

Al momento de hacer la actualización de su copia de trabajo tome nota de los ficheros editados por los demás colaboradores del proyecto, verifique que no existan conflictos, nuevos ficheros que no haya considerado, entre otros. Una vez que haya solventado la actualización de su copia de trabajo, construya (su proyecto tiene un sistema de autoconstrucción, ¿verdad?) el paquete, inicie su aplicación que contiene los cambios locales y asegúrese que el comportamiento obtenido sea igual al esperado.

Promueva un histórico de cambios descriptivo

El histórico de cambios debe ser comprensible por cualquier colaborador del proyecto solamente con la información suministrada en dicho registro, evite depender de información fuera del contexto del envío de cambios. Se le recomienda colocar toda la información importante que no pueda obtenerse a partir de un svn diff del código fuente.

¿Está corrigiendo un error?

Si usted está reparando un error en la aplicación que se encuentra presente en la rama principal de desarrollo (trunk), considere seriamente portar esa reparación a otras ramas de desarrollo (branches) en el caso que su proyecto posea una versión estable que requiere actualmente de mantenimiento. Trate en lo posible de aprovechar el mismo commit para enviar la corrección de la rama principal de desarrollo (trunk) y las ramas de mantenimiento.

Si el error fue reportado a través del sistema de seguimiento de errores (usted mantiene un sistema de seguimiento de errores, ¿verdad?), agregue en el registro del mensaje de envío el número del ticket, boleto o reporte que usted está atendiendo. Seguidamente proceda a cerrar el ticket o boleto en el sistema de seguimiento de errores.

No agregue ficheros generados por otras herramientas al repositorio

No agregue ficheros innecesarios al repositorio, el origen de estos ficheros suele ser:

  • Proceso de autoconstrucción (Ej. distutils, autotools, scons, entre otros).
  • Herramientas de verificación de calidad del código (Ej. PyLint, CppLint, entre otros).
  • Herramientas para generar documentación (Ej. Doxygen, Sphinx, entre otros).

Estos ficheros generados por otras herramientas no es necesario versionarlos, puede considerarlos cache, este tipo de datos son localmente generados como resultado de operaciones I/O o cálculos, las herramientas deben ser capaces de regenerar o restaurar estos datos a partir del código presente en el repositorio central.

Realice envíos atómicos

Recuerde que SVN le brinda la posibilidad de enviar más de un fichero en un solo commit. Por lo tanto, envíe todos los cambios relacionados en múltiples ficheros, incluso si los cambios se extienden a lo largo de varios directorios en un mismo commit. De esta manera, se logra que SVN se mantenga en un estado compatible antes y después del commit.

Recuerde asegurarse al enviar un cambio al repositorio central reflejar un solo propósito, el cual puede ser la corrección de un error de programación o bug, agregar una nueva funcionalidad al sistema, ajustes de estilo del código o alguna tarea adicional.

Separe los ajustes de formato de código de otros envíos

Ajustar el formato del código, como la sangría, los espacios en blanco, el largo de la línea incrementa considerablemente los resultados del diff, si usted mezcla los cambios asociados al ajuste de formato de código con otros estará dificultando un posterior análisis al realizar un svn diff.

Haga uso intensivo de la herramienta de seguimiento de errores

Trate en lo posible de crear enlaces bidireccionales entre el conjunto de cambios hechos en el repositorio de subversion y la herramienta de seguimientos de errores tanto como sea posible.

  • Trate de hacer una referencia al id o número del ticket al cual está dando solución desde su mensaje de registro o log previo a realizar el commit.
  • Cuando agregue información o responda a un ticket, ya sea para describir su avance o simplemente para cerrar el ticket, indique el número o números de revisión que atienden o responden a dichos cambios.

Indique en el registro de envío el resultado del merge

Cuando usted está enviando el resultado de un merge, asegúrese de indicar sus acciones en el registro de envío, tanto aquello que fue fusionado como los números de revisiones que fueron tomadas en cuenta. Ejemplo:

Fusión de las revisiones 10:40 de /branches/foo a /trunk.

Tenga claro cuando es oportuno crear una rama

Esto es un tema polémico. Sin embargo, dependiendo del proyecto en el que esté involucrado usted puede definir una estrategia o mezcla de ellas para manejar el desarrollo del proyecto. Generalmente puede encontrar lo siguiente:

Los proyectos que requieren un alto manejo y supervisión recurren a estrategias de siempre crear ramas, acá puede encontrarse que cada colaborador crea o trabaja en una rama privada para cada tarea de codificación. Cuando el trabajo individual ha finalizado, algún responsable, ya sea el fundador del proyecto, un revisor técnico, analiza los cambios hechos por el colaborador y hace la fusión con la línea principal de desarrollo. En estos casos la rama principal de desarrollo suele ser bastante estable. Sin embargo, este modo de trabajo conlleva normalmente a crear mayores conflictos en la etapa de fusión o merge, además, las labores de fusión bajo este esquema son constantemente requeridas.

Existe otro enfoque que permite mantener una línea base de desarrollo estable, el cual consiste en crear ramas de desarrollo solo cuando se ameritan. Sin embargo, esto requiere que los colaboradores tengan mayor dominio del uso de Subversion y una constante comunicación entre ellos. Además de aumentar el número de pruebas antes de cada envío al repositorio centralizado.

Estudie, analice las distintas opciones y defina un método para la creación de ramas en su proyecto. Una vez definido, es sumamente importante que cada colaborador tenga clara esta estrategia de trabajo.

Las estrategias antes mencionadas y otras más pueden verse en detalle en:

Los sistemas de control de versiones no substituyen la comunicación entre los colaboradores

Por último pero no menos importante, los sistemas de control de versiones no substituyen la comunicación entre los desarrolladores o colaboradores del proyecto de software. Cuando usted tenga planes de hacer un cambio que pueda afectar una cantidad de código considerable en su proyecto, establezca un control de cambios, haga un análisis de las posibles consecuencias o impacto de los mismos, difunda esta información a través de una lista de discusión (usted mantiene una lista de discusión para desarrolladores, ¿verdad?) y espere la respuesta, preocupaciones, sugerencias de los demás colaboradores, quizá juntos se encuentre un modo más eficaz de aplicar los cambios.

Referencias:

subversion: Recuperar cambios y eliminaciones hechas

Muchos compañeros de trabajo y amigos en general que recién comienzan con el manejo de sistemas de control de versiones centralizados, en particular subversion, regularmente tienen inquietudes en cuanto al proceso de recuperación de cambios una vez que han sido enviados al repositorio, así como también la recuperación de ficheros y directorios que fueron eliminados en el pasado. Trataré de explicar algunos casos en base a ejemplos para que se tenga una idea más clara del problema y su respectiva solución.

En el primero de los casos se tiene recuperar la revisión previa a la actual, suponga que usted mantiene un repositorio de recetas, una de ellas en particular es la ensalada caprese, por error o descuido añadió el ingrediente Mostaza tipo Dijón a la lista, si usted posee siquiera un lazo con italinos sabe que está cometiendo un error que puede devenir en escarnio público, desprecio e insultos.

~/svn/wc/trunk$ svn diff -r 2:3 ${URL}/trunk/caprese
Index: caprese
===================================================================
--- caprese	(revision 2)
+++ caprese	(revision 3)
@@ -7,3 +7,4 @@
  - Albahaca fresca
  - Aceite de oliva
  - Pimienta
+ - Mostaza tipo Dijon

Note que el comando anterior muestra las diferencias entre las revisiones 2 y 3 del repositorio, en el resumen se puede apreciar que en la revisión 3 ocurrió el error. Un modo rápido de recuperarlo es como sigue.

~/svn/wc/trunk$ svn merge -c -3 ${URL}/trunk/caprese
--- Reverse-merging r3 into 'caprese':
U    caprese

En este caso particular se están aplicando las diferencias entre las revisiones consecutivas a la copia de trabajo. Es hora de verificar que los cambios hechos sean los deseados:

~/svn/wc/trunk$ svn status
M      caprese
~/svn/wc/trunk$ svn diff
Index: caprese
===================================================================
--- caprese	(revision 3)
+++ caprese	(working copy)
@@ -7,4 +7,3 @@
  - Albahaca fresca
  - Aceite de oliva
  - Pimienta
- - Mostaza tipo Dijon

Una vez verificado enviamos los cambios hechos al repositorio a través de comando svn commit.

Seguramente usted se estará preguntando ahora que sucede si las revisiones del ficheros no son consecutivas como en el caso mostrado previamente. En este caso es importante hacer notar que la opción -c 3 es equivalente a -r 2:3 al usar el comando svn merge, en nuestro caso particular -c -3 es equivalente a -r 3:2 (a esto se conoce como una fusión reversa), substituyendo la opción -c (o --changes) en el caso previo obtenemos lo siguiente:

~/svn-tests/wc/trunk$ svn merge -r 3:2 ${URL}/trunk/caprese
--- Reverse-merging r3 into 'caprese':
U    caprese

Referencias: svn help merge, svn help diff, svn help status.

Recuperando ficheros o directorios eliminados

Una manera bastante sencilla de recuperar ficheros o directorios eliminados es haciendo uso de comando svn cp o svn copy, una vez determinada la revisión del fichero o directorio que desea recuperar la tarea es realmente sencilla:

~/svn-tests/wc/trunk$ svn cp ${URL}/trunk/panzanella@6 panzanella
A         panzanella

En este caso se ha duplicado la revisión 6 del fichero panzanella en la copia de trabajo local, se ha programado para su adición incluyendo su historial, esto último puede verificarse en detalle al observar el signo ‘+’ en la cuarta columna del comando svn status.

~/svn-tests/wc/trunk$ svn status
A  +   panzanella

Referencias: svn help copy, svn help status.

Generando contraseñas aleatorias con Perl

El día de hoy se manifestó la necesidad de generar una serie de claves aleatorias para un proyecto en el que me he involucrado recientemente, la idea es que la entrada que tenemos corresponde más o menos al siguiente formato:

username_1
username_2
...
username_n

La salida que se desea obtener debe cumplir con el siguiente formato:

username_1 pass_1
username_2 pass_2
username_3 pass_3
...
username_n pass_n

En este caso debía cumplir un requisito fundamental, las contraseñas deben ser suficientemente seguras.

No pensaba en otra cosa que usar el lenguaje de programación Perl para realizar esta tarea, así fue, hice uso del poderío que brinda Perl+CPAN y en menos de 5 minutos ya tenía la solución al problema planteado, el tiempo restante me sirvió para comerme un pedazo de torta que me dió mi hermana, quien estuvo de cumpleaños el día de ayer.

En primer lugar, debemos instalar el módulo String::MkPasswd, el cual nos permitirá generar contraseñas de manera aleatoria. Si usted disfruta de una distribución decente como Debian1 la instalación del módulo es realmente trivial.

# aptitude install libstring-mkpasswd-perl

Además, si usted se detiene unos segundos y lee la documentación del módulo String::MkPasswd2, se dará cuenta que la función mkpasswd() toma un hash de argumentos opcionales. Si no le pasa ningún argumento a esta función estará generando constraseñas aleatorias con las siguientes características:

  • La longitud de la contraseña será de 9.
  • El número mínimo de dígitos será de 2.
  • El número mínimo de caracteres en minúsculas será de 2.
  • El número mínimo de caracteres en mayúsculas será de 2.
  • El número mínimo de caracteres no alfanuméricos será de 1.
  • Los caracteres no serán distribuidos entre los lados izquierdo y derecho del teclado.

Ahora bien, asumiendo que el fichero de entrada es users.data, la tarea con Perl es resumida en una línea de la siguiente manera.

perl -MString::MkPasswd=mkpasswd -nli -e 'print $_, " ", mkpasswd()' users.data

La línea anterior hace uso de la función mkpasswd del módulo String::MkPasswd para generar las contraseñas aleatorias para cada uno de los usuarios que se encuentran en el fichero de entrada users.data, además, la opción -i3 permite editar el fichero de entrada in situ, ahora bien, quizá para algunos paranoicos (me incluyo) no sea suficiente todo esto, así que vamos a generar contraseñas aleatorias aún más complicadas.


#!/usr/bin/perl -li

use strict;
use warnings;
use String::MkPasswd qw(mkpasswd);

while(<>){
        chomp;
        print $_, " ", mkpasswd(
                -length => 16,
                -minnum => 5,
                -minlower => 5,
                -minupper => 3,
                -minspecial => 3,
                -distribute => 1
        );
}

En esta ocasión el la función mkpasswd() generará claves aún más complejas, dichas claves cumplirán con las siguientes condiciones.

-length

La longitud total de la contraseña, 16 en este caso.
-minnum
El número mínimo de digitos. 5 en este caso.
-minlower
El número mínimo de caracteres en minúsculas, en este caso 5.
-minupper
El número mínimo de caracterés en mayúsculas, en este caso 3.
-minspecial
El número mínimo de caracteres no alfanuméricos, en este caso será de 3.
-distribute

Los caracteres de la contraseña serán distribuidos entre el lado izquierdo y derecho del teclado, esto hace más díficil que un fisgón vea la contraseña que uno está escribiendo. El valor predeterminado es falso, en este caso el valor es verdadero.

El script mostrado anteriormente lo podemos reducir a una línea, aunque preferí guardarlo en un fichero al que denomine genpasswd.pl por cuestiones de legibilidad.

  1. Recuerde, Debian es inexorable[regresar]
  2. Este modulo en particular no solo se encuentra perfectamente integrado con nuestra distribución favorita, sino que además sus dependencias están resueltas. Esto es una simple muestra del poderío que ofrece una distribución como Debian.[regresar]
  3. Para mayor información acerca de las distintas opciones usadas se le sugiere referirse a man perlrun[regresar]