Programación literaria para sysadmins / devops

7 minutos de lectura Publicado:

Mencioné de pasada el concepto de programación literaria en el articulo de mi configuración de emacs, ahora voy a explicar en que consiste este y cómo usarlo con emacs y org-mode.

La definición de la wikipedia al rescate.

El paradigma de programación literaria […] permite a los programadores desarrollar sus programas en el orden fiado por la lógica y el flujo de sus pensamientos.

Los programas literarios están escritos como una exposición lógica no interrumpida en un lenguaje humano, de forma similar al texto de un ensayo, en el cual se incluye el código fuente tradicional oculto tras macros. Las herramientas de programación se encargan de separar el programa de forma que pueda ser compilado y ejecutado y la documentación del mismo programa. Mientras que las primera generación de herramientas de programación literaria estaban centradas en un lenguaje de programación específico, las últimas son independientes de lenguaje y se sitúan por encima de los lenguajes de programación.

Dicho de otro modo y enfocado al uso que le puedan dar las sysadmins, permite documentar los pensamientos y procesos lógicos y ejecutar estos en el mismo contexto. Puede ser un poco complejo de entender si no se ve, por eso, y aún odiando el formato, he hecho una pequeña grabación de lo que puede hacer con ello. El caso concreto que presento es un ejemplo real con el que me he encontrado recientemente, que es cambiar lo codificación del contenido de una columna de una base de datos sqlite en una máquina remota. El programa en si da igual, pero creo que es un buen modo de ver el potencial que tiene.

El vídeo a continuación. Si, está acelerado.

Ahora explicaré algunos conceptos básicos y cómo configurar emacs para lograr lo anterior. Para dejarlo claro, usaré emacs, org-mode y los bloques babel de este. Los bloques babel permiten que varios lenguajes de programación vivan de forma natural en org-mode. Permite que estos se ejecuten, se compilen, se documenten y se creen los ficheros que se programen a parte, todo desde el mismo sitio.

Que ventajas ofrece org-mode?

  • Mejor documentación del código.

  • Útil para compartir con otra gente, si se tiene algún problema.

  • Clarificación de los pensamientos en situaciones complicadas.

  • Facilidad para mezclar distintos lenguajes de programación.

  • Las funcionalidades de la agenda y las tareas.

Configuración

Se puede usar tanto emacs 24.5 como 25.1, hasta dónde he probado. La versión de org-mode que he probado es la rama maint del git, que en este momento es la versión 8.3.6. Teniendo esto claro, vamos a configurar babel. La variable que hay que tener en cuenta es org-babel-load-languages, que es dónde se definirá que lenguajes permitirá usar. Las posibilidades que hay se pueden ver en el siguiente enlace. Para la demostración solo he usado python y shell, que permite el uso de bash. La variable se define así:

(org-babel-do-load-languages
 'org-babel-load-languages
 '((dot . t)
 (lisp . t)
 (gnuplot . t)
 (latex . t)
 (ledger . t)
 (python . t)
 (shell . t)
 (sh . t)
 (sql . t)
 (sqlite . t)))

Con esto ya podemos funcionar en lo que a org-mode se refiere. Ahora varios conseos para el tema del ssh y tramp, que es el programa que usa emacs para gestionar las conexiones por ssh. Lo primero de todo es que lo más cómodo siempre será poder loguearse en el servidor con la clave pública. Lo segundo es modificar la variable PS1 del servidor en cuestión para facilitarle el parseo da tramp. Es la forma más sencilla que he encontrado de hacer que funcione sin más modificaciones. En el ~/.bashrc del servidor se añade lo siguiente:

[[ $TERM == "dumb" ]] && PS1='$ '
[[ $TERM == "emacs" ]] && PS1='$ '

Se define la variable PS1 en función del TERM que se use. Me he encontrado con que tramp ha usado ambos TERM, según si lo uso en un bloque de babel o a pelo, así que vale la pena poner ambos. Tramp usa scp por defecto. Yo prefiero usar ssh, por que soy de darle a guardar cada 3 segundos y con scp tarda más.

(setq tramp-default-method "ssh")

Con esto deberíamos poder funcionar correctamente. Ahora miremos un poco el código del archivo del vídeo. La fuente completa está aquí, por si alguien la quiere.

Conceptos básicos de org-mode y babel

A continuación veremos los parámetros y variables que uso en el archivo de la demostración, que seguramente sean los más relevantes en el ámbito de administración de sistemas. El fichero de la demostración está aquí. Aún así, hay muchísimas más y no puedo cubrirlas todos. Al final del articulo pondré varias fuentes y otros recursos que recomiendo leer. Están en inglés, por eso.

Como siempre que se usa org-mode, usamos los árboles, es decir, los asteriscos. De este modo se pueden definir varias propiedades, como la de header-args. Pero antes, veamos un bloque babel normal y corriente. Para ejecutar el bloque, hay que presionar C-c C-c en este.

#+BEGIN_SRC sh
ls ~/
#+END_SRC

#+RESULTS:
| Descargas  |
| Documentos |
| Imagenes   |
| Instalados |
| Scripts    |
| Videos     |

Esto devuelve en mi caso. El resultado por defecto lo devuelve en forma de tabla. Si lo queremos tal cómo devuelve la terminal, podemos pasarle la variable results raw al bloque.

#+BEGIN_SRC sh :results raw
ls ~/
#+END_SRC

#+RESULTS:
Descargas
Documentos
Imagenes
Instalados
Scripts
Videos

Otra variable útil que se usa es dir. Permite establecer en que directorio se ejecutará el bloque.

#+BEGIN_SRC sh :dir /
ls
#+END_SRC

#+RESULTS:
bin
boot
dev
etc
home
la
lib
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

Esto es más útil de lo que pueda parecer a primera vista, por que como tal vez os habéis fiado en el vídeo, permite establecer que todos los bloques se ejecuten en el servidor, sin más. Para ello hay que usar la sintaxis de tramp, que seria tal que así en mi caso (en el ejemplo, igual que en el vídeo, me conecto a mi banana pi).

#+BEGIN_SRC sh :dir /ssh:drymer@banana:
ls
#+END_SRC

#+RESULTS:
backups  instalados  owncloud_data
scripts  decrypted   owncloud_data_backup

Si esto lo unimos a la variable header-args que mencioné antes, que se puede definir en las propiedades, todo lo que se ejecute debajo del primer árbol se ejecutará en la máquina remota.

Algunos bloques tienen definido encima un parámetro, #+name, seguido de un nombre. Esto permite usar los bloques como funciones, como en el caso del bloque de python de la demostración. También se pueden definir variables vacías para que puedan ser usadas dentro de los bloques.

#+NAME: decode_string
#+BEGIN_SRC python :results output :var linea=""
print(linea.encode('latin1').decode('utf8'))
#+END_SRC

Esta función devuelve el valor de la variable linea codificada en utf-8 cuando se llama desde fuera.

#+BEGIN_SRC sh :results raw :post decode_string(linea=*this*)
echo "Un+par+de+funciones+útiles"
#+END_SRC

#+RESULTS:
Un+par+de+funciones+útiles

En el ejemplo esta función se usa de un modo particular, ya que se llama con el parámetro post. Este está pensado para procesar la salida del bloque en bash con el bloque en python.

También se puede llamar a un bloque desde otro bloque y establecer el resultado a una variable a usar en el segundo, como se ve aquí.

#+NAME: num
#+BEGIN_SRC sh
echo $(sqlite3 -line /var/lib/isso/comments.db "select * from archive;" | grep title | wc -l)
#+END_SRC

#+NAME: encoded_title
#+BEGIN_SRC bash :results output :post decode_string(linea=*this*) :var NUM=num
for ((i=1;i<=$NUM;i++))
do
echo $i$(sqlite3 -line /var/lib/isso/comments.db "select title from archive where id = $i;" | cut -d'=' -f2)";"
done
#+END_SRC

El bloque encoded_title llama al bloque num estableciendo la salida de este a la variable NUM, que en bucle for usa como final de este.

Por último mencionar la exportación de los bloques a otros formatos. Quienes usen org-mode ya sabrán que una de sus grandes cualidades es que permite exportar este lenguaje de marcado a otros formatos, tales como latex, html, pdf, odt, etc. Los bloques van incluidos en el paquete y pueden ser gestionados con la variable :exports, pasando como valor none, code, results o both, que es lo que se usa por defecto. none no exporta nada, code solo el código, results el resultado y both ambos.

Y esto es todo por ahora. Tal vez en el futuro escriba otro articulo más, si llego a controlar más del tema. Por si acaso, y mientras tanto, recomiendo la lectura de los siguientes enlaces, en los que se encuentran varios artículos de Howard Abrahams, del cual yo descubrí recientemente todo el potencial de la programación literaria y recomiendo encarecidamente a quien sepa leer en inglés.

Cualquier duda o comentario, me puedes contactar en los canales descritos en la página principal