Creando builds manifests en sourcehut

2020/11/29

Contexto

En un post anterior comenté que elegí migrar mis repositorios personales a sourcehut. Ya que todavía no tienen un servicio similar al de github pages -donde tengo hosteado este blog a la fecha-, decidí mantener el repositorio jumapico.github.io en github, pero realizando los siguientes cambios:

A pesar de la sencillez de la página principal, esta brinda toda la información necesaria:

Welcome to builds.sr.ht! This provides continuous integration services for sourcehut, the hacker’s forge. Features of this service include:

Runs fully virtualised builds on various Linux distros and BSDs Run anonymous jobs that aren’t tied to a repository Post-build triggers for email, webhooks, etc Powerful API for complex build scenarios

Si se está logueado al servicio se obtiene simplemente un link a la documentación:

Welcome back, jumapico! This is the sourcehut build service. Documentation for its use is available here.

A continuación se describirá cual fué el proceso que utilicé para utilizar builds.sr.ht en reemplazo de Travis CI para la generación del blog.

Explorando builds.sr.ht

Lo primero a tener en cuenta es que builds.sr.ht tiene varias formas de generar trabajos descriptas en la documentación. A nosotros nos interesarán dos:

  1. La integración con git.sr.ht mediante la existencia de un archivo .build.yml en la base del repositorio.
  2. Via web, mediante la url https://builds.sr.ht/submit.

El primer caso será utilizado para generar el sitio y contiene la configuración a la que deseamos llegar.

El segundo caso será utilizado para ir explorando como elegir las opciones disponibles en el archivo .build.yml descriptas en el Build manifest reference.

Se comienza eligiendo una de las imágenes disponibles. Ya que personalmente utilizo debian voy a elegir esa distribución. La versión será buster, que al día de hoy es la rama estable, y como architectura amd64 (la única al día de hoy y la que está marcada como estable, pero ya que explicito es mejor que implicito…). De esa forma la elección de la imagen queda como:

image: debian/buster
arch: amd64

Ya que se desean realizar pruebas en la máquina virtual provista por builds.sr.ht se habilitará el acceso mediante ssh estableciendo la opción shell a true

shell: true

Como repositorio a clonar dentro de la máquina virtual se elige el del blog utilizando git sobre http:

sources:
  - https://git.sr.ht/~jumapico/jumapico.ws

Hay que tener en cuenta que en caso de ser un repositorio privado o utilizar git como transporte se require configurar una clave ssh.

Por último como tarea a realizar utilizaremos simplemente una llamada a sleep 60, que nos dará un minuto de tiempo luego de comenzada la tarea para conectarnos por ssh. Si la tarea termina muy rápido (por ejemplo, si imprimimos un echo) cuando intentemos conectarnos a la máquina virtual esta ya habrá sido terminada ya que la tarea terminó:

tasks:
- wait-for-ssh: |
    sleep 60

El archivo final, build-tests.yml, queda como:

image: debian/buster
arch: amd64
shell: true
sources:
  - https://git.sr.ht/~jumapico/jumapico.ws
tasks:
- wait-for-ssh: |
    sleep 60

Para generar un trabajo (job) vamos a la url https://builds.sr.ht/submit, copiamos el manifiesto y presionamos el botón Submit.

Submit Manifest

Allí somos reenviados a la página correspondiente al trabajo creado, en nuestro caso https://builds.sr.ht/~jumapico/job/354813.

Job Info

Si prestamos atención, en el paso de setup, aparece el mensaje:

[#354813] 2020/11/29 16:21:55 Shell access for this build was requested.
[#354813] 2020/11/29 16:21:55 To log in with SSH, use the following command:
[#354813] 2020/11/29 16:21:55
[#354813] 2020/11/29 16:21:55 	ssh -t builds@azusa.runners.sr.ht connect 354813

Generando el archivo .build.yml

Una vez que tenemos el acceso ssh a la máquina virtual nos conectamos a esta:

$ ssh -t builds@azusa.runners.sr.ht connect 354813
Connected to build job #354813 (running): https://builds.sr.ht/~jumapico/job/354813
[#354813] 2020/11/29 16:21:55
[#354813] 2020/11/29 16:21:55 	ssh -t builds@azusa.runners.sr.ht connect 354813
[#354813] 2020/11/29 16:21:55
[#354813] 2020/11/29 16:21:55 Sending tasks
[#354813] 2020/11/29 16:21:55 Sending build environment
[#354813] 2020/11/29 16:21:56 Cloning repositories
Cloning into 'jumapico.ws'...
+ cd jumapico.ws
+ git submodule update --init
[#354813] 2020/11/29 16:21:56 Running task wait-for-ssh
[wait-for-ssh] + sleep 60


Your VM will be terminated 4 hours from now, or when you log out.

Linux build 4.19.0-11-amd64 #1 SMP Debian 4.19.146-1 (2020-09-17) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Nov 29 16:21:57 2020 from 172.17.0.1
$

Por lo que podemos apreciar del mensaje anterior, tenemos cuatro horas máximo para realizar pruebas.

Comenzamos por algunas pruebas básicas:

$ who
build    pts/0        Nov 29 16:21 (172.17.0.1)
$ sudo echo Yes!
Yes!
$ ls
jumapico.ws

O sea:

A partir de aquí se irán realizando pruebas hasta poder crear el archivo build.yml que generará el blog.

Este archivo comenzará con la misma imagen y repositorio que el anterior, pero ya que suponemos que será definitivo no estamos interesados en que se tenga acceso de shell:

image: debian/buster
arch: amd64
sources:
  - https://git.sr.ht/~jumapico/jumapico.ws

Sobre los comandos que realizará la tarea de generación del blog, si bien estos se encuentran en el archivo Makefile, basicamente consiste en:

  1. Instalar el generador del blog: nanoc y sus dependencias.
  2. Generar el blog.
  3. Subirlo a github pages.

Para instalar el generador y sus dependencias se utiliza el programa bundle que según el Makefile se instala utilizando el comando gem. Ejecutandolo en la máquina virtual:

$ cd jumapico.ws
$ gem install --user-install bundler:2.1.4
-sh: 14: gem: not found

Lo cual es razonable, ya que gem es parte de ruby, el cual no está instalado por defecto en la VM. Ya que tenemos permisos de sudo, en lugar de crear otro manifiesto que indique la instalación del paquete ruby lo instalaremos en esta instancia, teniendo el cuidado de agregarlo en el manifest que estamos creando en el archivo build.yml.

La versión de ruby en debian buster es la 2.5, por lo que instalamos el paquete utilizando:

$ sudo apt-get install -V ruby2.5

Se instala rápido, sin problemas, y ahora probamos nuevamente de instalar bundler:

$ gem install --user-install bundler:2.1.4
$ gem install --user-install bundler:2.1.4
Fetching: bundler-2.1.4.gem (100%)
WARNING:  You don't have /home/build/.gem/ruby/2.5.0/bin in your PATH,
	  gem executables will not run.
Successfully installed bundler-2.1.4
Parsing documentation for bundler-2.1.4
Installing ri documentation for bundler-2.1.4
Done installing documentation for bundler after 4 seconds
1 gem installed

Instala sin problemas, pero hay que tener en cuenta el mensaje que indica que se debe agregar al PATH la ruta $HOME/.gem/ruby/2.5.0/bin.

Hacemos esto y continuamos instalando las dependencias agregando la ruta anterior al PATH:

$ export PATH=$PATH:$HOME/.gem/ruby/2.5.0/bin
$ bundle install --path vendor
Installing commonmarker 0.21.0 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /home/build/jumapico.ws/vendor/ruby/2.5.0/gems/commonmarker-0.21.0/ext/commonmarker
/usr/bin/ruby2.5 -r ./siteconf20201129-685-sw4fux.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /home/build/jumapico.ws/vendor/ruby/2.5.0/gems/commonmarker-0.21.0 for inspection.
Results logged to /home/build/jumapico.ws/vendor/ruby/2.5.0/extensions/x86_64-linux/2.5.0/commonmarker-0.21.0/gem_make.out

An error occurred while installing commonmarker (0.21.0), and Bundler cannot continue.
Make sure that `gem install commonmarker -v '0.21.0' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  commonmarker

Otro error. Esta vez porque no se encuentra el archivo /usr/lib/ruby/include/ruby.h necesario para compilar extensiones nativas de ruby. La solución es fácil: se instala el paquete ruby2.5-dev que como indica su descripción:

Header files for compiling extension modules for the Ruby 2.5

y se vuelven a instalar las dependencias:

$ sudo apt-get install -Vy ruby2.5-dev
$ bundle install --path vendor
...
Installing nokogiri 1.10.9 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /home/build/jumapico.ws/vendor/ruby/2.5.0/gems/nokogiri-1.10.9/ext/nokogiri
/usr/bin/ruby2.5 -r ./siteconf20201129-811-eydcqn.rb extconf.rb
checking if the C compiler accepts ... yes
Building nokogiri using packaged libraries.
Using mini_portile version 2.4.0
checking for gzdopen() in -lz... no
zlib is missing; necessary for building libxml2
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.
...

Otro error. Esta vez por falta de la librería zlib. Se instalan los archivos necesarios para el desarrolo mediante el paquete zlib1g-dev y se vuelve a probar:

$ sudo apt-get install -Vy zlib1g-dev
$ bundle install --path vendor
...
Bundle complete! 10 Gemfile dependencies, 55 gems now installed.
Bundled gems are installed into `./vendor`

y esta vez ya se instalan las dependencias sin problemas.

Escribiendo la informacion anterior como parte del archivo build.yml tenemos:

packages:
- ruby2.5
- ruby2.5-dev         # for native extensions
- zlib1g-dev          # for nokogiri

y

tasks:
  - build: |
      export PATH=$PATH:$HOME/.gem/ruby/2.5.0/bin
      cd jumapico.ws
      bundle install --path vendor

Siguendo en la consola con la generación del sitio:

$ bundle exec nanoc
...

Site compiled in 0.80s.

Resta subir los archivos al repositorio jumapico.github.io.

Manejo de secretos - variables de entorno

El envió del sitio generado a github pages se realiza igual que con Travis CI:

Ya que git está instalado en la máquina virtual (es necesario para clonar nuestro repositorio) no es necesario ninguna acción, extra para el primer paso:

$ cd output
$ git init
Initialized empty Git repository in /home/build/jumapico.ws/output/.git/
$ git config user.name "Build SourceHut"
$ git config user.email "build@sr.ht"
$ git add .
$ git commit -m "Deployed '$(cd ..; git rev-parse --short HEAD)' to Github pages"
[master (root-commit) d201e38] Deployed '2d5dc77' to Github pages
 204 files changed, 25611 insertions(+)
...

La nueva dificultad que aparece es como disponibilizar la variable de entorno GITHUB_TOKEN con el token obtenido de github.

Para ello se utilizará la incidencia #221 de builds.sr.ht que da una pista de como solucionar el problema: se agregan las variables de entorno en un archivo de secretos y se cargan en el shell. Ya que por defecto las tareas del shell se ejecutan utilizando -x, debe deshabilitarse temporalmente la impresión de comandos ejecutados, para que no aparezcan los secretos en el log cuando son cargados:

set +x
. ~/.buildsecrets
set -x

El archivo ~/.buildsecrets es creado en la web accediendo a la página builds.sr.ht secret management dashboard y agregando como contenido del archivo

export GITHUB_TOKEN=XXXXX

El tipo de secreto File, la ruta ~/.buildsecrets y modo 600. Al presionar el botón Add secret se nos muestra el mensaje:

Successfully added secret b714e84b-1f5e-46fd-9aa1-737e0e5fe881.

Y recargando la página vemos todos nuestros secretos:

Secrets Dashboard

que ahora puede ser utilizando agregando en el manifiesto:

secrets:
  - b714e84b-1f5e-46fd-9aa1-737e0e5fe881

Pasando en limpio

De lo anterior tenemos que el manifiesto escrito en el archivo build.yml nos estaría quedando como:

image: debian/buster
arch: amd64
packages:
  - ruby2.5
  - ruby2.5-dev
  - zlib1g-dev
sources:
  - https://git.sr.ht/~jumapico/jumapico.ws
secrets:
  - b714e84b-1f5e-46fd-9aa1-737e0e5fe881
tasks:
  - build: |
      set +x
      . ~/.buildsecrets
      set -x
      export PATH=$PATH:$HOME/.gem/ruby/2.5.0/bin
      cd jumapico.ws
      make publish-from-sourcehut

Como puede verse los comandos utilizados para generar y pushear el blog a github pages fueron puestos en el archivo Makefile, en lugar de agregarlos en la tarea.

Conclusion

Como puede verse de los pasos anteriores, es sencillo lanzar trabajos de prueba desde la web y conectarse por ssh a la máquina virtual para realizar pruebas que nos permitan escribir el manifiesto.

Realmente quedé impresionado por la interacción con la UI y la facilidad de uso que tiene, además de la posibilidad de conexión por ssh.

Recuerdo que en su momento configurar Travis CI me llevó mucho más tiempo y fué más engorroso, ya que la única forma de trabajo era modificar el manifiesto (.travis.yml) hacer un commit, push y ver si funcionaba o no.

Lo único que le faltaría a la interfaz web es que la página https://builds.sr.ht/ que muestra los trabajos permita ocultarlos.

Para más información conviene ver la documentación de sourcehut y manifiestos de ejemplo:

Extra - envío de email

Para que nos envíen un email cada vez que se realiza un build agregar al manifiesto:

triggers:
  - action: email
    condition: always
    to: Juan Picca <jumapico@gmail.com>