Protegiendo repositorios de Git con Latch y Latch-cmd (parte 2)

En la anterior entrada vimos cómo crear una aplicación en Latch para proteger repositorios de Git. En esta vamos a ver cómo completar la integración creando un hook que se ejecutará al realizar un push a un repositorio y comprobará si la rama está bloqueada o no antes de permitir dicho push.

Paso 2: Definir un fichero de ramas y operaciones

Antes de definir el hook necesitamos realizar un paso previo. Cuando se haga un push al repositorio, el hook determinará qué repositorio es (el directorio donde está alojado) y la rama contra la que se está haciendo el push. Una vez obtenida esta información, se debe asociar ese nombre de repositorio y rama a un ID de operación de Latch (las que hemos definido en el Paso 1). Para ello, vamos a crear un sencillo fichero de texto plano donde definiremos esta relación. El fichero tendrá una línea por cada repositorio y rama con la siguiente estructura:

NombreRepositorio:NombreRama:IDOperacionLatch

Así pues, para el ejemplo que vimos en la anterior entrada, el fichero quedaría de la siguiente manera:

/srv/git/my_repo:master:QJ8WFHsFcz8enMzBvyuP
/srv/git/my_repo:dev:gHu3bpuyqLWPCQv8jB34

Podemos llamar a este fichero como queramos (en mi caso lo voy a llamar git-latch.accounts) y guardarlo en cualquier directorio del servidor de Git. Los IDs de operación se pueden consultar en la sección operaciones de nuestra aplicación, en el área de desarrollador de Latch:

Operaciones

Hay que tener en cuenta que, aunque hayamos definido los repositorios y ramas en Latch, hasta que no los añadamos a este fichero no serán utilizados. Con este fichero definido ya podemos crear nuestro hook y empezar a proteger repositorios de Git.

Paso 3: Definir el hook de Git

Para poder proteger un repositorio de Git con Latch necesitamos definir un hook que se ejecute en cada push a dicho repositorio. Los hooks son scripts escritos en bash que se ejecutan en respuesta a alguna operación de Git. Existen hooks del lado de cliente como pre-commit que se ejecuta antes de cada commit y de lado del servidor como update, que se ejecuta para cada rama al realizar un push. En nuestro caso vamos a definir un hook de tipo update.

En Git, definir un hook para un repositorio es tan sencillo como crear un script bash llamado igual que el hook (en nuestro caso update.sh) y guardarlo en el directorio /hooks del repositorio. Así pues, si nuestro repositorio está en el directorio /srv/git/my_repo deberemos crear un fichero update.sh en /srv/git/my_repo/hooks.

El contenido de nuestro hook será el siguiente:

#!/bin/sh
#
# An update hook to prevent pushes against a repository using latch and latch-cmd (https://github.com/millenc/latch-cmd)
# author: Mikel Pintor (millen@gmail.com)
#
 
# --- Variables
LATCH_APP="" # Your AppID
LATCH_SECRET="" # Your Secret Key
LATCH_ACCOUNT="" # Your Account ID
GIT_LATCH_ACCOUNTS="/etc/git-latch/git-latch.accounts" # Path to the accounts file
GIT_LATCH_SEP=":"
 
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
 
# --- Safety check
if [ -z "$GIT_DIR" ]; then
    echo "Don't run this script from the command line." >&2
    echo " (if you want, you could supply GIT_DIR then run" >&2
    echo "  $0   )" >&2
    exit 1
fi
 
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
    echo "Usage: $0   " >&2
    exit 1
fi
 
# --- Get the current repository directory
repo_dir="$( pwd )"
 
# --- Get the current branch
branch=`echo $refname | grep -oP "^refs/heads/\K\w+" | head -1`
 
# --- Get the operation ID for this repository and branch
operation=`grep -oP "^$repo_dir$GIT_LATCH_SEP$branch$GIT_LATCH_SEP\K\w+" $GIT_LATCH_ACCOUNTS | head -1`
 
if [ -n "$operation" ]
then
    # --- Check Latch status for this repository and branch
    # --- If the status is off (latch is on) exit with a non-zero value to prevent the push from happening
    status=`latch-cmd app operation status --app="$LATCH_APP" --secret="$LATCH_SECRET" --account="$LATCH_ACCOUNT" --operation="$operation" --bare`
    if [ $status = "off" ]; then
        echo "This repository/branch is protected by Latch and is not currently accepting new pushes. Please try again later or contact the repository's administrator."
        exit 1
    fi
fi
 
# --- Finished
exit 0

En este fichero deberemos configurar las siguientes variables (en las primeras líneas):

  • LATCH_APP: ID de nuestra aplicación de Latch.
  • LATCH_SECRET: Clave secreta de la aplicación.
  • LATCH_ACCOUNT: ID de la cuenta que pareamos en el Paso 1.
  • GIT_LATCH_ACCOUNTS: Ruta al directorio creado en el Paso 2.

NOTA: Se podrían utilizar también variables de entorno para definir esta configuración y evitar añadir estos valores directamente en el script. Se ha hecho de este modo por cuestiones de claridad.

Una vez editada esta información deberemos guardar este fichero udpate.sh en el directorio /hooks de todos los repositorios que queramos proteger con Latch. Cuando se realice un push a uno de estos repositorios el script realizará las siguientes operaciones:

  1. Obtener el nombre del repositorio (directorio) y de la rama.
  2. Comprobar si en el fichero GIT_LATCH_ACCOUNTS se ha definido una operación para este repositorio y rama. En caso afirmativo obtener el ID.
  3. Consultar el estado de esta operación (utilizando su ID) para la cuenta definida en LATCH_ACCOUNT utilizando Latch-cmd (instrucción status=latch-cmd app operation status –app=”$LATCH_APP” –secret=”$LATCH_SECRET” –account=”$LATCH_ACCOUNT” –operation=”$operation” –bare)
  4. Si el estado es off (el pestillo está activo) mostrar un mensaje de error y devolver un valor distinto de 0 (exit 1) para indicar a Git que no permita el push.
  5. Si el estado es on o el repositorio no está protegido por Latch devolver el valor 0 para permitir el push.

Así pues, si por ejemplo activamos el pestillo de la rama master de nuestro repositorio e intentamos hacer un push contra esa rama obtendremos el siguiente resultado:

vagrant@precise64:~/my_repo$ git push origin master
vagrant@precise64:~/my_repo$ git push origin master
Counting objects: 5, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 288 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
remote: This repository/branch is protected by Latch and is not currently accepting new pushes. Please try again later or contact the repository's administrator.
remote: error: hook declined to update refs/heads/master
To /srv/git/my_repo/
 ! [remote rejected] master -> master (hook declined)
error: failed to push some refs to '/srv/git/my_repo/'

Y el siguiente aviso en la aplicación móvil:

Aviso

Si desactivamos el pestillo y volvemos a hacer el push, éste se realizará con normalidad.

Paso 4 (Opcional): Aplicar el hook a todos los repositorios

Como hemos visto, para proteger un repositorio con Latch debemos copiar el hook update.sh en su directorio /hooks. Esto puede no suponer un problema la primera vez que se hace, pero sí que es un incordio si queremos hacerlo para todos los nuevos repositorios que vayamos creando en nuestro servidor. Para solucionarlo podemos utilizar una plantilla de Git.

El mecanismo es sencillo, en algún lugar de nuestro sistema creamos un directorio (por ejemplo /home/git/git_template) y guardamos en un subdirectorio /hooks del mismo nuestro fichero update.sh (la ruta completa sería /home/git/git_template/hooks/update.sh). Una vez hecho, utilizamos el siguiente comando:

$ git config --global init.templatedir /home/git/git_template/

De esta manera estamos configurando Git para que cada vez que se cree un nuevo repositorio (con git init) se copie el contenido de la carpeta /home/git/git_template/hooks a su correspondiente directorio de /hooks. En este directorio de plantilla podemos también añadir otros hooks y configuraciones por defecto. Por supuesto seguiremos teniendo que definir las ramas y los IDs de operación en el fichero git-latch.accounts manualmente.

A partir de la versión 2.9 de Git también podemos utilizar la opción core.hooksPath para que todos los repositorios utilicen los hooks definidos en un directorio centralizado:

$ git config core.hooksPath /home/git/git_template/hooks

Conclusiones

En este par de entradas hemos visto una forma sencilla de proteger repositorios de Git utilizando Latch y Latch-cmd. Con este sistema podemos bloquear directamente desde el móvil repositorios y ramas contra pushes no deseados y, de esta forma, controlar las actualizaciones de código realizadas en dichos repositorios.

Eso es todo. Espero que os haya parecido interesante y ojalá os haya dado alguna idea para futuras integraciones o plugins de Latch. ¡Hasta la próxima!

The following two tabs change content below.

Mikel Pintor

Desarrollador web.

Latest posts by Mikel Pintor (see all)

Compartir: