DEV Community

loading...
Cover image for Write a kubectl plugin in Java with jbang and fabric8

Write a kubectl plugin in Java with jbang and fabric8

ikwattro profile image Christophe Willemsen ・5 min read

If you use kubectl frequently, you might be find yourself running the same sequence of commands over and over again.

A frequent use case yielding such sequences is for viewing a secret which requires to find the secret and decode its base64 form.

In this tutorial, I'll go through how to create a kubectl plugin in Java. The plugin will do the following after we execute the kubectl lp command :

  • List the pods with their name, status and a default empty message
  • If the pod is not in running state, display a 🔥 icon in front of its line, otherwise display a ✅ icon
  • Display a message why the pod is not running

Jbang

To write the plugin in Java, I will use jbang.
Jbang is a framework that brings scripting to the Java land.

If you don't have Jbang yet installed, just run the following :

brew install jbangdev/tap/jbang
Enter fullscreen mode Exit fullscreen mode

Or check the documentation to find the instructions for your operating system.

The next step is to create a template with the initializer, I'll call the script lp for list pods :

jbang init --template=cli lp.java
Enter fullscreen mode Exit fullscreen mode

It will create a fully functional script lp.java with the following content :

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.5.0

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

import java.util.concurrent.Callable;

@Command(name = "lp", mixinStandardHelpOptions = true, version = "lp 0.1",
        description = "lp made with jbang")
class lp implements Callable<Integer> {

    @Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
    private String greeting;

    public static void main(String... args) {
        int exitCode = new CommandLine(new lp()).execute(args);
        System.exit(exitCode);
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        System.out.println("Hello " + greeting);
        return 0;
    }
}

Enter fullscreen mode Exit fullscreen mode

Which is a simple template that greets you, try it with the following command :

$ jbang lp.java Chris
Hello Chris
$
Enter fullscreen mode Exit fullscreen mode

Adding dependencies to your scripts is as simple as adding a comment in the beginning of the file with the dependency artifact :

//DEPS io.fabric8:kubernetes-client:4.13.0
Enter fullscreen mode Exit fullscreen mode

Here, we added the fabric8 kubernetes client library. While we're at it, let's see how this client works.


Fabric8

The fabric8 kubernetes client is a library that provides access to the Kubernetes Rest API via a simple and human friendly DSL.

For eg, here is how you find only the pods that are not in a RUNNING status :

kc.pods().list().getItems()
                .stream()
                .filter(pod -> !PodStatusUtil.isRunning(pod))
                .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

For our scenario, we will use the client to retrieve all the pods and create a new domain model PodInfo with the name, the state and an optional message :

private static List<PodInfo> getPods() {
        KubernetesClient kc;
        try {
            kc = new DefaultKubernetesClient();
        } catch (Exception e) {
            throw new RuntimeException("Unable to create default Kubernetes client", e);
        }

        return kc.pods().list().getItems().stream().map(pod -> {
            PodInfoState state = PodStatusUtil.isRunning(pod) ? PodInfoState.RUNNING : PodInfoState.FAILING;
            String message = null;
            if (!state.equals(PodInfoState.RUNNING)) {
                message = PodStatusUtil.getContainerStatus(pod).get(0).getState().getWaiting().getMessage();
            }

            return new PodInfo(pod.getMetadata().getName(), state, message);
        }).collect(Collectors.toList());
    }

    static class PodInfo {

        private final String name;
        private final PodInfoState state;
        private final String message;

        public PodInfo(String name, PodInfoState state, String message) {
            this.name = name;
            this.state = state;
            this.message = message;
        }

        public String getName() {
            return name;
        }

        public PodInfoState getState() {
            return state;
        }

        public String getMessage() {
            return message;
        }
    }

    enum PodInfoState {
        RUNNING,
        FAILING
    }
Enter fullscreen mode Exit fullscreen mode

We'll also add the j-text-utils java library so we can display everything in a table

//DEPS com.massisframework:j-text-utils:0.3.4
Enter fullscreen mode Exit fullscreen mode
private static void printTable(List<PodInfo> list) {
        final Object[][] tableData = list.stream()
                .map(podInfo -> new Object[]{
                        podInfo.getState().equals(PodInfoState.RUNNING) ? CHECK_MARK : FIRE,
                        podInfo.getName(),
                        podInfo.getState(),
                        podInfo.getMessage()
                })
                .toArray(Object[][]::new);
        String[] columnNames = {"", "name", "state", "message"};
        new TextTable(columnNames, tableData).printTable();
    }
