Quarkus + JKube: Qute template with markdown processing from different sources


Introduction

Quarkus provides its very own templating engine, Qute. In this post, I will show you how to use it to render Markdown from different sources using flexmark-java.

Besides showcasing Qute, another reason for this post is to show complex assembly configurations to build container images using Eclipse JKube.

I designed the project to be run in Kubernetes, so some of the features won’t be available if the application is run locally. The application renders a template with markdown fragments loaded from different locations, highlighting how these fragments can be assembled into the container image using different techniques and ultimately packaged with Eclipse JKube.

Quarkus application

This is a really simple application with a single Template and resource.

Main template

The main template file is located in src/main/resources/templates/main.html, Qute loads templates located in this directory.

The file contains a few placeholders/values that will be replaced with markdown content parsed using flexmark-java.

Qute will replace each of the placeholders with the content provided in the data method for the loaded template. The .raw suffix prevents Qute from escaping the HTML provided by flexmark-java.

Main resource class

I implemented the MainResource class to expose two endpoints.

The first endpoint delivers the processed template file and is accessible in the root path of the web application. I use the data method to replace 4 placeholders. The markdownInResources is located in a packaged resource, so it can always be loaded. The rest of the markdown resources depend on the assembly I describe later on. So if you don’t deploy the application using JKube, an error will be displayed instead of the markdown fragment.

The second endpoint delivers files located in the /opt/static directory. The regex pattern in the @Path annotation allows us to retrieve the file name of the requested file from the URL path (e.g. static/javaee-api-8.0.jar). Again, this only makes sense in the JKube deployed version of the application.

Project pom.xml

The Maven project pom.xml file is divided into several sections, let’s analyze the most important ones.

Properties

In the properties element, I’ve included the versions of the project dependencies that will be used later on and the Java target and sources version.

Besides, there is a buildTimestamp property that will be used in one of the filtered resources to replace a property placeholder in a Markdown file.

Note the JKube specific property too, this one is used to define the Kubernetes Service port type so that the service can be exposed later on using minikube service example.

Dependencies

The dependencies section, as usual, contains the project dependencies required to build and run the application.

However, I’ve included an additional random dependency with scope provided. As I’ll explain later on, I configured the Maven Assembly Plugin to process it.

Build: Resources

In addition to the standard convention src/main/resources directory for resources, I’m going to add an additional src/main/filtered-resources directory. This directory is configured to be filtered, which means that any property placeholder (e.g. ${project.artifactId}) will be replaced with the applicable Maven property when processed.

This directory contains a single file filtered-fragment.md with several properties including the buildTimestamp described earlier.

Build: Maven Assembly Plugin

I’m using the Maven Assembly Plugin to show how to include a depdendencySet in the container image.

The plugin configuration references an assembly descriptor preassemble.xml, that will be processed with its output placed in the target directory. The finalName and appendAssemblyId configurations force the assembly to be placed in the resulting directory target/preassembled.

I’ve also bound the execution of the plugin to the package phase. The plugin execution will be triggered whenever the Maven package lifecycle phase runs.

Following is the content of the preassemble.xml file:

I’ve configured the assembly to define a single dependencySet that will copy all dependencies with scope provided and their transitive dependencies to the assembly directory root (target/preassembled). Given the current combination of dependencies and assembly configuration, this directory should contain the following files when you package the project:

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

Build: Eclipse JKube

For this project, I’ve configured Eclipse JKube’s Kubernetes Maven Plugin to build a customized image.

Please note that there are other opinionated configuration-less approaches to build a Quarkus image. But in this case, since I want to demonstrate the power of assemblies I chose this procedure instead.

In the build section of the image I’ve defined the base image (from), the exposed port and the image entry point with the standard values.

Next comes the most important part, assembly configuration. The targetDir defines the directory under which files will be copied within the container. In this case, since I want to copy files to several locations, I used the root directory /.  Later on, I can define specific directories for each fileSet.

The first fileSet configures JKube to copy all files in src/main/static to the container’s /opt/static directory.

The second fileSet configures JKube to grab the files previously processed and filtered by the Maven Resources Plugin (build:resources) and copy them to the /opt/static directory too.

The third fileSet configures JKube to copy the JAR files processed by the Maven Assembly Plugin to the /opt/static directory.

You can probably see the pattern now. We use other Maven plugins to gather files in the package phase and then JKube will assemble those files in the container image during the k8s:build goal.

The fourth and fifth fileSet configure JKube to copy the Quarkus generated artifact and dependencies to the /deployments and /deployments/lib directory.

Running the Quarkus application

For simplicity purposes and to avoid pushing the generated image to a shared registry I will be using Minikube.

To avoid having to push the image to a shared registry accessible from the cluster, I will share Minikube’s Docker daemon with my local environment:

Next, I’ll instruct Maven to package the application and to run JKube’s Kubernetes Maven Plugin goals:

We can now open the application in our browser by running:

An image displaying a browser with the deployed application

If everything goes well, we will see a browser window with content similar to the previous image.

As you can see, the three markdown fragments have been replaced and loaded correctly with their appropriate styles and markup. The fragment with the debug information shows up with the placeholders correctly replaced with Maven properties. If you click on the Go! link, the JAR preassembled from the provided dependency should be downloaded too.

Conclusion

In this article, I showed you how to use Quarkus’ Qute template engine to render Markdown from different sources using flexmark-java. I’ve also explained how you can assemble these sources using different Maven Plugins and Eclipse JKube.

You can find the full source code for this post at GitHub.

Leave a comment

Your email address will not be published. Required fields are marked *