Personal Website

My Web: MindEchoes.com

Thursday, October 21, 2010

Como manejar Post-Commit Web Hooks

Estaba buscando una forma de poder hacer que NINJA-IDE se actualizara automáticamente sin tener que andar haciendo nuevos instaladores, o paquetes con código que hubiera que bajarse, etc. Sino que fuera mucho más directo, simplemente notificara que hay actualizaciones y que prioridad tienen y si el usuario confirma baja los nuevos archivos y actualiza la aplicación.

El siguiente problema es que tampoco quería tener que ir armando la lista de archivos que deberían actualizarse a mano, porque se me podía olvidar alguno, pero más que nada porque seria molesto...

Entonces buscando encontré: "Post-Commit Web Hooks" de Google Code!!
NINJA-IDE tiene su  repositorio en Google Code, y si dentro del proyecto (cualquier de Google Code) van a la solapa de "Administer" y luego a la opción "Source" van a encontrar al final de la página esta opción para habilitar los "post-commit web hooks":

Básicamente que es lo que permite esta opción? simplemente que al subir código al repositorio se envié un request HTTP POST a la url que especificamos, la cual recibe un JSON con la siguiente estructura:

{
   "project_name": "atlas-build-tool",
   "repository_path": 
   "http://atlas-build-tool.googlecode.com/svn/",
   "revision_count": 1,
   "revisions": [
     { "revision": 33,
       "url": 
   "http://atlas-build-tool.googlecode.com/svn-history/r33/",
       "author": "mparent61",
       "timestamp":   1229470699,
       "message": "working on easy_install",
       "path_count": 4,
       "added": ["/trunk/atlas_main.py"],
       "modified": ["/trunk/Makefile", "/trunk/constants.py"],
       "removed": ["/trunk/atlas.py"]
     }
   ]
 }

Más información acerca de: PostCommitWebHook

Y teniendo esta información puedo hacer tranquilamente una aplicación en Google App Engine que almacene esos datos esperando que yo los acepte y los catalogue con algún tipo de prioridad para que luego puedan ser notificados a las distintas aplicaciones clientes de NINJA-IDE.

Entonces para la aplicación de Google App Engine es necesario definir en el archivo "app.yaml" los distintos handlers:

handlers:
- url: /static
  static_dir: static
  
- url: /favicon.ico
  static_files: static/img/favicon.ico
  upload: static/img/favicon.ico
  
- url: /admin
  script: administrator.py
  login: admin
  
- url: /adminrpc
  script: administrator.py
  login: admin
  
- url: /commit
  script: commit.py

El primer handler especifica donde van a estar los recursos (que usualmente se usa para los archivos de javascript, imágenes, y css).
El segundo es el icono que le vamos a dar a la aplicación.
El tercero "/admin", va a ser donde vamos a entrar a través de "http://ninja-ide.appspot.com/admin" para el cual se necesita permiso de administrador y es donde voy a ver cuales son los nuevos commits y les voy a poder setear la prioridad.
El cuarto "/adminrpc" es adonde van a estar dirigidas las llamadas RPC (con permiso de administrador).
Y el quinto "/commit" es el que va a recibir los request HTTP POST de Google Code y tiene permiso para que pueda ser accedido por cualquiera.

En mi caso decidí almacenar cada una de las revisiones que me lleguen de Google Code como una entrada en la Base de Datos, para lo cual cree la siguiente estructura dentro del módulo "datamodel":

from google.appengine.ext import db

class Updates(db.Model):
    timestamp = db.IntegerProperty()
    modified = db.StringListProperty()
    removed = db.StringListProperty()
    message = db.StringProperty()
    marked = db.BooleanProperty()
    priority = db.BooleanProperty()

Y luego solo me faltaría crear mi módulo "commit.py" al que van a estar dirigidas las request a "http://ninja-ide.appspot.com/commit".

Para que no cualquiera pueda enviar datos a "http://ninja-ide.appspot.com/commit" (por si alguien ya lo estaba pensando :P) se usa autenticación por cada request recibido.
Post-Commit Web hook hace uso de HMAC-MD5 para autenticar las solicitudes. Cada proyecto tiene una única "llave secreta", visible para los propietarios del proyecto en el Tab Administer/Source. Esta clave se utiliza como semilla en el algoritmo HMAC-MD5. Cada encabezado de la solicitud POST contiene un HMAC utilizada para autenticar la carga. Este valor es una cadena hexadecimal de 32 caracteres que figuran en la cabecera de "Google-Code-Project-Hosting-Hook-Hmac". Mediante la combinación de la clave secreta de su proyecto y el valor de la solicitud POST de HMAC, puede autenticar la solicitud.

#import para autenticacion
import hmac

#Agregamos los imports necesarios
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from django.utils import simplejson
#Agregamos la estructura definida para la BD
from datamodel import Updates

class Commit(webapp.RequestHandler):

    def post(self):
        # De: Administer/Source tab
        project_secret_key = "123456789qwertf"

        #Autenticamos la solicitud
        m = hmac.new(project_secret_key)
        m.update(self.request.body)
        digest = m.hexdigest()
        if digest == self.request.headers[
          "Google-Code-Project-Hosting-Hook-Hmac"]:
            #Si la soliciud fue válida creamos 
            #el diccionario con los datos
            data = simplejson.loads(self.request.body)
            #Recorremos las distintas revisiones
            for rev in data["revisions"]:
                #Creamos por cada revision un 
                #Objeto Updates
                up = Updates()
                up.timestamp = rev['timestamp']
                up.message = rev['message']
                up.marked = False
                up.priority = False
                up.modified = rev.get('added', []) + \
                                 rev.get('modified', [])
                up.removed = rev.get('removed', [])
                #Guardamos el Objeto Updates en la BD
                up.put()


def main():
    application = webapp.WSGIApplication([
        ('/commit', Commit)
        ], debug=True)
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

Y si colocamos en Google Code la URL para el Post-Commit Web Hook como se mostró al principio, ya tenemos una aplicación de Google App Engine que recibe notificaciones de Google Code y las almacena para su posterior utilización!! :D

Esta es la pantalla de Administración que tengo para NINJA-IDE:

4 comments:

kikorb said...

Muy interesante y con ena explicación bien detallada.

Lo guardo para futuras necesidades

Elías said...

soy un navo en el tema, pero la clave que figura en el screenshot es la clave del proyecto? esta bien que la muestres o metiste la pata?

Lo mas seguro que mi pregunta es Muy pava! :P

Otra cosa, que pasa si google eleimina esta opcion Hook de Google code, o bien si realiza cambios? NINJA se queda sin actualizaciones?
Lo digo por el tema de que GoogleGroups ahora limitara la carga de archivos. etc.

Elías said...

soy un navo en el tema, pero la clave que figura en el screenshot es la clave del proyecto? esta bien que la muestres o metiste la pata?

Lo mas seguro que mi pregunta es Muy pava! :P

Otra cosa, que pasa si google eleimina esta opcion Hook de Google code, o bien si realiza cambios? NINJA se queda sin actualizaciones?
Lo digo por el tema de que GoogleGroups ahora limitara la carga de archivos. etc.

Diego Sarmentero said...

jejej esta claro que no es la clave real..... si fíjate que es: 123456789qwertf, una tecla al lado de la otra apreté.

El hook no tendría porque eliminarlo sino genera problemas de almacenamiento en google code ni nada de eso, simplemente hace un request cuando hay un push, asi como manda el mail de notificación... esto se usa mucho en realidad para continuos integration y cosas así, yo agarre eso y lo uso para otro fin nomas.