Mostrando entradas con la etiqueta linux. Mostrar todas las entradas
Mostrando entradas con la etiqueta linux. Mostrar todas las entradas

06 abril 2023

Cómo crear tus propias Actions para GitHub Actions

En  mi artículo sobre GitHub Actions vimos que se pueden crear flujos de trabajo completo simplemente usando unidades ya hechas, denominadas Actions, que se pueden encontrar en el GitHub Marketplace. 

En ese marketplace puedes encontrar muchísimas Actions listas para ser usadas, pero probablemente en algún momento te veas en la necesidad de hacer algo para lo que no haya aún Action alguna en el marketplace. En esos caso podrías usar tus propios scripts. De hecho, en aquel artículo ya utilicé un script (./ci_scripts/get_version.py) en la sección "Compartir datos entre pasos y flujos". El problema con ese enfoque es que si quieres hacer las mismas tareas en otro proyecto tendrás que copiar los scripts entre proyectos y adaptarlo. Lo suyo en realidad es transformar los scripts en Actios que puedan ser fácilmente reutilizables no sólo en tus proyectos sino también en los de otros desarrolladores.

Todas las Actions que se pueden encontrar en el marketplace se han desarrollado usando uno de los métodos que voy a explicar aquí. En realidad, si entrar en la página del marketplace de cualquier Action encontrarás un enlace, a mano derecha, al repositorio de git de esa Action, por lo que puedes analizar su código y ver cómo funciona.

Hay tres métodos principales para crear tu propio Action para GitHub:

  • Composite Actions: Son las más sencillas y rápidas de desarrollar, pero tienen que cumplir la condición de que se basen en un script autosuficiente que no requiera que se instalen dependencias adicionales. Es decir, deben ser capaces de funcionar con lo que un Linux estándar ofrezca de base.
  • Docker Actions: Si tu Action va a necesitar la instalación de alguna dependencia para funcionar, entonces tendrás que usar esta opción.
  • Javascript Actions: Bueno... es cierto que también puedes desarrollar tus Actions usando javascript, pero aborrezco ese lenguaje, así que no voy a desarrollar esta opción en este artículo.
El problema con las dependencias de las Actions es que pueden contaminar el entorno donde se vaya a usar la Action. Si no tienes cuidado, y optas por una Composite Action instalando dependencias, estas pueden colisionar con la de la app que quieras compilar con la Action. Esa es la razón por la que resulta imprescindible encapsular nuestra Action y sus dependencias para que sean independientes del entorno en el que compilemos la app. Por supuesto, esto no será un problema si el propósito explícito de la Action es precisamente instalar algo para preparar el entorno. Hay Actions, por ejemplo, para instalar y configurar Pandoc para usarlo en el workflow. El problema surge cuando se supone que el propósito explícito de tu Action es algo ajeno a la instalación de paquete alguno (por ejemplo, copiando ficheros) y sin embargo acaba instalando algo de manera inadvertida comprometiendo el entorno del flujo de trabajo. Por eso, lo mejor es que cuando necesites instalar algo para tu Action lo hagas en un contenedor de Docker y hagas que el script de tu Action se ejecute en ese contenedor de Docker, de manera que sus dependencias sean completamente independientes de los paquetes instalados en el entorno del flujo.  

Composite Actions

Si a tu Action le basta con un puñado de comandos de Bash o con un script de Python que utilice exclusivamente la librería estándar, entonces las Composite Actions son tu opción.

Como ejemplo de una Composite Action, vamos a revisar cómo funciona mi Action rust-app-version. Esa Action busca un fichero de configuración Cargo.toml y lee qué versión consta en él para la aplicación de Rust. La cadena de esa versión es precisamente la salida del Action, de manera que puedas usar esa cadena en otros puntos de tu flujo, por ejemplo para etiquetar una nueva release en GitHub. Esta Action en concreto sólo usa módulos disponibles en la distribución estándar de Python. Es decir, que no tiene que instalar nada en la máquina virtual de GitHub Actions (el runner). Es cierto que si analizas el repositorio de rust-app-version verás un fichero requierements.txt pero este sólo es para instalar paquetes necesarios para el testeo unitario de la Action (recuerda que una Action no deja de ser una app más).

Para tener tu propia Action, lo primero es crear un repositorio de GitHub para albergarlo. Allí podrás ubicar los poquitos ficheros que se necesitan en realidad para hacer funcionar tu Action.

En la raíz del repositorio debes crear un fichero que se llame "action.yml". Este fichero es realmente importante ya que es el que le da forma a tu Action. Debes conseguir que tus usuarios puedan pensar en tu Action como si fuera una caja negra, como si fuera un módulo en el que metes entradas (inputs) y recibes salidas (outputs). Precisamente esos inputs y outputs se definen en el fichero action.yml.

Si lees el fichero action.yml file de rust-app-version verás que esa Action sólo necesita un input denominado "cargo_toml_folder" y que en realidad el input es opcional, ya que si lo dejamos vacío asumirá el valor de ".":



Los outputs son algo diferentes, dado a que deben referirse al output de un caso específico de tu Action:


En la última sección especificamos que esta Action tendrá un único output llamado "app_version" y que ese output será el output llamado "version" de un paso con el valor de id "get-version".

Esos inputs y outputs definen lo que tu Action consume y ofrece, es decir, lo que tu Action hace. El cómo lo hace tu Action se define bajo la etiqueta "runs:". Ahí es donde defines que tu Action es una composite, en la que vas a llamar a una secuencia de pasos. Este ejemplo en particular sólo tiene un paso, pero puedes tener todos los que necesites:



Fíjate que en la línea 22 es donde el paso recibe el id "get-version". Ese nombre es importante para poder referirse a este paso en particular desde la configuración de los outputs.

La línea 24 es donde se ejecuta el comando. Aquí sólo he ejecutado un comando, pero si necesitases ejecutar varios comandos dentro del mismo paso bastaría con usar una barra después de la etiqueta run: "run: |". De esa manera, señalas que las siguientes líneas, que se indenten bajo la etiqueta "run:", son líneas separadas de comandos que deben ser ejecutadas secuencialmente.

El comando de la línea 24 también es interesante por 3 cosas:
  • Llama a un script situado en el repositorio de nuestro Action. Para referirse a la raíz del repositorio del Action hay que usar la variable de entorno github.action_path. Lo bueno es que aunque nuestro script esté en el repositorio del Action GitHub lo ejecuta de manera que tenga visibilidad del repositorio del flujo de trabajo desde el que se llama a la Action. Nuestro script (el del Action) verá los ficheros del repositorio del flujo como si hubiese sido ejecutado desde su raíz.
  • Al final de la línea se puede ver cómo se pueden utilizar los inputs, a través del contexto inputs.
  • Lo más raro de esa línea es probablemente como configuras el output del paso. Para fijar el output de un paso de bash debes hacer echo "::set-output name=<ouput_name>::<output_value>". En este caso, name es version y su valor es lo que  get_version.py imprime en la consola. Ten en cuenta que output_name se usa para recuperar el output, una vez que el paso finaliza, mediante ${{ steps.<id>.outputs.<output_name> }}, en este caso ${{ steps.get_version.outputs.version }}
