Implementing Callable in a Jenkins plugin
I recently had the task to integrate an external library into a Jenkins plugin. It is straightforward, of course! But it becomes a bit of a problem when the library is handling I/O on its own, using java.io.File.
As known, Jenkins provides a handy abstraction with FilePath and Launcher to resolve the node (master, slaves…) where the Job is running. For this reason, whenever one needs to execute a command line or write to a file, these methods are used without any concern about the Jenkins distribution. If regular java.io.File is used instead, the build step will try to write directly into the Master node even when the Job has been allocated somewhere else.
What if an external library is doing I/O with java.io.File?
If so, we wrap the whole execution and send it to the correct node!
The wrapper is a Callable class, conveniently provided through four self-descriptive extensions: MasterToSlaveCallable, SlaveToMasterCallable, MasterToSlaveFileCallable and SlaveToMasterFileCallable.
A Callable is sent through a Channel, which is in turn provided by the launcher of the builder plugin itself.
Example
Our Jenkins plugin uses the SimpleIOExample created for this example. This external library simply gets an input message and write it to a file. The plugin uses this library to write whatever is provided via its Build Step in the Jenkins interface.
Our wrapper Callable – WriteToNode – is very uncomplicated. The external library is only referenced within the call() method.
Then, in the perform() method of our Build Step, the execution is resolved in a single line:
To change the return type of the call() method, simply set the extension MasterToSlaveCallable<TYPE, IOException>. Same for the Exception type. For the complete implementation – with comments – inspect code here.
For more details, check out Making your plugin behave in distributed Jenkins. Special mention for the Performance consideration: FilePath can be used to read data from the Slave filesystem for further processing in the Master node. For bulky data, however, it might be more efficient to send the code to where the data is. The solution in this post applies then.