I'm create a plugin for Jenkins which adds a new type of post build step (a Publisher). When I try to create a new job, I'm only able to add my new step once (afer that, it is grayed out in the post-build-step menu). I'd like to be able to add it any number of times to the same job, with different configuration for each one (i.e., different instances of my Publisher subclass). How can this be done, and what's telling Jenkins to only allow it to be added once?
Update
I looks like this is somehow related to the <f:repeatable>
jelly element, but I can't figure out how to use it, and can't find any information on it. I tried to follow the HTML Publisher plugin, but kept getting errors. If anyone can explain how to use this, or point to a reference, that would be great!
Finally able to figure it out after much trial and error, based on the HTML Publisher plugin.
The build step I'm creating is repeatable, meaning you can only add it once to each job, but you can effectively add multiple "instances" of it. Each instance has three properties: name
, file
, and height
. My config.jelly
file looks like this:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry>
<f:repeatable field='targets'>
<f:textbox field='name' default='Report' />
<f:textbox field='file' default='report.html' />
<f:number field='height' default='300' /> px
<f:repeatableDeleteButton value='Delete report' />
</f:repeatable>
</f:entry>
</j:jelly>
This produces something like the following in the project-config screen:
This is after having manually added and edited the two "instances" on this particular page.
The key is that each "instance" of the repeatable needs to be built into some kind of object. This works just like normally instantiating a build step from the form, except it isn't the build-step itself, it's a subclass of AbstractDescribablyImpl
. Mine looks like this:
public static class Target extends AbstractDescribableImpl<Target>
{
public String name;
public String file;
public int height;
// Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
@DataBoundConstructor
public Target(String name, String file, int height) {
this.name = name;
this.file = file;
this.height = height;
if(this.name == null) {
throw new RuntimeException("Name is NULL!");
}
}
public String getName() {
return this.name;
}
public String getFile() {
return this.file;
}
public int getHeight() {
return this.height;
}
@Extension
public static class DescriptorImpl extends Descriptor<Target> {
@Override
public String getDisplayName() {
return "";
}
}
}
That's pretty simple, it's basically just an Object wrapper around the fields.
So then to actually instantiate the BuildStep itself, you have to accept a list of that type you just created to represent each "instance" of the step (in my case, the Target
class from above, which was an inner class of my build step). So it looks something like this:
public class EmbedReportPublisher extends Publisher
{
private List<Target> targets;
public static class Target extends AbstractDescribableImpl<Target>
{
// ... shown above ...
}
@DataBoundConstructor
public EmbedReportPublisher(List<Target> targets) {
if (targets == null) {
this.targets = new ArrayList<Target>(0);
}
else {
this.targets = new ArrayList<Target>(targets);
}
}
}
And that's about it. Now in the perform
method, and things like getProjectActions
, you can iterate over this.targets
, for instance, or whatever you need to do.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With