<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jorge </title>
    <description>The latest articles on DEV Community by Jorge  (@jagedn).</description>
    <link>https://dev.to/jagedn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F232573%2F71100381-fb77-441e-9f69-a63689f0e98d.jpeg</url>
      <title>DEV Community: Jorge </title>
      <link>https://dev.to/jagedn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jagedn"/>
    <language>en</language>
    <item>
      <title>Tired of AI</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Tue, 29 Jul 2025 05:23:17 +0000</pubDate>
      <link>https://dev.to/jagedn/tired-of-ai-pg3</link>
      <guid>https://dev.to/jagedn/tired-of-ai-pg3</guid>
      <description>&lt;p&gt;now I understand what feel my seniors when I was all the day refactoring VBasic Desktop application to Web application in the old K2 days&lt;/p&gt;

</description>
      <category>career</category>
      <category>refactoring</category>
      <category>productivity</category>
    </item>
    <item>
      <title>QaQatua</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Fri, 21 Mar 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/jagedn/qaqatua-491l</link>
      <guid>https://dev.to/jagedn/qaqatua-491l</guid>
      <description>&lt;p&gt;Hace unas semanas le eché una mano a un amigo QA, Oscar Islas, a "aterrizar" una idea con la que andaba trabajando y que me resultó muy atractiva desde el primer momento.&lt;/p&gt;

&lt;p&gt;Como QA muchas veces tienes que preparar flujos de pruebas contra servicios que manejan información "sensible", por ejemplo "cuenta bancaria", "password", etc.&lt;/p&gt;

&lt;p&gt;Algunos de estos servicios requieren el uso de criptografía de tal forma que estos campos van encriptados junto con el resto y a su vez son devueltos encriptados.&lt;/p&gt;

&lt;p&gt;De cara al desarrollo no deja de ser "una capa más". Quiero decir, cuando desarrollo mi programa para enviar/recibir estos payloads, configuro mi aplicación con las claves publicas/privadas que el admin del entorno me proporcione y con unas cuantas líneas de código encripto/desencripto los mensajes.&lt;/p&gt;

&lt;p&gt;El trabajo del QA es más laborioso. Debe preparar juegos de prueba que contemplen todos los escenarios así que le toca o bien pregenerar los mensajes o incluir algun tipo de librería que le haga lo mismo que hace el programador lo cual tampoco es fácil&lt;/p&gt;

&lt;p&gt;Oscar Islas se enfrenta casi a diario a este tipo de situaciones. Como usa Postman pudo desarrollar una pequeña librería Groovy que embeber a la que invoca para que le haga el trabajo de encriptar/desencriptar payloads.&lt;/p&gt;

&lt;p&gt;Tras echarle una mano revisando y poniendo a punto la librería Groovy se nos ocurrió que esta funcionalidad podría ser una buena herramienta para la comunidad y así comenzamos QaQatua (un giño a QA y a la funcionalidad que implementa que no deja de ser una cacatua que repite lo que le dices)&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;Básicamente QaQatua es una aplicación web que, tras configurarla, permite transformar las partes de un payload que le indiques tanto para encriptar como para desencriptar.&lt;/p&gt;

&lt;p&gt;Así pues el "flujo" de un usuario (QA principalmente) sería:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;preparar una prueba con datos "en claro"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invocar al endpoint QaQatua de encriptación, el cual le devolverá el payload transformado&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invocar al servicio con el payload obtenido de la llamada a QaQatua&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invocar al endpoint QaQatua de desencriptación, el cual le devolverá el payload transformado&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;validar que los campos retornados, ya "en claro", son los esperados&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usando Postman, SoapUI, o simple scripts con &lt;code&gt;curl&lt;/code&gt; el uso es sencillo y evita tener que añadir dependencias a la herramienta&lt;/p&gt;

&lt;h2&gt;
  
  
  QaQatua
&lt;/h2&gt;

&lt;p&gt;QaQatua permite a un usuario registrarse en el sistema mediante email/password.&lt;/p&gt;

&lt;p&gt;Una vez identificado, el usuario puede crear hasta 3 proyectos, proporcionando un nombre identificativo a cada uno.&lt;/p&gt;

&lt;p&gt;Cada proyecto consta de los siguientes campos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;fields, una lista de campos, separados por comas, a modificar en los payloads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;priv/pub keys, una pareja de clave pública/privada para operar con el payload.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si el usuario no quiere "complicarse" con la gestión de claves puede solicitar a QaQatua que genere un juego de claves.&lt;/p&gt;

&lt;p&gt;En cualquier caso estos tres campos son editables en todo momento por lo que puede empezar a diseñar sus casos de prueba y posteriormente proporcionar las claves que requiera el servicio a invocar.&lt;/p&gt;

&lt;p&gt;Así mismo, al permitir más de un proyecto, es fácil crear casos de pruebas donde intervienen más de un servicio cada uno con claves diferentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Api
&lt;/h2&gt;

&lt;p&gt;QaQatua ofrece un API realmente sencillo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/api/projects:&lt;br&gt;
-- get/post/put/delete&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/api/encrypt:&lt;br&gt;
-- post&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/api/decrypt:&lt;br&gt;
-- post&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Además de contar con un interface Web, el api de projects permite la gestión vía REST&lt;/p&gt;

&lt;p&gt;Los endpoints de encrypt/decrypt son altamente configurables y permiten especificar qué proyecto usar en cada petición (si el usuario tiene sólo un proyecto se usa por defecto) e incluso añadir &lt;code&gt;fields&lt;/code&gt; a usar en una petición específica&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;En los próximos posts iremos contando más detalles de este proyecto, como tecnología usada, casos de éxito, nuevas funcionalidades, etc.&lt;/p&gt;

</description>
      <category>qaqatua</category>
      <category>qa</category>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Clean your dockers trash</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Tue, 03 Dec 2024 09:03:49 +0000</pubDate>
      <link>https://dev.to/jagedn/clean-your-dockers-trash-1j50</link>
      <guid>https://dev.to/jagedn/clean-your-dockers-trash-1j50</guid>
      <description>&lt;p&gt;Your computer is claiming your hard disk is running out of space?&lt;/p&gt;

