Vistas

Mejora G-Forge 10: Mejoras en la portabilidad de los proyectos

De MorfeoWiki

Para permitir la portabilidad lo que se ha hecho ha sido modificar la interfaz de administración de proyectos, incluyendo una opción de Descargar backup actualizado del proyecto. Pulsando sobre esta opción, se genera un archivo .tar.gz que contiene copia de seguridad de:

  • Repositorio de código CVS o SVN.
  • Archivos de las listas de correo de Mailman.
  • Documentos asociados al proyecto.
  • Ficheros asociados al proyecto.

El archivo generado es recibido directamente a través del navegador web[#foot169 2].

Cambios en el código

  • /usr/share/gforge/www/project/admin/backup_lib.php: Se ha creado un archivo nuevo, aunque se podía haber modificado uno de Gforge ya existente para incluir la función generate_backup(group_id), encargada de generar el backup. La razón de implementar esta función en otro archivo es simplemente mantener el código más limpio, pero perfectamente se podría haber incluído en el archivo detallado a continuación.
<?php
function generate_backup($gid){
   $result= db_query("select group_id, 
                             unix_group_name, 
                             scm_box 
                      from groups 
                      where group_id='$gid'");
   while ($row=pg_fetch_row($result)){
      $timestamp = rand();
      $syscall= "/root/bin/backup-project $row[1] ".substr($row[2], 0, 3) ." $timestamp";
      $ret=system($syscall);
      $filename="backup-$row[1]-$timestamp.tar.gz";
      $url="https://$row[1].forge.morfeo-project.org/backup-$row[1]-$timestamp.tar.gz";
      echo "<HEAD>
                    <META HTTP-EQUIV=REFRESH CONTENT=\"2;URL=$url\">
                 </HEAD>";
     }
}
?>
De esta manera lo que hacemos es generar un archivo de la forma 
backup-<nombre_proyecto>-<numero_aleatorio>.tar.gz
llamando al script backup-project con el nombre del proyecto, el tipo de scm y el número aleatorio como parámetros de entrada.
  • /usr/share/gforge/www/project/admin/index.php: En este archivo se encuentra la interfaz de gestión de un proyecto. Será necesario referenciar desde él el archivo recién creado para albergar la función incluyendo la siguiente línea dentro del bloque de código PHP.
include 'backup_lib.php';

En este archivo, lo primero que se hace es comprobar que el usuario es realmente administrador del proyecto, que el proyecto existe... etc. Acto seguido debemos introducir la siguiente linea:

if ($_GET['backup']=='true') {generate_backup($_GET['group_id']);}

Justo debajo de

if (!$perm->isAdmin()) {
   exit_permission_denied();
}

Para que cuando se llama a index.php, si la variable $backup, pasada en el método GET de HTTP tiene como valor true, se genere el backup del proyecto que se le pasa como argumento. Después se introduce en el código HTML devuelto al cliente un código que hace que éste cargue en 2 segundos el archivo generado.Más adelante, será necesario añadir la linea:

[ <a href="https://forge.morfeo-project.org/project/admin/?group_id= <?php echo $group_id; ?> &backup=true"> <?php echo $Language->getText('project_admin', 'download_backup');  ?> </a> ]

}
Debajo de la linea que contiene el siguiente texto:   
$Language->getText('project_admin', 'download_tarball')
Con esto conseguimos varias cosas:
    • En función del idioma seleccionado, aparecerá un texto en el enlace del backup.
    • Cuando se hace click sobre el enlace, se autollama a la misma página con los mismos argumentos en el GET, pero además añadiendo la variable backup=true, lo que hará que se descargue el archivo de backup. Evidentemente, será necesario modificar los archivos de idioma y añadir una nueva entrada, como veremos ahora:
  • /usr/share/gforge/www/include/languages/*.tab: Estos son los ficheros de idioma castellano. Hay que añadir la siguiente linea (no importa la posición en el archivo:
project_admin       download_backup Descargar backup actualizado del proyoecto
Para cada idioma se introducirá la cadena de texto necesaria. Se han incluído las correspondientes cadenas de texto para Base.tab y Spanish.tab

Creación de scripts

Antes se hacía referencia a un script que generaba el backup. Existe un problema con esto. Para que el usuario que ejecuta los scripts tenga permisos para hacer el backup de TODOS los proyectos así como de archivos de mailman y demás, es necesario que ese usario sea root. Ante el fallo de seguridad que sería hacer un sudo con la clave de root en claro, lo que se hace es un binario con el bit suid activo, de manera, que el usuario que ejecuta el archivo tenga permisos de root. En este binario lo que se hace es llamar a un script en sh que genera el tarball con el backup.

El código de dicho script (backup-project.sh) es el siguiente:

 
 #!/bin/sh
 
 ######################
 # Check for username #
 ######################
 
 if [ "`whoami`" != "root" ]; then
   echo "This script must be executed as root." >> $log_file;
   exit -1;
 fi
 
 ###########################
 # Check script arguments. #
 ###########################
  
 if [[ -z $1 ]]; then
   echo "No project given as argument.";
   exit -1;
 fi 
 
 if [[ -z $2 ]]; then
   echo "No SCM type given as argument";
   exit -1;
 fi
 
 
 if [[ -z $3 ]]; then
   echo "No timestamp given as argument";
   exit -1;
 fi
 
 project="$1"
 scm="$2"
 timestamp="$3"
 project_dir="/var/lib/gforge/chroot/home/groups/$project/htdocs"
 log_file="$project_dir/backup-$project-$timestamp.log"
 tarball_file="$project_dir/backup-$project-$timestamp.tar.gz"
 
 #################
 # Init log file #
 #################
 
 echo "Log for backup session `date`" >> $log_file
 
 ###############################
 # Deleting previous backup(s) #
 ###############################
 
 echo "Deleting previos versions of $project backups..." >> $log_file
 rm -f $project_dir/backup-$project-*.tar.gz
 rm -r $project_dir/backup-$project-*.log
 echo "Deletion completed!" >> $log_file
 
 ######################
 # Check for SCM type #
 ######################
 
 case $scm in
   cvs)
   ;;
   svn)
   ;;
   *)
     echo "Unknow SCM type $scm" >> $log_file
     exit -1
   ;;
 esac 
 
 ############################
 # Locate mailman archives. #
 ############################
 
 archives_dir="/var/lib/mailman/archives/"
 
 mailman_dirs="`find $archives_dir -name $project*`"
 
 echo "Next mailman files will be saved:" >> $log_file
 for file in $mailman_dirs; do
   echo "      $file" >> $log_file;
 done
 echo "" >> $log_file
 
 #####################
 # Locate SCM files #
 #####################
 
 case $scm in
   cvs)
     scm_dir="/cvsroot/$project"
   ;;
   svn)
     scm_dir="/svnroot/$project"
   ;;
 esac
 
 if [ ! -d $scm_dir ] ; then
   echo "Invalid SCM directory $scm_dir";
   exit -1;
 fi
 
 echo "Using $scm_dir as SCM directory." >> $log_file
 
 ####################################
 # Locate project downloadble files #
 ####################################
 
 projects_download_dir="/var/lib/gforge/download"
 
 download_dir="$projects_download_dir/$project"
 
 echo "Using $download_dir as download files dirrectory" >> $log_file 
 
 #############################
 # Generate database content #
 #############################
 
 echo -n "Generating database content in HTML format... " >> $log_file
 db_datafile="`backup-project-db.sh $project`"
 echo "OK" >> $log_file
 
 ####################
 # Generate tarball #
 ####################
 
 echo -n "Generating tarball to $tarball_file... " >> $log_file
 if ! `tar cfzv $tarball_file $mailman_dirs $scm_dir 
 $download_dir $db_datafile > /dev/null 2>&1` ; then
   echo "unknown error" >> $log_file
   exit -1;
 fi
 
 chgrp www-data $tarball_file
 
 chmod go+w $tarball_file
  
 echo "OK" >> $log_file
 
 echo -n "Removing HTML database content from $db_datafile... " >> $log_file
 rm -rf $db_datafile
 echo "OK" >> $log_file
 

Este script se encarga de realizar una copia de respaldo de las siguientes fuentes de datos:

  • Repositorio de código, CVS o SVN.
  • Datos de las listas de distribución de correo Mailman.
  • Ficheros públicos del proyecto.
  • Contenido de la base de datos PostgreSQL.

Para extraer el contenido de la base de datos, se emplea el script adicional backup-project-db.sh, cuyo código es:

 #!/bin/sh
 
 if [[ -z $1 ]]; then
   echo "No project name given as argument";
   exit -1;
 fi
 
 project_name=$1
 seed="`date +%s%N`"
 db_name="gforge"
 db_user="postgres"
 sql_file="/tmp/$project_name-db-$seed.sql"
 html_file="/tmp/$project_name-db-$seed.html"
 query_command="sudo -u $db_user psql -P format=html $db_name -f $sql_file"
 
 echo "select group_name from groups where unix_group_name='$project_name'" > $sql_file
 project_full_name="`sudo -u $db_user psql -f $sql_file $db_name | head -n 3 | tail -n 1 `" 
 
 echo "select group_id from groups where unix_group_name='$project_name'" > $sql_file
 project_id="`sudo -u $db_user psql -f $sql_file $db_name | head -n 3 | tail -n 1 `" 
 
 extract_table() {
   echo "<p/><h2>Tabla '$table_name'</h2>" >> $html_file
   echo "$sql_statement" > $sql_file
   $query_command >> $html_file
 }
 
 ##########################
 # Fill html header file #
 ##########################
 
 rm -rf $html_file
 
 
 echo "
 <!doctype>
 <html>
 <head>
 <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8" />
 <title>Relación de Datos en Postgres de GForgeepara el Proyecto $project_full_name</title>
 </head>
 <body>
 <h1>Relación de Datos en Postgres de GForge para el Proyecto $project_full_name</1>
 " >> $html_file
 
 
 ########################
 # Fill html body file #
 ########################
 
 # Table 'licenses'
 table_name="licenses"
 sql_statement="select licenses.* from licenses, groups where 
   licenses.license_id = groups.license and 
   groups.unix_group_name='$project_name';"
 extract_table
 
 # Table 'groups'
 table_name="groups"
 sql_statement="select * from groups where unix_group_name='$project_name'"
 extract_table
 
 # Table 'supported_languages'
 table_name="supported_languages"
 sql_statement="select distinct supported_languages.* from supported_languages,
                                                           users,user_group
   where user_group.group_id=$project_id and 
     user_group.user_id=users.user_id and
     supported_languages.language_id=users.language;"
 extract_table
 
 # Table 'themes'
 table_name="themes"
 sql_statement="select distinct themes.* from themes,users,user_group where 
   user_group.group_id=$project_id and 
   user_group.user_id=users.user_id and
   themes.theme_id=users.theme_id;"
 extract_table
 
 # Table 'country_code'
 table_name="country_code"
 sql_statement="select distinct country_code.* from country_code,users,
                                               user_group where 
   user_group.group_id=$project_id and 
   user_group.user_id=users.user_id and
   country_code.ccode=users.ccode;"
 extract_table
 
 # Table 'user_type'
 table_name="user_type"
 sql_statement="select distinct user_type.* from user_type,users,
                                                 user_group where 
   user_group.group_id=$project_id and 
   user_group.user_id=users.user_id and
   user_type.type_id=users.type_id;"
 extract_table
 
 # Table 'users'
 table_name="users"
 sql_statement="select distinct users.* from users,user_group where 
   user_group.group_id=$project_id and 
   user_group.user_id=users.user_id;"
 extract_table
 
 # Table 'role_setting'
 table_name="role_setting"
 sql_statement="select distinct role_setting.* from role_setting,role,
                                                    user_group where 
   role.group_id=$project_id and 
   role_setting.role_id=role.role_id and
   role.role_id=user_group.role_id;"
 extract_table
 
 # Table 'role'
 table_name="role"
 sql_statement="select distinct role.* from role,user_group where 
   role.group_id=$project_id and 
   user_group.role_id=role.role_id;"
 extract_table
 
 # Table 'user_group'
 table_name="user_group"
 sql_statement="select * from user_group where group_id =$project_id;"
 extract_table
 
 # Table 'doc_states'
 table_name="doc_states"
 sql_statement="select doc_states.* from doc_states, doc_data
   where doc_data.group_id=$project_id and doc_data.stateid=doc_states.stateid;"
 extract_table
 
 # Table 'doc_groups'
 table_name="doc_groups"
 sql_statement="select doc_groups.* from doc_groups, doc_data
   where doc_data.group_id=$project_id and 
     doc_data.doc_group=doc_groups.doc_group and
     doc_groups.group_id=$project_id;"
 extract_table
 
 # Table 'doc_data'
 table_name="doc_data"
 sql_statement="select * from doc_data where group_id=$project_id;"
 extract_table
 
 # Table 'forum_group_list'
 table_name="forum_group_list"
 sql_statement="select * from forum_group_list where group_id=$project_id;"
 extract_table
 
 # Table 'forum_perm'
 table_name="forum_perm"
 sql_statement="select distinct forum_perm.* from forum_perm,forum_group_list,
                                         users,user_group where 
   forum_group_list.group_id=$project_id and 
   forum_perm.group_forum_id=forum_group_list.group_forum_id and
   users.user_id=user_group.user_id and
   user_group.group_id=$project_id;"
 extract_table
 
 # Table 'frs_package'
 table_name="frs_package"
 sql_statement="select * from frs_package where group_id=$project_id;"
 extract_table
 
 # Table 'frs_release'
 table_name="frs_release"
 sql_statement="select frs_release.* from frs_release,frs_package,users,
                                          user_group where 
   frs_package.group_id=$project_id and 
   frs_release.package_id=frs_package.package_id and
   frs_release.released_by=users.user_id and
   users.user_id=user_group.user_id and
   user_group.group_id=$project_id;"
 extract_table
 
 # Table 'frs_status'
 table_name="frs_status"
 sql_statement="select distinct frs_status.* from frs_status,frs_release,
                                                  frs_package,users,
                                                  user_group where
   frs_status.status_id = frs_release.status_id and
   frs_package.group_id=$project_id and
   frs_release.package_id=frs_package.package_id and
   frs_release.released_by=users.user_id and
   users.user_id=user_group.user_id and
   user_group.group_id=$project_id;"
 extract_table
     
 # Table 'project_group_list'
 table_name="project_group_list"
 sql_statement="select distinct * from project_group_list where 
   group_id=$project_id;"
 extract_table
 
 # Table 'project_task'
 table_name="project_task"
 sql_statement="select distinct project_task.* from project_task, 
                                                    project_group_list, users, 
                                                    user_group,
                                                    project_category where
   project_group_list.group_id=$project_id and
   project_task.group_project_id=project_group_list.group_project_id and
   project_task.created_by=users.user_id and
   users.user_id=user_group.user_id and
   user_group.group_id=$project_id
 ;"
 extract_table
 
 # Table 'project_perm'
 table_name="project_perm"
 sql_statement="select distinct project_perm.* from project_perm, project_task, 
                                           project_group_list,
                                           users, user_group where
   project_group_list.group_id=$project_id and
   user_group.group_id=$project_id and
   user_group.user_id=users.user_id and
   project_perm.group_project_id=project_group_list.group_project_id and
   project_perm.user_id=users.user_id and
   users.user_id=user_group.user_id
 ;"
 extract_table
 
 # Table 'project_category'
 table_name="project_category"
 sql_statement="select distinct project_category.* from project_category, project_group_list where
   project_group_list.group_id=$project_id and
   project_category.group_project_id=project_group_list.group_project_id
 ;"
 extract_table
 
 # Table 'artifact_group_list'
 table_name="artifact_group_list"
 sql_statement="select * from artifact_group_list where group_id=$project_id;"
 extract_table
 
 # Table 'artifact_perm'
 table_name="artifact_perm"
 sql_statement="select distinct artifact_perm.* from artifact_perm, artifact_group_list
   where artifact_perm.group_artifact_id=artifact_group_list.group_artifact_id and
     artifact_group_list.group_id=$project_id;"
 extract_table
 
 # Table 'artifact_type_monitor'
 table_name="artifact_type_monitor"
 sql_statement="select distinct artifact_type_monitor.* from artifact_type_monitor, artifact_group_list
   where artifact_type_monitor.group_artifact_id=artifact_group_list.group_artifact_id and
     artifact_group_list.group_id=$project_id;"
 extract_table
 
 # Table 'artifact'
 table_name="artifact"
 sql_statement="select distinct artifact.* from artifact, artifact_group_list
   where artifact.group_artifact_id=artifact_group_list.group_artifact_id and
     artifact_group_list.group_id=$project_id;"
 extract_table
 
 # Table 'artifact_query'
 table_name="artifact"
 sql_statement="select distinct artifact_query.* from artifact_query, artifact_group_list
   where artifact_query.group_artifact_id=artifact_group_list.group_artifact_id and
     artifact_group_list.group_id=$project_id;"
 extract_table
 
 # Table 'artifact_status'
 table_name="artifact_status"
 sql_statement="select distinct artifact_status.* from artifact_status, artifact, artifact_group_list
   where artifact.group_artifact_id=artifact_group_list.group_artifact_id and
     artifact_status.id=artifact.status_id and
     artifact_group_list.group_id=$project_id;"
 extract_table
 
 # Table 'trove_agg'
 table_name="trove_agg"
 sql_statement="select * from trove_agg where group_id=$project_id;"
 extract_table
 
 # Table 'trove_group_link'
 table_name="trove_group_link"
 sql_statement="select * from trove_group_link where group_id=$project_id;"
 extract_table
 
 # Table 'trove_cat'
 table_name="trove_cat"
 sql_statement="select distinct trove_cat.* from trove_cat, trove_agg,trove_group_link where
   trove_cat.trove_cat_id=trove_agg.trove_cat_id and
   trove_cat.trove_cat_id=trove_group_link.trove_cat_id and
   trove_agg.group_id=$project_id and
   trove_group_link.group_id=$project_id;"
 extract_table
 
 
  
 ##########################
 # Fill html footer file #
 ##########################
 
 echo "</body></html>" >> $html_file
 
 ###############
 # Clean files #
 ###############
 
 clean_files="$sql_file"
 rm -rf $clean_files
 
 ########################
 # Print html file name #
 ########################
 
 echo $html_file
 

Este script extrae los datos de las tablas relacionadas con el proyecto, seleccionando únicamente aquellas tuplas que tengan relación con éste.

Resultados

Desde la interfaz de administración del proyecto, el administrador encuentra un enlace: Descargar backup actualizado del proyecto, sobre el cual, al hacer click se genera y descarga un .tar.gz con toda la información necesaria para restaurar un proyecto.