Iván Zaera Avellón

Consultor tecnológico de desarrollo de proyectos informáticos.

Ingeniero en Informática

Ver todos los tutoriales del autor

Fecha de publicación del tutorial: 2007-07-19

Tutorial visitado 6.259 veces Descargar en PDF

Integración unidireccional entre Sourceforge y Bugzilla

Introducción

Este tutorial describe una posible integración entre Sourceforge y Bugzilla. Como sabéis, en Autentia, estamos desarrollando la aplicación de gestión TNTConcept, disponible en Sourceforge. Además, desarrollamos software para nuestros clientes y, como buena empresa de desarrollo informático, en nuestros sistemas internos tenemos instalando un sistema de trazabilidad de cambios: Bugzilla.

En Bugzilla llevamos la gestion de cambios al software de nuestros clientes, pero ahora nos encontramos con el problema de que los errores de TNTConcept los tenemos que llevar por Sourceforge y eso nos causa dificultades, al no estar todo centralizado. Hemos decidido que lo mejor es centralizar todo en Bugzilla, pero no queremos renunciar a Sourceforge como primer nivel de soporte de los usuarios de TNTConcept. Para ello hemos realizado una integración entre Sourceforge y Bugzilla de forma que todo lo que se introduzca en Sourceforge aparezca "mágicamente" en nuestro Bugzilla.

Con esta integración, Sourceforge pasa a ser un canal más de comunicación con nuestros "clientes" (como el correo o el telefono) y el gestor principal de cambios se unifica en nuestra instalación interna de Bugzilla, permitiéndonos más control sobre nuestros flujos de trabajo.

Pero bueno, ya está bien de palabrería y vayamos al grano...

El grano

Configurar Sourceforge

Lo primero que tenemos que hacer es configurar el proyecto en Sourceforge para que mande correos a una dirección nuestra cada vez que se efectúe una modificación en un tracker (Sourceforge llama tracker a cualquier elemento de información, como "Bug" o "Feature request", asociado al proyecto). Para ello iremos a la ventana de configuración del tracker que queramos integrar con Bugzilla:

Ahora escribiremos la dirección elegida y también marcaremos el cuadro para que envíe todos los cambios (si no nos envía sólo correos cuando se da de alta algo):

A partir de este momento, Sourceforge enviará correos a la dirección especificada cada vez que alguien introduzca un cambio o cree un tracker.

Configurar Bugzilla

Lo siguiente que haremos es instalar Bugzilla en un equipo. En nuestro caso hemos instalado Bugzilla 3.0 en una máquina Debian siguiendo el tutorial anterior a este (Como instalar Bugzilla 3.0 en Debian). Despues de la instalación debemos configurar Bugzilla para que se puedan dar de alta y modificar bugs por correo. Para ello usaremos el script "email_in.pl" que viene con Bugzilla.

Primero creamos un usuario en el sistema llamado "bugzilla" con el comando típico de Debian:

$ adduser bugzilla

Entramos al sistema con dicho usuario y en el directorio HOME creamos un fichero llamado ".forward" para que el MTA (Mail Transfer Agent, o sea, el demonio de correo) invoque nuestro script cada vez que llega un correo a este usuario. El fichero ".forward" debe contener lo siguiente:

\bugzilla,"|/home/bugzilla/email_in.sh"

Es importante que nuestro MTA sea "exim4" (uno de los varios disponibles en Debian) para que este fichero ".forward" funcione bien.

El fichero "email_in.sh" contendraoacute;:

#!/bin/sh cd /opt/bugzilla ./email_in.pl -vvv

Tenemos que asegurarnos de que el script "email_in.sh" tiene permisos de ejecución porque si no rebotarán todos los correos enviados al usuario "bugzilla". Tambien es importante cambiar "/opt/bugzilla" por el directorio donde hayamos instalado Bugzilla.