&lt;p&gt;If you use Docker frequently, probably pulling images, building new images to push, testings docker-compose files, ... and you don't clean once you don't need anymore them, soon or later you'll run out of space in your hard drive.&lt;/p&gt;

&lt;p&gt;This happened to me yesterday so I executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker system prune --all -f --volumes
....
a few minutes later
....
some minutes more
...
Total reclaimed space: 361.6GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;361 GB recovered !!! &lt;/p&gt;

&lt;p&gt;But pay attention because this command will remove all images and volumes in your system which are not used at this moment so you can lost some important data (as a mysql-volume with a backup of the prod database ejem ejem)&lt;/p&gt;

</description>
      <category>docker</category>
    </item>
    <item>
      <title>Reading millions of json items</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Thu, 21 Nov 2024 18:27:28 +0000</pubDate>
      <link>https://dev.to/jagedn/reading-millions-of-json-items-5c0p</link>
      <guid>https://dev.to/jagedn/reading-millions-of-json-items-5c0p</guid>
      <description>&lt;p&gt;I have wrote an small example to read a big file (vía http) with an array of items in json&lt;/p&gt;

&lt;p&gt;10 millions are parsed in 5sec in my laptop!!!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jagedn/huge-json" rel="noopener noreferrer"&gt;https://github.com/jagedn/huge-json&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>the only one tool you need to be a good dev</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Tue, 22 Oct 2024 08:24:07 +0000</pubDate>
      <link>https://dev.to/jagedn/the-only-one-tool-you-need-to-be-a-good-dev-4c46</link>
      <guid>https://dev.to/jagedn/the-only-one-tool-you-need-to-be-a-good-dev-4c46</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomtwxh6kk9eosog6ytlm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomtwxh6kk9eosog6ytlm.png" alt="expeso machine" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;



</description>
    </item>
    <item>
      <title>Cuando te rechazan por tu nivel de inglés</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Tue, 01 Oct 2024 17:26:23 +0000</pubDate>
      <link>https://dev.to/jagedn/cuando-te-rechazan-por-tu-nivel-de-ingles-25a8</link>
      <guid>https://dev.to/jagedn/cuando-te-rechazan-por-tu-nivel-de-ingles-25a8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faegn1t2ahizko9e5jpxj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faegn1t2ahizko9e5jpxj.jpg" alt="gloria" width="214" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>from aguilera.soy to jorge-aguilera.blog</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Thu, 26 Sep 2024 10:51:56 +0000</pubDate>
      <link>https://dev.to/jagedn/from-aguilerasoy-to-jorge-aguilerablog-5hkp</link>
      <guid>https://dev.to/jagedn/from-aguilerasoy-to-jorge-aguilerablog-5hkp</guid>
      <description>&lt;p&gt;A few days ago I've migrated my personal blog from "jorge.aguilera.soy" to "jorge-aguilera.blog"&lt;/p&gt;

