Integración continua: Automatizando la creación de repositorios con Git y Subversion
La automatización de procesos es una de las tareas esenciales en integración continua, ya que permite estandarizar los procesos (que siempre sean iguales) y ahorrar muchísimo tiempo en el desarrollo de los proyectos.
Lo primero que vamos a automatizar es la creación de repositorios. Para ello vamos a utilizar un script en Bash para poder ejecutarlo en consola a través de ssh. Es posible crear algo parecido con otros lenguajes de programación y además, con una interfaz web que facilite la gestión; pero de momento vamos con lo más básico.
CONVENCIONES
Como recordarás del último post (Integración continua: Metodologías y convenciones), debemos especificar unas convenciones o reglas que nos permitan diseñar cómo se van a crear los repositorios. En este caso esas reglas son las siguientes:
- Se utilizará Git y Subversion.
- Para Subversion se automatizará la creación de las ramas «trunk«, «branches» y «tags«.
- Los repositorios se guardarán en «/var/repos«.
- Dentro de cada repositorio se crearán los siguientes directorios: src, metrics, changes, test, db, phing y sh. En el directorio src guardaremos los datos del proyecto. En metrics guardaremos las métricas en subdirectorios, uno para cada una de las métricas (phpcs, pdepend, phpcpd, phpmd). El directorio changes dispondrá de un subdirectorio llamado modules que nos permitirá guardar en un archivo los cambios realizados sobre módulos o plugins que se distribuyan con el cms. En los directorio test habrá dos subdirectorios: units y functionals, para separar los test unitarios de los funcionales. En db habrá tres subdirectorios: basic, modules-dev y modules, que nos permitirá guardar una copia del proyecto al inicio sin módulos ni plugins (basic); con los módulos en desarrollo (modules-dev) y en producción (modules). Por último los directorios phing y sh nos permitirán guardar los xml de algunas tareas automatizadas específicas para el proyecto (phing, lo veremos más adelante) y los scripts en bash, también específicos para el proyecto en el que estemos trabajando, en sh.
- Cada proyecto se nombrará con su dominio (interadictos.es). El nombre de los repositorios en Git terminarán en «.git».
- Se creará un archivo «.conf» en Apache para poder acceder a los repositorios de Subversion desde el navegador.
Con esto ya podemos empezar a desarrollar nuestro script.
PERO ANTES
Como ves, el último elemento de la lista es la creación de un archivo «.conf» para poder ver el repositorio a través del navegador. Esta es una opción que te puedes saltar si no tienes necesidad de esta característica. Pero con el fin de hacer esta guía lo más detallada posible lo comentaré.
Para realizar estos cambios debes ser «root» o disponer del comando «sudo».
Nos vamos al directorio de Apache:
cd /etc/apache2
Y creamos el directorio
mkdir repos
Comprueba que los permisos del directorio son correctos.
CREAR UN SUBDOMINIO AL REPOSITORIO
Ahora entra en el directorio «sites-available»:
cd sites-available
Crearemos un subdominio para el dominio «ic.net», que te recuerdo era el dominio de nuestro servidor de integración continua.
Utiliza tu editor favorito y crea un archivo llamado «001-repos.ic.net.conf». El número al principio es simplemente una forma de ordenar los archivos. Puedes obviarlo si lo deseas.
Ahora introduce el siguiente código en el archivo y guárdalo:
- <VirtualHost *:80>
- ServerName repos.ic.net
-
- DocumentRoot /var/www/repos.ic.net
- <Directory />
- Options FollowSymLinks
- AllowOverride None
- </Directory>
- <Directory /var/repos>
- Options Indexes FollowSymLinks MultiViews
- AllowOverride None
- Order allow,deny
- allow from all
- </Directory>
-
- ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
- <Directory "/usr/lib/cgi-bin">
- AllowOverride None
- Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
- Order allow,deny
- Allow from all
- </Directory>
-
- ErrorLog ${APACHE_LOG_DIR}/repos.ic.net.error.log
-
- # Possible values include: debug, info, notice, warn, error, crit,
- # alert, emerg.
- LogLevel warn
-
- CustomLog ${APACHE_LOG_DIR}/repos.ic.net.access.log combined
-
- Alias /doc/ "/usr/share/doc/"
- <Directory "/usr/share/doc/">
- Options Indexes MultiViews FollowSymLinks
- AllowOverride None
- Order deny,allow
- Deny from all
- Allow from 127.0.0.0/255.0.0.0 ::1/128
- </Directory>
-
- Include /etc/apache2/repos
-
- </VirtualHost>
Lo más destacable del archivo es el segundo directorio al que le hemos dado permisos:
<Directory /var/repos> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory>
Y la última línea antes de cerrar la etiqueta «VirtualHost»:
Include /etc/apache2/repos
Esta línea le dice a Apache que incluya también los archivos de configuración que haya en ese directorio. No incluyas esta línea si no quieres que tus repositorios se puedan acceder desde el navegador.
DESARROLLO DEL SCRIPT
Abrimos Gedit o el Bloc de notas si estás en Windows, o cualquier otro editor de textos o IDE de desarrollo que permita crear scripts en Bash y escribimos lo siguiente:
- #!/bin/bash
-
- #Variables necesarias
- DIR_BASE_REPOS='/var/repos'
-
- #Limpiamos la pantalla
- clear
-
- #Iniciamos el script
- echo 'Bienvenido al creador automatizado de repositorios.'
- echo 'A continuación se le realizarán algunas preguntas para realizar el proceso.'
- echo ' '
- echo '¿Está de acuerdo? (y/n):'
- read ACEPTADO
-
- #Comprobamos que el usuario está de acuerdo
- if [ "$ACEPTADO" == 'n' ] || [ "$ACEPTADO" != 'y' ]
- then
- #Si no está de acuerdo se le echa
- echo 'Hasta luego xD'
- exit 0
- else
-
- fi
- exit
Bien, en primer lugar indicamos que el archivo que vamos a crear es un script en Bash con la línea:
#!/bin/bash
A continuación creamos la variable que guardará el directorio donde guardaremos los repositorios:
DIR_BASE_REPOS='/var/repos/'
Después limpiamos la pantalla con el comando «clear».
Iniciamos el script y le preguntamos al usuario si acepta que se le realicen unas preguntas para realizar el proceso.
Si contenta con la letra «n» o con algo distinto a «y» entonces se le envía un mensaje de despedida y se finaliza el script:
#Iniciamos el script echo 'Bienvenido al creador automatizado de repositorios.' echo 'A continuación se le realizarán algunas preguntas para realizar el proceso.' echo ' ' echo '¿Está de acuerdo? (y/n):' read ACEPTADO #Comprobamos que el usuario está de acuerdo if [ "$ACEPTADO" == 'n' ] || [ "$ACEPTADO" != 'y' ] then #Si no está de acuerdo se le echa echo 'Hasta luego xD' exit 0
En el caso de que haya aceptado, añadiremos todos bloques de código siguientes dentro del «else» del condicional.
Le preguntamos el nombre del proyecto:
- #Se pide el nombre del repositorio o proyecto
- echo 'Nombre del nuevo repositorio o proyecto:'
- read NOMBRE_REPOSITORIO
-
- #Se comprueba que la variable no esté vacía
- if [ -z $NOMBRE_REPOSITORIO ]
- then
- echo 'No se ha escrito el nombre del repositorio. Adios.'
- exit 0
- fi
Si no se ha escrito el nombre del repositorio, el script finaliza. En caso contrario, en la variable «NOMBRE_REPOSITORIO» tendremos el nombre que el usuario le ha dado al proyecto.
Ahora le preguntamos qué repositorio desea usar para el proyecto:
- #Preguntamos el tipo de cvs a usar
- echo '¿Qué repositorio usará, git o subversion? (git/svn):'
- read TIPO_REPOSITORIO
-
- #Se comprueba que se ha elegido un cvs
- if [ "$TIPO_REPOSITORIO" != 'git' ] && [ "$TIPO_REPOSITORIO" != 'svn' ]
- then
- #Si no se ha elegido ninguno, por defecto será git
- echo "No se ha elegido repositorio."
- echo "Por defecto se usará git"
- $TIPO_REPOSITORIO='git'
- fi
Como necesitamos un repositorio para nuestro proyecto, si el usuario no ha elegido ninguno le ponemos por defecto Git.
Creamos varias variables con la url del repositorio:
- #Dependiendo del tipo de repositorio elegido creamos las variables correspondientes
- #a la url y la ruta en el sistema de archivos
- if [ "$TIPO_REPOSITORIO" == 'git' ]
- then
- URI_REPO="${DIR_BASE_REPOS}${NOMBRE_REPOSITORIO}.git/"
- URL_REPO="${NOMBRE_REPOSITORIO}.git"
- elif [ "$TIPO_REPOSITORIO" == 'svn' ]
- then
- URI_REPO="${DIR_BASE_REPOS}$NOMBRE_REPOSITORIO"
- URL_REPO="$NOMBRE_REPOSITORIO"
- fi
En «URL_REPO» se guarda el nombre del repositorio que, como puedes observar, si es Git se añade al final del nombre el texto «.git», tal como hemos acordado en nuestras convenciones.
A continuación creamos el directorio del repositorio:
- echo 'Creo el directorio del repositorio.'
-
- REPO_DIR=$(mkdir $URI_REPO)
-
- if [ $REPO_DIR ]
- then
- echo 'No se ha podido crear el directorio del repositorio'
- exit 0
- else
- echo 'Creado'
- fi
Si por alguna razón (quizá por los permisos) no se ha podido crear el repositorio, mostramos un mensaje y finalizamos el script.
Ahora creamos el repositorio propiamente dicho:
- #Se crea el repositorio utilizando la uri o ruta del sistema de archivos
- #donde se encuentra el repositorio.
- if [ $TIPO_REPOSITORIO == 'git' ]
- then
- REPO_GIT=$(git init --bare $URI_REPO)
-
- if [ "$REPO_GIT" == "Initialized empty Git repository in ${URI_REPO}" ]
- then
- echo 'Creado'
- else
- echo 'Algo falló.'
- echo $REPO_GIT
- exit 0
- fi
- elif [ $TIPO_REPOSITORIO == 'svn' ]
- then
- REPO_SVN=$( svnadmin create "$URI_REPO" )
-
- if [ -z $REPO_SVN ]
- then
- echo 'Creado'
- else
- echo 'Algo falló.'
- echo $REPO_SVN
- exit 0
- fi
- fi
Como tenemos dos tipos de repositorios (Git y Subversion) tenemos que comprobar cual de ellos a elegido el usuario. Seguidamente se ejecuta el comando correspondiente para crear el repositorio, y se comprueba si ha funciona o se ha producido algún error, con lo que habría que finalizar el script.
Tenemos que crear un directorio temporal para crear los directorios que hemos acordado en las convenciones (src y metrics):
- echo "Creo un directorio temporal para crear los directorios src y metrics"
-
- TEMP_DIR=$(mkdir /tmp/directory)
-
- if [ $TEMP_DIR ]
- then
- echo 'Algo falló'
- echo $TEMP_DIR
- exit 0
- else
- if [ $TIPO_REPOSITORIO == 'git' ]
- then
- TEMP_DIR='/tmp/directory'
- TEMP_DIR_TRUNK='/tmp/directory'
- elif [ $TIPO_REPOSITORIO == 'svn' ]
- then
- echo "Creo los directorios trunk, branches y tags para el repositorio de subversion."
- $(mkdir -pv /tmp/directory/trunk /tmp/directory/branch /tmp/directory/tag)
- TEMP_DIR='/tmp/directory'
- TEMP_DIR_TRUNK='/tmp/directory/trunk'
- fi
- fi
Observa que dependiendo del tipo de repositorio, la variable TEMP_DIR_TRUNK tendrá un subdirectorio «trunk», si es Subversion, o no si es Git.
Ahora los directorios de la covención:
- # Creamos los directorios para las métricas y la aplicación
- echo "Creo las carpetas para las métricas y la aplicación."
- mkdir ${TEMP_DIR_TRUNK}/src
- mkdir ${TEMP_DIR_TRUNK}/metrics
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpcs
- mkdir ${TEMP_DIR_TRUNK}/metrics/pdepend
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpcpd
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpmd
- mkdir ${TEMP_DIR_TRUNK}/changes
- mkdir ${TEMP_DIR_TRUNK}/changes/modules
- mkdir ${TEMP_DIR_TRUNK}/test
- mkdir ${TEMP_DIR_TRUNK}/test/functionals
- mkdir ${TEMP_DIR_TRUNK}/test/units
- mkdir ${TEMP_DIR_TRUNK}/db
- mkdir ${TEMP_DIR_TRUNK}/db/basic
- mkdir ${TEMP_DIR_TRUNK}/db/modules-dev
- mkdir ${TEMP_DIR_TRUNK}/db/modules
- mkdir ${TEMP_DIR_TRUNK}/phing
- mkdir ${TEMP_DIR_TRUNK}/sh
Si quieres que sea posible acceder a los repositorios de Subversion desde el navegador, deberás añadir las siguientes líneas:
- #Se modifica Apache para permitir ver el repositorio desde el navegador
- if [ $TIPO_REPOSITORIO == 'svn' ]
- then
- echo 'Creo la ruta en Apache para el repositorio'
- FILE_CONF="/etc/apache2/repos/${URL_REPO}.conf"
- echo "<Location /${URL_REPO}>" >> $FILE_CONF
- echo " DAV svn" >> $FILE_CONF
- echo " SVNPath /var/repos/${URL_REPO}" >> $FILE_CONF
- echo "</Location>" >> $FILE_CONF
-
- echo 'Recarga Apache'
- $( /etc/init.d/apache2 reload )
- fi
Este código crea un archivo «.conf» con los datos necesarios para poder navegar por el repositorio desde el navegador.
Para finalizar realizamos el primer commit del repositorio:
- echo 'Entro al directorio temporal y hago un commit'
- cd $TEMP_DIR
-
- #Se hace un primer commit con el código base
- if [ "$TIPO_REPOSITORIO" == 'git' ]
- then
- git init
- $(git update-server-info)
- git remote add origin "ssh://nombre_usuario@repos.ic.net/${DIR_BASE_REPOS}${URL_REPO}"
- $(git add *)
- git commit -m "Commit inicial"
- $(git push origin master)
- elif [ "$TIPO_REPOSITORIO" == 'svn' ]
- then
- $(svn checkout http://repos.ic.net/$NOMBRE_REPOSITORIO $URI_REPO)
- $(svn commit -m "Commit inicial" http://repos.ic.net/$NOMBRE_REPOSITORIO)
-
- fi
En el caso de que el repositorio sea Git tendrás que sustituir «nombre_usuario» por el nombre de un usuario que tenga permisos para acceder al directorio /var/repos.
Y aquí el script final:
- #!/bin/bash
-
- #Variables necesarias
- DIR_BASE_REPOS='/var/repos'
-
- #Limpiamos la pantalla
- clear
-
- #Iniciamos el script
- echo 'Bienvenido al creador automatizado de repositorios.'
- echo 'A continuación se le realizarán algunas preguntas para realizar el proceso.'
- echo ' '
- echo '¿Está de acuerdo? (y/n):'
- read ACEPTADO
-
- #Comprobamos que el usuario está de acuerdo
- if [ "$ACEPTADO" == 'n' ] || [ "$ACEPTADO" != 'y' ]
- then
- #Si no está de acuerdo se le echa
- echo 'Hasta luego xD'
- exit 0
- else
- #Se pide el nombre del repositorio o proyecto
- echo 'Nombre del nuevo repositorio o proyecto:'
- read NOMBRE_REPOSITORIO
-
- #Se comprueba que la variable no esté vacía
- if [ -z $NOMBRE_REPOSITORIO ]
- then
- echo 'No se ha escrito el nombre del repositorio. Adios.'
- exit 0
- fi
-
- #Preguntamos el tipo de cvs a usar
- echo '¿Qué repositorio usará, git o subversion? (git/svn):'
- read TIPO_REPOSITORIO
-
- #Se comprueba que se ha elegido un cvs
- if [ "$TIPO_REPOSITORIO" != 'git' ] && [ "$TIPO_REPOSITORIO" != 'svn' ]
- then
- #Si no se ha elegido ninguno, por defecto será git
- echo "No se ha elegido repositorio."
- echo "Por defecto se usará git"
- $TIPO_REPOSITORIO='git'
- fi
-
- #Dependiendo del tipo de repositorio elegido creamos las variables correspondientes
- #a la url y la ruta en el sistema de archivos
- if [ "$TIPO_REPOSITORIO" == 'git' ]
- then
- URI_REPO="${DIR_BASE_REPOS}${NOMBRE_REPOSITORIO}.git/"
- URL_REPO="${NOMBRE_REPOSITORIO}.git"
- elif [ "$TIPO_REPOSITORIO" == 'svn' ]
- then
- URI_REPO="${DIR_BASE_REPOS}$NOMBRE_REPOSITORIO"
- URL_REPO="$NOMBRE_REPOSITORIO"
- fi
-
- echo 'Creo el directorio del repositorio.'
-
- REPO_DIR=$(mkdir $URI_REPO)
-
- if [ $REPO_DIR ]
- then
- echo 'No se ha podido crear el directorio del repositorio'
- exit 0
- else
- echo 'Creado'
- fi
-
- #Se crea el repositorio utilizando la uri o ruta del sistema de archivos
- #donde se encuentra el repositorio.
- if [ $TIPO_REPOSITORIO == 'git' ]
- then
- REPO_GIT=$(git init --bare $URI_REPO)
-
- if [ "$REPO_GIT" == "Initialized empty Git repository in ${URI_REPO}" ]
- then
- echo 'Creado'
- else
- echo 'Algo falló.'
- echo $REPO_GIT
- exit 0
- fi
- elif [ $TIPO_REPOSITORIO == 'svn' ]
- then
- REPO_SVN=$( svnadmin create "$URI_REPO" )
-
- if [ -z $REPO_SVN ]
- then
- echo 'Creado'
- else
- echo 'Algo falló.'
- echo $REPO_SVN
- exit 0
- fi
- fi
-
- echo "Creo un directorio temporal para crear los directorios src y metrics"
-
- TEMP_DIR=$(mkdir /tmp/directory)
-
- if [ $TEMP_DIR ]
- then
- echo 'Algo falló'
- echo $TEMP_DIR
- exit 0
- else
- if [ $TIPO_REPOSITORIO == 'git' ]
- then
- TEMP_DIR='/tmp/directory'
- TEMP_DIR_TRUNK='/tmp/directory'
- elif [ $TIPO_REPOSITORIO == 'svn' ]
- then
- echo "Creo los directorios trunk, branches y tags para el repositorio de subversion."
- $(mkdir -pv /tmp/directory/trunk /tmp/directory/branch /tmp/directory/tag)
- TEMP_DIR='/tmp/directory'
- TEMP_DIR_TRUNK='/tmp/directory/trunk'
- fi
- fi
-
- # Creamos los directorios para las métricas y la aplicación
- echo "Creo las carpetas para las métricas y la aplicación."
- mkdir ${TEMP_DIR_TRUNK}/src
- mkdir ${TEMP_DIR_TRUNK}/metrics
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpcs
- mkdir ${TEMP_DIR_TRUNK}/metrics/pdepend
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpcpd
- mkdir ${TEMP_DIR_TRUNK}/metrics/phpmd
- mkdir ${TEMP_DIR_TRUNK}/changes
- mkdir ${TEMP_DIR_TRUNK}/changes/modules
- mkdir ${TEMP_DIR_TRUNK}/test
- mkdir ${TEMP_DIR_TRUNK}/test/functionals
- mkdir ${TEMP_DIR_TRUNK}/test/units
- mkdir ${TEMP_DIR_TRUNK}/db
- mkdir ${TEMP_DIR_TRUNK}/db/basic
- mkdir ${TEMP_DIR_TRUNK}/db/modules-dev
- mkdir ${TEMP_DIR_TRUNK}/db/modules
- mkdir ${TEMP_DIR_TRUNK}/phing
- mkdir ${TEMP_DIR_TRUNK}/sh
-
- #Se modifica Apache para permitir ver el repositorio desde el navegador
- if [ $TIPO_REPOSITORIO == 'svn' ]
- then
- echo 'Creo la ruta en Apache para el repositorio'
- FILE_CONF="/etc/apache2/repos/${URL_REPO}.conf"
- echo "<Location /${URL_REPO}>" >> $FILE_CONF
- echo " DAV svn" >> $FILE_CONF
- echo " SVNPath /var/repos/${URL_REPO}" >> $FILE_CONF
- echo "</Location>" >> $FILE_CONF
-
- echo 'Recarga Apache'
- $( /etc/init.d/apache2 reload )
- fi
-
- echo 'Entro al directorio temporal y hago un commit'
- cd $TEMP_DIR
-
- #Se hace un primer commit con el código base
- if [ "$TIPO_REPOSITORIO" == 'git' ]
- then
- git init
- $(git update-server-info)
- git remote add origin "ssh://nombre_usuario@repos.ic.net/${DIR_BASE_REPOS}${URL_REPO}"
- $(git add *)
- git commit -m "Commit inicial"
- $(git push origin master)
- elif [ "$TIPO_REPOSITORIO" == 'svn' ]
- then
- $(svn checkout http://repos.ic.net/$NOMBRE_REPOSITORIO $URI_REPO)
- $(svn commit -m "Commit inicial" http://repos.ic.net/$NOMBRE_REPOSITORIO)
-
- fi
- fi
- exit
Este es el script inicial para crear repositorios en Git y Subversion en nuestro servidor de integración continua. Más adelante iremos añadiendo nuevas tareas como los hooks de los repositorios, los archivos ignore, el código base de los frameworks o cms, unirlos con Jenkins, etc, etc, etc.