Con esta configuración, cada vez que el usuario "bugzilla" reciba un correo se evaluará con el script "email_in.pl" y se dará de alta o modificará un bug en Bugzilla. Para más información sobre la estructura del correo visitar la página web de Bugzilla que lo explica: http://www.bugzilla.org/docs/3.0/html/api/email_in.html. Conviene, a pesar de haber configurado el script, que alguien se haga responsable de la cuenta de correo "bugzilla" para que pueda ver correos que no sean de bugs o posibles "rebotes" y que, de paso, vacie la cuenta de vez en cuando.

Configurar usuario Sourceforge

Ahora crearemos un usuario llamado "sforge" que recibirá todos los correos de Sourceforge, los transformará mediante un script, y se los reenviará al usuario "bugzilla" para que los de de alta en Bugzilla. Ejecutamos el comando típico de creación de usuarios:

$ adduser sforge

Entramos al sistema con el usuario nuevo y, en su directorio HOME, creamos un fichero ".forward" que contenga:

\sforge, "|/bin/sh /home/sforge/sf2bz.sh"

Esto hará que se ejecute el script "sf2bz.sh" cada vez que se recibe un correo. Dicho script debe contener:

#!/bin/sh

log() 
{
  LOGFILE=$1
  MSG=$2

  echo `date` - $MSG >> $LOGFILE
}

extract_id()
{
  HEADER=$1
  FILE=$2

  LINE=`grep "$HEADER" $FILE`
  I=`expr index "$LINE" '\('`
  I=`expr $I + 1`
  J=`expr index "$LINE" '\)'`
  J=`expr $J - $I`
  expr substr "$LINE" $I $J
}

extract_header()
{
  HEADER=$1
  FILE=$2

  RET=`grep "$HEADER" $FILE | awk 'BEGIN {FS=":"} {print $2}'`
  echo $RET
}

extract_first()
{
  LINE=$1
  FILE=$2

  grep -m 1 "$LINE" $FILE
}

extract_section()
{
  FILE=$1
  SECTION=$2
  OFFSET=$3

  I=`grep -n "$SECTION" $FILE | awk 'BEGIN {FS=":"} {print $1}'`
  I=`expr $I + $OFFSET`
  N=`wc -l < $FILE`
  I=`expr $N - $I`
  J=`tail -n $I $FILE | grep -n -- "-----------------" | awk 'BEGIN {FS=":"} {print $1}'`
  J=`expr $J - 1`
  tail $FILE -n $I | head -n $J
}

generate_new()
{
  FILE=$1
  SEVERITY=$2
  SUMMARY=$3
  TICKET_ID=$4
  TICKET_URL=$5
  SUBMITTER=$6
  TICKET_DESC=$7

  cat << EOF > $FILE
@product=TestProduct
@version=unspecified
@op_sys=Linux
@component=TestComponent
@rep_platform=PC
@short_desc=$SUMMARY
@assigned_to=admin
@bug_severity=$SEVERITY
@cf_sforge_id=$TICKET_ID
@bug_file_loc = $TICKET_URL

==== SOURCEFORGE.NET INFO ====
$TICKET_URL
Ticket ID: $TICKET_ID
Submitter: $SUBMITTER
==== SOURCEFORGE.NET INFO ====

$TICKET_DESC
EOF
}

generate_comment()
{
  FILE=$1
  BUG_ID=$2
  SUBMITTER=$3
  COMMENT=$4

  cat << EOF >$FILE
@bug_id=$BUG_ID

==== Submitted by: $SUBMITTER ====

$COMMENT
EOF
}

# Define variables
WORKDIR=/home/sforge/sf2bz
MSGFILE=$WORKDIR/sfmsg
LOGFILE=$WORKDIR/log

# Create workdir if it does not exist
log $LOGFILE "================================================================"
if [ ! -d $WORKDIR ]
then
  mkdir $WORKDIR
fi

# Dump received mail to temporary file
log $LOGFILE "Processing new message"
cat /dev/null > $MSGFILE
while read LINE
do
  echo $LINE >> $MSGFILE
done

