A logo showing the text blog.marcnuri.com
English
Inicio»Java»Quarkus + JKube: Qute template con procesado de ficheros markdown

Entradas Recientes

  • Kubernetes MCP Server se une a la organización Containers
  • MCP Tool Annotations: Añadiendo Metadatos y Contexto a Tus Herramientas de IA
  • Fabric8 Kubernetes Client 7.2.0 está disponible!
  • Conectarse a un servidor MCP con JavaScript y AI SDK
  • Conectarse a un servidor MCP con JavaScript y LangChain.js

Categorías

  • Antiguo
  • Front-end
  • Go
  • Herramientas
  • Industria y negocios
  • Inteligencia Artificial
  • Java
  • JavaScript
  • Operaciones
  • Personal
  • Proyectos personales

Archivos

  • julio 2025
  • mayo 2025
  • abril 2025
  • marzo 2025
  • febrero 2025
  • enero 2025
  • diciembre 2024
  • noviembre 2024
  • agosto 2024
  • junio 2024
  • mayo 2024
  • abril 2024
  • marzo 2024
  • febrero 2024
  • enero 2024
  • diciembre 2023
  • noviembre 2023
  • octubre 2023
  • septiembre 2023
  • agosto 2023
  • julio 2023
  • junio 2023
  • mayo 2023
  • abril 2023
  • marzo 2023
  • febrero 2023
  • enero 2023
  • diciembre 2022
  • noviembre 2022
  • octubre 2022
  • septiembre 2022
  • agosto 2022
  • julio 2022
  • junio 2022
  • mayo 2022
  • marzo 2022
  • febrero 2022
  • enero 2022
  • diciembre 2021
  • noviembre 2021
  • octubre 2021
  • septiembre 2021
  • agosto 2021
  • julio 2021
  • enero 2021
  • diciembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • junio 2020
  • mayo 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • noviembre 2019
  • octubre 2019
  • julio 2019
  • diciembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • marzo 2018
  • febrero 2018
  • noviembre 2017
  • octubre 2017
  • agosto 2017
  • julio 2017
  • enero 2017
  • julio 2016
  • enero 2016
  • diciembre 2015
  • noviembre 2015
  • diciembre 2014
  • marzo 2014
  • febrero 2011
  • junio 2008
  • mayo 2008
  • abril 2008
  • enero 2008
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007

Quarkus + JKube: Qute template con procesado de ficheros markdown

2020-08-19 en Java etiquetado Eclipse / Java / Eclipse JKube / Quarkus / Web por Marc Nuri | Última actualización: 2021-05-16
English version

Introducción

Quarkus proporciona su propio motor de plantillas, Qute. En esta publicación te mostraré cómo usarlo para renderizar Markdown desde diferentes fuentes usando flexmark-java.

Además de mostrar Qute, otra razón para esta publicación es demostrar configuraciones complejas de ensamblado para construir imágenes de contenedor usando Eclipse JKube. Puedes aprender más sobre JKube y cómo empezar aquí.

He diseñado el proyecto de prueba para ejecutarse en Kubernetes, por lo que algunas de las características no estarán disponibles si la aplicación se ejecuta localmente. La aplicación renderiza una plantilla con fragmentos de markdown cargados desde diferentes ubicaciones, destacando cómo estos fragmentos pueden ensamblarse en la imagen del contenedor usando diferentes técnicas y finalmente empaquetarse con Eclipse JKube.

Aplicación Quarkus

Esta es una aplicación realmente simple con una sola Plantilla y recurso.

Main template

El archivo de la plantilla principal está ubicado en src/main/resources/templates/main.html, Qute carga plantillas ubicadas en este directorio.

El archivo contiene algunos placeholders o valores que serán reemplazados con contenido markdown procesado usando flexmark-java.

<!-- ... -->
<div>{markdownInResources.raw}</div>
<div>{markdownInJKubeAssembly.raw}</div>
<div class="debug-info">{markdownFiltered.raw}</div>
<!-- ... -->

Qute reemplazará cada uno de los placeholders con el contenido proporcionado en el método data para la plantilla cargada. El sufijo .raw evita que Qute escape el HTML proporcionado por flexmark-java.

Main resource class

@Path("/")
public class MainResource {
  /* ... */
  @GET
  @Produces(MediaType.TEXT_HTML)
  public TemplateInstance get() throws IOException {
    return main
      .data("title", "Hello Quarkus, you're so qute!")
      .data("markdownInResources", md.render(MainResource.class.getResourceAsStream("/main.md")))
      // Los siguientes archivos solo estarán disponibles si la aplicación fue desplegada usando Eclipse JKube
      .data("markdownInJKubeAssembly", md.render(new File(OPT_STATIC,"main.md")))
      .data("markdownFiltered", md.render(new File(OPT_STATIC, "filtered-fragment.md")));
  }

  @GET
  @Path("static/{fileName:.+}")
  @Produces(MediaType.APPLICATION_OCTET_STREAM)
  public File getStaticFile(@PathParam("fileName") String fileName) {
    // Archivo solo disponible si la aplicación fue desplegada usando Eclipse JKube
    return new File(OPT_STATIC, fileName);
  }
}

Implementé la clase MainResource para exponer dos endpoints.

El primer endpoint sirve la plantilla ya procesada y es accesible en la ruta raíz de la aplicación web. Uso el método data para reemplazar 4 marcadores de posición. El markdownInResources está ubicado en un recurso empaquetado, por lo que siempre puede cargarse. El resto de los recursos markdown dependen del ensamblado que describo más adelante. Así que si no despliegas la aplicación usando JKube, se mostrará un error en lugar del fragmento markdown.

El segundo endpoint sirve los archivos ubicados en el directorio /opt/static. El patrón regex en la anotación @Path nos permite recuperar el nombre del archivo solicitado desde la ruta URL (ej. static/javaee-api-8.0.jar). Esto solo tiene sentido en la versión desplegada con JKube de la aplicación.

Proyecto pom.xml

El archivo pom.xml del proyecto Maven está dividido en varias secciones, analicemos las más importantes.

Propiedades

<properties>
  <maven.compiler.source>11</maven.compiler.source>
  <maven.compiler.target>11</maven.compiler.target>
  <buildTimestamp>${maven.build.timestamp}</buildTimestamp>
  <version.quarkus>1.7.0.Final</version.quarkus>
  <version.flexmark>0.62.2</version.flexmark>
  <version.jkube>1.0.0</version.jkube>
  <jkube.enricher.jkube-service.type>NodePort</jkube.enricher.jkube-service.type>
</properties>

En el elemento properties, he incluido las versiones de las dependencias del proyecto que se usarán más adelante y la versión de Java compatible con el proyecto.

Además, hay una propiedad buildTimestamp que se usará en uno de los recursos filtrados para reemplazar un placeholder en un archivo Markdown.

Nota también la propiedad específica de JKube, esta se usa para definir el tipo de puerto del Servicio de Kubernetes para que el servicio pueda exponerse más tarde usando minikube service example.

Dependencias

<dependencies>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-qute</artifactId>
    <version>${version.quarkus}</version>
  </dependency>
  <dependency>
    <groupId>com.vladsch.flexmark</groupId>
    <artifactId>flexmark-all</artifactId>
    <version>${version.flexmark}</version>
  </dependency>
  <dependency>
    <!-- Dependencia aleatoria para incluir en la imagen con preassemble.xml, Maven Assembly Plugin y JKube -->
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

La sección de dependencias, como es habitual, contiene las dependencias del proyecto requeridas para construir y ejecutar la aplicación.

Sin embargo, he incluido una dependencia aleatoria adicional con scope provided. Como explicaré más adelante, la configuración del Maven Assembly Plugin se encargará de procesarla.

Build: Resources

<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
    </resource>
    <resource>
      <directory>${project.basedir}/src/main/filtered-resources</directory>
      <targetPath>${project.basedir}/target/filtered-resources</targetPath>
      <filtering>true</filtering>
    </resource>
  </resources>
  <!-- ... -->
</build>

Además del directorio estándar src/main/resources para recursos, voy a añadir un directorio adicional src/main/filtered-resources. Este directorio está configurado para ser filtrado, lo que significa que cualquier placeholder (ej. ${project.artifactId}) será reemplazado con la propiedad Maven aplicable cuando se procese.

Este directorio contiene un solo archivo filtered-fragment.md con varias propiedades incluyendo el buildTimestamp descrito anteriormente.

Build: Maven Assembly Plugin

<build>
  <!-- ... -->
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <configuration>
        <outputDirectory>target</outputDirectory>
        <archiveBaseDirectory>/</archiveBaseDirectory>
        <appendAssemblyId>false</appendAssemblyId>
        <finalName>preassembled</finalName>
        <descriptors>
          <descriptor>preassemble.xml</descriptor>
        </descriptors>
      </configuration>
      <executions>
        <execution>
          <id>preassemble-files</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <!-- ... -->
  </plugins>
  <!-- ... -->
</build>

Estoy usando el Maven Assembly Plugin para mostrar cómo incluir un dependencySet en la imagen del contenedor.

La configuración del plugin hace referencia a un descriptor de ensamblado preassemble.xml, que será procesado con su salida colocada en el directorio target. Las configuraciones finalName y appendAssemblyId fuerzan que el ensamblado se coloque en el directorio resultante target/preassembled.

También he vinculado la ejecución del plugin a la fase package. La ejecución del plugin se activará cada vez que se ejecute la fase del ciclo de vida package de Maven.

A continuación está el contenido del archivo preassemble.xml:

<assembly>
  <id>preassembled</id>
  <formats>
    <format>dir</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <useProjectArtifact>false</useProjectArtifact>
      <outputDirectory>/</outputDirectory>
      <scope>provided</scope>
    </dependencySet>
  </dependencySets>
</assembly>

He configurado el ensamblado para definir un solo dependencySet que copiará todas las dependencias con scope provided y sus dependencias transitivas a la raíz del directorio de ensamblado (target/preassembled). Dada la combinación actual de dependencias y configuración de ensamblado, este directorio debería contener los siguientes archivos cuando empaquetes el proyecto:

  • activation-1.1.jar
  • javaee-api-8.0.jar
  • javax.mail-1.6.0.jar

Build: Eclipse JKube

<build>
  <!-- ... -->
  <plugins>
    <!-- ... -->
    <plugin>
      <groupId>org.eclipse.jkube</groupId>
      <artifactId>kubernetes-maven-plugin</artifactId>
      <version>${version.jkube}</version>
      <configuration>
        <images>
          <image>
            <name>%g/%a:%l</name>
            <build>
              <from>adoptopenjdk/openjdk11:jre-11.0.8_10-ubi</from>
              <ports>8080</ports>
              <entryPoint>
                <exec>
                  <arg>java</arg>
                  <arg>-jar</arg>
                  <arg>-Dquarkus.http.host=0.0.0.0</arg>
                  <arg>/deployments/${project.artifactId}-${project.version}-runner.jar</arg>
                </exec>
              </entryPoint>
              <assembly>
                <targetDir>/</targetDir>
                <excludeFinalOutputArtifact>true</excludeFinalOutputArtifact>
                <inline>
                  <fileSets>
                    <fileSet>
                      <directory>src/main/static</directory>
                      <outputDirectory>opt/static</outputDirectory>
                    </fileSet>
                    <fileSet>
                      <directory>target/filtered-resources</directory>
                      <outputDirectory>opt/static</outputDirectory>
                    </fileSet>
                    <fileSet>
                      <directory>target/preassembled</directory>
                      <outputDirectory>opt/static</outputDirectory>
                    </fileSet>
                    <fileSet>
                      <directory>target</directory>
                      <outputDirectory>deployments/</outputDirectory>
                      <includes>
                        <include>*-runner.jar</include>
                      </includes>
                    </fileSet>
                    <fileSet>
                      <directory>target/lib</directory>
                      <outputDirectory>deployments/lib</outputDirectory>
                      <includes>
                        <include>*.jar</include>
                      </includes>
                    </fileSet>
                  </fileSets>
                </inline>
              </assembly>
            </build>
          </image>
        </images>
      </configuration>
    </plugin>
  </plugins>
