Writing Jenkins Plugins
I learned firsthand that writing your own plugins for Jenkins is no picnic. If a plugin already exists that does anything close to what you need, spare yourself a lot of work and use it. I wanted to get that out of the way up front. If you’re planning to write a plugin, or just want to know how hard it would be, read on.
The Jenkins technology stack
Imagine the strangest technology stack you know and multiply it by two. That’s roughly the hodgepodge you’ll find in Jenkins. Several things unfortunately worked against it.
A large number of plugins
There are more than 600 publicly available Jenkins plugins. That makes sweeping changes difficult for the maintainers. If they broke backward compatibility, one of Jenkins’s biggest advantages would vanish overnight.
When it was created
I couldn’t find an official date, but it looks like about 2006 or 2007. At the time it wasn’t clear, or the principal creator, Kohsuke Kawaguchi, may not have realized, how much AJAX would change the interactivity and design of applications. When your entire presentation layer is page-centric, from controllers to templates, that’s difficult to change.
Not Invented Here
Just about anything you use in Jenkins gives off a strong whiff of Not Invented Here syndrome. When the authors hadn’t written something themselves, they reached for the most obscure library available at the time. Take the templating system.
Technology fragmentation
Look in the directory of third-party libraries Jenkins uses and you’ll find, for example, two dependency injection libraries and several for XML. Much the same goes for libraries that make JavaScript easier to use. It looks as though the creators couldn’t settle on an approach and used whichever library appealed to them at the time.
Templating
Jenkins generates HTML with Apache Jelly, which might have looked appealing back when people were disillusioned with JSP 1.0, but not five years later. The official documentation calls it “a tool for turning XML into executable code” that borrows plenty of good ideas from JSP custom tags, Velocity, Cocoon and Ant. If you’ve ever written an if-else or an ampersand in XML, you know what awaits you. I immediately broke out in hives at the memory of when programming in XML was still commonplace. Ant and XSLT pioneered that approach, and Jelly proudly claims them as ancestors. If you don’t know what to expect, I’ll point you to this article from about five years ago: Programming in XML: Spaghetti Code Squared.
Jelly’s drawback is the dire documentation. It was last updated three years ago. Look up a built-in tag and all you get is a very superficial description. You can forget about examples. I’ll also point you to Jelly: Scripting for the Soulless.
Groovy is an alternative to Jelly. Unfortunately, as with most things in Jenkins, you’ll run into a lack of documentation. Here it’s fatal because even a Google search comes up empty. You know it ought to work, but have no idea how to use it.
Update, January 7, 2013: Karel Rank pointed me to a tool for converting Jelly views into Groovy views: Jenkins — Jelly to Groovy.
Three links that might help when getting started with Jelly in Jenkins:
Stapler
Stapler is, would you believe it, a library by Kohsuke Kawaguchi that “staples” your application objects to URLs, making it easier to write web applications. The core idea of Stapler is to automatically assign URLs for your objects, creating an intuitive URL hierarchy. If I had to make a comparison, it’s an MVC framework that tries to act as though it isn’t one.
Each Jenkins plugin has an application part and a presentation part. Stapler connects the two. It selects the template for generating HTML, the HTTP response, and hands processing over to the application code for the HTTP request. The trouble is that it all happens automagically. Convention over configuration has gone too far. One rule is simple: if the fully qualified name of your plugin class is cz.dagblog.plugin.HelloWorld, Stapler looks for its view in the directory corresponding to its package, in this case cz.dagblog.plugin. The template filename depends on the kind of extension you’re writing. Here are a few tips that might help.
Handling form submissions
To handle a form, define a method whose name begins with do and ends with the path the form submits to.
public void doFoo(final StaplerRequest rq, final StaplerResponse rs){}Redirecting to a page
If you need to return to the page from which you handled the request:
public void doFoo(final StaplerRequest rq, final StaplerResponse rs){
rs.forwardToPreviousPage(rq);
}Plugin lifecycle
Whichever extension point you use, you’ll run into a few quirks.
- The instance of each plugin class is automatically serialized to and deserialized from disk using XStream. Don’t count on the normal object lifecycle or you’ll get badly burned. With serialization, the constructor doesn’t run. The instance variables are all populated when your plugin class is deserialized. Deserialization happens on server startup, and serialization happens each time the configuration changes.
- The corresponding Jelly template has access to your plugin class. While rendering the response, it can call any of its methods. In other words, the class is both controller and model.
- Forget about dependency injection. If you need an instance of another class, you either create it yourself or get to it through a singleton. That makes the code hard to test and feels like a return to the Stone Age.
- If you need a third-party library, watch how the class loader behaves. In a rather counterintuitive choice it prefers libraries on the Jenkins classpath to those supplied with the plugin. You can change that in the configuration of the Maven Jenkins plugin.
JavaScript and AJAX
JavaScript and AJAX support follow the same pattern. You get Prototype and YUI 2. Fortunately, plugin authors can use a framework of their choice. AJAX support boils down to calling annotated methods on your plugin class. The template generates JavaScript that wraps the call. That’s a nice idea, but it feels bolted on. Fully asynchronous communication with JSON exchanges would be fairly complicated.
Documentation
The documentation is in a sorry state. I don’t know whether that’s because Jenkins split from the original Hudson or for some other reason. Documentation exists, but it’s incredibly fragmented, and you have to read between the lines to find what you need. Since you usually don’t know what you’re looking for and are just feeling your way, it’s no use to you. There’s essentially one way around it: find a plugin that does something similar and read its source directly. This applies even to little things like displaying a custom image, handling a form, or hiding part of the standard UI. It also helps to have the Jenkins source itself to search through.
Conclusion
Those paragraphs may make Jenkins sound like bad technology. It isn’t. If you’re an end user, these limitations won’t bother you. If you’re writing your own plugin, bring plenty of patience. Trial and error will almost always get you there.
I used Eclipse for development, and Jenkins isn’t particularly friendly to it. I couldn’t get Jenkins to start embedded, so Maven came in handy. Debugging the plugin was a much bigger problem.
Restarting the production server was out of the question, so I copied the jobs I needed and their results to make them available locally. Where I needed to test integration, I called the production Jenkins remotely. I wrote my own proxy to make either a local or a remote call according to its configuration.
Related articles
Related CZ Podcast episodes
- #261 · Leadership
On leading people and teams. · Czech audio
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio