Eramos pocos y parió la abuela (cuarta parte, agregando webhooks)

Webhooks

Vamos con la cuarta entrega de la saga: cómo hacer que mis cambios en GitHub se publiquen automáticamente en mi repo privado.

En la tercera parte, vimos cómo hacer un script para actualizar nuestro Gitlab desde nuestro repo original de GitHub, y luego regenerar nuestro satis. De esta forma cada vez que tocamos nuestra extensión, se va a ver reflejado en nuestro repo privado.

El problema es que como no sabemos cuando nuestro repo original se actualiza, entonces tuvimos que poner un cron que cada cierto tiempo actualice todo, claro que ese cierto tiempo es 100% caprichoso.

Lo que vamos a hacer ahora, es lograr que solo se actualice cuando es necesario, es decir cuando el repo original de GitHub fue modificado.

Para eso vamos a utilizar los webhooks que nos provee github.

La idea entonces es agregar un webhook a nuestro repo en GitHub. De esta forma, cada vez que cambia algo en el repo, llama a nuestro webhook para avisar que algo cambió, y nosotros vamos a poder hacer lo que queremos hacer, o sea… regenerar todo nuestro monstruito.

Para todo esto vamos a necesitar un endpoint en el mismo servidor que tenemos el Gitlab/Satis, por ejemplo podemos poner un endopoint extra en nuestro satis.

Luego, vamos a escribir un par de phps.

El primero, yo lo llame deployer.php, y lo que hace (básicamente) es chequear que la llamada viene de GitHub y que contiene una token válida, y si todo esta bien, crea un archivo vacío dentro de un directorio con el nombre del repo asociado con la extensión .git.

Con eso alcanza para que alguien después sepa que ese repo tiene que ser actualizado.

Podríamos hacer algo mas sofisticado, guardar en base de datos o lo que se les ocurra, pero la manera mas fácil, me pareció, es simplemente guardarme el nombre del repo para luego actualizarlo, y para eso basta con un touch de un archivo con el nombre del repo.

<?php
$content = file_get_contents("php://input");
$json = json_decode($content, true);
$file = fopen(LOGFILE, "a");
$time = time();
$token = false;
// retrieve the token
if (!$token && isset($_SERVER["HTTP_X_HUB_SIGNATURE"])) {
list($algo, $token) = explode("=", $_SERVER["HTTP_X_HUB_SIGNATURE"], 2) + array("", "");
} elseif (isset($_SERVER["HTTP_X_GITLAB_TOKEN"])) {
$token = $_SERVER["HTTP_X_GITLAB_TOKEN"];
} elseif (isset($_GET["token"])) {
$token = $_GET["token"];
}
// log the time
date_default_timezone_set("UTC");
fputs($file, date("d-m-Y (H:i:s)", $time) . "\n");
// function to forbid access
function forbid($file, $reason) {
// explain why
if ($reason) fputs($file, "=== ERROR: " . $reason . " ===\n");
fputs($file, "*** ACCESS DENIED ***" . "\n\n\n");
fclose($file);
// forbid
header("HTTP/1.0 403 Forbidden");
exit;
}
// function to return OK
function ok() {
ob_start();
header("HTTP/1.1 200 OK");
header("Connection: close");
header("Content-Length: " . ob_get_length());
ob_end_flush();
ob_flush();
flush();
}
// Check for a GitHub signature
if (!empty(TOKEN) && isset($_SERVER["HTTP_X_HUB_SIGNATURE"]) && $token !== hash_hmac($algo, $content, TOKEN)) {
forbid($file, "X-Hub-Signature does not match TOKEN");
// Check for a GitLab token
} elseif (!empty(TOKEN) && isset($_SERVER["HTTP_X_GITLAB_TOKEN"]) && $token !== TOKEN) {
forbid($file, "X-GitLab-Token does not match TOKEN");
// Check for a $_GET token
} elseif (!empty(TOKEN) && isset($_GET["token"]) && $token !== TOKEN) {
forbid($file, "\$_GET[\"token\"] does not match TOKEN");
// if none of the above match, but a token exists, exit
} elseif (!empty(TOKEN) && !isset($_SERVER["HTTP_X_HUB_SIGNATURE"]) && !isset($_SERVER["HTTP_X_GITLAB_TOKEN"]) && !isset($_GET["token"])) {
forbid($file, "No token detected");
} else {
// check if pushed branch matches branch specified in config
if( array_key_exists('repository',$json)&&array('name',$json['repository'])) {
$repo = $json['repository']['name'];
fputs($file, "Refreshing repo $repo");
touch(DATADIR . $repo . '.git');
}
}
fputs($file, "\n\n" . PHP_EOL);
fclose($file);

