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.
