Implementation details
Implementation Details
The configuration file uses a YAML hierarchical data structure.
Every node of this data structure is passed to a Configurator
responsible
to apply the adequate configuration on a Jenkins live instance.
Configurator
A Configurator
is managing a specific Jenkins component, and as such knows
about data this component exposes to end users for configuration.
It has:
- a
name
to match a YAML entry - a
target
component type - a
describe
method to document the attributes the target component exposes to configuration - a
configure
method to configure the target component
From a YAML node with an associated Configurator
, JCasC will handle every
child node in the YAML structure based on the current node's Attribute
s, as described by the Configurator
.
Root Configurator selection
Root elements are identified by YAML entry name, and a matching
RootElementConfigurator
is selected.
RootElementConfigurator
is a special interface used to identify a Configurator
which manages a top level
configuration element in the YAML document.
JCasC provides a custom RootElementConfigurator
for the jenkins
root entry in the YAML document,
as well as generic RootElementConfigurator
s to model global configuration categories.
Attributes
Configurator
documents the target component by implementing the describe
method. This method returns a set
of Attribute
s
to document both name AND type for a configurable attribute.
We don't want to expose the whole Jenkins Java API to JCasC. Many components define setter methods for technical reasons or to support backward compatibility. So JCasC excludes:
- setter methods marked as
@Deprecated
- setter methods marked as
@Restricted
Attribute
also is responsible to document the target component type. We use Java reflection to extract this
information from the Java API, including parameterized types.
As a resume, a component which exposes a method like:
will be detected as attribute named foo
with target type Foo
with multiple values expected.
Configuration
Configurator
also has to implement the configure
method to process YAML content and instantiate or configure
the target component. The actual mechanism depends on the target. JCasC provides generic
mechanisms which cover most Jenkins components.
This generic mechanism assumes Jenkins core components and plugins follow some conventions, but custom "glue-code" can also be provided as a (temporary) workaround, or to expose a configuration model which doesn't reflect the internal data model, but better matches the end user experience.
A typical sample for this scenario is the credentials plugin.
CredentialsRootConfigurator
exposes a simplified configuration API for the system
credentials store, which is hardly configurable
using the other general purpose JCasC component due to the internal design of this plugin.
General purpose configurators
Using Data binding
As we want to reflect web UI user experience, relying on the same configuration mechanisms as the web UI is a natural approach.
org.jenkinsci.plugins.casc.impl.configurators.DataBoundConfigurator
can configure arbitrary
Jenkins components to rely on DataBoundConstructor
and DataBoundSetter
s for UI data-binding. It uses the same attribute names as
the web UI, which are expected to be human friendly.
When, for technical or legacy reasons, the technical attribute name isn't user friendly, we also support
@Symbol
annotation on setters to offer a better user experience.
Descriptors
org.jenkinsci.plugins.casc.DescriptorRootElementConfigurator
can configure
global configuration for Descriptors, to mimic the global.jelly
UI exposed
to end users on the web UI.
Jenkins has hundreds of Descriptors, most of them for internal technical reasons,
so only the ones that have a global
view are accessible from JCasC.
For Descriptors to work well with JCasC, they need to follow some design best practices in terms of data binding. This is not such a common thing, so we expect this will require some evangelism on plugin developers.
As short term workaround, a custom Configurator
glue-code implementation can be implemented.
Initial secrets
Initial secrets are handled by the concrete implementations of the SecretSource. In order to implement a new
secret source, subclass SecretSource
by extending it, and mark the new class with the @Extension
annotation.