&lt;p&gt;the migration was as simple as run a &lt;code&gt;cp -R blog/* newblog/*&lt;/code&gt; as I'm using a static site blog&lt;/p&gt;

&lt;p&gt;no database, no exports, no imports, only provision a new domain name and a copy&lt;/p&gt;

&lt;p&gt;but the shock come to me when I've checked the visits stats and verified I've doubled the numbers!!!&lt;/p&gt;

&lt;p&gt;same blog, no (yet) new contents, ... &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmqj9860ddtds980b6tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmqj9860ddtds980b6tj.png" alt="full stats" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also I could see a significant increment in USA visitors. Although I'm trying to write more post in English most of my content is still in Spanish &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0srhn5l945p1w7flym12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0srhn5l945p1w7flym12.png" alt="Countries" width="781" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a class="mentioned-user" href="https://dev.to/litlyx"&gt;@litlyx&lt;/a&gt; for this super-simple-util tool&lt;/p&gt;

</description>
      <category>writing</category>
    </item>
    <item>
      <title>CorreQueVuelan addons para Firedox</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Sat, 21 Sep 2024 20:56:51 +0000</pubDate>
      <link>https://dev.to/jagedn/correquevuelan-addons-para-firedox-6c2</link>
      <guid>https://dev.to/jagedn/correquevuelan-addons-para-firedox-6c2</guid>
      <description>&lt;p&gt;He creado una extension para #Firefox, CorreQueVuelan, útil si usas el servicio de Bicimad para ir y venir al trabajo &lt;/p&gt;

&lt;p&gt;Es muy común que llegada la hora de salida del trabajo, la estación se vaya quedando sin bicis libres, así que la idea es una extensión del navegador que va a estar consultando la disponibilidad y te muestra un icono cuando queden pocas ... corre que vuelan &lt;/p&gt;

&lt;p&gt;Una vez instalada la extension te aparecerá el icono de una bicicleta &lt;/p&gt;

&lt;p&gt;en la configuracion de la extension indicas la estacion que tienes más cerca (en futuras versiones podria hacer que usara tu ubicacion) &lt;/p&gt;

&lt;p&gt;en cualquier momento puedes consultar cuantas bicis quedan libres simplemente pasando el rato por encima de la extension &lt;/p&gt;

&lt;p&gt;Y cuando queden pocas bicis la extension mostrará un icono para llamar tu atención para que salgas corriendo sin salvar lo que estés haciendo &lt;/p&gt;

&lt;p&gt;La URL por si quieres instalarlo &lt;/p&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/es/firefox/addon/correquevuelan/" rel="noopener noreferrer"&gt;https://addons.mozilla.org/es/firefox/addon/correquevuelan/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n0sphnrx83sff4zvj61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n0sphnrx83sff4zvj61.png" alt="addon" width="276" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglfri8061v6go80qcbpx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglfri8061v6go80qcbpx.png" alt="addon" width="698" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8o4piw7jvh5t0pxlpavq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8o4piw7jvh5t0pxlpavq.png" alt="addon" width="272" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou90d4qq5n79mlf904vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou90d4qq5n79mlf904vc.png" alt="addon" width="268" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>RemoveCookieWall, una extension de Firefox</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Wed, 11 Sep 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jagedn/removecookiewall-una-extension-de-firefox-1pab</link>
      <guid>https://dev.to/jagedn/removecookiewall-una-extension-de-firefox-1pab</guid>
      <description>&lt;p&gt;¿Harto del banner que se ha puesto de moda en las webs para que aceptes cookis de terceros o pases por caja? En este post explico cómo me hecho (y publicado) una extensión de Firefox para evitarlo en la mayoría de sites&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;INFO&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;El código de esta extensión está publicado en &lt;a href="https://github.com/jagedn/removecookiewall-addon" rel="noopener noreferrer"&gt;https://github.com/jagedn/removecookiewall-addon&lt;/a&gt;
y lo puedes instalar en Firefox (también en móvil) desde &lt;a href="https://addons.mozilla.org/es/firefox/addon/removecookiewall/" rel="noopener noreferrer"&gt;https://addons.mozilla.org/es/firefox/addon/removecookiewall/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Desde hace unos meses, y por una exigencia de Europa (creo), la mayoría de las webs te muestran un banner la primera vez que accedes a ellas que no te dejan continuar hasta que no decides entre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;te voy a colocar miles de cookies de terceros en tu navegador que van a espiar lo que navegas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pasa por caja y págame para que no lo haga&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La mayoría de estas librerias ejecutan un javascript nada más cargar la página que leen tus cookies. Si ven que no has pasado por caja te muestran un dialogo HTML y bloquean el body cambiando el style a "block" (o similar)&lt;/p&gt;

&lt;p&gt;Este diálogo no te deja leer lo que hay debajo pero …​ no deja de ser un elemento DOM del HTML, así que, como los navegadores te permiten abrir una consola de desarrollo e inspeccionar el HTML, me surgió la idea de eliminar manualmente el díalogo (simplemente le das a inspeccionar, buscas en el HTML donde está definido y le das a suprimir) y chimpón, el diálogo desaparece. Luego busco la declaración del "body" y dando doble click en el atributo style le quito la propiedad que lo bloquea y ya puedo hacer scroll.&lt;/p&gt;

&lt;p&gt;Poca magia.&lt;/p&gt;

&lt;p&gt;Qué es lo que está pasando entonces? Pues simplemente el código javascript sigue esperando que le llegue un evento de usuario diciendole qué botón has pulsado, pero estos botones ya no están, así que nunca le llegará y no te instalará cookies de terceros.&lt;/p&gt;

&lt;p&gt;Ok, pero ¿y si refresco la página?. Pues vuelta a empezar …​ así que esto es perfecto para una nueva extensión del navegador que lo haga por mí.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extension RemoveCookieWall
&lt;/h2&gt;

&lt;p&gt;Una extensión Firefox, de forma resumida, es un espacio de memoria del navegador reservado donde se ejecuta un código javascript que puede dialogar con él.&lt;/p&gt;

&lt;p&gt;Puede (si le concede permisos el usuario) inyectar código en las páginas que visitas, abrir pestañas, cerrarlas, comunicarse con servicios remotos, …​&lt;/p&gt;

&lt;p&gt;RemoveCookieWall es una extensión de Firefox que lo "único" que necesita es que el navegador inyecte en todas las páginas que el usuario visita un pequeño código javascript.&lt;/p&gt;

&lt;p&gt;Este javascript según se ha cargado la página inspeccionará si existe un elemento DOM que coincida con alguno de los que he investigado que ese están usando. Si lo detecta usará funciones standard de Javascript para borrarlo.&lt;/p&gt;

&lt;p&gt;Como el banner a veces puede aparecer (mili)segundos después de que nuestro código se ejecute lo que hace el script es repetir la búsqueda durante un par de segundos. Pasado este tiempo si el banner no ha aparecido la extensión asume que la página no tiene un CookieeWall y termina&lt;/p&gt;

&lt;p&gt;Y esto es todo. Sólo queda empaquetar el código, añadir un fichero Manifest que indique los permisos que requiere nuestra extensión y publicarlo en Firefox&lt;/p&gt;

&lt;h2&gt;
  
  
  Código
&lt;/h2&gt;

&lt;p&gt;El código JS básicamente es:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var readyStateCheckInterval;
var counter = 0;

function sanitizeBody() {
    document.body.style.overflow = "unset"
    document.body.classList.remove('sxnlzit')
    document.body.classList.remove('didomi-popup-open')
    document.body.parentNode.classList.remove('sp-message-open')
}

function removeMe(element) {
    element.remove();
    sanitizeBody();
}

readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
        counter++;
        const removeParent = ['div.pmConsentWall']; //elpais
        [...removeParent].forEach(s =&amp;gt; {
            var divs = document.body.querySelectorAll(s);
            [...divs].forEach(element =&amp;gt; {
                removeMe(element.parentNode);
            });
        });
        const removeThis = [
            'div[data-nosnippet="data-nosnippet"]',
            '#mrf-popup',
            '#didomi-popup',
            '[id^="sp_message_container_"]',
            '#cl-consent',
            'dialog.cookie-policy'
        ];
        [...removeThis].forEach(s =&amp;gt; {
            var divs = document.body.querySelectorAll(s);
            [...divs].forEach(element =&amp;gt; {
                removeMe(element);
            });
        });
        if (counter &amp;gt; 30) {
            clearInterval(readyStateCheckInterval);
        }
    }
}, 100);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nada más ser inyectado el código en la página se inicia un &lt;code&gt;interval&lt;/code&gt; cada 100 milis&lt;/p&gt;

&lt;p&gt;El scrip busca si el &lt;code&gt;document.body.querySelectorAll&lt;/code&gt; encuentra algún elmento tipo &lt;code&gt;#mrf-popup&lt;/code&gt;, &lt;code&gt;#didomi-popup&lt;/code&gt;, etc. Si lo encuentra simplemente lo elimina con &lt;code&gt;element.remove()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Después de unos cuantos intentos termina borrando el &lt;code&gt;interval&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Toda extensión tiene que tener un fichero Manifest. El de esta extensión es simplemente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

    "description": "Remove CookieWall",
    "manifest_version": 2,
    "name": "RemoveCookieWall",
    "version": "0.11",
    "homepage_url": "https://github.com/jagedn/removecookiewall-addon",
    "icons": {
        "48": "icons/border-48.png"
    },
    "content_scripts": [{
        "matches": [
            "*://*/*"
        ],
        "js": ["removeCookieWall.js"]
    }],
    "browser_specific_settings": {
        "gecko": {
            "id": "remove-cookiewall@aguilera.soy"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como ves, content_scripts indica que queremos inyectar el js en todas las páginas. Otras extensiones pueden indicar sólo un site, otras ejecutar un javascript en backuground, …​&lt;/p&gt;

&lt;h2&gt;
  
  
  Build and publish
&lt;/h2&gt;

&lt;p&gt;Para publicar en Firefox simplemente tenemos que proveer un zip donde esten todos los ficheros que requiere la extension. Para hacerlo fácil me he hecho un &lt;code&gt;build.sh&lt;/code&gt; que simplemente ejecuta el zip:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zip -r -FS ../remove-cookiewall.zip * --exclude '.git' --exclude 'build.sh'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Publicar una extension en Firefox no tiene ninguna complicación y es gratis. Lo único que tu extensión tiene que pasar una revisión inicial que puede tardar uno (o varios) días&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>firefox</category>
    </item>
    <item>
      <title>Porqué leer Neuromante en el 2024</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Sun, 08 Sep 2024 20:23:42 +0000</pubDate>
      <link>https://dev.to/jagedn/porque-leer-neuromante-en-el-2024-1k8k</link>
      <guid>https://dev.to/jagedn/porque-leer-neuromante-en-el-2024-1k8k</guid>
      <description>&lt;p&gt;Estas vacaciones me he llevado un libro conmigo que compré allá por el 1995 y que había intentado leer un par de veces sin conseguir terminarlo&lt;/p&gt;

&lt;p&gt;Pero esta vez lo he leído casi del tirón &lt;/p&gt;

&lt;p&gt;Seguramente mucha gente lo habrá leído y entendido a la primera, pero a mí se me hizo bola.&lt;/p&gt;

&lt;p&gt;Puede que ahora, en el 2024, cuando The Matrix ya va por su veteasaber parte y me la he visto muchas veces, o que el concepto de la AI está tan de moda, o que ya haya madurado, el caso es que ahora es un libro más fácil de entender, y no por ello más simple&lt;/p&gt;

&lt;p&gt;Jonny Nmenomic, Microsoft, Matriz, son creaciones de un chaval que en 1984 imaginó un futuro cyberpunk al que nos acercamos peligrosamente &lt;/p&gt;

&lt;p&gt;En fin toda una obra de arte IMHO&lt;/p&gt;

</description>
      <category>scifi</category>
      <category>microsoft</category>
      <category>ai</category>
    </item>
    <item>
      <title>Apisix Gateway con autentificación Keycloak (y SSL con Caddy)</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Fri, 06 Sep 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jagedn/apisix-gateway-con-autentificacion-keycloak-y-ssl-con-caddy-5di2</link>
      <guid>https://dev.to/jagedn/apisix-gateway-con-autentificacion-keycloak-y-ssl-con-caddy-5di2</guid>
      <description>&lt;p&gt;En este post voy a instalar en una máquina (linux obviamente) desde cuasi-cero un stack que nos permita tener un acceso &lt;code&gt;https&lt;/code&gt; a unos microservicios pasando por un api gateway (con Apisix) que dialogará con Keycloack para gestionar la autentificación y autorización del usuario a los mismos.&lt;/p&gt;

&lt;p&gt;De forma visual la arquitectura será algo parecido a:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5xocfpadtkuve830zk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5xocfpadtkuve830zk6.png" alt="Diagram" width="583" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proyecto
&lt;/h2&gt;

&lt;p&gt;La idea del proyecto es crear un stack en Internet de tal forma que se expongan unos servicios a los que el usuario accederá previa identificación.&lt;/p&gt;

&lt;p&gt;Dicha identificación podrá ser mediante usuario/password o bien mediante un proveedor de identidades, en este caso Linkedin (de igual forma puede ser Github, Gitlab, Google, etc.)&lt;/p&gt;

&lt;p&gt;El acceso a los servicios se realizará mediante https y la gestión de la autentificación residirá en una pieza ajena a los servicios. Estos recibirán una cabecera con un JWT donde podrán extraer la información del usuario sin preocuparse de cómo se ha obtenido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requisitos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Una máquina Linux (la mia tiene 2GB de memoria y unos pocos gigas de disco)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tener acceso como root (seguramente podrías con otro usuario pero no me voy a complicar)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker instalado&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Un dominio en internet (para este post usaré &lt;code&gt;apisix.edn.es&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Una entrada en el DNS que apunte a la IP pública de la máquina&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;acceso ssh a la máquina.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;si se desea probar la integración con Linkedin (o similar) se necesita crear una App en &lt;a href="https://developer.linkedin.com/" rel="noopener noreferrer"&gt;https://developer.linkedin.com/&lt;/a&gt; y generar un ApiClient y SecretClient&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crear un dominio/subdominio suele tardar unos minutos, al igual que la propagación de la entrada DNS. Recomiendo hacer esto lo primero para que se vaya realizando la propagación DNS y así Caddy aprovisionará un certificado sin esperas.&lt;/p&gt;

&lt;p&gt;Recomiendo tener todos los puertos de la máquina expuesta a internet cerrados (e incluso cambiar el puerto por defecto 22 de ssh a otro) salvo el 80 y el 443. El puerto 80 es necesario para que Caddy pueda gestionar la provisión del certificado&lt;/p&gt;

&lt;h2&gt;
  
  
  Servicios Web
&lt;/h2&gt;

&lt;p&gt;La idea es tener nuestros microservicios "limpios" de cualquier lógica de identificar a un usuario. Simplemente recibirán un JWT donde podrá extraer el nombre, email, etc.&lt;/p&gt;

&lt;p&gt;Para este ejemplo vamos a usar una imagen &lt;code&gt;httpbin.org&lt;/code&gt; que simplemente muestra los parámetros que se le envían en la petición y así comprobar que el servicio recibe dichos parámetros.&lt;/p&gt;

&lt;p&gt;Así mismo vamos a crear dos servicios, &lt;code&gt;web1&lt;/code&gt; y &lt;code&gt;web2&lt;/code&gt; para darle más contenido y poder explorar cómo manejarlos con Apisix&lt;/p&gt;

&lt;p&gt;docker-compose.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  web1:
    image: kennethreitz/httpbin

  web2:
    image: kennethreitz/httpbin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caddy
&lt;/h2&gt;

&lt;p&gt;Caddy es un reverse proxy que gestiona de forma automática el aprovisionar un certificado SSL con Let’s Encrypt de tal forma que será nuestra "puerta de entrada" al sistema mediante &lt;code&gt;https&lt;/code&gt;. Una vez que securiza la conexión con el cliente mediante SSL realiza la labor de reverse proxy y le pasa la petición al servicio que se le indique&lt;/p&gt;

&lt;p&gt;docker-compose.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  caddy:
   image: caddy
   ports:
     - "80:80"
     - "443:443"
   volumes:
     - ./caddy/data:/data/
     - ./caddy/config:/config/
     - ./caddy/Caddyfile:/etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La configuración es además bastante simple, al menos para el alcance de este artículo:&lt;/p&gt;

&lt;p&gt;caddy/Caddyfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  email TU_EMAIL@TU_EMAIL
}

apisix.edn.es {
  reverse_proxy http://apisix:9080
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este fichero indicarás tu correo y el dominio a usar ( &lt;code&gt;apisix.edn.es&lt;/code&gt; en mi ejemplo)&lt;/p&gt;

&lt;p&gt;Una vez que Caddy resuelve el SSL realizará el reverse proxy hacia el servicio &lt;code&gt;apisix&lt;/code&gt; que crearemos a continuación&lt;/p&gt;

&lt;h2&gt;
  
  
  Keycloak
&lt;/h2&gt;

&lt;p&gt;Keycloak es un sistema de autenticación y autorización seguro (Open Source). Es decir, es el encargado de "gestionar los usuarios" y los "accesos a los recursos"&lt;/p&gt;

&lt;p&gt;En su uso más básico podremos dar de alta usuarios vía interface web y los servicios podrán dialogar con él para validar si un usuario es quien dice ser y saber a qué tiene acceso.&lt;/p&gt;

&lt;p&gt;También puedes conectarlo con directorios de usuarios típicos en empresa (Kerberos, Active Directory, etc.) e incluso usar &lt;code&gt;identity providers&lt;/code&gt; como Google, Linkedin, Github, etc.&lt;/p&gt;

&lt;p&gt;Para este ejemplo vamos a crear un usuario con el interface web y además añadir Linkedin como identity provider (es decir, un usuario podrá identificarse mediante user/password o usando su cuenta de Linkedin)&lt;/p&gt;

&lt;p&gt;docker-compose.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    container_name: keycloak
    env_file:
      - .env
    command: start-dev
    depends_on:
      - keycloakdb
    ports:
      - 8080:8080

  keycloakdb:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    env_file:
     - .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El interface de Keycloak realiza dos funciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;mostrar el dialogo para autentificar un usuario&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mostrar el dashboard de admin que permite la configuración y gestion del propio Keycloak&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Obviamente NO queremos que el dashboard esté accesible a todo el mundo así usaremos las variables de entorno que nos ofrece para configurarle dos URLs diferentes. Una será accesible a todos los usuarios que quieran acceder a nuestro sistema y otra sólo estará disponible para administradores&lt;/p&gt;

&lt;p&gt;Para nuestro ejemplo estas URLs serán:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://apisix.edn.es/keycloak" rel="noopener noreferrer"&gt;https://apisix.edn.es/keycloak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  .env
&lt;/h2&gt;

&lt;p&gt;Usamos un fichero &lt;code&gt;.env&lt;/code&gt; para tener en un sitio las variables de entorno de configuración, así como usuarios y password de sistema, así que este fichero NO debería ser versionado&lt;/p&gt;

&lt;p&gt;.env&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KC_DB=postgres
KC_DB_URL=jdbc:postgresql://keycloakdb:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=password

KC_HOSTNAME=https://apisix.edn.es/keycloak
KC_HOSTNAME_PORT=443
KC_HOSTNAME_STRICT=true
KC_HOSTNAME_STRICT_HTTPS=true

KC_HOSTNAME_ADMIN=http://localhost:8080

KC_LOG_LEVEL=info

KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin

POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;dl&gt;
&lt;dt&gt;INFO&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;Básicamente, las variables que empiezan por &lt;code&gt;KC_&lt;/code&gt; son propias de Keycloak y las que empiezan por &lt;code&gt;POSTGRES&lt;/code&gt;
son del motor de la base de datos. De ahí que estén duplicados los valores&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2&gt;
  
  
  Preparando Keycloak
&lt;/h2&gt;

&lt;p&gt;A estas alturas estamos listos para levantar la primera fase de nuestro proyecto&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Si todo va bien, Caddy gestionará el tema del SSL y Keycloak preparará la base de datos …​ pero cómo acceder a la consola de Keycloak si está corriendo en una máquina en nuestro proveedor?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh -p 2222 -L 8080:localhost:8080 root@TU.IP&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Es decir, abrimos una conexión ssh con nuestra máquina (yo uso el puerto 2222) y hacemos enrutado de puertos 8080 entre la máquina remota y la nuestra.&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;WARNING&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;Esta NO es la manera de segurizar un acceso remoto en una máquina en internet, pero para este
artículo no me voy a complicar.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Existen tutoriales más detallados sobre cómo manejar Keycloak. En este artículo simplemente voy a enumerar los pasos a realizar, pues son realmente muy fáciles e intuitivos&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;crear un realm &lt;code&gt;apisix_test&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;en este realmn crear un client &lt;code&gt;apisix&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;configurar en este cliente &lt;code&gt;valid redirect URL&lt;/code&gt; con &lt;code&gt;https://apisix.edn.es/web1/anything&lt;/code&gt; y &lt;code&gt;https://apisix.edn.es/web2/anything&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;asegurarnos en este cliente que está marcada la opción "Client authentication". De esta forma la autentificación en este realm es gestionada entre servicios. Si estuviera OFF sería para aplicaciones Javascript por ejemplo&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez creado, Keycloak nos creará un client y un secret para este client que deberemos proporcionar a Apisix (paso siguiente)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;crearemos un usuario &lt;code&gt;test&lt;/code&gt; con password &lt;code&gt;test&lt;/code&gt; y marcaremos como que su email ha sido validado y que no necesita cambiar la password en el primer acceso. (Por no complicarnos)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si quieres añadir Linkedin como Identity Provider, en el menú de la izquierda te permitirá elegirlo y añadir las claves creadas desde Linkedin&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Apisix
&lt;/h2&gt;

&lt;p&gt;Apisix es un ApiGateway Open Source (la lógica de negocio no reside en él, sino que se dedica a enrutar, transformar, etc. peticiones hacia los servicios).&lt;/p&gt;

&lt;p&gt;En este post lo vamos a usar en modo "standalone" de tal forma que la configuración y gestión sea lo más sencilla posible.&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;INFO&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;Puedes instalarlo con varias instancias, que use &lt;code&gt;etcd&lt;/code&gt; como respaldo de la configuración, puedes
instalarlo en kubernetes, etc.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;docker-compose.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  apisix:
    image: apache/apisix:${APISIX_IMAGE_TAG:-3.10.0-debian}
    volumes:
      - ./apisix_conf/apisix-standalone.yaml:/usr/local/apisix/conf/apisix.yaml
    env_file:
      - .env
    environment:
      - APISIX_STAND_ALONE=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Añadiremos en el fichero &lt;code&gt;.env&lt;/code&gt; la configuración creada por Keycloak&lt;/p&gt;

&lt;p&gt;.env&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KC_OIDC_CLIENTID=apisix
KC_OIDC_SECRET=myhg------------
KC_OIDC_ISSUER=apisix_test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último el fichero &lt;code&gt;apisix-standalone.yml&lt;/code&gt; donde ocurre toda la magia:&lt;/p&gt;

&lt;p&gt;apisix-standalone.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routes:
  -
      uris: ["/keycloak/js/*", "/keycloak/resources/*", "/keycloak/realms/*", "/keycloak/robots.txt"]
      upstream_id: 3
      plugins:
         proxy-rewrite:
           regex_uri: ["/keycloak/(.*)","/$1"]

  -
      uri: /web1/*
      upstream_id: 1
      plugins:
        proxy-rewrite:
           regex_uri: ["/web1/(.*)"]
        openid-connect:
           client_id: ${{KC_OIDC_CLIENTID}}
           client_secret: ${{KC_OIDC_SECRET}}
           discovery: https://apisix.edn.es/keycloak/realms/${{KC_OIDC_ISSUER}}/.well-known/openid-configuration
           redirect_uri: https://apisix.edn.es/web1/anything
           scope: openid profile

  -
      uri: /web2/*
      upstream_id: 2
      plugins:
        proxy-rewrite:
           regex_uri: ["/web2/(.*)"]
        openid-connect:
           client_id: ${{KC_OIDC_CLIENTID}}
           client_secret: ${{KC_OIDC_SECRET}}
           discovery: https://apisix.edn.es/realms/${{KC_OIDC_ISSUER}}/.well-known/openid-configuration
           redirect_uri: httsp://apisix.edn.es/web2/anything
           scope: openid profile

upstreams:
  -
      id: 1
      nodes:
          "web1:80": 1
      type: roundrobin

  -
      id: 2
      nodes:
          "web2:80": 1
      type: roundrobin

  -
      id: 3
      nodes:
         "keycloak:8080": 1
      type: roundrobin

#END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo primero: El fichero debe acabar en "#END" !!!! Esto es así porque estamos usando la versión standalone. En una instalación en producción NO usaríamos este modo y la gestión de rutas, etc las tendríamos en ficheros separados por ejemplo.&lt;/p&gt;

&lt;p&gt;Como ves este fichero es donde configuramos tanto las rutas, como los backends (upstreams) y plugins.&lt;/p&gt;

&lt;p&gt;Para nuestro ejemplo vamos a usar 3 upstreams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;web1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;web2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;los endpoints de keycloak "abiertos"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Así mismo en este ejemplo sencillo vamos a configurar 3 tipos de rutas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Los endpoints abiertos de keycloak los dirigimos tal cual&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creamos rutas para web1 y web2, y para complicar el ejemplo cada uno podría estar configurado para usar diferentes realm (web1 por ejemplo permitiría accesos a un realm y web2 a otros)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En el caso de web1 (idem para web2) podríamos dirigir las llamadas al endpoint del servicio &lt;code&gt;/api&lt;/code&gt; por ejemplo. Como estamos usando la imagen de &lt;code&gt;httpbin.org&lt;/code&gt; usamos un endpoint suyo llamado &lt;code&gt;anything&lt;/code&gt; que simplemente vuelca los parámetros de la llamada (y así poder comprobar que recibe el JWT entre otros)&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejecutando
&lt;/h2&gt;

&lt;p&gt;Si levantamos todos los servicios &lt;code&gt;docker compose up -d&lt;/code&gt; nuestro stack debería estar completo ahora:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;caddy&lt;/code&gt; recibe una petición https y la pasa a &lt;code&gt;apisix&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;apisix&lt;/code&gt; evalúa que &lt;code&gt;route&lt;/code&gt; despachar&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;si es para &lt;code&gt;web1&lt;/code&gt; el plugin &lt;code&gt;openid&lt;/code&gt; valida si hay una autentificadion valida en la petición&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;si no la hay o es inválida &lt;code&gt;openid&lt;/code&gt; se la envía a &lt;code&gt;keycloak&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;keycloak&lt;/code&gt; presenta el interface para que el usuario haga login y dialoga con el usuario&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;una vez validado el acceso, &lt;code&gt;keycloak&lt;/code&gt; redirige la petición original de &lt;code&gt;web1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;apisix&lt;/code&gt; la recibe y la enruta a &lt;code&gt;web1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;web1&lt;/code&gt; puede acceder al JWT&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Puedes probarlo accediendo a &lt;code&gt;http://apisix.edn.es/web1/index.html&lt;/code&gt; (al menos mientras tenga esta máquina de pruebas levantada, que cuesta sus dineros)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfavffsy6gepgqtl4xje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfavffsy6gepgqtl4xje.png" alt="apisix keycloak" width="800" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Este ejemplo NO debería de usarse como tal en producción pero creo que sirve de ejemplo para ver cómo encajan las diferentes piezas al montar una arquitectura de microservicios con un ApiGateway y con autentificadión delegada&lt;/p&gt;

</description>
      <category>apisix</category>
      <category>keycloak</category>
      <category>apigateway</category>
      <category>docker</category>
    </item>
    <item>
      <title>Executing Nextflow pipelines with Nomad by Hashicorp</title>
      <dc:creator>Jorge </dc:creator>
      <pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jagedn/executing-nextflow-pipelines-with-nomad-by-hashicorp-7p3</link>
      <guid>https://dev.to/jagedn/executing-nextflow-pipelines-with-nomad-by-hashicorp-7p3</guid>
      <description>&lt;p&gt;Nextflow enables scalable and reproducible scientific workflows using software containers. It allows the adaptation of pipelines written in the most common scripting languages.&lt;/p&gt;

&lt;p&gt;Nomad is a simple and flexible scheduler and orchestrator to deploy and manage containers and non-containerized applications across on-premises and clouds at scale.&lt;/p&gt;

&lt;p&gt;In this post, I’ll explore how to use a Nomad cluster to run Nextflow pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Nomad
&lt;/h2&gt;

&lt;p&gt;In some ways, Nomad is an alternative to Kubernetes (and similar orchestrators) without its complexity. You can create a cluster of low-resources machines and Nomad orchestrates how/where deploy your workloads.&lt;/p&gt;

&lt;p&gt;Workloads are defined in Nomad by a &lt;code&gt;Job&lt;/code&gt; witch contains 1 or more &lt;code&gt;TaskGroup&lt;/code&gt;, and every &lt;code&gt;TaskGroup&lt;/code&gt; contains 1 or more &lt;code&gt;Task&lt;/code&gt;. This task can be a Jar application, a system process or a Docker image&lt;/p&gt;

&lt;p&gt;Also, you can share volumes across your cluster. These volumes can be a local folder, in case you have only one machine, or use some of the available plugins to share CSI volumes as NFS, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  About nf-nomad
&lt;/h2&gt;

&lt;p&gt;As you can imagine, this is enough to run Nextflow pipelines (a container orchestrator and shared volumes) and the "only" missing piece is some Nextflow &lt;code&gt;Executor&lt;/code&gt; to submit tasks into the cluster and control their execution and this missing piece is the &lt;code&gt;nf-nomad&lt;/code&gt; plugin&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nf-nomad&lt;/code&gt; is a new Nextflow plugin, similar to the &lt;code&gt;nf-k8s&lt;/code&gt; kubernetes plugin, implementing the bridge logic between Nextflow and Nomad.&lt;/p&gt;

&lt;p&gt;When you execute a pipeline in Nextflow using this plugin as &lt;code&gt;executor&lt;/code&gt;, it will translate, create and submit the Nextflow process to the cluster the Nomad &lt;code&gt;Job&lt;/code&gt; specification.&lt;/p&gt;

&lt;p&gt;Once submitted, the plugin will maintain the status of every task in the Nexflow session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The plugin is open source, and you can find it at &lt;a href="https://github.com/nextflow-io/nf-nomad" rel="noopener noreferrer"&gt;https://github.com/nextflow-io/nf-nomad&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running a Nomad Cluster
&lt;/h2&gt;

&lt;p&gt;Install and run a Nomad cluster it’s as easy as download and execute the binary from the official website. In the same way, configure and attach clients to the cluster are straightforward, and it only requires a text configuration file.&lt;/p&gt;

&lt;p&gt;For simplicity, In this post, we’ll create a cluster with only one machine on (our computer). We’ll use, also, a local folder as shared volume.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Steps in this post have been tested using a Linux machine, not sure if they will work on a Mac. Surely it will not work in a Windows OS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Download the Git repo &lt;a href="https://github.com/nextflow-io/nf-nomad" rel="noopener noreferrer"&gt;https://github.com/nextflow-io/nf-nomad&lt;/a&gt; and unzip it (or clone with &lt;code&gt;git clone&lt;/code&gt;) in some folder&lt;/p&gt;

&lt;p&gt;Open a terminal console and navigate to the &lt;code&gt;validation&lt;/code&gt; sub-folder and run on it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./start-nomad.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This sh will perform the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;download the &lt;code&gt;nomad&lt;/code&gt; binary from the official website&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a &lt;code&gt;server&lt;/code&gt; and a &lt;code&gt;client&lt;/code&gt; configuration files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a &lt;code&gt;nomad_temp&lt;/code&gt; folder and configure it as a "shared" volume called &lt;code&gt;scratchdir&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;run the nomad executable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all goes well, you have running a Nomad cluster into your computer. You see the UI at &lt;a href="http://localhost:4646/" rel="noopener noreferrer"&gt;http://localhost:4646/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running a "hello world" pipeline
&lt;/h2&gt;

&lt;p&gt;In this &lt;code&gt;validation&lt;/code&gt; folder you can find several pipelines examples. They are used to validate different plugin features.&lt;/p&gt;

&lt;p&gt;We’ll try to run a simple "hello world" nextflow pipeline.&lt;/p&gt;

&lt;p&gt;Open a terminal console and navigate to the &lt;code&gt;validation&lt;/code&gt; sub-folder and run on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export NOMAD_PLUGIN_VERSION=0.2.0 (1)
nextflow run -w $(pwd)/nomad_temp/scratchdir/ -c basic/nextflow.config basic/main.nf (2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;| &lt;strong&gt;1&lt;/strong&gt; | Last version at this moment |&lt;br&gt;
| &lt;strong&gt;2&lt;/strong&gt; | This post assumes you have nextflow 24.x.x installed in your PATH |&lt;/p&gt;

&lt;p&gt;Using the NOMAD_PLUGIN_VERSION environment variable, we instruct to basic/nextflow.config about which version we want to use. Feel free to use a fixed value in basic/nextflow.config&lt;/p&gt;

&lt;p&gt;If all goes well, you will see the typical "'Bonjour world', 'Ciao world', 'Hello world', 'Hola world'" output&lt;/p&gt;

&lt;p&gt;&lt;code&gt;basic/main.nf&lt;/code&gt; is the typical Nextflow "hello world", nothing special here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;basic/nextflow.config&lt;/code&gt; configures &lt;code&gt;nomad&lt;/code&gt; as the default executor. Also, it contains the minimal configuration required by the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process {
    executor = "nomad"
}

nomad {
    client {
        address = "http://localhost:4646"
    }
    jobs {
         volume = { type "host" name "scratchdir" }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the plugin requires the endpoint to the cluster (localhost in our case) and a volume to attach to the &lt;code&gt;Job&lt;/code&gt;. This volume was created previously by the &lt;code&gt;start-nomad.sh&lt;/code&gt;. In a more realistic situation probably this volume will be a &lt;code&gt;csi&lt;/code&gt; type&lt;/p&gt;

&lt;h2&gt;
  
  
  nf-core/demo
&lt;/h2&gt;

&lt;p&gt;In the same terminal and in the &lt;code&gt;validation&lt;/code&gt; folder run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export NXF_ASSETS=$(pwd)/nomad_temp/scratchdir/assets
export NXF_CACHE_DIR=$(pwd)/nomad_temp/scratchdir/cache
nextflow run -w $(pwd)/nomad_temp/scratchdir/ -c basic/nextflow.config nf-core/demo -profile test,docker --outdir $(pwd)/nomad_temp/scratchdir/outdir

....
* Software dependencies
  https://github.com/nf-core/demo/blob/master/CITATIONS.md
------------------------------------------------------
executor &amp;gt; nomad (7)
[21/35f6ba] process &amp;gt; NFCORE_DEMO:DEMO:FASTQC (SAMPLE1_PE) [100%] 3 of 3 ✔
[9b/5e0157] process &amp;gt; NFCORE_DEMO:DEMO:SEQTK_TRIM (SAMPLE3_SE) [100%] 3 of 3 ✔
[da/0eadbc] process &amp;gt; NFCORE_DEMO:DEMO:MULTIQC [100%] 1 of 1 ✔
-[nf-core/demo] Pipeline completed successfully-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if all goes well, you’ll be able to see the &lt;code&gt;nf-core/demo&lt;/code&gt; outputs at &lt;code&gt;./nomad_temp/scratchdir/outdir/pipeline_info&lt;/code&gt; folder&lt;/p&gt;

&lt;h2&gt;
  
  
  Volumes
&lt;/h2&gt;

&lt;p&gt;In all these examples, we’ve used a local folder as a shared volume between our nextflow command line and containers created into the nomad cluster. However, you can also use a S3 bucket (with wave+fusion), or install some of the available Nomad plugins and mount an NFS server, for example.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nf-nomad&lt;/code&gt; plugin allows also mounting more than one volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs {
    volumes = [
        { type "host" name "scratchdir" },
        { type "host" name "scratchdir" path "/var/data" }, // can mount same volume in different path
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Delete Jobs
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;jobs.deleteOnCompletion&lt;/code&gt; boolean configuration you can specify if the jobs are removed once completed or maintain them for posterior inspection (using the nomad UI for example)&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets
&lt;/h2&gt;

&lt;p&gt;One feature of Nomad is to store variables into the cluster (and configure which roles can access to them,) and you can use them in your &lt;code&gt;Job&lt;/code&gt; definition avoiding using fixed values into it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nf-nomad&lt;/code&gt; plugin use this feature to provide a Nextflow &lt;code&gt;SecretsProvider&lt;/code&gt; as a bridge between your pipelines and these variables:&lt;/p&gt;

&lt;p&gt;Create a key=value variable into the cluster using the nomad cli.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./nomad var put secrets/nf-nomad/MY_ACCESS_KEY MY_ACCESS_KEY=TheAccessKey&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;use this variable as a secret into your pipeline&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflow.onComplete {
    println("The secret is: ${secrets.MY_ACCESS_KEY}")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;dl&gt;
&lt;dt&gt;INFO&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;The &lt;code&gt;start-nomad.sh&lt;/code&gt; shell create a namespace and two variables. You can see how to use them in the &lt;code&gt;secrets/main.nf&lt;/code&gt; pipeline&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2&gt;
  
  
  Stop and clean
&lt;/h2&gt;

&lt;p&gt;Stop the cluster is so easy as kill the &lt;code&gt;nomad&lt;/code&gt; process. You can use the &lt;code&gt;stop-nomad.sh&lt;/code&gt; shell to do it and clean the temporal folder&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;nomad&lt;/code&gt; closure you can configure different aspects of the plugin as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;endpoint to the cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;access token (in case you’ve protected the access with a token)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;list of datacenters to use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;region where allocate jobs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;namespace to use in jobs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;list of volume specs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;list of affinities and constraints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;attempts in case of failure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;…​&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, in current version, some of them can be overwritten using environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NOMAD_ADDRESS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NOMAD_TOKEN&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NOMAD_DC (datacenters)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NOMAD_REGION&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NOMAD_NAMESPACE&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;As a side/fun note, I would like to comment I was able to run the &lt;code&gt;bactopia&lt;/code&gt; pipeline in a raspberry pi attached to the cluster&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At this moment the plugin is in version &lt;code&gt;0.2.0&lt;/code&gt; and it is being testing by the Universitey Gent team.&lt;/p&gt;

&lt;p&gt;The Current version covers almost all the requirements to run typical pipelines, but surely new features will be included in the plugin&lt;/p&gt;

</description>
      <category>nextflow</category>
      <category>nomad</category>
    </item>
  </channel>
</rss>