Enter fullscreen mode Exit fullscreen mode

The full code can be found here : https://github.com/ikwattro/kubectl-plugin-java-jbang

If we run our script, we can see it in action :

$ jbang lp.java
[jbang] Building jar...
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
________________________________________________________________________________________
|   | name                             | state  | message                               |
|=======================================================================================|
| ✅ | neo4j-neo4j-core-0               | RUNNING|                                       |
| ✅ | nginx-app                        | RUNNING|                                       |
| 🔥| nginx-deployment-77fbdb7874-8gkcr| FAILING| Back-off pulling image "nginx2:1.16.1"|
| 🔥| nginx-deployment-77fbdb7874-8jps7| FAILING| Back-off pulling image "nginx2:1.16.1"|
| 🔥| nginx-deployment-77fbdb7874-vx27r| FAILING| Back-off pulling image "nginx2:1.16.1"|
| ✅ | postgres                         | RUNNING|                                       |
| ✅ | coredns-f9fd979d6-qwgfm          | RUNNING|                                       |
| ✅ | etcd-minikube                    | RUNNING|                                       |
| ✅ | kube-apiserver-minikube          | RUNNING|                                       |
| ✅ | kube-controller-manager-minikube | RUNNING|                                       |
| ✅ | kube-proxy-s5bcc                 | RUNNING|                                       |
| ✅ | kube-scheduler-minikube          | RUNNING|                                       |
| ✅ | storage-provisioner              | RUNNING|                                       |
$
Enter fullscreen mode Exit fullscreen mode

Extending kubectl

Extending kubectl is very easy as it has mainly 3 conventions :

  • You need to place your script in your path
  • The name of the script should start with kubectl- and not have an extension
  • The script should be executable

We will need to pay attention, because when generating the jbang template, the very first line was commented for playing nicely with IDEs when editing the script.

Just uncomment the line :

#!/usr/bin/env jbang
Enter fullscreen mode Exit fullscreen mode

Secondly, we will copy the script to our path :

cp lp.java /usr/local/bin/kubectl-lp
Enter fullscreen mode Exit fullscreen mode

And make it executable :

chmod +x /usr/local/bin/kubectl-lp
Enter fullscreen mode Exit fullscreen mode

You can now run kubectl lp for listing your pods :

$ kubectl lp
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
________________________________________________________________________________________
|   | name                             | state  | message                               |
|=======================================================================================|
| ✅ | neo4j-neo4j-core-0               | RUNNING|                                       |
| ✅ | nginx-app                        | RUNNING|                                       |
| 🔥| nginx-deployment-77fbdb7874-8gkcr| FAILING| Back-off pulling image "nginx2:1.16.1"|
| 🔥| nginx-deployment-77fbdb7874-8jps7| FAILING| Back-off pulling image "nginx2:1.16.1"|
| 🔥| nginx-deployment-77fbdb7874-vx27r| FAILING| Back-off pulling image "nginx2:1.16.1"|
| ✅ | postgres                         | RUNNING|                                       |
| ✅ | coredns-f9fd979d6-qwgfm          | RUNNING|                                       |
| ✅ | etcd-minikube                    | RUNNING|                                       |
| ✅ | kube-apiserver-minikube          | RUNNING|                                       |
| ✅ | kube-controller-manager-minikube | RUNNING|                                       |
| ✅ | kube-proxy-s5bcc                 | RUNNING|                                       |
| ✅ | kube-scheduler-minikube          | RUNNING|                                       |
| ✅ | storage-provisioner              | RUNNING|                                       |
$
Enter fullscreen mode Exit fullscreen mode

Conclusion

The Kubectl tool is easily extensible and Jbang allows to write scripts in Java in no time, where we would probably have turned to Python before that tool existed.

References :

Discussion (1)

pic
Editor guide
Collapse
peaonunes profile image
Rafael Nunes

Great post. I usually create aliases for the frequently used commands, but this is a much more powerful and scalable solution 👍