Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Creator: HOWTO Create a Generator

Tako Schotanus edited this page Aug 14, 2019 · 14 revisions

Generators create the code for Cloud projects. In their simplest form they just have a set of template files that get copied to a project under construction. They can also make changes to already existing files. Among the more advanced features is the creation of Resource Descriptors for deployment on Kubernetes/OpenShift.

So let's go through the steps of what it takes to create a Generator:

  1. Setup - Make sure your are set up by following the Quick Setup for Devs guide
  2. Choose a name - First we need to choose a name for our new Generator. Let's go with generator-example.
  3. Create a folder - All the Generator's files need to live in a folder with the name of the Generator
$ mkdir creator/src/main/resources/META-INF/catalog/generator-example
  1. Files - Put any files that you want copied in a subfolder called files. Imagine the following folder structure for a very simple Java application:
generator-example/
   files/
      src/
         main/
            java/
               example/
                  GreetingEndpoint.java
                  Greeting.java
   merge/
      pom.xml
Show code

GreetingEndpoint.java

package example;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Path("/")
@ApplicationScoped
public class GreetingEndpoint {

    private static final String template = "Hello, %s!";

    @GET
    @Path("/greeting")
    @Produces("application/json")
    public Greeting greeting(@QueryParam("name") String name) {
        String suffix = name != null ? name : "World";
        return new Greeting(String.format(template, suffix));
    }
}

Greeting.java

package io.openshift.booster.http;

public class Greeting {
    private final String content;

    public Greeting() {
        this.content = null;
    }
    public Greeting(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <properties>
        <version.resteasy>3.0.24.Final</version.resteasy>
        <version.restassured>3.0.7</version.restassured>
        <version.assertj>3.8.0</version.assertj>
        <version.awaitility>3.1.0</version.awaitility>
    </properties>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>io.thorntail</groupId>
            <artifactId>jaxrs-jsonp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>${version.resteasy}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>${version.restassured}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.awaitility</groupId>
            <artifactId>awaitility</artifactId>
            <version>${version.awaitility}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>${version.assertj}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>${version.resteasy}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
If you think that this doesn't seem to be enough to actually build an application you would be right. We will get to that next.

Another thing you might have noticed is that pom.xml file in the merge directory instead of the among the files. And if you looked at the actual XML provided as an example you might even have noticed the POM file doesn't seem complete! What's up with that? Good question!

So the thing is that, as you'll learn later on, a POM file will already be provided for us. The only thing that we want is to be able to add new things to it, things like dependencies and properties that are needed by the code this Generator is adding.

So that's why the POM file is not in the files directory, but in merge: we don't want it copied, possibly overwriting any existing POM files, we want it merged with the provided POM file.

  1. Create metadata - Our Generator also needs an info.yaml in the root of our folder that contains some important metadata. For our simple example the following would be enough:
type: generator
config:
  base: "runtime-vertx

The interesting part here is the "base": "runtime-vertx"1 which tells the Creator that before applying this Generator it should actually first apply the Runtime Generator named runtime-vertx. If you were wondering before why there seemed so little code in our example, it's because we can delegate certain tasks to other Generators. In this case runtime-vertx is a Generator that sets up a project to use the Vert.x framework. It will provide us with all the basic necessary to build and run our code. We'll talk more about Runtime Generators later on.

  1. Register the Generator - Finally we need to register our Generator in the GeneratorInfo list. Just add a item to the end of the list with the exact same name we gave our Generator in the first step. It would look something like this:
enum class GeneratorInfo(val klazz: GeneratorConstructor) {
    `database-crud-dotnet`,
    `database-crud-nodejs`,
    `database-crud-quarkus`,
      .
      .
      .
    `rest-dotnet`,
    `rest-nodejs`,
    `rest-quarkus`,
    `rest-springboot`,
    `rest-thorntail`,
    `rest-vertx`,
    `rest-wildfly`,
    `example-generator`;

That's it! That's all there is to creating a simple Generator.

Test your Generator

Now Generators aren't really meant to be used directly, they are the building blocks for Capabilities after all, but if you'd always have to set up a Capability just to test your work that would be a nuisance.

So to try out the effect of a single Generator you can run the following command:

$ creator apply --project testproject --name test component --generator=example-generator

If everything works as it's supposed to, which it never does of course the first time, but when it does the output should look somewhat like this:

$ creator apply --project testproject --name test component --generator=example-generator
Applied capability to 'testproject'
Go into that folder and type './gap deploy' while logged into OpenShift to create the application
in the currently active project. Afterwards type './gap push' at any time to push the current
application code to the project.

Congratulations! You just wrote your first Generator and created a project with it!

When you're done celebrating, let's continue to HOWTO Create a Runtime Generator

Further Reading