The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Dev productivity - Quarkus CLI

People hardly realize that the Quarkus CLI was available from the first public release of Quarkus back in 2019. It originally only allowed project creation and extension manipulation. The following command shows the list of supported commands:

quarkus --help Usage: quarkus <command> [<args>] These are the common quarkus commands used in various situations Options: -h, --help quarkus commands: list-extensions List extensions for a project add-extension Adds extensions to a project create-project Creates a base Quarkus maven project

Today, in version 3.1.2.Final it includes almost 30 commands spread across 6 main categories. 3 of those categories were part of the 3.0 roadmap and will be the focus of this post. In particular, this post is about building container images, deploying and extending the Quarkus CLI.

Building container images using the Quarkus CLI

Providing a simple way for creating container images with Quarkus is not something new. Since, its early days Quarkus provided extensions that took care of building container images with technologies like:

Using these extensions required their addition to the project, for example:

quarkus ext add quarkus-container-image-docker

Also, it required additional configuration options, in order to trigger the container image build:

./mvnw package -Dquarkus.container-image.build=true

While this is something that works well, users still needed to know about these extensions and the special configuration options needed to enable them. In other words, users needed to have a link to Quarkus container image documentation handy in order to check the available and their usage options.

Moreover, users needed to modify the project configuration each time they needed to switch between extensions. This is trivial, but something that should be optional as the actual application does not depend on how the container images are built. Also, it can potentially increase the noise in the version control log.

Building container images using the Quarkus CLI

Quarkus 3.0 introduces an alternative way of building container images using the Quarkus CLI. In the recent version of the CLI new sub commands are available for building and pushing container images. These are listed in the output of quarkus --help.

quarkus --help | grep image image Build or push project container image. build Build a container image. docker Build a container image using Docker. buildpack Build a container image using Buildpack. jib Build a container image using Jib. openshift Build a container image using Openshift. push Push a container image.

For example in order to perform a docker build:

quarkus image build docker

Note, that the command does not require users to edit their build files (e.g. pom.xml or build.gradle) in any way and can be run in any project without requiring any particular extension. It can be even run on blank quarkus project:

quarkus create app hello cd hello quarkus image build docker

No additional configuration needed, even when users decide to switch to a different container image technology like jib:

quarkus image build jib

Last but not least, the CLI does provide additional help like code completion and help messages:

quarkus image build jib --help

Deploying applications

In a way similar to building container images Quarkus allowed the application deployment to platforms like Kubernetes and OpenShift. Again, this is something the required the use of extensions and additional build options to enable deployment. For example to deploy an application on Kubernetes one needed to explicitly add the extension to the project and enable deployment using the quakrus.kubernetes.deploy property.

quarkus ext add quarkus-kubernetes ./mvnw package -Dquarkus.kubernetes.deploy=true

Deploying using the Quarkus CLI

In Quarkus 3.0 the CLI includes the deploy sub command that is the entry point to commands related to deployment. Using quarkus --help one can list all the related commands:

quarkus --help | grep deploy deploy Deploy application. kubernetes Perform the deploy action on kubernetes. openshift Perform the deploy action on openshift. knative Perform the deploy action on knative. kind Perform the deploy action on kind. minikube Perform the deploy action on minikube.

These commands allow developers to easily deploy their Quarkus application from one platform to the other without messing with their project files.

Imagine a team with some developers using kind and some others minikube. Prior to 3.0 they would have to stash and apply the extension of their choice each time they needed to pull changes from version control. Alternatively, they could configure build profiles. Using the CLI users are able to deploy to the platform of their choice even in cases where it’s not aligned with what is present in the project configuration. For example if the project includes the Quarkus Kubernetes exntension but user prefers to use kind extension and make use of optimized manifests for kind:

quarkus deploy kind

It’s important to note, that by having a command per platform, users can easily set platform specific configuration when executing these commands (see the --help output).

Summarizing image building and deployment commands

Quarkus 3.0 introduces new CLI commands for building container images and deploying. The commands improve the developer experience as they eliminate steps related to project setup and configuration. They allow developers to easily experiment with different technologies and guide them by providing help messages, hints and completion.

Future releases of Quarkus will expand this concept to cover areas like Quarkus Azure Functions and Quarkus Amazon Lambda.

CLI Plugins

The CLI brings some really interesting features for Developers, but unfortunately it can’t grow indefinitely as it needs to be reasonably sized. This need lead to the implementation of a plugin system for the CLI, that allows the dynamic addition of commands in the form of plugins.