# See if message should be processed
grep "From.*noreply@sourceforge.net" $MSGFILE
if [ $? != 0 ]
then
  log $LOGFILE "Message does not come from noreply@sourceforge.net: ignored"
  exit 0
fi

# Get field values
SF_TICKET_URL=`extract_first "https://sourceforge.net/tracker" $MSGFILE`
log $LOGFILE "SF_TICKET_URL=$SF_TICKET_URL"

SF_TICKET_REASON=`extract_header "X-SourceForge-Tracker-itemupdate-reason" $MSGFILE`
log $LOGFILE "SF_TICKET_REASON=$SF_TICKET_REASON"

SF_TICKET_CATEGORY=`extract_header "X-SourceForge-Tracker-trackerid" $MSGFILE`
log $LOGFILE "SF_TICKET_CATEGORY=$SF_TICKET_CATEGORY"

case "$SF_TICKET_CATEGORY" in
  933043) 
    SF_TICKET_SEVERITY=enhancement
    ;;
  933040) 
    SF_TICKET_SEVERITY=normal
    ;;
  *) 
    log $LOGFILE "Unknown ticket category: will file as enhancement"
    SF_TICKET_SEVERITY=enhancement
    ;;
esac
log $LOGFILE "SF_TICKET_SEVERITY=$SF_TICKET_SEVERITY"

SF_TICKET_ID=`extract_header "X-SourceForge-Tracker-itemid" $MSGFILE`
log $LOGFILE "SF_TICKET_ID=$SF_TICKET_ID"

SF_TICKET_SUMMARY=`extract_header "Summary" $MSGFILE`
log $LOGFILE "SF_TICKET_SUMMARY=$SF_TICKET_SUMMARY"

SF_TICKET_SUBMIT=`extract_header "Submitted By" $MSGFILE`
log $LOGFILE "SF_TICKET_SUBMIT=$SF_TICKET_SUBMIT"

SF_TICKET_ASSIGN=`extract_header "Assigned to" $MSGFILE`
log $LOGFILE "SF_TICKET_ASSIGN=$SF_TICKET_ASSIGN"

SF_TICKET_DESC=`extract_section $MSGFILE "Initial Comment" 0`
log $LOGFILE "SF_TICKET_DESC=$SF_TICKET_DESC"

# See if ticket has new comment attached
if [ "$SF_TICKET_REASON" == "Comment added" ]
then 
  # Get ticket new comment
  SF_TICKET_COMMENT=`extract_section $MSGFILE ">Comment By" 7`
  log $LOGFILE "SF_TICKET_COMMENT=$SF_TICKET_COMMENT"

  # Get ticket new comment submitter
  SF_TICKET_COMMENT_SUBMIT=`extract_header ">Comment By" $MSGFILE`
fi

# Check what to do with ticket
SEND_MAIL=0
case "$SF_TICKET_REASON" in

  "Tracker Item Submitted")
    generate_new $WORKDIR/bzmsg "$SF_TICKET_SEVERITY" "$SF_TICKET_SUMMARY" 
    "$SF_TICKET_ID" "$SF_TICKET_URL" "$SF_TICKET_SUBMIT" "$SF_TICKET_DESC"
    log $LOGFILE "Generated new ticket mail for Bugzilla"
    SEND_MAIL=1
    ;;

  "Comment added")
    wget -o /dev/null -O $WORKDIR/search "http://server/bugzilla/buglist.cgi?
    query_format=advanced&
    short_desc_type=allwordssubstr&
    shor&long_desc_type=substring&long_desc=&
    bug_file_loc_type=allwordssubstr&
    bug_file_loc=&deadlinefrom=&deadlineto=&
    bug_status=&emailassigned_to1=1&
    emailtype1=substring&email1=&emailassigned_to2=1&
    emailreporter2=1&emailcc2=1&
    emailtype2=substring&email2=&bugidtype=include&
    bug_id=&votes=&chfieldfrom=&
    chfieldto=Now&chfieldvalue=&cmdtype=doit&
    order=Reuse+same+sort+as+last+time&
    field0-0-0=cf_sforge_id&
    type0-0-0=equals&value0-0-0=$SF_TICKET_ID"

    BZ_ID=`grep "show_bug.cgi.id=" $WORKDIR/search`
    log $LOGFILE "grep returned -$BZ_ID-"
    BZ_ID=`echo $BZ_ID | cut -d = -f 3`
    BZ_ID=`echo $BZ_ID | cut -d \" -f 1`
    log $LOGFILE "BZ_ID=$BZ_ID"

    generate_comment $WORKDIR/bzmsg $BZ_ID "$SF_TICKET_COMMENT_SUBMIT" 
    "$SF_TICKET_COMMENT"
    log $LOGFILE "Generated comment mail for Bugzilla" 
    SEND_MAIL=1
    ;;

  *)
    log $LOGFILE "Unknown ticket reason: $SF_TICKET_REASON"
    ;;

