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
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.