Por otro lado, lo que llamé deploy.php

<?php
define("TOKEN", "anysectrettoken"); // The secret token to add as a GitHub or GitLab secret, or otherwise as https://www.example.com/?token=secret-token
define("LOGFILE", "deploy.log"); // The name of the file you want to log to.
define("DATADIR","./myrepostoupdate/");
require_once("deployer.php");

Bien, acá lo importante es que pongas tu propia secret token (que la vas a tener que especificar en GitHub cuando agreguemos el webhook) y un directorio donde van a estar los archivos cuyos nombres con repos vas actualizar en tu Gitlab/Satis.

¿Qué más vamos a necesitar? Un script muy simple que se fije si hay algún repo para actualizar y lo haga.

Si recordamos la tercera parte, tenemos un script que es capaz de refrescar y publicar un repo al que llamaremos /root/refreshrepo.sh.

!/bin/bash
cd /var/opt/gitlab/git-data/repositories/tuempresa/
rm -rf $1/*
git clone --bare git@github.com:tuempresa/$1 temp
chown -R git.git temp/
cp -R temp/* $1
rm -rf temp
if [ "$2" = "refresh" ] ; then
/usr/bin/gitlab-rake cache:clear
cd /var/www/html/satis
/usr/bin/php bin/satis -vvv build satis.json html
fi
cd

Ahora vamos a necesitar el script que revisa si un repo debe ser actualizado, al que llamaremos /root/refreshwebhook.sh

!/bin/bash
cd /var/www/html/satis/html/myrepostoupdate
for filename in *; do
if [ -f $filename ] ; then
/root/refreshrepo.sh $filename refresh
rm /var/www/html/satis/html/myrepostoupdate/$filename
fi
done

Y lo que vamos a hacer es meter en nuestra crontab, una linea del estilo:

*/5 * * * * /root/refreshwebhook.sh >> /root/refreshwebook.log

Para que cada 5 minutos se fije si hay algun repo para actualizar y lo haga.

Por último, lo que tenemos que hacer, es agregar el webhook en GitHub.

Vamos a ir a nuestro repo en GitHub y luego a Settings.

Configuración de nuestro repositorio en GitHub.

Y luego vamos a elegir Webhooks.

Configuración de un webhook en GitHub.

Vamos a agregar un webhook.

Agregando un webhook en GitHub.

Y vamos a rellenarlo así:

Configuración de nuestro webhook en GitHub.

En payload vamos a poner la url exacta de nuestro endpoint (atención: debe ser por https).

Vamos a elegir application/json en el content type. Y en secret vamos a poner la misma secretkey que pusimos en nuestro deploy.php.

Con eso estarías listo, no tenes más que precuparte por actualizar tu Satis y/o Gitlab cada vez que cambia algo en tu repo de GitHub.

Y con esto cerramos la serie de como distribuir tus extensiones pagas, que espero que haya sido de alguna ayuda, cualquier cosa, siempre podes preguntar por twitter y trataré de aclarar.

Publicado por Gonzalo Dominguez

Magento fanatic developer. Do not try and bend the spoon. That's impossible. Instead... only try to realize the truth. There is no spoon. @gonzalezuy