JCasC is designed so any plugin can be managed without the need to implement any custom API, but still require plugins to respect some contract, aka "convention over extension". This documentation is here to explain plugin maintainers those conventions and provide guidance on expected design.
JCasC relies on ability to introspect jenkins configurable components to build a "data model" from a live jenkins instance. For this purpose it relies on web UI data-binding conventions.
For legacy reasons, Jenkins do offer multiple ways to support UI data-binding, but the sole one to be introspection friendly is to offer @DataBoundSetter fields or setters in your code.
Surprisingly, this is well adopted by most plugins for Describable components, but not for Descriptors, despite the exact same mechanism can be used for both. And unfortunately, in many case the interesting components to offer configuration one want to expose to JCasC is attached to a Descriptors.
Check implementation of Descriptor#configure(StaplerRequest,JSONObject) in your descriptors. This one should not use any of the JSONObject.get*() methods to set value for an internal field. Prefer exposing javabean setter methods, and use req.bindJSON(this,JSONObject) to rely on introspection-friendly data-binding.
Within a Descriptor such setters don't have to be annotated as @DataBoundSetter but we suggest to do anyway, as it make it clear about the intent for such public methods.
sample :
public boolean configure(StaplerRequest req, JSONObject json) throws FormException { smtpHost = nullify(json.getString("smtpHost")); replyToAddress = json.getString("replyToAddress"); ... save(); return true; }
to be replaced by :
public boolean configure(StaplerRequest req, JSONObject json) throws FormException { req.bindJSON(this, json); save(); return true; } @DataBoundSetter public void setSmtpHost(String smtpHost) { this.smtpHost = nullify(smtpHost); } @DataBoundSetter public void setReplyToAddress(String address) { this.replyToAddress = Util.fixEmpty(address); }
note: you might not even need to implement configure once #3669 is merged.
You might have a set of fields which only make sense when set altogether, and have jelly view to use <f:optionalBlock>based on some boolean pseudo-property to show/hidde the matching section in web UI.
Doing so require had-written data-binding code, so based on rule 1 should be prohibited.
Hopefully there's a simple (and arguably better) way to handle this, by just using nested components and group all related fields into an optional sub-element.
sample :
<f:optionalBlock name="useAuth" title="${%Use Authentication}" checked="${descriptor.username!=null}"> <f:entry title="${%User Name}" field="username"> <f:textbox /> </f:entry> ...
private String username; private Secret password; public boolean configure(StaplerRequest req, JSONObject json) throws FormException { if(json.has("useAuth")) { JSONObject auth = json.getJSONObject("useAuth"); username = nullify(auth.getString("username")); password = Secret.fromString(nullify(auth.getString("password"))); } }
to be replaced by :`
<f:optionalProperty field="Authentication" title="${%Use Authentication}"/>
private Authentication authentication;
With a fresh new Authentication Describable class to host username and password, all the <f:optionalBlock> body being moved Authentication/config.jelly view.
note: this also require some data migration logic, please read PLUGINS for a step by step migration guide.
Checking support for JCasC is easy as long as your plugin required java 8 / jenkins 2.60+.
You just need CasC as a test dependency and a sample yaml file for your component
<dependency> <groupId>io.jenkins</groupId> <artifactId>configuration-as-code</artifactId> <version>1.0</version> <scope>test</scope> </dependency>
public class ConfigAsCodeTest { @Rule public JenkinsRule r = new JenkinsRule(); @Test public void should_support_configuration_as_code() throws Exception { ConfigurationAsCode.get().configure(ConfigAsCodeTest.class.getResource("configuration-as-code.yml").toString()); assertTrue( /* check plugin has been configured as expected */ ); }
Benefits for you to write such a testcase :
Really, if you need any assistance getting your plugin to support JCasC, want code review or anything, ping us on Gitter.