esac

# Send mail to Bugzilla
if [ $SEND_MAIL -eq 1 ]
then
  mail -i bugzilla -s "Automatic bug submission from Sourceforge" < $WORKDIR/bzmsg
  log $LOGFILE "Sent mail to Bugzilla"
fi

Por supuesto, es importante cambiar los textos en color verde por los valores adaptados a nuestro sistema. El script básicamente lo que hace es buscar en el mensaje de correo cabeceras que envía Sourceforge y también obtiene ciertos contenidos del cuerpo del mensaje. Despues, con los valores extraídos compone un mensaje en el formato de Bugzilla y se lo envía.

Ni que decir tiene que este script funcionará mientras Sourceforge no cambie el formato de sus correos. Si esto sucediese, habría que volver a hacer la ingeniería inversa y retocar el script. Para efectuar la ingeniería inversa basta con ver el código fuente de algún correo que Sourceforge nos haya enviado.

También hay que prestar especial interés a la línea en la que se invoca a "wget". Esta línea consulta a Bugzilla cuando Sourceforge nos envía un correo sobre un bug ya existente. Esta consulta es necesaria para mapear el id del bug de Sourceforge al de Bugzilla. Lo que hace el script es buscar todos los bugs de Bugzilla cuyo campo "cf_sforge_id" contiene el id del ticket en Sourceforge. El campo "cf_sforge_id" es un campo de usuario de Bugzilla que nosotros hemos creado ad hoc para almacenar el id del ticket en Sourceforge. Se rellena cuando el script da de alta un bug por vez primera. El retorno de la consulta es una página HTML con el listado de bugs encontrados (debería haber sólo uno si todo está bien). Despues se busca en este HTML, con "grep", el id del ticket devuelto y se extrae con "cut". Obviamente este código sólo funcionará mientras la estructura del HTML de Bugzilla no cambie. Si cambia hay que volver a hacer la ingeniería inversa lanzando una busqueda desde Bugzilla y viendo el código fuente de la página devuelta. Se supone que tiene que haber formas más civilizadas de hacer esto llamando a los scripts Perl de Bugzilla pero aún no las hemos investigado.

Para crear el campo de usuario "cf_sforge_id" debemos acceder en Bugzilla a la página de administración "Custom fields" y seleccionar "Add a new custom field":

Configurar fetchmail

El siguiente paso es configurar "fetchmail". Este paso sólo será necesario cuando los mensajes enviados a la dirección de correo que hemos dado de alta en Sourceforge no se reciban directamente en la máquina donde hemos creado el usuario "sforge". Por ejemplo, imaginemos que hemos creado una direccion en GMail llamada "errores@gmail.com" donde estamos enviando los correos de Sourceforge. Es evidente que los correos que lleguen a esa dirección se quedan en la bandeja de entrada de GMail y no se reenviaacute;an a nuestro equipo. Por eso necesitamos "fetchmail" para que se baje los correos por POP del buzón de GMail y haga como que llegan al usuario Bugzilla.

Primero instalamos fetchmail con el usuario root:

# apt-get install fetchmail

A continuación lo configuramos para que arranque como servicio. Para ello editamos el fichero "/etc/default/fetchmail" y cambiamos el valor de la variable "START_DAEMON" de "no" a "yes". El servicio "fetchmail" arrancará automáticamente con el proximo reinicio.

Creamos ahora un fichero llamado "/etc/fetchmailrc" con root como dueño y permisos "600". En este fichero escribiremos los parametros de configuración de la cuenta de correo cuyos mensajes queremos descargar con "fetchmail". Por ejemplo:

set postmaster "root"
set nobouncemail
set no spambounce
set properties ""
set daemon 1
poll pop.gmail.com with proto POP3
    user 'errores@gmail.com' there with password 'ERRORES123' 
    is 'bugzilla' here options no rewrite mimedecode ssl

Hecho esto lanzamos con root el servicio "fetchmail" con el comando (también lo podemos hacer reiniciando la máquina):

# /etc/init.d/fetchmail start

A partir de este momento "fetchmail" debería bajarse los correos de "errores@gmail.com" y reenviarselos al usuario "sforge".

Conclusión

Ya tenemos montado el chiringo. Vamos a recapitular ahora que pasa cuando alguien da de alta un bug en Sourceforge:

  1. Un usuario da de alta un bug en Sourceforge.
  2. Sourceforge envía un correo de notificacion a "errores@gmail.com".
  3. Fetchmail recupera el correo de "errores@gmail.com" usando POP y se lo envía al usuario local "sforge".
  4. Se lee el fichero ".forward" en el directorio HOME de "sforge" y se invoca el script "sf2bz.sh" pasandole el correo descargado de "errores@gmail.com".
  5. El script "sf2bz.sh" analiza el correo y genera otro nuevo con el formato de Bugzilla.
  6. El script "sf2bz.sh" envía el correo generado al usuario local "bugzilla".
  7. Se lee el fichero ".forward" en el directorio HOME de "bugzilla" y se invoca el script "email_in.pl" pasandole el correo.
  8. El script "email_in.pl" da de alta el bug creado en Sourceforge en Bugzilla.

Y ahora que pasa cuando alguien modifica un bug en Sourceforge y que, por tanto, ya se ha creado en Bugzilla:

  1. Un usuario modifica un bug en Sourceforge.
  2. Sourceforge envía un correo de notificacion a "errores@gmail.com".
  3. Fetchmail recupera el correo de "errores@gmail.com" usando POP y se lo envía al usuario local "sforge".
  4. Se lee el fichero ".forward" en el directorio HOME de "sforge" y se invoca el script "sf2bz.sh" pasandole el correo descargado de "errores@gmail.com".
  5. El script "sf2bz.sh" analiza el correo.
  6. El script "sf2bz.sh" hace una busqueda en Bugzilla para encontrar el bug de Bugzilla que se corresponde con el de Sourceforge.
  7. El script "sf2bz.sh" genera un nuevo correo con el formato de Bugzilla que incluye el id del bug buscado.
  8. El script "sf2bz.sh" envía el correo generado al usuario local "bugzilla".
  9. Se lee el fichero ".forward" en el directorio HOME de "bugzilla" y se invoca el script "email_in.pl" pasandole el correo.
  10. El script "email_in.pl" modifica el bug correspondiente al de Sourceforge en Bugzilla.

Sobre el autor

Iván Zaera Avellón, Ingeniero en Informática por la Universidad Autánoma de Madrid - izaera@autentia.com
Consultor tecnológico de Autentia (Formación, Consultoría, Desarrollo de sistemas transaccionales)

Autentia Real Business Solutions S.L. - Soporte a Desarrollo - http://www.autentia.com

A continuación puedes evaluarlo:

Regístrate para evaluarlo

Por favor, vota +1 o compártelo si te pareció interesante

Share |
Anímate y coméntanos lo que pienses sobre este TUTORIAL: