Wednesday, March 23, 2011

Writing Automatic Tool Installer for Jenkins

As you can see in Ant, Maven, JDK, and so on, Jenkins has an ability to automate tool installation necessary for builds. This page discusses how a plugin developer can add this to their own plugin, by using Gradle as an example.

Write a crawler

The first piece you need is a crawler, which generates metadata that in turn tells Jenkins where to download the tools from. The goal is to produce a JSONP file that looks like this:
downloadService.post('hudson.plugins.gradle.GradleInstaller',{"list": [
{
 "id": "1.0-milestone-1",
 "name": "Gradle 1.0-milestone-1",
 "url": "http://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-1-bin.zip"
},
{
 "id": "0.9.2",
 "name": "Gradle 0.9.2",
 "url": "http://repo.gradle.org/gradle/distributions/gradle-0.9.2-bin.zip"
},
...
]})
More about the structure of the JSON file. The first "hudson.plugins.gradle.GradleInstaller" portion is the fully-qualified class name that you'll be writing later. Then a list of tuples follow, where each tuple contains an unique ID, a human readable display name, and URL to download a zip file from. The list should be sorted so that newer ones appear first. This is the order users will see in their drop-down combobox.
The crawler is a program that generates this file. It can be any program, but this is the crawler that produces the above JSONP, written in Groovy. Once you are ready with this, you can run this at your own machine or we can run it for you on our CI infrastructure. Please drop us a note at the dev list so that we can discuss.

Write an installer

Next, you write a new extension point implementation for the installer. This code tells Jenkins that you have an auto-installer for this tool. Gradle follows the standard file structure, so there's really no need to override any behaviour of the installer.
Every time the administrator sets up a new Gradle installation, a new GradleInstaller instance will created and it gets the ID that you set in the metadata JSON file above. The isApplicable method is saying that this installer can only apply to GradleInstallation. That is, using this installer for Ant doesn't make sense.
public class GradleInstaller extends DownloadFromUrlInstaller {
 @DataBoundConstructor
 public GradleInstaller(String id) {
     super(id);
 }

 @Extension
 public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<GradleInstaller> {
     public String getDisplayName() {
         return "Install from Gradle.org";
     }

     @Override
     public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
         return toolType==GradleInstallation.class;
     }
 }
}

Make Auto-installer a default option

You can make the auto-installer selected by default when the user adds a new tool installation. This is desirable since there's really no reason our users run around and install their own tools. To do so, add the getDefaultInstallers method to your ToolInstallation's descriptor, like this:
public class GradleInstallation extends ToolInstallation {
 ...

 @Extension
 public static class DescriptorImpl extends ToolDescriptor<GradleInstallation> {

     @Override
     public List<? extends ToolInstaller> getDefaultInstallers() {
         return Collections.singletonList(new GradleInstaller(null));
     }

     ...
 }
}
That's it. It wasn't that hard, was it.

More complex installation scenarios

What's discussed in this page takes advantages of the stock implementation in Jenkins that's suitable for simple tool installations that only involves unzipping a zip file. If your tool installation scenario is more complex, you can still do that by extending from ToolInstaller directly instead of DownloadFromUrlInstaller. See the JDKInstaller class in the core as the starting point. It involves going through the gated download link via page scraping, choosing the right bundle based on the platform, and then installing a tool by executing an installer.

- Kohsuke Kawaguchi

5 comments:

  1. Great, I have been waiting for a tutorial on how to write an installer. Thanks!

    ReplyDelete
  2. Great! Looking forward to seeing it in your plugins!

    ReplyDelete
  3. It's interesting you'd pick gradle for this example; 'cause gradle is already kinda' meta like that with the gradle wrapper task.

    ReplyDelete
  4. I'm confused. I know this is just an example, but I see a gradle tool at https://wiki.jenkins-ci.org/display/JENKINS/Gradle+Plugin but my DEV@ Jenkins install does not list gradle among its plugins.

    Is there really no gradle build tool available for DEV@ ?

    ReplyDelete
  5. @jdtangney:

    Unfortunately tool support in DEV@Cloud is a little more complicated for now. We hope to resolve this soon, but you can for now request support for tools here: https://cloudbees.zendesk.com/forums/177873-rfe-and-ideas-dev-cloud

    Thanks,
    Ryan

    ReplyDelete