Aparte de eso, no hay más que configurar los metadatos del Action. Para eso, no se necesitan más que unas pocas líneas:


Ten en cuenta que el valor de "name:" es el nombre que tendrá tu Action en el marketplace. El otro parámetro "description:" es la explicación corta que se mostrará junto al nombre en los resultados de las búsquedas del marketplace. Por último, la etiqueta "branding:" sirve para configurar el icono (de los disponibles en la suite Feather icon) y el color de este que representarán a nuestra Action en el marketplace.

Con esas 24 líneas en el fichero action.yml y tu script en su ruta respectiva ( en este caso la subcarpeta 
 rust_app_version/ ), ya puedes usar tu Action. Sólo necesitas pulsar el botón que aparecerá en tu repositorio para publicar el Action en el marketplace. De todos modos, antes de lanzarte a hacerlo te recomiendo que te leas este artículo hasta el final porque tengo algunos consejos que sospecho que te serán útiles.

Una vez publicada, tu Action ya será visible para el resto de los usuarios de GitHub. Además, se creará automáticamente la página de tu Action en el marketplace. Para usar una Action como esta lo único que tienes que hacer es incluir en tu flujo una configuración como la siguiente:



Docker actions

Si tu Action necesitase instalar alguna dependencia entonces deberías empaquetarla dentro de un contenedor de docker. De esa manera las dependencias de la Action no se mezclarán con las dependencias del flujo desde el que se llame a la Action.

Como ejemplo de una Docker Action, vamos a revisar cómo funciona mi Action markdown2man. Esta Action coge un fichero README.md y lo convierte en un manpage. Con esta Action no necesitas mantener dos fuentes diferentes para la documentación de cómo usar tu aplicación de consola. En vez de eso, puedes usar un único fichero README.md para documentar el uso de tu aplicación y generar automáticamente a partir de él el manpage.

Para hacer esa conversión markdwon2man necesita que se instale el paquete Pandoc. Además, Pandoc tiene también sus respectivas dependencias por lo que instalar todo eso en el runner del usuario puede romper su flujo de trabajo. En vez de eso, lo que haremos será instalar esas dependencias en una imagen de docker y ejecutar nuestro script desde esa imagen. Recuerda que docker te permite ejecutar scripts desde el contenedor interactuando con los ficheros del anfitrión del contenedor.

Igual que en el caso de las Composite Action, tendremos que crear un fichero action.yml en la raiz del repositorio del Action. Allí configuraremos nuestros metadatos, los inputs y los outputs tal y como hicimos para las Composite Actions. La diferencia en este caso es que concretamente markdown2man Action no emite output alguno, por lo que esa sección se omite. La sección para "runs:" también es diferente:


En esa sección definimos que esta Action es del tipo docker (en "using:"). Hay dos maneras de usar una imagen de docker en tu Action: 
  • Generar una imagen específicamente para esa Action y guardarla en el registro de docker de GitHub. En ese caso usaríamos la etiqueta "image: Dockerfile".
  • Usar una imagen del registro de DockerHub registry. Para hacer eso hay que usar la etiqueta "image: <dockerhub_user>:<docker-image-tag>".
Si la imagen que vamos a construir estuviera pensada para ser usada sólo en una Action de GitHub yo iría por el primer enfoque (el del Dockerfile). En el caso de markdwon2man elegí la opción del Dockerfile. Cada vez que actualicemos nuestro Dockerfile, GitHub se encargará de de construir una nueva imagen y guardarla en su registro para poder ofrecerla rápidamente a los Actions que hagan uso de esa imagen. Recuerda que un Dockerfile es una especie de receta para "cocinar" una imagen, por lo que los comandos que se incluyan en él solo se ejecutarán cuando se construya la imagen (cuando se "cocine"). Una vez construida la imagen, el único comando que se ejecutará es el que fijemos en la etiqueta "entrypoint" al llamar al contenedor (y pasarle argumentos) con el comando "docker run".

La etiqueta "args:" define los parámetros que se pasarán al script de nuestro contenedor. Probablemente uses alguno de los input de tu Action para pasárselo como parámetro al script del contenedor. Ten en cuenta que, como pasaba con las Composite Actions, los ficheros del repositorio del usuario que llama al Action también son visibles para el contenedor de dicho Action.

Al tener que definir un Dockerfile, las Docker Actions pueden parecer más complejas que las Composite Actions. Sin embargo, el fichero Dockerfile de markdown2man es bastante sencillo. Como nuestro script markdown2man está escrito en python, hice que la imagen se basase en la imagen de docker oficial para python 3.8:



Después, fijé los metadatos de la imagen:


Para configurar tu image, por ejemplo para instalar cosas, puedes usar los comandos RUN:


El comando ENV genera variables de entorno que pueden ser utilizados en otros comandos del Dockerfile:


Puedes usar el comando COPY para introducir ficheros de tu repositorio en la imagen. En este caso lo utilicé para copiar el fichero requirements.txt desde el repositorio a dentro de la imagen. Tus scripts se pueden incluir dentro de imagen, desde el repositorio del Action, de la misma manera:


Una vez que copié el script dentro de la imagen, lo hice ejecutable y lo enlacé desde la carpeta /usr/bin para incluirlo en el path del sistema:


Después de eso, se configura el script como el entrypoint de la imagen, de manera que se ejecute en cuanto se arranque la imagen con los argumentos mencionados en la etiqueta "args:" del fichero action.yml.



Puedes probar esa imagen en tu ordenador construyendo la imagen desde el Dockerfile y ejecutándola como un contenedor:

dante@Camelot:~/$ docker run -ti -v ~/your_project/:/work/ dantesignal31/markdown2man:latest /work/README.md mancifra


dante@Camelot:~$

Si el script del contenedor procesa ficheros del repositorio, nos vendrá bien probarlo localmente montando una carpeta de nuestro ordenador con el flag -v. Los dos últimos argumentos del ejemplo  (work/README.md y mancifra) son los argumentos que se le pasan al entrypoint.

Y eso es todo. Una vez que lo hayas probado todo ya puedes publicar tu Action y usarla en tus flujos:


Con una llamada como esa, se creará un fichero man llamado cifra.2.gz en una carpeta llamada man. Si la carpeta manpage_folder no existiese, entonces markdown2man la crearía para ti.

Tus Action son código de primera clase

Aunque tus Actions probablemente sean pequeñas, deberías tratarlas como lo harías con una app completa. Ten en cuenta que mucha gente encontrará tus Action a través del marketplace y los incluirá en sus flujos. Un error en tu Action puede romper los flujos de mucha gente, por eso hay que ser diligente y probar tu Action como harías con cualquier otra app.

