Skip to main content

Develop a Custom Java Plugin


Develop a Custom Java Plugin

This tutorial covers:

  • Why you might benefit from a custom Rundeck Java plugin
  • How to set up your development environment to write, build and test Rundeck Java plugins

Why create a Rundeck plugin in Java?

Rundeck Community is an open source runbook automation platform that comes with a lot of functionality out of the box, like running script commands on your nodes with a command step. If you can already run commands with the default functionality, why would you want to write a new plugin to do that?

Several reasons:

  • To encapsulate and reuse functionality across projects and jobs
  • To expose parameters to the UI with descriptions, types and defaults
  • To securely access secrets stored in Key Storage

These benefits apply to both script plugins and Java plugins. Additional benefits of writing a Java plugin would be:

  • Fully cross-platform
  • All code dependencies can be packaged with the plugin
  • Robust access to Rundeck context data
  • Testability
  • Use any JVM language

Fully cross-platform

Script plugins depend on a script interpreter like bash or Powershell deployed on the node running your plugin. If you run a heterogeneous environment with nodes of different operating systems or different versions of the same operating system, you can avoid those inconsistencies using a Java plugin. Since Java can run on essentially any platform, our plugin logic will run the same way regardless of what operating system it runs on.

All code dependencies can be packaged with the plugin

Many scripts rely on external tools like awk, jq or your cloud provider's command line tool, requiring you to ensure that those tools are deployed everywhere your plugin will run, and are the correct version that your script expects. A Java plugin would be packaged into a single jar file that can also bundle any necessary third party library dependencies alongside your plugin code so your dependencies are deployed atomically with your plugin logic.

Robust access to Rundeck context data

Java step plugins can read data from the job context that previous jobs have written or write data to the job context so that other jobs have access to it. Accessing the job context data is exposed as a Java object, which is easier and less error prone than how script plugins read context data as environment variables and write context data via log capture parsing.

Testability

Java plugins are testable in the same way any Java app is. You can use familiar libraries like JUnit and jMock to ensure that your plugin can be well-tested before being deployed. Tested code gives developers who are new to the project more confidence in fixing bugs or adding features in the future.

Use any JVM language

You can write Java plugins in more languages than just Java the language. You can use any JVM language to write your plugin, like Groovy, Scala or Kotlin.

How to create a simple workflow step Java plugin

Now that we see the value in creating our own workflow step Java plugin, we can walk through a simple Hello World example. We will:

  • Set up a simple Java development environment
  • Create the plugin project structure
  • Test that we can make changes to the plugin and see them take effect in a Rundeck instance

Setting up your development environment

If you're not a Java programmer by trade, the thought of setting up a Java development environment might seem a little daunting. We'll try to walk you through a simple setup that may not have all the bells and whistles that a fully Java IDE might have, but will get you up and running quickly, while still understanding each of the tools you're working with.

Our ultimate goal is to build an archive file of Java bytecode-compiled files that we can easily ship off to a Rundeck server instance. The archive file is a .jar file which is just a .zip file with extra metadata. The bytecode is compiled and packaged with Gradle, a DSL for building Java applications. We need to be able to write code and run the compiler to check that it compiles. Any other IDE features are icing on the cake.

Installing the development dependencies

The easiest way to install all the dependencies on macOS is with Homebrewopen in new window:

brew cask install java visual-studio-code docker
brew install gradle docker-compose make git

Otherwise you can install the dependencies manually:

Install Visual Studio Code's Java Extension Pack

The editor we'll be using in this tutorial Visual Studio Code (vscode for short) has an extensible architecture to add support for whatever language or framework you need to work with. To get Java functionality, install the Java Extension Pack by pressing cmd+p then entering ext install vscjava.vscode-java-pack in the prompt.

Create the plugin

Create a basic plugin using the rundeck plugin bootstrap tool:

rundeck-plugin-bootstrap -d <plugins-path> -n hellojava -t java -s WorkflowStep

gradle build

Creating a job to use our plugin

