Jenkins Configuration as Code (a.k.a. JCasC) Plugin
Introduction
Setting up Jenkins is a complex process, as both Jenkins and its plugins require some tuning and configuration,
with dozens of parameters to set within the web UI manage
section.
Experienced Jenkins users rely on groovy init scripts to customize Jenkins and enforce desired state. Those scripts directly invoke Jenkins API and as such can do everything (at your own risk). But they also require you know Jenkins internals, and are confident in writing groovy scripts on top of Jenkins API.
The Configuration as Code plugin has been designed as an opinionated way to configure Jenkins based on human-readable declarative configuration files. Writing such a file should be feasible without being a Jenkins expert, just translating into code a configuration process one is used to executing in the web UI.
Below configuration file includes root entries for various components of your primary Jenkins installation. The jenkins
one is for the root Jenkins object, and other ones are for various global configuration elements.
jenkins:
systemMessage: "Jenkins configured automatically by Jenkins Configuration as Code plugin\n\n"
globalNodeProperties:
- envVars:
env:
- key: VARIABLE1
value: foo
- key: VARIABLE2
value: bar
securityRealm:
ldap:
configurations:
- groupMembershipStrategy:
fromUserRecord:
attributeName: "memberOf"
inhibitInferRootDN: false
rootDN: "dc=acme,dc=org"
server: "ldaps://ldap.acme.org:1636"
nodes:
- permanent:
name: "static-agent"
remoteFS: "/home/jenkins"
launcher:
jnlp:
workDirSettings:
disabled: true
failIfWorkDirIsMissing: false
internalDir: "remoting"
workDirPath: "/tmp"
slaveAgentPort: 50000
agentProtocols:
- "jnlp2"
tool:
git:
installations:
- name: git
home: /usr/local/bin/git
credentials:
system:
domainCredentials:
- credentials:
- basicSSHUserPrivateKey:
scope: SYSTEM
id: ssh_with_passphrase_provided
username: ssh_root
passphrase: ${SSH_KEY_PASSWORD}
description: "SSH passphrase with private key file. Private key provided"
privateKeySource:
directEntry:
privateKey: ${SSH_PRIVATE_KEY}
In addition, we want to have a well documented syntax file, and tooling to assist in writing and testing, so end users have full guidance in using this tool set and do not have to search for examples on the Internet.
Also see the presentation slides from DevOps World - Jenkins World 2018 for overview.
Getting Started
First, start a Jenkins instance with the Configuration as Code plugin installed.
- Those running Jenkins as a Docker container (and maybe also pre-installing plugins), do include Configuration as Code plugin.
Second, the plugin looks for the CASC_JENKINS_CONFIG
environment variable. The variable can point to any of the following:
- Path to a folder containing a set of config files. For example,
/var/jenkins_home/casc_configs
. - A full path to a single file. For example,
/var/jenkins_home/casc_configs/jenkins.yaml
. - A URL pointing to a file served on the web. For example,
https://acme.org/jenkins.yaml
.
If CASC_JENKINS_CONFIG
points to a folder, the plugin will recursively traverse the folder to find file(s) with .yml,.yaml,.YAML,.YML suffix. It will exclude hidden files or files that contain a hidden folder in any part of the full path. It follows symbolic links for both files and directories.
Exclusion examples
CASC_JENKINS_CONFIG=/jenkins/casc_configs
✔️ /jenkins/casc_configs/jenkins.yaml
✔️ /jenkins/casc_configs/dir1/config.yaml
❌ /jenkins/casc_configs/.dir1/config.yaml
❌ /jenkins/casc_configs/..dir2/config.yaml
CASC_JENKINS_CONFIG=/jenkins/.configs/casc_configs
contains hidden folder .config
❌ /jenkins/.configs/casc_configs/jenkins.yaml
❌ /jenkins/.configs/casc_configs/dir1/config.yaml
❌ /jenkins/.configs/casc_configs/.dir1/config.yaml
❌ /jenkins/.configs/casc_configs/..dir2/config.yaml
Instead of setting the CASC_JENKINS_CONFIG
environment variable, you can also define using
the casc.jenkins.config
Java property. This is useful when installing Jenkins via a package
management tool and can't set an environment variable outside of a package-managed file which could
be overwritten by an update. For RHEL/CentOS systems, you can append the following to the
JENKINS_JAVA_OPTIONS
entry in /etc/sysconfig/jenkins
-Dcasc.jenkins.config=/jenkins/casc_configs
If you do not set the CASC_JENKINS_CONFIG
environment variable or the casc.jenkins.config
Java
property, the plugin will default to looking for a single config file in
$JENKINS_HOME/jenkins.yaml
.
If everything was setup correctly, you should now be able to browse the Configuration as Code page with Manage Jenkins
-> Configuration as Code
.
Initial Configuration
When configuring the first Jenkins instance, browse the examples shown in the demos
directory of this repository. If you have a plugin that does not have an example, consult the reference
help document. Click the Documentation
link at the bottom of the Configuration as Code page.
If you want to configure a specific plugin, search the page for the name of the plugin. The page will
show you which root element belongs to the configuration. Most installed plugins belong under the unclassified
root
element.
Examples
See demos folder with various samples.
LDAP
Replacing user interface based configuration for LDAP with the text based configuration.
jenkins:
securityRealm:
ldap:
configurations:
- groupMembershipStrategy:
fromUserRecord:
attributeName: "memberOf"
inhibitInferRootDN: false
rootDN: "dc=acme,dc=org"
server: "ldaps://ldap.acme.org:1636"
Yaml Aliases and Anchors
Replace repeated elements with yaml anchors.
Due note anchor keys must be prefixed with x-
due to JCasC handling of unknown root elements.
x-jenkins-linux-node: &jenkins_linux_node_anchor
remoteFS: "/home/jenkins"
launcher:
jnlp:
workDirSettings:
disabled: true
failIfWorkDirIsMissing: false
internalDir: "remoting"
workDirPath: "/tmp"
jenkins:
nodes:
- permanent:
name: "static-agent1"
<<: *jenkins_linux_node_anchor
- permanent:
name: "static-agent2"
<<: *jenkins_linux_node_anchor
Which produces two permanent agent nodes which can also be written like this.
jenkins:
nodes:
- permanent:
name: "static-agent1"
remoteFS: "/home/jenkins"
launcher:
jnlp:
workDirSettings:
disabled: true
failIfWorkDirIsMissing: false
internalDir: "remoting"
workDirPath: "/tmp"
- permanent:
name: "static-agent2"
remoteFS: "/home/jenkins"
launcher:
jnlp:
workDirSettings:
disabled: true
failIfWorkDirIsMissing: false
internalDir: "remoting"
workDirPath: "/tmp"
Installing plugins
We don't support installing plugins with JCasC you need to use something else for this,
Dockers users can use:
https://github.com/jenkinsci/docker/#preinstalling-plugins
Kubernetes users:
https://github.com/helm/charts/tree/master/stable/jenkins
Supported Plugins
Most plugins should be supported out-of-the-box, or maybe require some minimal changes. See this dashboard for known compatibility issues.
Adding JCasC support to a plugin
Plugin developers wanting to support JCasC in their plugin should check out our how-to guide.
Compatibility with Jenkins >= 2.199 for JCasC < 1.36
Jenkins 2.199 introduced a check to prevent saving global configuration before loading the configuration has occurred. Configurations As Code needs to apply global configuration before Jenkins loads jobs (so they can load and correctly reference any global state) and as such there exists a race condition where by Jenkins may fail to start when used with this plugin.
If you encounter the race condition Jenkins will fail to start with an exception message similar to the following:
SEVERE jenkins.InitReactorRunner$1#onTaskFailed: Failed ConfigurationAsCode.init
java.lang.IllegalStateException: An attempt to save the global configuration was made before it was loaded
If you encounter this you can tell the plugin to delay configuration for an amount of time to give Jenkins time to load the global configuration before the configuration is applied by the plugin.
To enable this set the io.jenkins.plugins.casc.ConfigurationAsCode.initialDelay
system property to a number of milliseconds to delay the initialisation by.
The required value will be dependant on aspects of your system (cpu/disk) and configuration, and how it can be found is mostly a trial and error.
A suggestion would be to start with 5000 (5 Seconds) and then increment by 2000 (2 seconds) until you no longer exhibit the issue and finally add 1000 (1 second) for some extra safety.
For example, to delay the configuration by 9 seconds you would use something like the following command java -Dio.jenkins.plugins.casc.ConfigurationAsCode.initialDelay=9000 -jar jenkins.war
.
Exactly how and where you specify this option depends on the installation method used to install Jenkins.
Jenkins 2.220 includes JENKINS-51856 so the instance will not face the race condition. Starting with JCasC 1.36, the system property is not needed anymore.
Configuration-as-Code extension plugins
- configuration-as-code-groovy-plugin
Allows to specify groovy code that should run on during configuration.
Jenkins Enhancement Proposal
As configuration as code is demonstrated to be a highly requested topic in Jenkins community, we have published JEP 201 as proposal to make this a standard component of the Jenkins project. The proposal was accepted. 🎉