Por eso, con mis Actions sigo la misma aproximación que en otros proyectos y configuro un flujo de GitHub para ejecutar tests contra cualquier push en la rama staging del repositorio de mi Action. Solo cuando los tests se ejecutan con éxito, se produce el merge automático de la rama staging con la principal y se genera una nueva release de la Action.

Vamos a usar el flujo de markdown2man como ejemplo. En él puedes ver que hay dos tipos de tests:

  • Tests unitarios: Comprueban el script de python en el que se basa markdown2man.

  • Tests de integración: Comprueban el comportamiento de markdown2man como Action. Aunque tu Action no se haya publicado aún, la puedes instalar desde un flujo del mismo repositorio (como puedes ver en las líneas 42 a 48). Así que lo que yo hago es llamar al Action precisamente desde la rama staging que estoy probando y uso ese Action con un fichero markdown que tengo preparado en la carpeta de test. si se genera un fichero manpage, entonces el test funcional tiene éxito (línea 53). Tener la oportunidad de probar tu Action contra tu propio repositorio está muy bien, ya que te permite comprobar cómo utilizaría la gente tu Action, sin necesidad de publicarlo.

Además de probarlo, deberías escribir un fichero README.md para tu Action, con el fin de explicar con detalle como usarlo. En ese documento deberían incluir al menos la siguiente información: 
  • Una descripción de los que hace el Action.
  • Los input y outputs obligatorios.
  • Los inputs y outputs opcionales.
  • Cualquier contraseña que el Action pudiera necesitar que se le facilite.
  • Que variables de entorno utiliza el Action.
  • Un ejemplo de cómo usar tu Action en un flujo.
Por último, yo añadiría también un fichero LICENSE, explicando los términos legales de uso de tu Action.

Conclusión

El punto fuerte de GitHub Actions es el alto grado de reusabilidad y compartición que promueve. Cada vez que te encuentres repitiendo el mismo conjunto de comandos, será el momento de hacer un Action con esos comandos y compartirlos a través del Marketplace. Si lo haces así obtendrás una pieza de funcionalidad que será mucho más fácil de usar en tus flujos que tirando de copia-pegas de montones de comandos y además estarás contribuyendo a mejorar el marketplace permitiendo que otros se puedan beneficiar del Action que hayas desarrollado.

Gracias a esa filosofía, el GitHub Marketplace ha crecido hasta hospedar una cantidad enorme de Actions, listos para ser usados y para salvarte de implementar una y otra vez la misma funcionalidad por ti mismo.

11 noviembre 2021

Cómo escribir manpages con Markdown y Pandoc

Una aplicación de consola decente siempre cuenta con una manpage para documentar cómo usarla. Sin embargo, las interioridades de las manpages son bastantes arcanas, aunque para ser un formato de fichero de 1971 ha aguantado bastante bien.

Hoy en día hay dos manera estándar de aprender a usar un comando de consola (aparte de buscar en google ;) ): tecleando el nombre de la aplicación, seguido de "--help", para obtener un resumen de cómo usar la aplicación, o teclear "man" seguido del nombre de la aplicación para obtener información detallada de cómo usarla.

Para implementar "--help" en nuestra aplicación podemos incluir un parseado manual de "--help", aunque lo más recomendable es usar una librería como la de python ArgParse para parsear argumentos de usuario.

El enfoque de usar el comando "man" implica escribir una manpage de tu aplicación..

La manera estándar de crear manpages es usar el formato troff. Linux tiene su propio estándar de implementación de troff, denominado groff. Si quieres cacharrear para hacerte una idea de cómo es el formato con el que se escriben las manpages, puedes teclear lo siguiente como el contenido de un fichero que se llame, por ejemplo, corrupt.1:

.TH CORRUPT 1 .SH NAME corrupt \- modify files by randomly changing bits .SH SYNOPSIS .B corrupt [\fB\-n\fR \fIBITS\fR] [\fB\-\-bits\fR \fIBITS\fR] .IR file ... .SH DESCRIPTION .B corrupt modifies files by toggling a randomly chosen bit. .SH OPTIONS .TP .BR \-n ", " \-\-bits =\fIBITS\fR Set the number of bits to modify. Default is one bit.

Una vez salvado, ese fichero puede visualizarse conel comando man. Suponiendo que estemos en la misma carpeta que el fichero corrupt.1, podemos teclear:

dante@Camelot:~/$ man -l corrupt.1

La salida será: 

CORRUPT(1)                                                 General Commands Manual 

NAME
corrupt - modify files by randomly changing bits

SYNOPSIS
corrupt [-n BITS] [--bits BITS] file...

DESCRIPTION
corrupt modifies files by toggling a randomly chosen bit.

OPTIONS
-n, --bits=BITS
Set the number of bits to modify. Default is one bit.

CORRUPT(1)


 

Puedes eligir escribir directamente manpages para tu aplicación siguien, por ejemplo, esta pequeña chuleta. 

De todos modos, el formato troff/groff es bastante raro y en mi opinión mantener dos fuentes de información (tu README.md y tu manpage) no es sino fuente errores y un desperdicio de esfuerzos. Por eso sigo un enfoque diferente: escribo y mantengo actualizado mi README.md y luego lo convierto a una manpage. Es cierto que hay que mantener un formato estándar para que la conversión de README.md cuadre con lo que se espera de un manpage, pero al menos evitaremos teclear las mismas cosas dos veces, en dos ficheros diferentes, y con diferentes lenguajes de etiquetado.

La clave de la conversión es la herramienta denominada Pandoc. Esa herramienta es la navaja suiza de la conversión de documentos. La puedes usar para convertir entre muchos formatos documentales como word (docx), openoffice (odt), epub... o markdown (md) y groff man. Pandoc suele estar disponible en los repositorios estándar de la mayor parte de distribuciones de linux habituales, por lo que en un ubuntu no hay más que teclear:

dante@Camelot:~/$ sudo apt install pandoc

Una vez que Pandoc esté instalado, hay que respetar una serie de convenciones en nuestro README.md opara hacer que la conversión sea más sencilla. Para ilustrar las explicaciones de este artículo, vamos a seguir el ejemplo del fichero README.md de mi proyecto Cifra.

Como se puede ver, el README.md enlazado contiene medallas de GitHub justo al comienzo aunque las quitaremos antes de hacer la conversión, tal y como veremos más adelante. Todo lo demás está estructurado para cumplir con lo que se espera que tenga una manpage estándar.

Puede ser que lo más sospechoso sea la primera línea. No es normal encontrar una línea como esta en un README de GitHub:

En realidad, esa línea contiene metadatos para el visor de manpages. Tras el carácter "%" aparece el título del documento (generalmente el nombre de la aplicación), la sección del manual, una versión, un separador "|" y finalmente una cabecera. La sección del manual es 1 para los comandos de usuario, 2 para las llamadas de sistema y 3 para las funciones de C. Tus aplicaciones encajarán en la sección 1 el 99% de las veces. No he incluido una versión para Cifra en esa línea, pero lo podría haber hecho. Además, la cabecera indica a que categoría de documentación pertenece este manpage.