Now that we've deployed our plugin, we want to make sure that we can use it in a job. Log into the local Rundeck at http://localhost:4440/open in new window with the default user admin and password admin. Then create a new job in the hello-project project:

Create a new job
Create a new job

In the job creation screen, fill in some name like "My Java Plugin job" in Job Name. Then scroll to the "Add a Step" section, type "hellojava" in the search box and click the "Workflow Steps" tab to find the hellojava plugin.

Add hellojava step
Add hellojava step

Click the hellojava plugin, fill in some text in the Example string parameter field, then click Save to add the step to the job. Click on Create to create the job with the new plugin in its workflow and finally run the job by clicking on Run Job Now.

Plugin log output
Plugin log output

If the job succeeded with some output similar to the screenshot, then your Java plugin is working!

Verify that we can modify the plugin

Now we want to make changes to the plugin and see them take effect so we know how to iterate on the plugin. Open up Hellojava.java in vscode by pressing cmd+p and entering Hellojava.java in the prompt. This is the plugin class that the plugin bootstrap script generated. Some things to note:

  • The @PluginDescription annotation sets the title and description of the plugin as it should appear in Rundeck.
  • The getDescription() method describes the parameters to the plugin that Rundeck will render as Rundeck GUI elements.
  • The executeStep() method is where the plugin logic happens.

Let's update executeStep() to list the files in the current directory. Update Hellojava.java to the following:

package com.plugin.hellojava;

import com.dtolabs.rundeck.core.execution.workflow.steps.StepException;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.plugins.descriptions.PluginDescription;
import com.dtolabs.rundeck.plugins.{PluginLogger, ServiceNameConstants};
import com.dtolabs.rundeck.plugins.step.{PluginStepContext, StepPlugin};
import java.io.File;
import java.util.Map;

@Plugin(service=ServiceNameConstants.WorkflowStep,name="hellojava")
@PluginDescription(title="hellojava", description="My WorkflowStep plugin description")
public class Hellojava implements StepPlugin {
   @Override
   public void executeStep(final PluginStepContext context, final Map<String, Object> configuration)
   throws StepException {
        PluginLogger logger = context.getLogger();
        File dir = new File(".");
        File[] filesList = dir.listFiles();
        for(File f : filesList) {
            logger.log(2, "File: " + f.getName());
        }
    }
}

We've replaced the sample code with some code to log the files in the current directory. Note that since we're using Java libraries to list the files, the plugin is platform-independent, unlike a script plugin that would rely on OS-specific tools like ls.

Then we can compile our plugin:

./gradlew build

> Task :test FAILED

com.plugin.hellojava.HellojavaSpec > check Boolean parameter FAILED
    org.spockframework.runtime.WrongExceptionThrownError at HellojavaSpec.groovy:28

com.plugin.hellojava.HellojavaSpec > run OK FAILED
    org.spockframework.mock.TooFewInvocationsError at HellojavaSpec.groovy:41

2 tests completed, 2 failed


FAILURE: Build failed with an exception.

Whoops! Looks like the plugin bootstrap generated some sample tests and we deleted the sample code that was making them pass. Let's find the tests and remove them. The test file would end as follows:

package com.plugin.hellojava

import com.dtolabs.rundeck.plugins.step.PluginStepContext
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException
import com.dtolabs.rundeck.plugins.PluginLogger
import spock.lang.Specification

class HellojavaSpec extends Specification {
    def getContext(PluginLogger logger){
        Mock(PluginStepContext){
            getLogger()>>logger
        }
    }
}

We'll leave the getContext() helper method there so we can mock the context when writing tests later. But for now, we're ready to compile by running ./gradlew build and deploy using your instance guiopen in new window.

Tips

Change the plugin version property from the rundeck-plugins/hellojava/build.gradle file.

Then re-run the job from the Rundeck GUI and you should see it print out a list of files.

Updated plugin log output
Updated plugin log output

Success!

If you want to try your plugin on your live Rundeck server instead of on your workstation, see Installing plugins

References