Projectless setup

Projectless setup means you do use Java 21 in preview mode or Java >= 25. It enables you to do java main.java (or whatever java file name) directly without any project.

To ease this mode we do provide on central all jars (fatjars) which have JSON-B and the descriptor model. Using them, it is sufficient to add this jar in the classpath to run the script:

java -cp kubernetes-java-1.34.x-1.0.1-all.jar main.java
Tip
for vscode (and Java Project extension) you can add the jar in the editor completion scope by creating a file .vscode/settings.json containing { "java.project.referencedLibraries": [ "/path/to/kubernetes-java-1.34.x-1.0.1-all.jar" ] } or by putting the jar in lib/ folder.

A sample main can look like:

import static java.util.stream.Collectors.joining;

import io.yupiik.kubernetes.bindings.v1_34_x.v1.*;

void main() {
  // (1)
  var name = "nginx";
  var labels = Map.of("app", name);

  // (2)
  var deployment = new Deployment()
      .metadata(new ObjectMeta()
          .name(name)
          .labels(labels))
      .spec(new DeploymentSpec()
          .replicas(2)
          .selector(new LabelSelector()
              .matchLabels(labels))
          .template(new PodTemplateSpec()
              .metadata(new ObjectMeta()
                  .labels(labels))
              .spec(new PodSpec()
                  .containers(List.of(
                      new Container()
                          .name("nginx")
                          .image("nginx")
                          .imagePullPolicy("Always"))))))
      .validate() // (3)
      .asJson();

  // (4)
  var service = new Service()
      .metadata(new ObjectMeta()
          .name(name)
          .labels(labels))
      .spec(new ServiceSpec()
          .type("ClusterIP")
          .selector(Map.of("app", "nginx"))
          .ports(List.of(new ServicePort()
              .port(80)
              .targetPort("nginx"))))
      .validate()
      .asJson();

  // (5)
  System.out.println(Stream.of(
          deployment,
          service)
      .collect(joining("\n---\n")));
}
  1. We create shared variables reused accross all instances (avoids to copy/paste or recompute them),

  2. Create a deployment for nginx server,

  3. Do not forget to call validate which will fill the apiVersion and kind if not set (or explicitly set them),

  4. Create a service for the nginx server,

  5. Concatenate all the descriptors in a multi-document YAML file so and dump it over stdout so it can be piped to kubectl command.

Then to apply it you can simply do:

java -cp /path/to/kubernetes-java-1.34.x-1.0.1-all.jar main.java | kubectl apply -f -
Tip
it is also possible to generate a fake helm chart or kustomization descriptors this way (writing the descriptors instead of printing them over stdout) to reuse helm deployment "reconciliation"/diff/drift handling.

Maven setup

Maven setup is likely the easiest, what you have to do is:

  1. Create a descriptor generation module,

  2. Add the needed dependencies in this pom module (see bindings page if needed),

  3. Write a main with the generation,

  4. Use exec-maven-plugin to run your main.

Here is a skeleton:

public class MyGenerator {
    public static void main(final String... args){
        final var output = Files.createDirectories(Path.of(args[0])); (1)

        final var deployment = new Deployment() (2)
                    .spec(new DeploymentSpec()
                    // ... complete the generation as needed
        );

        Files.writeString( (3)
                output.resolve("my-deployment.json"),
                deployment.asJson());
    }