Tras esa línea, cada sección es de las habituales en un manpage, pero las únicas que deberían ser incluidas como obligatorias son: 

  • Name: El nombre del comando.
  • Synopsis: Un resumen de una línea explicando los argumentos y las opciones.
  • Description: Describe en detalle como usar el comando.

Otras secciones que se pueden añadir son:

  • Options: Opciones del comando.
  • Examples: Ejemplos de uso del comando.
  • Files: Útil si tu aplicación incluye ficheros de configuración.
  • Environment: Aquí se describe si la aplicaciónusa variables de entorno.
  • Bugs: ¿Se deben reportar los bugs detectados? Aquí se puede enlazar la página de issues de GitHub.
  • Authors: ¿Quién es el autor de esta pieza maestra?
  • See also: Referencias a otros manpages.
  • Copyright | License: Un buen lugar para incluir la licancia de tu aplicación.

Una vez decididas las secciones a incluir en el manpage, hay que escribirlas siguiendo un formato fácilmente convertible por pandoc en un manpage con una estructura estándar. Esa es la razón por la que en el README de ejemplo las secciones principales están todas marcadas con un nivle de indentación ("#"). Un problema con la indentación es que aunque en markdown se pueden conseguir múltiples niveles de indentación usando el carácter "#" ( "#" para los títulos principales, "##" para los subtítulos, "###" para las secciones, etc) esos subniveles sólo son reconocidos por Pandoc hasta el subnivel 2. Para conseguir más subniveles he tenido que tirar de las"pandoc lists", un formato que puedes usar en tu markdown y que luego lo reconoce Pandoc:

En las líneas 42 y 48 tenemos lo que Pandoc llama  "line blocks". Estos son líneas iniciadas por una barra vertical ( | ) seguidas por un espacio. Esos espacios entre la barra vertical y el comando se conservarán en el texto del manpage que genere Pandoc.

Todo lo demás del fichero README.md de ejemplo es formato markdown clásico.

Supongamos que hemos escrito todo nuestro README.md y que queremos hacer nuestra conversión. Para hacer eso podemos usar un script como este desde una carpeta temporal:

Las líneas 3 y 4 limpian cualquier fichero usado en una conversión anterior, mientras que la línea 5 es la que en realidad copia el README.md desde la carpeta de origen a la de destino.

La línea 6 borra cualquier medalla de GitHub que podamos tener en nuestro README.md.

La línea 7 es donde llamamos a Pandoc para realizar la conversión.

Hecho eso, ya tendremos un fichero que puede ser abierto con man:

dante@Camelot:~/$ man -l man/cifra.1

Aunque los manpages suelen estar comprimidos con gzip, como se puede ver con los manpages de nuestro sistema:

dante@Camelot:~/$ ls /usr/share/man/man1

Esa es la razón por la que la línea 8 del script comprime el manpage generado. A pesar de estar comprimido, el manpage sigue siendo leíble por man:

dante@Camelot:~/$ man -l man/cifra.1.gz

Aunque son unos cuantos pasos, se puede ver que son fácilmente automatizables mediante un script o como un paso más de tu flujo de integración y despliegue continuo.

Como se puede ver, con estos sencillos pasos sólo se necesita mantener actualizado el README.md, dado que el manpage puede ser generado a partir de él.

04 septiembre 2021

Usando JFrog Artifactory para distribuir nuestros paquetes

 
En un artículo anterior revisé algunas maneras de generar paquetes debian o rpm para las aplicaciones que desarrollásemos. Pero después de empaquetar tus aplicaciones hay que encontrar una manera para que tus usuarios puedan descargarse e instalarse esos paquetes.

Siempre se pueden dejar los paquetes en la sección de releases de tu repositorio en Github y que tus usuarios lo descarguen de allí, pero la pega de eso será que no habrá una manera fácil de anunciar las actualizaciones y distribuirlas. Es mucho mejor usar un repositorio de paquetes y dejar que el usuario instale el paquete mediante el gestor de paquete de su sistema (por ejemplo apt o yum).

Puedes intentar meter tu paquete en los repositorios oficiale de tu distribución pero probablemente no cumplas los requisitos para ello, por eso un repositorio personal es la vía más factible.

Durante bastante tiempo utilicé Bintray para alojar mis paquetes deb y rpm, pero Bintray finalizó su servicio en marzo de 2021, por lo que tuve que buscar una alternativa. Finalmente encontré JFrog Artifactory, el heredero oficial de Bintray.

JFrog Artifactory tiene una modalidad gratuita para proyectos open source. Si tu proyecto no es tan popular como para superar 50 GB de descarga mensual, esta modalidad debería ser más que suficiente para tus proyectos personales.

La única pega es que Artifactory es más complejo (y completo) que Bintray, por eso es más complejo ponerlo en marcha si lo usas para tus proyectos personales. En este artículo voy a explicar lo que he aprendido hasta ahora para que te resulte más fácil empezar con Artifactory de lo que me ha resultado a mí.

Una vez registrado en la plataforma, se accede al menú de configuración rápida:

Ahí se puede crear un repositorio de cualquiera de los paquetes soportados. Para este artículo voy a usar Debian. Haz click en el icono de Debian y selecciona crear un nuevo repositorio.

En la siguiente ventana se te pide dar un nombre (un prefix) para el repositorio:


En la captura de pantalla he llamado vdist a mi repositorio. Artifactory crea repositorios virtuales, remotos y locales. El único repositorio que me ha resultado útil hasta el momento es el local, por eso cuando sigas este artículo asegurate de seleccionar siempre la opción "debian-local".

La siguiente ventana es engañosamente simple ya que te puede hacer pensar que ya estás listo para subir paquetes siguiendo las instrucciones de las pestañas "Deploy" y "Resolve":

 
El problema es que necesitas configurar algunas cosas antes de que tu repositorio esté completamente funcional, como he tenido que aprender a las duras.
 
Lo primero es que necesitas permitir el acceso anónimo al repositorio para que la gente pueda descargar tus paquetes. Lo que confunde aquí es que el acceso anónimo está configurado (se puede ver el permiso en Administration > Identity y Access > Permissions) pero aparentemente no parece funcionar en absoluto, por lo que cuando intentas acceder a tu repositorio usando apt lo único que consigues es un unauthorized error. La trampa es que premiero necesitas permitir globalmente el acceso anónimo en Administration > Security > Settings:

 


Sólo después de chequear esa opción dejarás de recibir el error al usar apt.

Para que los usuarios configuren sus linux para usar el repositorio, sólo hay que incluir en el fichero /etc/sources.list lo siguiente:

 deb https://dlabninja.jfrog.io/artifactory/<REPOSITORY_NAME>-debian-local <DISTRIBUTION> <COMPONENT>