What is a Plugin ?

A plugin implements a command in one of the following ways:

  • As an arbitrary executable

  • As a java source file

  • As a jar (with main)

  • As a maven artifact

  • As a JBang alias

Plugins are added to the CLI either explicitly using the Quarkus CLI, or implicitly by adding extensions to the project.

Let’s see what the CLI commands related to plugins are available:

quarkus --help | grep plug plugin, plug Configure plugins of the Quarkus CLI. list, ls List CLI plugins. add Add plugin(s) to the Quarkus CLI. remove Remove plugin(s) to the Quarkus CLI.

Initially, there are no plugins installed so, quarkus plug list returns an empty list:

quarkus plug list No plugins installed! To include the installable plugins in the list, append --installable to the command.

It also returns a hint suggesting the use of the --installable, but what are installable plugins ?

Installable refers to executables found in PATH, or JBang aliases prefixed with the quarkus prefix. Note: The command does require JBang (and prompts users for installation if not already installed).

quarkus plug list --installable Name Type Scope Location Description fmt jbang user quarkus-fmt@quarkusio kill jbang user quarkus-kill@quarkusio quarkus jbang user quarkus@quarkusio

The plugins listed are JBang aliases that are available in the quarkus.io JBang catalog (enabled by default). More catalogs can be added using the JBang binary.

Writing plugins

Let’s see how to create a plugin for the Quarkus CLI. Out of the box the Quarkus CLI provides 3 ways of creating projects:

  • A webapp

  • A command line application

  • A Quarkus extension

quarkus --help | grep -A3 create create Create a new project. app Create a Quarkus application project. cli Create a Quarkus command-line project. extension Create a Quarkus extension project

We are going to create a plugin for create that creates new applications using Quarkus Quickstarts. This is as simple as writing a script that clones the repository from Github and copies the quickstart of choice. To add some extra value on top of it let’s use a Sparse Checkout and also limit depth to 1. This minimizes the amount of data transferred and speeds things up. Moreover, recalling the actual steps needed for a Sparse Checkout is not easy, therefore it’s something that is really handy to have as a script:

#!/bin/bash DIRECTORY=$1 REPO_URL="https://github.com/quarkusio/quarkus-quickstarts.git" # Create a new directory for your Git repo and navigate into it mkdir $DIRECTORY cd $DIRECTORY # Initialize a new Git repository here git init # Add the repository from GitHub as a place your local Git repo can fetch from git remote add origin $REPO_URL git config core.sparseCheckout true echo "$DIRECTORY" >> .git/info/sparse-checkout # Fetch just the history of the specific directory git fetch --depth 1 origin main:$DIRECTORY # Checkout the specific directory git checkout $DIRECTORY mv $DIRECTORY/* . rm -rf $DIRECTORY rm -rf .git

Let’s save the script above in a file named quarkus-create-from-quickstart and add it to the PATH. The quarkus- is the required prefix and create is the name of the command under which the plugin is going to be installed. Next time quarkus plug list --installable is run it picks up the script:

quarkus plug list --installable Name Type Scope Location Description create-from-quickstart executable user /home/iocanel/bin/quarkus-create-from-quickstart fmt jbang user quarkus-fmt@quarkusio kill jbang user quarkus-kill@quarkusio quarkus jbang user quarkus@quarkusio Use the 'plugin add' subcommand and pass the location of any plugin listed above, or any remote location in the form of URL / GACTV pointing to a remote plugin.

The plugin can be now installed using:

quarkus plug add create-from-quickstart Added plugin: Name Type Scope Location Description * create-from-quickstart executable user /home/iocanel/bin/quarkus-create-from-quickstart

The plugin now appears in the quarkus --help under the create command:

quarkus --help | grep -A4 create create Create a new project. app Create a Quarkus application project. cli Create a Quarkus command-line project. extension Create a Quarkus extension project from-quickstart

And it can be used as regular command. Let’s use it to create an application from the Hibernate ORM Panache Quickstart:

quarkus create from-quickstart hibernate-orm-panache-quickstart
Using your Java skills to write plugins

Using shell scripts or arbitrary binaries (written in any language) is one of writing plugins. Java developers can alternatively put their java skills to use. Any source file that contains a main or any jar that defines a main class can be used directly by passing their location (Path or URL). In case of jars maven coordinates in the form of GACTV (<Group ID>:<Artifact Id>:<Classifier>:<Type>:<Version>) are also supported.

Let’s rewrite the create-from-github in Java and see how we can plug a java source file to the Quarkus CLI. The implementation will use jgit and commons.io. To simplify dependency management the source file includes JBang meta comments that define the fore mentioned dependencies:

///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r //DEPS commons-io:commons-io:2.11.0 //JAVA_OPTIONS -Djava.io.tmpdir=/tmp import org.eclipse.jgit.api.*; import org.eclipse.jgit.lib.*; import org.eclipse.jgit.transport.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Set; import org.apache.commons.io.FileUtils; public class CreateFromQuickstart { private static final String REPO_URL = "https://github.com/quarkusio/quarkus-quickstarts.git"; private static final String FETCH = "+refs/heads/*:refs/remotes/origin/*"; public static void main(String[] args) { String directory = args[0]; Set<String> paths = Set.of(directory); try { Path cloneDir = Files.createTempDirectory("create-from-quickstart-"); Git git = Git.init().setDirectory(cloneDir.toFile()).call(); StoredConfig config = git.getRepository().getConfig(); config.setString("remote", "origin", "url", REPO_URL); config.setString("remote", "origin", "fetch", FETCH); config.setBoolean("core", null, "sparseCheckout", true); config.setBoolean("core", null, "sparseCheckout", true); config.save(); Path file = cloneDir.resolve(".git").resolve("info").resolve("sparse-checkout"); file.getParent().toFile().mkdirs(); Files.write(file, directory.getBytes()); FetchResult result = git.fetch().setRemote("origin").setRefSpecs(new RefSpec(FETCH)).setThin(false).call(); git.checkout().setName("origin/main").call(); File source = cloneDir.resolve(directory).toFile(); File destination = new File(directory); FileUtils.copyDirectory(source, destination); FileUtils.deleteDirectory(cloneDir.toFile()); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }

To add this source file as a Quarkus CLI plugin:

quarkus plug add /path/to/CreateFromQuickstart.java Added plugin: Name Type Scope Location Description * CreateFromQuickstart java user /path/to/CreateFromQuickstart.java

Note that the name derived from the actual file/class name that is using Camel Case and therefore is not matched to the create sub command. Instead, it is added as a sibling to create:

quarkus --help Commands: create Create a new project. app Create a Quarkus application project. cli Create a Quarkus command-line project. extension Create a Quarkus extension project # more commands here CreateFromQuickstart

As of 3.1.2.Final there is no direct way to alias a plugin. However, aliases are supported by JBang. Here’s how aliases can be used:

jbang alias add --name quarkus-create-from-quickstart ~/path/to/CreateFromQuickstart.java [jbang] Alias 'quarkus-create-from-quickstart' added to '/home/user/.jbang/jbang-catalog.json' quarkus plug add create-from-quickstart Added plugin: Name Type Scope Location Description * create-from-quickstart jbang user quarkus-create-from-quickstart
Project specific plugins

In all the examples so far the plugins listed as user scoped. This means that the plugins are global to the user. It is possible however to also have project scoped plugins. This is important as it allows:

  • Having project specific plugins

  • Overriding versions per project

  • Sharing the plugin catalog (via version control)

  • Support extension provided plugins

When the quarkus plug add command is called from within a project, the plugin is added to the project catalog, unless the --user options is used. The plugin catalog is persisted in .quarkus in the root of the project. By adding this folder to version control, the project plugin catalog is shared between users of the project. In this case, its a good idea to also include the actual plugin source files in version control, or use a shared JBang catalog.

Let’s create script that allows users to setup their project in an ArgoCD developer instance. ArgoCD is a GitOps continous delivery tool for Kubernetes. The following example demonstrates its setup process can be automated as a Quarkus CLI plugin:

More specifically the plugin performs the following:

  • Installs the ArgoCD binary

  • Installs the ArgoCD resources to the target cluster

  • It generated Kubernetes manifests for the project

  • It adds the generated resources to version control

  • It setups the project to ArgoCD

Even though some of the steps above need only need to be performed once (e.g. adding manifests to version control) the remaining steps have to be performed for each developer environment. So, instead of adding the script to some shared folder or repository forever to be forgotten, it makes sense to have it inside the project as a CLI plugin. The source of the script could be something like:

#!/bin/bash set -e ARGOCD_VERSION="v2.7.4" check_requirements() { if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "Error: The current folder is not under version control." exit 1 fi if [ ! -f "target/kubernetes/kubernetes.yml" ]; then mvn quarkus:deploy -Dquarkus.kubernetes.deploy=false if [ ! -f "target/kubernetes/kubernetes.yml" ]; then echo "Error: The target/kubernetes/kubernetes.yml file does not exist." exit 1 fi fi } install_argocd_binary() { OS="`uname`" case $OS in 'Linux') OS='linux' ;; 'Darwin') OS='darwin' ;; *) ;; esac if ! command -v argocd &> /dev/null then curl -sSL -o $HOME/bin/argocd https://github.com/argoproj/argo-cd/releases/download/${ARGOCD_VERSION}/argocd-${OS}-amd64 chmod +x $HOME/bin/argocd fi } install_argocd_resources() { if ! kubectl get namespace | grep -q 'argocd'; then kubectl create namespace argocd fi if ! kubectl get pods -n argocd | grep -q 'argocd-server'; then kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=120s fi } wait_for_port() { local PORT=$1 local TIMEOUT=5 local START_TIME=$SECONDS while : do if nc -v $1 &> /dev/null; then nc -z localhost $PORT && return fi if (( SECONDS - START_TIME >= TIMEOUT )); then return fi sleep 1 done } cleanup() { kill $PORT_FORWARD_PID } create_app() { NAMESPACE=`kubectl config view --minify --output 'jsonpath={..namespace}'` GIT_URL=`git remote get-url origin | sed -s "s/git@github.com:/https:\/\/github.com\//"` GIT_BRANCH=`git branch -l | grep "*" | awk '{print $2}'` APP_DIR=`git rev-parse --show-toplevel` APP_NAME=`git rev-parse --show-toplevel | xargs basename` ARGOCD_PASSWORD=`argocd admin initial-password argo -n argocd | head -n1` if [ -f "$APP_DIR/.argocd" ]; then mkdir $APP_DIR/.argocd fi cp target/kubernetes/kubernetes.yml $APP_DIR/.argocd/ if [ -n "$(git status --porcelain | grep -v '?')" ]; then git add $APP_DIR/.argocd git commit -m "Add generated manifests to argocd" && git push origin $BRANCH fi kubectl port-forward svc/argocd-server -n argocd 9443:443 > /dev/null 2>&1 & PORT_FORWARD_PID=$! trap "cleanup" EXIT SIGINT SIGTERM wait_for_port 9443 argocd login localhost:9443 --username admin --password $ARGOCD_PASSWORD --insecure argocd app create $APP_NAME --repo $GIT_URL --path .argocd --dest-server https://kubernetes.default.svc --dest-namespace default argocd app sync $APP_NAME } check_requirements install_argocd_binary install_argocd_resources create_app

Let’s save the file under bin/quarkus-argocd-setup and add it as a plugin:

quarkus plug add bin/quarkus-argocds-setup

Now by calling quarkus argocd-setup the application is setup for use with ArgoCD.

Extension provided plugins

A Quarkus extension may contribute to the CLI plugins that are available to a project. At the moment the following Quarkiverse extensions provide plugins:

Let’s see how things work when such an extension is added to a project. The following command adds the Quarkus Helm extension, along with the Kubernetes and docker extensions that often are used together.

quarkus ext add quarkus-helm quarkus-kubernetes quarkus-container-image-docker [SUCCESS] ✅ Extension io.quarkiverse.helm:quarkus-helm:1.0.7 has been installed [SUCCESS] ✅ Extension io.quarkus:quarkus-kubernetes has been installed [SUCCESS] ✅ Extension io.quarkus:quarkus-container-image-docker has been installed

Now the `helm plugin should be automatically added next time the CLI used:

quarkus --help Plugin catalog last updated on: 07/06/2023 10:29:05. Syncing! Looking for the newly published extensions in registry.quarkus.io Options: # option details Commads: # commands helm

The plugin can now be used to install the application using Helm charts. The plugin itself is a simple wrapper around the official Helm binary that simplifies its use. For example the app can be easily installed using:

quarkus helm install
Summarizing plugins

The Quarkus CLI plugin system is not just a way for the Quarkus team to rightsize and modularize the Quarkus CLI, it also offers teams a way of creating scripts and recipes specific to their project and distribute them along with the code.

See also

If you want to see more about the new Quarkus CLI features make sure to check the following Quarkus Insights episodes. They demonstrate the new features in action and will hopefully inspire you with ideas for your own plugins.