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

Creator: Generators and Capabilities in depth

Tako Schotanus edited this page Jul 30, 2019 · 2 revisions

Details for implementing a Generator or Capability class

Both the Generator and the Capability interface have a single apply() function:

fun apply(resources: Resources, props: Properties, extra: Properties): Resources

It's arguments and their usage:

  • resources - An object of type Resources that makes it really easy to deal with K8s/OpenShift resources.
  • props - Properties, a freely usable HashMap containing the properties that can be used to tell the Generator/Capability to change its behavior. It's very recommended to not have any required properties. Make sure to create usable defaults in all cases.
  • extra - Properties, a freely usable HashMap the Generator/Capability can use to pass information it considers useful back up the chain. These properties can be used to convey information that can't be obtained, extracted, deduced somehow from the input properties (props).

The method returns the original resources with the Generator/Capability's changes applied (it's not necessary to create a new object, changes can be applied directly to the original)

To make life easier for the Generator/Capability developer it's really recommended that instead of implementing the Generator or Capability interface, as mentioned earlier, the Generator or Capability extends BaseGenerator or BaseCapability respectively instead. The reason for this is that it adds a set of utility methods that make it easy to perform the most common tasks (somewhat simplified):

val sourceDir: Path
val targetDir: Path
fun generator(generatorConstructor): Generator
fun name(vararg parts: Any?): String
fun copy(from: Path, to: Path?)
fun copyVersioned(runtime: Runtime?, to: Path?)
fun filesCopied(from: Path, to: Path?): Boolean
fun filesCopiedVersioned(runtime: Runtime?, to: Path?): Boolean
fun move(original: Path, to: Path)
fun transform(pattern: String, transformer: Transformer, dir: Path)
fun transform(patterns: List<String>, transformer: Transformer, dir: Path)
fun appendFile(targetFile: Path, sourceFile: Path)
fun updatePom(appName: String, groupId: String, artifactId: String, version: String, pomFile: Path)
fun mergePoms(sourcePom: Path, targetPom: Path)
fun mergeVersionedPoms(runtime: Runtime?, targetPom: Path)
fun mergePackageJson(source: Path, target: Path)
fun mergeVersionedPackageJson(runtime: Runtime?, target: Path)
fun addResources(resources: Resources, transformer: Transformer, from: Path)
fun addVersionedResources(resources: Resources, transformer: Transformer, runtime: Runtime?)
  • sourceDir - The path the the Generator/Capability's resource folder
  • targetDir - The path to the generated project's target folder
  • generator - The function to call when you want to apply another Generator as part of this Generator/Capability's work. The generatorConstructor is the reference to the Generator class itself, its "constructor", that you want to apply. You'll get back an instance of that Generator after which you can call its apply() method. (eg. await generator(SuperDuper).apply(resources, props, extra)
  • name - Just returns all its string arguments joined with hypens
  • copy - Copies all files from the given source file/folder to the destination file/folder. By default it will assume the source is the files sub folder from the Generator/Capability's own resource folder (ie. sourceDir) and the to is the generated project's target folder (ie. targetDir).
  • filesCopied - Returns a boolean indicating if all the indicated files have been copied. It uses the same defaults as copy(). This can be used to detect if all the files were copied during a previous execution of this or another Generator/Capability.
  • move - Moves/renames a single file. Both the from and to arguments are expected to be relative to the project's target folder (ie. targetDir).
  • transform - Transforms the indicated files using the given transformer. The pattern can be a file path with a glob pattern, eg. src/**/*.java. It can even be an list of patterns. All paths will be taken as being relative to the generated project's target folder (ie. targetDir). Take a look at transformers to see which are available and how they work.
  • appendFile - Appends the contents of the sourceFile to the end of the targetFile. The sourceFile is expected to be relative to the Generator's own resource folder (ie. sourceDir) while the targetFile is expected to be relative to the project's target folder (ie. targetDir).
  • updatePom - Updates the Application Name, GroupId, ArtifactId and Version of a Maven POM file. The pomFile is a reference to the POM file that should be updated. This path will be relative to the generated project's target folder (ie. targetDir). By default it will refer to the pom.xml file in the root of the target folder.
  • mergePoms - Merges the contents of the sourcePom into the targetPom. The sourcePom path will be relative to the Generator/Capability's own folder (ie. sourceDir) and by default will refer to merge/pom.xml. If you make sure to put your POM file in that location you don't need to specify anything else. The targetPom is a reference to the POM file that should be updated. This path will be relative to the generated project's target folder (ie. targetDir). By default it will refer to the pom.xml file in the root of the target folder.
  • mergeVersionedPoms - A special version of the mergePoms() function that takes a runtime as its source argument, not a file path. Instead the function will use the runtime's version to look for a file named merge-VERSION/pom.xml (where VERSION is replaced with the runtime's version).
  • mergePackageJson - Merges the contents of the sourceJson into the targetJson. The sourceJson path will be relative to the Generator/Capability's own folder (ie. sourceDir) and by default will refer to merge/package.json. If you make sure to put your POM file in that location you don't need to specify anything else. The targetJson is a reference to the POM file that should be updated. This path will be relative to the generated project's target folder (ie. targetDir). By default it will refer to the pom.xml file in the root of the target folder.

NB: Even though Capabilities have access to the same methods as Generators they really should never copy or change files by themselves, nor should they directly change the list of k8s/OpenShift resources. They should always delegate that to a Generator.

IMPORTANT: One thing to take into account when creating Generators is that they can be executed more than once on the same project. For example when you apply several Capabilities it's almost certain that all of them will apply a Runtime Generator to set up basic Runtime support. But if doing so would create duplicates or undo previously made changes then it's the responsibility of the Generator to make sure that this will not happen!

At the same time it's perfectly possible for a Generator to be applied multiple times with different properties (perhaps differently named services are created for example). So the code will need to check if Resources with the same name already exists or if files have already been copied/created and skip doing its work if true. These situations should be silently handled, no exceptions should be thrown or (log) messages written!

Post apply

Capabilities can have an optional extra method that gets called at the end of the apply process. It will actually be called on all Capabilities that have been added so far to the project, now or at a previous time. This means that the post processing can take into account the full combination of applied Capabilities to act upon.

This is what the method looks like:

fun postApply(resources: Resources, props: Properties, deployment: DeploymentDescriptor): Resources

It's arguments and their usage:

  • resources - An object of type Resources that makes it really easy to deal with K8s/OpenShift resources.
  • props - Properties, a freely usable HashMap containing the properties that can be used to tell the Generator/Capability to change its behavior. It's very recommended to not have any required properties. Make sure to create usable defaults in all cases.
  • deployment - This is the final Deployment Descriptor which is basically a record of all Capabilities that have been applied to the project so far. With this information it is possible to re-create a project from scratch.