En mi ejemplo REPOSITORY_NAME es vdist, DISTRIBUTION es el nombre de la distribución a la que nos estamos enfocando (por ejemplo, en Ubuntu, podría ser trusty) y para COMPONENT uso main. Por cierto, dlabninja es el nombre que le dí a mi cuenta cuando me registré en Artifactory, el nombre que le des a la tuya será diferente.

Se podría pensar que ya estás listo para subir paquetes, pero me temo que no es así todavía. Si intentas usar apt para acceder al repositorio en este punto te saldrá un mensaje diciendo que el repositorio no está firmado y que el acceso a él está prohibido. Para solucionarlo hay que crear un par de claves GPG (pública y privada) para firmar los paquetes y subirlos a Artifactory.

Para crear un certificado GPG puedes usar el siguiente comando:


Hay que meter el nombre identificativo, un correo y un password cuando te lo pida. Como nombre suelo usar el del repositorio. Toma nota del password que uses, si lo olvidas no hay manera de recuperarlo. La cadena que empieza en "F4F316" y acaba en "010E55" es el id del certificado. Tu id será similar. Te resultará útil para identificar tu certificado con los comandos gpg.

Puedes sacar una lista de los certificados:


Para subir las claves generadas, primero hay que exportarlas a un fichero. Ese proceso de exportación necesita generar dos ficheros: uno para tu clave pública y otro para tu clave privada:

 

Con el primer comando he exportado la clave pública y con el segundo la privada. Fíjate que le he puesto una extensión para poder identificarlas. Este es un buen momento para guardar esas claves en un lugar seguro.

Para subir esos ficheros a Artifactory necesitas ir a Administration > Artifactory > Security > Keys management:

 

Ahí, selecciona "+ Add keys" en la pestaña de "Signing keys". En la ventana que se abra mete el nombre para la clave (en este caso "vdist") y arrastra sobre la ventana los ficheros de las claves exportadas y mete la contraseña de la clave privada. Cuando lo hagas tendrás tu certificado correctamente importado en Artifactory y listo para ser usado.

Para configurar el certificado GPG en un repositorio ve a Administration > Repositories, selecciona tu repositorio y la pestaña "Advanced". Allí hay un combo "Primary key name" donde puedes seleccionar tu certificado. No olvides pulsar "Save & Finish" antes de salir o se perderán los cambios que hayas hecho:

 

Hecho eso, ddejará de salirte el error de repositorio sin firmar al usar apt, pero te seguirá saliendo el siguiente error:

(Click to enlarge)

En este caso el gestor de paquetes protesta porque aunque el repositorio está firmado con GPG no es capaz de reconocer su clave pública. Para resolverlo hay que subir la clave pública a uno de los servidores de claves PGP gratuitos de manera que nuestros usuarios puedan desscargárselos e importarlos. En estos casos yo suelo mandar mis claves públicas a ubuntu.keyserver.com:


Una vez que un registro público tiene una clave pública se sincroniza con los demás servidores PGP para compartirla. Nuestro usuario debe importar la clave pública y decirle a su gestor de paquetes que la clave pública es confiable. Para hacerlo en nuestro sistema debemos hacer sudo:

Si queremos instalar nuestro propio paquete, tendremos que hacer lo mismo.

Despues de eso, sudo apt update debería funcionar perfectamente:


Por fin estamos ya preparados para subir nuestro primer paquete a nuestro repositorio. Hay dos manera de hacerlo: manualmente y de manera automatizada.

Se pueden subir paquetes manualmente a través del interfaz web de Artifactory yendo a Artifactory > Artifacts > Selecting repository (in my example vdist-debian-local) > Deploy (el botón de la parte superior derecha). Eso abre una ventana emergente donde se puede arrastrar el fichero del paquete. Revisa que el campo de "Target repository" está correctamente fijado al tuyo (es fácil equivocarse y mandar el paquete al repositorio equivocado).

Además de lo anterior, Artifactory te deja subir paquetes desde la línea de comandos, lo que lo hace perfecto para automatizarlo en el marco de fujos de integración continua. Puedes ver el comando necesario en Artifactory > Artifacts > Set me up (el botón de la esquina superior derecha). Eso abre una ventana emergente con una pestaña denominada "Deploy" donde puedes ver el comando necesario para subir paquetes a un repositorio determinado:

Como se puede ver, los comandos tienen "place holders" en muchos campos. Si no estás seguro de qué poner en los campos USERNAME y PASSWORD, ve a la pestaña "Configure" y mete tu contraseña de tu usuario de Artifactory. Luego vuelve a las pestaña "Deploy" para ver cómo los campos de USERNAME y PASSWORD han sido rellenados por tí.

18 junio 2017

Empaquetando aplicaciones Python - Paquetes DEB (y RPM)

La manera nativa de distribuir código Python es a través de PyPI, como expliqué en un artículo previo. Pero para ser sinceros PyPI tiene varias pegas que limitan su uso a entornos de desarrollo.

El problema con PyPI es que no implementa una gestión de paquetes completa de tal manera que la desinstalación de paquetes de pip deja que desear a menudo y no hay manera de revertir una desinstallación con errores. Además, los procedimientos de instalación de pip a menudo compilan paquetes de código fuente lo que puede ser terriblemente lento para un virtualenv completo.

Desplegar aplicaciones Python debería ser rápido y debería permitir hacerlo de manera que, llegado el caso, las desinstalaciones fuese siempre límpias.

Debian tiene una gestión de paquetes de sistema realmente sólida y estable. Puede chequear dependencias tanto al instalar como al desinstalar, así como ejecutar scripts previos y posteriores a la instalación. Esa es la razón por la que es uno de los sistemas de paquetería más usados en el ecosistema Linux.

Este artículo va repasar maneras para empaquetar aplicaciones Python en paquetes Debian que puedan ser fácilmente instalados en distribuciones Debian/Ubuntu.

El formato de paquetes de Debian

Aunque otros sistemas de paquetes de Linux se basan en formatos binarios, los paquetes Debian son simplemente ficheros de tipo ar que contienen un par de ficheros empaquetados mediante tar y comprimidos mediante gzip o bzip. Unos de estos ficheros contiene el árbol de ficheros con los ficheros de nuestra aplicación y el otro contiene los ficheros de configuración del paquete.

Los ficheros de configuración son meros ficheros de texto, por lo que la manera clásica de crear paquetes Debian implica sólo crar carpetas, editar unos ficheros de texto y ejecutar un comando que, en su forma más simple es:

dante@Camelot:~/project-directory$ debuild -us -uc


Se puede encontrar un buen tutorial sobre el tema aquí.

El tema no es complicado pero puede tener su intríngulis y requiere crear y editar muchos ficheros de texto. Por eso, no resulta sorprendente ver como los desarrolladores han crado muchas herramientas para automatizar esta tarea. Vamos a ver algunas de estas herremientas especializadas en el empaquetado de aplicaciones Python en paquetes  Debian.


STDEB

Si ya estás acostumbrado al empaquetado para PyPI, no debería resultarte complicado coger los conceptos de stdeb. Si no sabes a qué me refiero deberías echarle un ojo al artículo de este blog al que enlazaba al comienzo de este.