</build>

Para este proyecto, he configurado el Kubernetes Maven Plugin de Eclipse JKube para construir una imagen personalizada.

Ten en cuenta que existen otros enfoques sin configuración y con opiniones preestablecidas para construir una imagen de Quarkus. Pero en este caso, como quiero demostrar el poder de los ensamblados, elegí este procedimiento en su lugar.

En la sección build de la imagen he definido la imagen base (from), el puerto expuesto y el punto de entrada de la imagen con los valores estándar.

Luego viene la parte más importante, la configuración de assembly. El targetDir define el directorio bajo el cual los archivos serán copiados dentro del contenedor. En este caso, como quiero copiar archivos a varias ubicaciones, usé el directorio raíz /. Más adelante, puedo definir directorios específicos para cada fileSet.

El primer fileSet configura JKube para copiar todos los archivos en src/main/static al directorio /opt/static del contenedor.

El segundo fileSet configura JKube para tomar los archivos previamente procesados y filtrados por el Maven Resources Plugin (build:resources) y copiarlos al directorio /opt/static también.

El tercer fileSet configura JKube para copiar los archivos JAR procesados por el Maven Assembly Plugin al directorio /opt/static.

Probablemente, puedas ver el patrón ahora. Usamos otros plugins de Maven para recopilar archivos en la fase package y luego JKube ensamblará esos archivos en la imagen del contenedor durante el objetivo k8s:build.

El cuarto y quinto fileSet configuran JKube para copiar el artefacto generado por Quarkus y las dependencias a los directorios /deployments y /deployments/lib.

Ejecutando la aplicación Quarkus

Por simplicidad y para evitar tener que subir la imagen generada a un registro compartido de imágenes, usaré Minikube.

Para evitar tener que subir la imagen a un registro compartido accesible desde el clúster, compartiré el daemon Docker de Minikube con mi entorno local:

eval $(minikube docker-env)

A continuación, instruiré a Maven para empaquetar la aplicación y ejecutar los targets del Kubernetes Maven Plugin de JKube:

mvn package k8s:build k8s:resource k8s:apply

Ahora podemos abrir la aplicación en nuestro navegador ejecutando:

minikube service example
Una imagen mostrando un navegador con la aplicación desplegada
Una imagen mostrando un navegador con la aplicación desplegada

Si todo va bien, veremos una ventana del navegador con contenido similar a la imagen anterior.

Como puedes ver, los tres fragmentos markdown han sido reemplazados y cargados correctamente con sus estilos y marcado apropiados. El fragmento con la información de depuración aparece con los marcadores de posición correctamente reemplazados con propiedades de Maven. Si haces clic en el enlace Go!, el JAR preensamblado de la dependencia proporcionada también debería descargarse.

Conclusión

En este artículo, te he mostrado cómo usar el motor de plantillas Qute de Quarkus para renderizar Markdown desde diferentes fuentes usando flexmark-java. También he explicado cómo puedes ensamblar estas fuentes usando diferentes Plugins de Maven y Eclipse JKube.

Puedes encontrar el código fuente completo para esta publicación en GitHub.

También puedes ver las demos de nuestro equipo de Quarkus+JKube en el stand virtual de Eclipse en J4K 2020.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Navegador de artículos
Apache Camel y Casandra en un cluster KubernetesQuarkus + Picocli: Web scraper para extraer proyectos dependientes en GitHub
© 2007 - 2025 Marc Nuri