    private MyGenerator() {
        // no-op
    }
}
  1. Get and create the output directory for this run (generally in target//${project.build.directory}),

  2. Create your in memory descriptor,

  3. Write your descriptor on the disk (or in a zip directly if you prefer).

Now to execute this main you can just use this exec-maven-plugin in your Apache Maven configuration:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.1.0</version>
  <executions>
    <execution>
      <id>generate-k8s-descriptors</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>java</goal>
      </goals>
      <configuration>
        <stopUnresponsiveDaemonThreads>false</stopUnresponsiveDaemonThreads>
        <cleanupDaemonThreads>false</cleanupDaemonThreads>
        <mainClass>org.superbiz.MyGenerator</mainClass>
        <arguments>
          <argument>${project.build.directory}/k8s-descriptors</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

Now if you run mvn prepare-package you will get your descriptors dumped in target/k8s-descriptors folder.

Tip
with java >= 21 (as preview) or >= 25 (default feature) you can write an unamed main() entrypoint which also simplifies running the generation for a CI build.

JShell setup

You can use this project with JShell, you just need to ensure the JShell classpath is correct when executing your script. As of today, you must ensure it contains:

  • (optional for some setup but recommended) a JSON-P API and implementation,

  • (optional) a JSON-B API and implementation,

  • The kubernetes-java-xxx version you need,

  • (optional) bundlebee-java if you use it.

Here is how to run a custom JShell script (note it assumes it runs on Linux and the jar were downloaded in current folder):

jshell \
  --class-path \
  geronimo-json_1.1_spec-1.4-jakarta.jar:johnzon-core-1.2.18-jakarta.jar:kubernetes-java-1.24.3-1.0.0.jar \
  my-script.jsh

Then you just need to write your script almost as in plain java:

import io.yupiik.kubernetes.bindings.v1_24_3.v1.*; // (1)

{ // (2)
    final var output = Files.createDirectories(Path.of("target/k8s-descriptors"));

    final var deployment = new Deployment()
        .spec(new DeploymentSpec()
        /* ... */);

    Files.writeString(
       output.resolve("my-deployment.json"),
        deployment.asJson());
}

/exit // (3)
  1. Import the classes you need (here we use a wildcard import on the model because it is safe for our script but you can use explicit imports),

  2. A small trick to write a readable script is to enable multiline snippet by wrapping the code in a block (rest of the code is similar to previous ones),

  3. Finally when finished we exit JShell.

Tip
a way to write the script as a standard Java code with classes and a final standard main class (Runner). This will enable you to setup the dependencies in your IDE and run the main as a standard class (ensure to name your file <scriptname>.java). Then to execute it through JShell - where /exit is needed - use this command: echo 'Runner.main();/exit' | jshell --class-path …​. --startup <scriptname>.java. This will automatically load your script and exeute the main before exiting the shell.
import io.yupiik.kubernetes.bindings.v1_24_3.v1.*;

class Generator {
    public void generate() {
        final var output = Files.createDirectories(Path.of("target/k8s-descriptors"));

        final var deployment = new Deployment()
                .spec(new DeploymentSpec()
                        /* ... */);

        Files.writeString(
                output.resolve("my-deployment.json"),
                deployment.asJson());
    }
}

class Runner { // enables to code in an IDE even if not needed by JShell
    public static void main(final String... args) throws Exception {
        new Generator().generate();
    }
}

JBang Setup

JBang can also be used to generate your descriptors. Here is a script directly runnable with one of these runner:

  • From Maven/Gradle

  • From the command line if you installed jbang (either making your script executable - ///usr/bin/env jbang "$0" "$@" ; exit $? or using jbang $script),

  • From jbang-actions (Github Actions integration)

descriptors.generator.java
//DEPS org.apache.geronimo.specs:geronimo-json_1.1_spec:1.4:jakarta
//DEPS org.apache.johnzon:johnzon-core:1.2.18:jakarta
//DEPS io.yupiik.kubernetes:kubernetes-java-1.24.3:1.0-SNAPSHOT

import io.yupiik.kubernetes.bindings.v1_24_3.v1.*;
import java.io.*;
import java.nio.file.*;

class Generator {
    public static void main(final String... args) throws IOException {
        final var output = Files.createDirectories(Path.of("target/k8s-descriptors"));

        final var deployment = new Deployment()
                .spec(new DeploymentSpec()
                    /*...*/);

        Files.writeString(
                output.resolve("my-deployment.json"),
                deployment.asJson());
    }
}

It is pretty much the same script than before but we handled the classpath thanks to //DEPS directive which can be convenient if you are already using JBang.

To set it with Github Actions use:

on: [push]

jobs:
  jbang:
    runs-on: ubuntu-20.04
    name: Generate descriptors
    steps:
    - name: checkout
      uses: actions/checkout@v3
    - uses: actions/cache@v3
      with:
        path: /root/.jbang
        key: $-jbang-$
        restore-keys: |
            $-jbang-
    - name: jbang
      uses: jbangdev/jbang-action@v0.97.0
      with:
        script: descriptors.generator.java
      env:
        JBANG_REPO: /root/.jbang/repository