Dado que stdeb llama a algunas herramientas nativas de Debian/Ubuntu, es necesario usarlo desde una de estas distribuciones. Si estás en un Debian/Ubuntu, se puede instalar stdeb desde los repositorios estándar del sistema:

dante@Camelot:~$ sudo aptitude search stdeb
p   python-stdeb    - Python to Debian source package conversion utility                                 p   python3-stdeb   - Python to Debian source package conversion plugins for distutils                  
dante@Camelot:~$ sudo aptitude install python-stdeb python3-stdeb


También se puede instalar stdeb desde PyPI, como paquete python, lo que tiene la ventaja de que así se puede instalar una versión más reciente.

El mejor flujo de trabajo para usar stdeb implica crear los mismos ficheros que se usarían para crear un paquete para PyPI, de esta manera stdeb puede utilizar el fichero setup.py para conseguir la información necesaria para crear los fichero de configuración de un paquete debian.

Por eso, supongamos que nuestra aplicación estuviese lista para ser empaquetada para PyPI, con un setup.py ya creado. Para este ejemplo he clonado el siguiente repositorio de git.

Para hacer que stdeb genere un paquete fuente de debian sólo hay que hacer lo siguiente:

dante@Camelot:~/geolocate$ python3 setup.py --command-packages=stdeb.command sdist_dsc

La parte de --command-packages puede ser un poco complicada pero el comando no puede funcionar sin ella, por eso confía en mi e inclúyelo.

La ejecución de este comando tiene una salida de texto bastante profusa pero al final te darás cuenta que se habrán creado algunas carpetas en nuestro directorio de trabajo. Tienes que fijarte en una carpeta llamada deb_dist. Esa carpeta contiene principalmente tres ficheros: un fichero .dsc, un fichero .orig.tar.gz y un fichero .diff.gz. Esos tres ficheros juntos son lo que denominamos un paquete fuente.

Dentro de la carpeta deb_dist encontrarás otra llamada como tu proyecto pero con la versión principal de tu aplicación añadida al final. Esa carpeta contiene todos los datos generados para compilar un paquete debian binario. Por eso, entra en dicha carpeta y ejecuta lo siguiente:

dante@Camelot:~/geolocate/deb_dist/glocate-1.3-0$ dpkg-buildpackage -rfakeroot -uc -us

La parte de fakeroot es sólo un flag para poder construir un paquete debian sin estar logado como root. Ese es el comando recomendado en la documentación, pero por las pruebas que he realizado también se puede usar el comando debuild visto antes:

dante@Camelot:~/geolocate/deb_dist/glocate-1.3-0$ debuild -uc -us

Al final debuild no es más que un wrapper para dpkg-buildpackage que automatiza algunas cosas que de otra manera tendrían que hacerse manualmente.

Después de cualquiera de esos comandos, deberías encontrar un paquete fuente de debian en la carpeta deb_dist:

dante@Camelot:~/geolocate/deb_dist/glocate-1.3-0$ ls *.deb python3-glocate_1.3.0-1_all.deb

Lo anterior es el "método en dos pasos", aunque puedes reducirlo a un único paso haciendo:

dante@Camelot:~/geolocate$ python3 setup.py --command-packages=stdeb.command bdist_deb

Aunque ambos métodos acaban con un paquete .deb en la carpeta debian_dist, deberías ser consciente de que el paquete debian generado depende de la arquitectura por mucho que su nombre incluya la coletilla "_all". Eso significa que si generas en paquete en un Ubuntu de arquitectura amd64 lo más probable es que tengas problemas si intentas instalar dicho paquete en un Ubuntu con otra arquitectura. Para evitar este problema, hay dos opciones:


  • Usar máquinas virtuales para compilar un paquete debian para cada arquitectura objetivo.
  • Usar un repositorio PPA: Ubuntu ofrece espacio personal para alojar proyectos de empaquetado. Se sube un paquete fuente (el contenido de la carpeta deb_dist tras ejecutar "python3 setup.py --command-packages=stdeb.command sdist_dsc"), y el servidor lo compila a cada arquitectura objetivo (principalmente x86 y amd64). Tras la compilación, los paquetes creados aparecen en el PPA personal hasta que los borras o los reemplazas con versiones más nuevas. 
Si tus aplicaciones python sólo usan los paquetes que vienen por defecto con python entonces el empaquetado acaba aquí, pero si usas paquetes adicionales, por ejemplo descargados desde PyPI, lo más probable es que el paquete generado no los incluya correctamente.

Continuando con nuestro ejemplo, si miras en el fichero setup.py de geolocate podrás ver que depende de los siguientes paquetes:
install_requires=["geoip2>=2.1.0", "maxminddb>=1.1.1", "requests>=2.5.0", "wget>=2.2"]
Veamos si estas dependencias han sido incluidos en los metadatas generados en el paquete debian:

dante@Camelot:~/geolocate/deb_dist$ dpkg -I python3-glocate_1.3.0-1_all.deb [...] Depends: python3, python3-requests, python3:any (>= 3.3.2-2~) [...]


Obviamente no han sido incluidos. De hecho nuestro comando de compilación nos avisó de que el chequeo de dependencias había fallado al construir el paquete. Si nos fijamos en la salida del comando de compilación nos encontraremos esta parte:
[...]
I: dh_python3 pydist:184: Cannot find package that provides geoip2. Please add package that provides it to Build-Depends or add "geoip2 python3-geoip2-fixme"
line to debian/py3dist-overrides or add proper  dependency to Depends by hand and ignore this info.
I: dh_python3 pydist:184: Cannot find package that provides maxminddb. Please add package that provides it to Build-Depends or add "maxminddb python3-maxmindd
b-fixme" line to debian/py3dist-overrides or add proper  dependency to Depends by hand and ignore this info.
I: dh_python3 pydist:184: Cannot find package that provides wget. Please add package that provides it to Build-Depends or add "wget python3-wget-fixme" line t
o debian/py3dist-overrides or add proper  dependency to Depends by hand and ignore this info.
[...]
El problema es que stdeb no identifica qué paquete de Linux incluye a esos paquetes de PyPI. Por eso tenemos que fijarlos manualmente.

Para configurar manualmente a stdeb tienes que crear un fichero llamado stdeb.cfg en la misma carpeta que setup.py. Lo normal es crear una sección [DEFAULT] donde poner la configuración pero también se pueden crear secciones llamadas [nombre_de_paquete], donde el nombre del paquete coincide con el mismo argumento que el comando setup().

Por ejemplo, si averiguamos que la librería de PyPI llamada geoip2 está incluida dentro del paquete del repositorio de Ubuntu denominado python3-geoip, y la librería request de PyPI está incluida en el paquete python3-request podríamos crear un fichero stdeb.cfg con el siguiente contenido:
[DEFAULT]
X-Python3-Version: >= 3.4
Depends3: python3-geoip (>=1.3.1), python3-requests
Todas las etiquetas siguen el mismo formato que seguirían si estuvieran dentro de un fichero de configuración debian/control. Para etiquetas de dependencias, la especificación del formato es esta.

Con esta configuración, las dependencias fijadas se incluyen en el paquete debian generado:

dante@Camelot:~/geolocate/deb_dist$ dpkg -I python3-glocate_1.3.0-1_all.deb
[...]
Depends: python3, python3-requests, python3:any (>= 3.3.2-2~), python3-geoip (>= 1.3.1)
[...]

Lo que no he conseguido averiguar aún es una manera de cambiar la etiqueta de arquitectura de los paquetes generados de manera que no se generen con "Architecture: all".

A primera vista, stdeb parece una opción estupenda y verdaderamente lo es, pero tiene algunos inconvenientes dignos de consideración.

El problema es que stdeb te limita a usar sólo librerías y paquetes de python disponibles en el repositorio de paquetes estándar de Linux. Por eso si desarrollas tu aplicación usando paquetes y librerías de PyPI lo más probable es que cuando intentes identificar qué paquete de Linux incluye dichas librerías te encuentres con que dichos paquetes sólo contienen versiones más antiguas que aquellos disponibles en PyPI. Peor aún, muchas librerías de PyPI no han sido portadas a los repositorios estándar de Linux, por lo que no será posible encontrar un paquete que cubra esa dependencia. Por ejemplo, geolocate necesita usar geoip2 (v.2.1.0) el cual es fácilmente descargable de PyPI pero sólo Ubuntu 15.04 tiene un paquete disponible denominado python3-geoip, pero este viene con la versión 1.3.2 de geoip. ¿Funcionará geolocate con la versión de geoip facilitada por el paquete python3-geoip?, probablemente no. Otras dependencias de geolocate ni siquiera están en el repositorio estándar, como por ejemplo la librería de python wget (o al menos no lo había cuando escribí esta parte del artículo).

Está claro que si te gusta utilizar librerías PyPI, stdeb puede no ser tu mejor opción. Pero si desarrollas usando sólo librarías disponibles a través del gestor de paquetes estándar de Linux entonces stdeb probablemente te solucionará el problema.

FPM

FPM es una herramienta de Ruby similar a stdeb. Su principal ventaja es que puedes usar FPM para crear muchos tipos de paquetes de sistema, no sólo de Debian sino también paquetes RPM (pata distribuciones Red Hat). Y lo que es incluso más interesante de FPM es que permite conversiones de paquetes, por ejemplo de .rpm a .deb.

FPM no tiene paquete en el repositorio principal de Ubuntu, por lo que hay que descargarlo del equivalente Ruby a PyPI. Para ello primero hay que instalar los paquetes de Ruby:


dante@Camelot:~/geolocate$ sudo aptitude install ruby-dev gcc make

Después de eso puedes instalar FPM desde los repositorios de Ruby:


dante@Camelot:~/geolocate$ gem install fpm

Crear un paquete DEB es bastante simple, sólo fija el parámetro de origen de FPM como python y su objetivo como deb y dale la ruta al setup.py de tu paquete:


dante@Camelot:~/geolocate$ fpm -s python -t deb ./setup.py

FPM toma los nombres de las dependencias del setup.py y los prefija con la etiqueta que se fija con la bandera --python-package-name-prefix (si no está fijado entonces se usa python como prefijo):


dante@Camelot:~/geolocate$ dpkg -I python-glocate_1.3.0_all.deb
[...]
Depends: python-geoip2 (>= 2.1.0), python-maxminddb (>= 1.1.1), python-requests (>= 2.5.0), python-wget (>= 2.2)
[...]

El problema aquí es similar que con stdeb: esas dependencias no existen en el repositorio estándar de Ubuntu. En caso de la que las dependencias existiesen pero con nombres diferentes a los autogenereados por FPM, entonces puedes fijarlos manualmente:


dante@Camelot:~/geolocate$ fpm -s python -t deb --no-auto-depends -d "python3-geoip>=1.3.1, python3-wget" ./setup.py
[...]>
dante@Camelot:~/geolocate$ dpkg -I python-glocate_1.3.0_all.deb 
[...]
Depends: python3-geoip>=1.3.1, python3-wget
[...] 


Otra bandera útil es "-a native". Esta bandera fija la arquitectura del paquete a la de tu sistema, por eso ya no se fija a "_all" en el nombre de los paquetes.

FPM es una gran herramienta. Te permite crear paquetes RPM y es muy configurable pero en mi opinión tiene un inconveniente muy serio: tal y como pasaba con stdeb es inútil si tu aplicación importa una librería disponible en PyPI pero no en el repositorio estándar del sistema operativo.

DH-VIRTUALENV

Llegados a este punto debería estar claro que el problema principal para empaquetar una aplicación python es asegurar sus dependencias, porque el desarrollador puede haber usado librerías PyPI que no estén disponibles a través de los repositorios estándar en el lado del usuario.

Los chicos de Spotify desarrollaron un empaquetador para resolver este problema: dh-virtualenv.

Esta herramienta asume que si estás usando PyPI para desarrollar entonces lo más probable es que uses virtualenvs. Por eso dh-virtualenv incluye el virtualenv entero dentro del paquete de manera que no tengas que instalarlo en el extremo del usuario final.

Sin embargo, en mi humilde opinión dh-virtualenv tiene un inconveniente: no es más limpio que stdeb o que FPM (porque tienes que crear manualmente una carpeta debian y un fichero de reglas) acabas usando debuild como al comienzo del artículo.

VDIST

Los principales conceptos de vdist son similares a los vistos en dh-virtualenv pero vdist usa una conbinación de docker y FPM para crear un paquete estándar de sistema operativo. Esta herramienta te permite construir paquetes de tus aplicaciones creando entornos aislados para tu proyecto usando virtualenv. A primera vista vdist puede parecer complejo pero su documentación es realmente clara y en realidad es bastante simple de usar y de automatizar.

Si tu mayor problema al empaquetar aplicaciones python es asegurar que las dependencias estén presentes en el extremo del usuario final, vdist resuelve dicho problema haciendo que tu aplicación esté autocontenida y sea autosuficiente de tal manera que no dependa de los módulos de Python facilitados por los paquetes del sistema. Esto significa que los paquetes generados por vdist contienen tu aplicación, todas las dependencias de python requeridas por la misma y un interpreter de python para ejecutarla. De esa manera tu aplicación puedes ser ejecutada con el intérprete de tu elección y no con el que traiga el sistema operativo en el que estés desplegando tu aplicación.

Para asegurar que el equipo usado para construir el paquete conserve sus paquetes de sistema intactos. vdist usa docker para crear un sistema operativo limpio al crear el paquete donde instalar las dependencias necesarias antes de empaquetar la aplicación en él. Gracias a esto el equipo usado para la compilación será revertido a su estado original en cada compilación.Para cargar to aplicación en una imagen de docker, vdist descarga el código fuente de la aplicación desde un repositorio de git, por eso tener tu aplicación en BitBucket o Github es una buena idea. El código fuente se coloca en un virtualenv creado en una imagen de docker. Las dependencias de PyPI serán instalados en el virtualenv.

La primera dependencia para usar vdist es contar con vdist instalado y su demonio en ejecución. Para instalarlo en Ubuntu sólo necesitas hacer lo siguiente:


dante@Camelot:~/geolocate$ sudo aptitude install docker.io python-docker python3-docker


Tras instalar docker recuerda añadir tu usuario al grupo de docker:


dante@Camelot:~/geolocate$ sudo usermod -a -G docker dante


Puede que necesites reiniciar el sistema para estar seguro de que el grupo se actualice realmente.

La manera más fácil de instalar vdist es usar el gestor de paquetes estándar de Linux. Los paquetes de vdist están alojados en Bintray, por eso para instalarlos primero hay que incluir a Bintray en los repositorios de paquetes del sistema antes de nada más. Para hacerlo, hay que introducir los siguientes comandos:


dante@Camelot:~$ sudo apt-get update
dante@Camelot:~$ sudo apt-get install apt-transport-https
dante@Camelot:~$ sudo echo "deb [trusted=yes] https://dl.bintray.com/dante-signal31/deb generic main" | tee -a /etc/apt/sources.list
dante@Camelot:~$ sudo apt-key adv --keyserver pgp.mit.edu --recv-keys 379CE192D401AB61

Una vez que hayas añadido a Bintray en tus repositorios puedes instalar y actualizar vdist como cualquier otro paquete de sistema. Por ejemplo, en Ubuntu:


dante@Camelot:~$ sudo apt-get update
dante@Camelot:~$ sudo apt-get install vdist

Si estás en un sistema donde no tienes permisos para instalar paquetes de sistema puede que te resulte interesante instalarlo desde PyPI en un virtualenv creado ad-hoc para empaquetar tu aplicación:


(env) dante@Camelot:~/geolocate$ pip install vdist


Tras instalar vdist dispondrás de un comando de consola llamado... vdist. Sólo se consciente de que si has instalado vdist en un virtualenv, ese comando de consola sólo estará disponible dentro de ese virtualenv.

Hay muchas maneras de usar vdist, aunque creo que la manera más fácil es crear un fichero de configuración y hacer que vdist lo lea. El mismo vdist se utiliza para generar sus propios paquetes, así que su fichero de configuración es un buen ejemplo:

[DEFAULT]
app = vdist
version = 1.1.0
source_git = https://github.com/dante-signal31/${app}, master
fpm_args = --maintainer dante.signal31@gmail.com -a native --url
    https://github.com/dante-signal31/${app} --description
    "vdist (Virtualenv Distribute) is a tool that lets you build OS packages
     from your Python applications, while aiming to build an
     isolated environment for your Python project by utilizing virtualenv. This
     means that your application will not depend on OS provided packages of
     Python modules, including their versions."
    --license MIT --category net
requirements_path = /REQUIREMENTS.txt
compile_python = True
python_version = 3.5.3
output_folder = ./package_dist/
after_install = packaging/postinst.sh
after_remove = packaging/postuninst.sh

[Ubuntu-package]
profile = ubuntu-trusty
runtime_deps = libssl1.0.0, docker.io
build_deps =

[Centos7-package]
profile = centos7
runtime_deps = openssl, docker-ce

La documentación de vdist es lo suficientemente buena como para saber para qué sirve cada parámetro. Sólo hay que tener en cuenta que puedes tener un único fichero de configuración por cada paquete que quieras compilar. Sólo manten los parámetros comunes en una sección [DEFAULT] y pon los parámetros particulares de cada distribución en secciones separadas (que pueden llamarse como quieras, aunque lo mejor es usar nombres expresivos).

Una vez que tienes tu fichero puedes lanzar vdist de la siguiente manera (supongamos que el fichero de configuración se llama configuration_file):


dante@Camelot:~/$ vdist batch configuration_file

A partir de ahí comenzarás a ver un montón de texto de salida en la pantalla mientras vdist construye tus paquetes. Los paquetes generados serán colocados en la carpeta fijada en el parámetro del fichero de configuración output_folder.

El tamaño de paquete más pequeño generado por vdist es de aproximadamente 50 MB, debido a que hay que incluir una distribución completa de python  en el paquete. Ese tamaño es el precio que hay que pagar por la autosuficiencia de nuestra aplicación con respecto a las dependencias de python. Descontados esos 50 MB, el resto del tamaño se corresponderá con nuestra aplicación y sus dependencias. A primera vista puede parecer mucho, pero hoy en día ese tamaño es bastante habitual para cualquier aplicación compilada que nos encontremos por internet.

Creo que vdist es la solución de empaquetado más completa que se encuentra disponible para desplegar aplicaciones python en sistemas Linux. Con él puedes deplegar incluso en equipos linux sin ningún tipo de python instalado en absoluto, con lo que aporta un valioso aislamiento en el extremo del usuario, facilitándole la instalación además a tu usuario final.

Disclaimer: Empecé a usar vdist para escribir este artículo y he acabado siendo el nuevo desarrollador principal de la aplicación, por eso sentíos libres de comentar cualquier mejora o aportación que creais interesante.

05 marzo 2012

Linux Firewalls

Linux Firewalls: Attack Detection and Response with iptables, psad, and fwsnort es una obra interesante que trata las posibilidades de configuración de iptables para dotarle de características UTM mediante su integración con firmas de ataque de Snort así como con extensiones concretas de iptables para la correlación de logs y la generación de bloqueo y apertura en tiempo real.

El libro cuenta con multitud de ejemplos reales y trata las configuraciones con gran detalle. No he probado las configuraciones propuestas en laboratorio por lo que no puedo dar fe del nivel de actualidad de las configuraciones propuestas, pero dada la rápida evolución de estas herramientas es de esperar que esta parte del libro vaya perdiendo valor a lo largo de los próximos meses debido a su gradual obsolescencia.

En mi humilde opinión, las herramientas presentadas son útiles a nivel personal o para pequeñas empresas que sólo se puedan permitir opciones open source gratuitas. A un nivel corporativo más serio, me parece que las soluciones propuestas están poco integradas, son de mantenimiento complejo a poco que la red sea más o menos amplia y heterogénea y además hoy en día hay opciones comerciales que superan ampliamente las capacidades presentadas por un coste prácticamente irrisorio para la mayor parte de las empresas.

Sí resulta mucho más jugosa la revisión que hace el autor de diversos ataques de red, explicados con todo lujo de detalle pero de una manera amena y fácilmente comprensible. Aún así, la mayor parte de estos ataques son de dominio público desde hace bastante tiempo por lo que los lectores con cierta experiencia tampoco descubrirán nada nuevo.

En definitiva: un libro correcto pero sólo realmente útil para los que estén dando sus primeros pasos en el mundo de la seguridad.