**Scenario: Globomantics, a fictional organization of which you are the Jenkins administrator.
Table of Contents
- 1.1 Introduction and philosophy of plugins
- 1.2 Jenkins plugin architecture
- 1.3 Demonstration: Anatomy of a plugin file
- 1.4 How Jenkins plugins are versioned
- 1.5 Why versioning is important
- 1.6 Real History: Versioning War
- 2.1 Quick tour of the Jenkins Plugin Management interface
- 2.2 How the Jenkins plugins “database” works
- 2.3 Demo: Install a new plugin and update an existing plugin
- 2.4 Demonstration: Configure a plugin
- 2.5 Demo: Manage the status of a plugin
- 3.1 Introduction to Groovy
- 3.2 Demo: Hello Plugin in the Script Console
- 3.3 Two ways to automate plugins with Groovy
- 3.4 Demonstration: Using the CLI for plugin management
- 3.5 Demo: Startup script for plugin management
- 3.6 Manage plugins via a Pipeline
- 3.7 Demonstration: Applying a whitelist of plugins in a Pipeline
- 4.1 Measure Jenkins plugins
- 4.2 Demo: Configuring Prometheus
- 4.3 Demonstration: Measuring the impact of a plugin with Prometheus
- 4.4 Demo: Working with Jenkins logs
- 4.5 Best practices for Jenkins plugins
- 5.1 Plugin recommendations
- 5.2 Demo: Working with Jenkins Security Advisories
- 5.3 Demo: Script to lock a plugin version
- 5.4 Course summary
1. Understand the Jenkins plugins model
1.1 Introduction and philosophy of plugins
Jenkins has been around since its name was Hudson. What makes it its central strength is its plugin model, directly inspired by a fundamental principle of software engineering:
“Rely on abstractions instead of concrete implementations. » “Depend on abstractions rather than concrete implementations. »
Software developers have learned — often the hard way — that they are extremely bad at predicting the future. The trainer’s concrete example: every time he wanted to represent a state with a boolean (true/false), a third state almost immediately popped up, ultimately requiring an integer or an enum.
It is precisely this recognition of the inability to anticipate all future needs that motivates the plugin model: rather than integrating all functionality into the core of Jenkins, we expose well-defined extension points, and the community implements them as needed.
The three categories of Jenkins plugins
Jenkins plugins are classified into three main families:
| Category | Description | Examples |
|---|---|---|
| Macro plugins | Take a simple user command and expand it into a series of console commands | Build Automation Plugins (Maven, Gradle, MSBuild) |
| Plugin interface | Edit Jenkins UI | Dashboard View, Blue Ocean |
| Plugin infrastructure | Provide basic capabilities used by other plugins | Credentials Plugin, Pipeline Plugin |
Key point about macro plugins: A macro plugin issues complex console commands in the background, just like a macro in C++. It’s essential to remember that you always have the option to revert to direct console commands when a plugin doesn’t do exactly what you want. This flexibility is fundamental in the Jenkins approach.
1.2 Jenkins plugin architecture
Understanding the technical structure of Jenkins plugins helps to better manage them, diagnose them and anticipate their behavior.
Core Technologies
Jenkins plugins are developed in Java. Although the primary language for automating Jenkins is Groovy (a Java-based scripting language), the plugins themselves are written in Java and depend on the JDK.
Jenkins plugins are built with Maven. Maven is a build automation tool primarily for Java. To make the analogy:
- Maven is to Java what NPM is to Node.js
- Maven manages plugin dependencies
- Maven runs build and test steps based on a build script called
pom.xml(Project Object Model)
Jenkins plugins are based on Maven archetypes. Maven archetypes are project templates, similar to project templates in the .NET SDK (website, class library, console application). You choose an archetype as the starting point for your plugin.
Jenkins plugins expose functionality through interfaces and abstract classes. Instead of depending on concrete implementations, plugins rely on interfaces such as Action, Builder, Publisher, etc.
Example of implementing a Jenkins interface
Here is an example from the Jenkins documentation illustrating how a plugin can take control of the interface:
public class MyAction implements Action {
@Override
public String getIconFileName() {
return "clipboard.png";
}
@Override
public String getDisplayName() {
return "Mon Plugin";
}
@Override
public String getUrlName() {
return "mon-plugin";
}
}
These three methods define:
- The icon displayed in the build side panel
- The displayed name in the interface
- The subpath URL to access the plugin functionality
By overriding these methods in Action (and other classes that implement Action), you take control of Jenkins’ behavior.
The role of POM.XML in a Jenkins plugin
Each Jenkins plugin has its pom.xml (Project Object Model) file, the Maven build script which:
- Declare the dependencies of the plugin (in particular on the Jenkins core and on other plugins)
- Configures compile and test steps
- Sets plugin metadata (name, version, author, URL)
1.3 Demonstration: Anatomy of a plugin file
Jenkins file system structure
To make Jenkins containers durable, the state of the Jenkins instance is isolated outside the container in a volume on the local disk. Example path: C:\volumes\jenkins.
Inside, we find the familiar structure of JENKINS_HOME:
jenkins_home/
├── plugins/ ← Tous les plugins installés
├── jobs/ ← Définitions et historique des builds
├── init.groovy.d/ ← Scripts d'initialisation Groovy
├── secrets/ ← Credentials chiffrés
├── workspace/ ← Espaces de travail des builds
└── config.xml ← Configuration principale de Jenkins
The plugins/ folder contains all installed plugins, even for a “vanilla” instance (minimal installation) — demonstrating that Jenkins requires a lot of them by default.
Plugin file extensions
Jenkins plugin files have two extensions:
.hpi— Hudson Plugin Interface (original format, from Hudson’s time).jpi— Jenkins Plugin Interface (current format)
These two extensions are actually JAR files (Java archives). They can be opened with tools like 7-Zip to explore their contents.
Internal structure of a .hpi/.jpi file
By opening a plugin file (example: the pipeline-build-step plugin):
pipeline-build-step.hpi
├── META-INF/
│ ├── MANIFEST.MF ← Métadonnées du plugin (Long-Name, version, dépendances)
│ └── maven/
│ └── org.jenkins-ci.plugins/
│ └── pipeline-build-step/
│ └── pom.xml ← Script de build Maven
└── WEB-INF/
└── lib/
└── pipeline-build-step.jar ← Code compilé du plugin
The compiled source code is in Java packages. For example, exploring the main JAR: org/jenkinsci/plugins/... — but the precise structure of the package varies depending on the plugin developer.
Important point: Folders like META-INF/maven/ contain metadata about the plugin (build information), not the plugin code itself. To access the actual code, you need to explore the main JAR in WEB-INF/lib/.
What happens when installing a plugin
When the Jenkins plugin manager installs a plugin:
- Downloading the
.hpifile to theplugins/folder - Extract to a folder with the same name as the plugin key
- Renaming the file with the extension
.jpi
This process allows Jenkins to know which version to use during an update — the .hpi file indicates that it is a new version to integrate.
1.4 How Jenkins plugins are versioned
Simple (and problematic) answer
The short answer: each developer chooses their own versioning scheme. Jenkins does not impose any strict schema, which the trainer strongly disapproves of.
There is no perfect versioning scheme, and different approaches can be advocated. What we cannot defend is the inconsistency between the diagrams.
The three versioning formats observed
A comprehensive analysis of Jenkins registry plugins reveals three main formats:
| Format | Example | Description |
|---|---|---|
| Semantic Versioning (SemVer) | 4.11.4, 2.3.0, 1.4 | MAJOR.MINOR.PATCH — Sometimes only two components (implicitly patch .0) |
| Build number + hash | 3.0.2-120.v5a_9591aa_dff3 | Artifact build number + original commit hash |
| SemVer + hash | 4.11.4.1-preview.13c1f8a | Hybrid between the two previous ones |
Explanation of non-SemVer formats
Why the build number + hash format? Some projects do not version releases as Git tags with SemVer. Instead, they use the build number of their CI system, combined with the hash of the source commit for traceability.
Practical impact: This inconsistency makes it difficult to write automated whitelist management scripts, because you cannot assume uniform comparison rules between versions.
1.5 Why versioning is important
The definition of compatibility
A version is used to determine compatibility. In the Jenkins context, the question is: what is the plugin compatible with?
There are two directions of compatibility:
Direction 1: Backwards compatibility (plugin → Jenkins)
- The plugin depends on a certain version of Jenkins
- If Jenkins introduces a new feature and a plugin exposes a wrapper for it, that plugin will only work with versions of Jenkins that have that feature
Direction 2: Backwards compatibility (Jenkins → plugin)
- Jenkins should be able to continue using the plugin
- If the plugin removes a public interface that Jenkins (or other plugins) relies on, anything dependent on it breaks
Semantics of SemVer applied to plugins
| Component | Increment | Meaning |
|---|---|---|
MAJOR (3.0.0) | Public interface removed or modified in an incompatible manner | Existing client code will break |
MINOR (2.3.0) | New features added in a backwards compatible way | Existing client code still working |
PATCH (2.2.1) | Bug fixes without API changes | Existing client code still working |
Practical rule: When we increment the MAJOR, we reset MINOR and PATCH to zero. For example: 2.11.2 → 3.0.0 if a public interface is removed.
The plugin dependency chain
Jenkins plugins have their own dependencies to other plugins (visible in the pom.xml). This creates a chain of dependencies similar to Microsoft’s famous “DLL Hell” or “Dependency Hell” in general:
- Plugin A depends on Plugin D version 11
- Plugin B depends on Plugin D version 13
- Update Plugin D to version 13 for Plugin B may break Plugin A
1.6 Real History: Versioning War
Context
The instructor was working on a course regarding the Docker Cloud plugin for Jenkins. He recorded demonstrations late on Friday evenings. The next morning, when he returned, the Jenkins agents were no longer working.
The root cause
The Docker command used to create the Jenkins container:
docker run \
--name jenkins \
-p 2113:8080 \
-v C:/volumes/jenkins:/var/jenkins_home \
jenkins/jenkins:lts
The lts tag is crucial here. lts is a pointer to a version (Long Term Support), not a fixed version. During the night, the Jenkins teams had promoted a new version to LTS. The scripts had automatically pulled the new image and updated Jenkins.
The cascade of ruptures
- The new version of Jenkins required a newer version of a Docker plugin dependency (not the Docker plugin itself, but one of its dependent plugins)
- This dependency existed for a while, but no one had updated it because the old one worked perfectly
- The Jenkins installation process automatically updated this dependency to the new version
- In the new version, a public interface had been removed
- The developer had only incremented the MINOR (not the MAJOR), incorrectly reporting that it was backwards compatible
- Jenkins considered auto-update safe (minor increment)
- The Docker plugin, which depended on this removed interface, stopped working
The lesson
Incrementing the minor version by removing a public interface is a serious error that can break production silently and unexpectedly.
Best practice: Freeze Docker images with a specific version tag when stability is critical, rather than using floating tags like lts or latest in production.
# À éviter en production (tag flottant)
docker run jenkins/jenkins:lts
# Préférer une version figée
docker run jenkins/jenkins:2.414.3-lts
2. Working with the Jenkins plugin management interface
2.1 Quick tour of the Jenkins Plugin Management interface
Access to the Plugin Manager
Navigation: Manage Jenkins → Plugins
The interface consists of four main tabs:
| Tab | Description |
|---|---|
| Updates | Installed plugins for which a new version is available |
| Available plugins | Plugins from the official registry that are not yet installed |
| Installed plugins | Plugins currently installed on this instance |
| Advanced settings | Advanced configuration (update center, proxy, manual upload) |
Available Plugins tab
For each plugin listed:
- Title and version in gray (immediately to the right of the title)
- Date of last publication (right) — gives an idea of the maturity of the plugin or the risk that it will no longer be maintained
- Description at the bottom of the plugin panel
- Tags/Labels (optional): ex.
Agent Management,Library plugins,Git
Use of tags: Filtering by tags (click on a tag → list of plugins with this tag). Useful for understanding dependency chains, but little used on a daily basis by experienced admins who already know which plugin they are looking for.
Installed Plugins tab
Similar to Available plugins with some differences:
- Instead of release date: toggle enable/disable for each plugin
- Hovering over an installed plugin → button to uninstall it
- Security indicators (warnings) directly visible
2.2 How the Jenkins plugin “database” works
The fundamental operation
This is a very common operation in computing:
Ensemble de tous les plugins possibles
- Ensemble des plugins installés
= Liste des plugins disponibles à installer
The same logic applies to database migration: the set of all possible migrations minus the set of those already applied gives the remaining migrations to be executed.
The official Jenkins plugin registry
Jenkins maintains a central registry of plugins at the URL visible in Manage Jenkins → Plugins → Advanced settings → Update Site. This registry is a JSON file (update-center.json) hosted by the Jenkins Foundation.
Conditions for appearing in the registry:
- Source code must be open source — this is the only way to integrate the registry
- The plugin must be accepted by the Jenkins community
Structure of the update-center.json file
This JSON file is large and contains several main sections:
{
"updateCenterVersion": "1",
"id": "default",
"connectionCheckUrl": "https://www.google.com/",
"signature": {
"correct_digest": "...",
"correct_digest512": "..."
},
"deprecations": {
"plugin-key": {
"url": "https://...",
"since": "2.xxx"
}
},
"plugins": {
"42crunch-security-audit": {
"name": "42crunch-security-audit",
"version": "3.1.0",
"url": "https://updates.jenkins.io/download/plugins/42crunch-security-audit/3.1.0/42crunch-security-audit.hpi",
"dependencies": [...],
"developers": [...],
"labels": [...],
"releaseTimestamp": "...",
"title": "42Crunch Security Audit",
"wiki": "https://..."
}
},
"warnings": [
{
"id": "...",
"message": "Exposure of secrets through system log",
"name": "structs",
"type": "plugin",
"url": "...",
"versions": [...]
}
]
}
Key sections:
deprecations: Plugins deprecated but not yet removed (long list)plugins: The main entry — the first plugin listed is42crunch-security-auditwarnings: Security notices — towards the end of the file
2.3 Demonstration: Install a new plugin and update an existing plugin
Install Dark Theme plugin
The dark-theme plugin makes the Jenkins interface dark — useful for reducing eye strain during long sessions. Its version is in build number + hash format.
Installation process visible in file system:
After installation, in the plugins/ folder (sorted by decreasing modification date):
dark-theme.jpi ← Plugin installé
dark-theme/ ← Dossier extrait
theme-manager.jpi ← Dépendance automatiquement installée
theme-manager/ ← Dossier de la dépendance
What exactly is happening:
- Jenkins uploads the
.hpifile toplugins/ - Extract it in a folder with the same name as the plugin key
- Rename the file with the extension
.jpi - Automatically install dependencies (here,
theme-manager)
Reboot after installation
Two options for restarting after an installation:
- Click Restart Jenkins in the progress interface
- Restart manually (Docker command, service, etc.)
# Redémarrage manuel d'un conteneur Jenkins Docker
docker restart jenkins
Update an existing plugin
Best practice when installing a new plugin:
- Check the installed version corresponds to the latest available version
- Consult the releases page of the plugin (link from the plugin home page)
- Evaluate the last release date — a plugin not updated for 2 years could mean that it is stable AND stable in its command interface, or that it is abandoned
Example: The .NET SDK plugin for Jenkins — last release 1.4.0 2 years ago. This is normal because the plugin is stable as long as the .NET SDK command interface remains stable.
2.4 Demonstration: Configure a plugin
Special feature of plugin configuration in Jenkins
There is no direct configuration link in the Plugin Manager. This is a quirk of the Jenkins interface. To configure a plugin, you must navigate to:
Manage Jenkins → System
All plugin configuration is grouped in the System page (and sometimes in other Manage Jenkins pages depending on the nature of the plugin).
Example with the Dark Theme plugin:
- Manage Jenkins → System → Section Themes
- Three options: Default, Dark, Custom
- Select Dark → Save → The interface switches to dark mode
Example with the .NET SDK plugin: Configuration requires defining the installation locations of different versions of the .NET SDK to use in builds. This configuration is more complex than the simple dark theme.
2.5 Demonstration: Managing the state of a plugin
The two states of an installed plugin
A plugin essentially has two status bits:
- Installed or not installed — An uninstalled plugin has no impact on configuration, security or builds
- Enabled or disabled — An installed plugin can be temporarily disabled without being removed
Disabling mechanism in file system
How does Jenkins know when a plugin is disabled? There is no central database. Instead, Jenkins uses a sentinel file:
plugins/
├── ws-cleanup/ ← Dossier extrait du plugin
├── ws-cleanup.jpi ← Fichier du plugin (actif)
└── ws-cleanup.jpi.disabled ← Fichier zéro-octet = plugin désactivé
The file ws-cleanup.jpi.disabled is a zero byte file. Its existence alone is the information Jenkins needs to consider the plugin disabled.
Practical implication for Jenkins admins:
As an administrator with access to the Jenkins file system volume, you can enable or disable plugins directly by creating or deleting this zero-byte file, without going through the interface, then restart Jenkins.
# Désactiver un plugin via le système de fichiers (Windows)
New-Item "C:\volumes\jenkins\plugins\ws-cleanup.jpi.disabled" -ItemType File
# Réactiver un plugin via le système de fichiers
Remove-Item "C:\volumes\jenkins\plugins\ws-cleanup.jpi.disabled"
# Désactiver un plugin via le système de fichiers (Linux)
touch /var/jenkins_home/plugins/ws-cleanup.jpi.disabled
# Réactiver un plugin
rm /var/jenkins_home/plugins/ws-cleanup.jpi.disabled
After modification, a restart of Jenkins is necessary.
Globomantics Scenario
The Globomantics Jenkins instance is slow. You are at the end of the release and do not want to make major changes. You must regularly activate/deactivate plugins. The interface is too slow. Directly handling .disabled files is the quick solution.
3. Automate Jenkins plugins
3.1 Introduction to Groovy
Philosophy: Simple vs Complete
There are two main approaches to automating Jenkins:
- The simplified CLI approach — Good for simple cases, documented in many articles
- The Groovy approach in the Script Console — Complete, comprehensive, full access to the Jenkins API
Trainer experience (100% of the time): The “simple” approach always ends up showing its limits. We spend time learning a simplified method, only to end up needing something it doesn’t support and having to relearn everything with the complete approach.
Learn the complete approach directly. The time invested in Groovy is an ongoing investment.
What is Groovy?
Groovy is a Java-based scripting language. Key Features:
- Dynamically typed with strict typing option
- Syntax very similar to JavaScript, Java and all C-like languages
- Full support for object-oriented programming (inheritance, polymorphism)
- Jenkins exposes extremely comprehensive automation API via Groovy
Dynamic vs strict typing in Groovy
Dynamic typing (type inference):
// Le type est inféré automatiquement
def username = "Chris B. Behrens"
println username
Strict typing (explicit declaration):
// Type explicitement déclaré
String username = "Chris B. Behrens"
println username
The instructor personally prefers strict typing for code clarity, but both are valid and produce the same result.
Groovy Closures
Groovy uses closures (anonymous blocks of code) extensively, with the { param -> code } syntax:
// Closure simple
def greet = { name -> println "Hello, ${name}!" }
greet("Jenkins")
// Closure utilisée avec les collections
def plugins = ["git", "pipeline", "docker"]
plugins.each { plugin ->
println plugin
}
Groovy Comments
Groovy uses C-like syntax for comments:
// Commentaire sur une ligne
/*
* Commentaire sur
* plusieurs lignes
*/
3.2 Demonstration: Hello Plugin in the Script Console
Access to the Script Console
Navigation: Manage Jenkins → (scroll down) → Script Console
The Script Console allows executing arbitrary Groovy commands directly on the Jenkins instance. It is a powerful and dangerous tool — to be protected with strict access rights.
Basic commands in the Script Console
Hello World:
println "Hello Console!"
Dynamic typing:
def username = "Chris B. Behrens"
println username
Strict typing:
String username = "Chris Behrens"
println username
List installed plugins — Minimum version
The simplest line of code to get the list of plugins:
Jenkins.instance.pluginManager.plugins
Result: a list that is difficult to read, without formatting.
Improvement with a for loop
// Obtenir la liste typée
List<PluginWrapper> plugins = Jenkins.instance.pluginManager.plugins
// Itérer et afficher
for (plugin in plugins) {
println(plugin)
}
Show plugin display name
List<PluginWrapper> plugins = Jenkins.instance.pluginManager.plugins
for (plugin in plugins) {
println(plugin.displayName)
}
The displayName property returns the name of the plugin as it appears in the Jenkins interface.
Full version with sorting and alignment
List<PluginWrapper> plugins = Jenkins.instance.pluginManager.plugins
.sort { it.displayName }
for (plugin in plugins) {
// padRight(50) aligne les noms pour une lecture facile
println(plugin.displayName.padRight(50) + plugin.version)
}
This version:
- Sort plugins by display name
- Align columns with
padRight(50)(right padding up to 50 characters) - Shows the name and version of each plugin
Example output:
Ant Plugin 497.v94e7d9fffa_b_9
Apache HttpComponents Client 4.x API Plugin 4.5.14-208.v438351a_3f82d
Bitbucket Branch Source Plugin 806.vf63b_a_cefd2ab_c
...
3.3 Two ways to automate plugins with Groovy
80/20 model of plugin management
Plugin management follows an 80/20 model:
- 20% of plugins cause 80% problems
- Often it is not an exhaustive whitelist that is needed, but rather one or two plugins to monitor and prevent from being accidentally updated or downgraded
Why is it difficult to control a plugin version?
For most plugins, controlling the version is simple: just don’t let updates happen automatically. But Dependency Hell complicates things.
Analogy with Microsoft’s Hell DLL:
The old Microsoft model shared DLLs (Dynamic Link Libraries) centrally between all applications. When App A depended on monlib.dll v11 and App B depended on monlib.dll v13, installing App B updated the DLL and broke App A. Microsoft finally fixed this with local app domains.
The same problem in Jenkins plugins:
Plugin A → dépend de → Plugin D version 11
Plugin B → dépend de → Plugin D version 13
Mettre à jour Plugin B → Plugin D passe à v13 → Plugin A casse
Recommended policy: Update plugins frequently and in a scheduled manner, testing for impacts. A regular update policy is better than letting obsolete versions accumulate.
Method 1: Script in the Script Console or init.groovy.d
- Direct access to the full Jenkins API
- No authentication token required
- Results visible immediately
- Ideal for ad-hoc management and startup scripts
Method 2: Jenkins CLI
- External tool (JAR file)
- Requires API authentication token
- Useful for integration with external tools
- Limits in advanced operations
3.4 Demonstration: Using the CLI to manage plugins
Get the Jenkins CLI JAR
The Jenkins CLI JAR file is available either on the Internet or directly packaged with Jenkins at the URL:
http://<votre-jenkins>:<port>/jnlpJars/jenkins-cli.jar
Download and place this file in the JENKINS_HOME or in the directory from which you will launch your CLI commands.
Access Jenkins command line in Docker
# Se connecter au conteneur Jenkins
docker exec -it jenkins2 /bin/bash
# Naviguer vers JENKINS_HOME
cd /var/jenkins_home
# Vérifier la présence du JAR
ls -la jenkins-cli.jar
Create a Token API
Using the Jenkins CLI requires an authentication token. This is a notable limitation compared to scripts in the Script Console (which don’t need it).
Navigation to create a token:
- Manage Jenkins → Users → admin → Configure → API Token → Add new Token
- OR: User drop-down menu (top right corner) → Configure → API Token
Security best practice: Create a dedicated user account with restricted rights specifically for API access, rather than using the admin account.
Important: The token is displayed only once upon creation. Copy it immediately to a secrets manager.
Common CLI Commands
# Lister les plugins installés
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
list-plugins
# Installer un plugin
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
install-plugin <nom-plugin>
# Installer plusieurs plugins
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
install-plugin git pipeline-stage-view docker-workflow
# Désactiver un plugin
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
disable-plugin <nom-plugin>
# Activer un plugin
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
enable-plugin <nom-plugin>
# Redémarrer Jenkins de manière sécurisée (attendre la fin des builds en cours)
java -jar jenkins-cli.jar \
-s http://localhost:8080 \
-auth admin:<votre-api-token> \
safe-restart
Why the trainer prefers Groovy to the CLI
Every authentication token floating around in your environment is a security risk to manage. Hook scripts and the Script Console do not need it. One less reason to use the CLI.
Additionally, the CLI reaches its limits quickly for advanced operations (like version locking, assembly comparison, etc.).
3.5 Demo: Startup script for plugin management
Jenkins init scripts (init.groovy.d)
Jenkins automatically runs all Groovy scripts present in the JENKINS_HOME/init.groovy.d/ folder when Jenkins starts. This is the ideal mechanism for applying a starting configuration.
jenkins_home/
└── init.groovy.d/
├── 01-disable-bitbucket-plugins.groovy
├── 02-configure-security.groovy
└── 03-configure-tools.groovy
Files are executed in alphabetical/numerical order of their name.
Scenario: Disable all Bitbucket plugins
Globomantics has just changed version control providers from Bitbucket to another. We want to disable all Bitbucket plugins for a week to monitor broken builds, before deleting them permanently.
Warning: Blue Ocean has ties to Bitbucket but is a separate plugin — it will not disappear with disabling specific Bitbucket plugins.
// JENKINS_HOME/init.groovy.d/disable-bitbucket-plugins.groovy
import jenkins.model.Jenkins
def pm = Jenkins.instance.pluginManager
// Trouver tous les plugins dont le "Long-Name" contient "bitbucket"
def bbPlugins = pm.plugins.findAll { plugin ->
def description = plugin.manifest
.getMainAttributes()
.getValue("Long-Name")
description != null && description.toLowerCase().contains("bitbucket")
}
println "Plugins Bitbucket trouvés : ${bbPlugins.size()}"
// Désactiver les plugins trouvés
bbPlugins.each { plugin ->
plugin.disable()
println "Désactivé : ${plugin.displayName}"
}
Jenkins.instance.save()
println "Configuration sauvegardée."
Manifest search explanation
The property manifest.getMainAttributes().getValue("Long-Name") accesses the manifest of the plugin (file META-INF/MANIFEST.MF in the plugin JAR). This manifest contains metadata such as:
Long-Name: Full and readable name of the pluginPlugin-Version: Plugin versionJenkins-Version: Minimum Jenkins version requiredPlugin-Dependencies: List of dependencies
Why check description != null?
Some plugins may not have the Long-Name attribute in their manifest. Without this check, calling .toLowerCase() would cause a NullPointerException.
Script performance optimization
The script above disables plugins on every startup, even if they are already disabled. This takes 26 seconds in the demonstration. Optimized version:
// init.groovy.d/disable-bitbucket-plugins.groovy (version optimisée)
import jenkins.model.Jenkins
def pm = Jenkins.instance.pluginManager
def bbPlugins = pm.plugins.findAll { plugin ->
def description = plugin.manifest
.getMainAttributes()
.getValue("Long-Name")
description != null && description.toLowerCase().contains("bitbucket")
}
// Ne désactiver que les plugins qui sont encore activés
def activePlugins = bbPlugins.findAll { it.isEnabled() }
if (activePlugins.isEmpty()) {
println "Tous les plugins Bitbucket sont déjà désactivés."
return
}
activePlugins.each { plugin ->
plugin.disable()
println "Désactivé : ${plugin.displayName}"
}
Jenkins.instance.save()
println "${activePlugins.size()} plugin(s) désactivé(s)."
Gain: On the second restart (and subsequent ones), the list is empty and the script stops immediately → 26 seconds saved on startup.
3.6 Manage plugins via a Pipeline
Plugins live on the Controller, not the Agents
Simple and fundamental rule:
Plugins live on the controller. If you think about plugins, the relevant scope is always the controller.
The controller sends commands to agents. By the time these commands reach the agent, any transformation driven by the plugin has already occurred on the controller. All the agent needs is the software necessary to process these commands (target SDK, runtime, etc.).
Consequence: An entire class of problems — knowing which agent a pipeline is building on versus which plugins — is entirely eliminated.
Run a pipeline on the Controller
This allows you to do something that would normally be discouraged: run a build on the controller.
Normally contraindicated for two reasons:
- Security: The access level of a pipeline is too high for the controller
- Practical: We want agents with different SDKs and build configurations
Justified exception: A pipeline that modifies the configuration of plugins can legitimately run on the controller, as long as the security implications are taken into account.
Comparison of automation methods
| Method | When runs | Advantages | Disadvantages |
|---|---|---|---|
| CLI | Anytime | Easy integration with external tools | Requires token auth, limited in functionality |
| init.groovy.d | When starting Jenkins | Automatic, without intervention | Only on startup (instances rarely restarted) |
| Pipeline | Scheduleable (cron), manually triggered | Flexible, versionable in Git, scheduleable | Security risk on the controller if poorly configured |
Note on production Jenkins instances: Production Jenkins instances tend to be active for a very long time without restarting. An init.groovy.d script might not run for weeks or months. A schedulable pipeline is often more appropriate for ongoing plugin management.
3.7 Demonstration: Applying a whitelist of plugins in a Pipeline
Prior warning
This is a super easy way to break everything in your Jenkins setup. Back up your
plugins/folder first.
Backup procedure:
# Depuis le host Docker
cp -r C:/volumes/jenkins/plugins/ C:/volumes/jenkins/plugins-backup/
# Ou depuis l'intérieur du conteneur
docker exec jenkins tar -czf /tmp/plugins-backup.tar.gz /var/jenkins_home/plugins
docker cp jenkins:/tmp/plugins-backup.tar.gz ./plugins-backup.tar.gz
In case of serious problem, restore the backup folder and restart Jenkins.
Creating the Pipeline
Navigation: New Item → Pipeline → (configure name) → OK
Configuration:
- In the Pipeline section, select Pipeline script (inline Groovy)
- In production, we would use Pipeline script from SCM (from Git) to keep everything in version control
The complete whitelist management Jenkinsfile
import jenkins.model.Jenkins
import hudson.PluginWrapper
import hudson.model.UpdateSite
pipeline {
agent any
stages {
stage("Plugin Management") {
steps {
script {
// =====================================================
// ÉTAPE 1 : Définir la whitelist inline
// En production : lire depuis un fichier versionné en Git
// =====================================================
def whitelist = [
"git",
"workflow-aggregator",
"pipeline-stage-view",
"dark-theme",
"cloudbees-disk-usage-simple",
"prometheus",
"matrix-auth",
"credentials",
"ssh-credentials",
"plain-credentials"
]
def pm = Jenkins.instance.pluginManager
// =====================================================
// ÉTAPE 2 : Obtenir l'ensemble des plugins installés
// =====================================================
List<String> installedKeys = pm.plugins.collect { it.shortName }
println "Plugins installés : ${installedKeys.size()}"
println "Plugins dans la whitelist : ${whitelist.size()}"
// =====================================================
// ÉTAPE 3 : Whitelist - Installés → À désinstaller
// =====================================================
def toUninstall = installedKeys.findAll { !whitelist.contains(it) }
println "Plugins à désinstaller : ${toUninstall.size()}"
toUninstall.each { key ->
def plugin = pm.getPlugin(key)
if (plugin != null) {
println "Désinstallation : ${plugin.displayName} (${key})"
plugin.doDoUninstall()
}
}
// =====================================================
// ÉTAPE 4 : Installés - Whitelist → À installer
// =====================================================
def toInstall = whitelist.findAll { !installedKeys.contains(it) }
println "Plugins à installer : ${toInstall.size()}"
if (toInstall.size() > 0) {
// Mettre à jour le center d'update pour avoir les dernières versions
Jenkins.instance.updateCenter.updateAllSites()
toInstall.each { key ->
println "Installation : ${key}"
}
// Installer de manière synchrone (attendre la fin)
pm.install(toInstall, true).get()
}
// =====================================================
// ÉTAPE 5 : Sauvegarder la configuration Jenkins
// =====================================================
Jenkins.instance.save()
println "Gestion des plugins terminée."
println "Un redémarrage peut être nécessaire pour appliquer les changements."
}
}
}
}
}
Imports required outside of the Script Console
Important: In the Script Console, several Jenkins classes are already implicitly imported. In a Jenkinsfile (pipeline), they must be imported explicitly:
import jenkins.model.Jenkins
import hudson.PluginWrapper
import hudson.model.UpdateSite
import hudson.PluginManager
Security Notes on this Pipeline
- This pipeline must be executed on
agent any(the controller), not on a specific agent - Accessing
Jenkins.instancefrom a pipeline requires script approval in Manage Jenkins → In-process Script Approval - Limit who can modify and trigger this pipeline
4. Monitor and troubleshoot plugins
4.1 Measuring Jenkins plugins
The Globomantics context
You have your plugins under control. The whitelist works. But now the builds take longer and the Jenkins interface lags for users. The cause must be identified. For this, you need metrics.
Introduction to Prometheus
Prometheus is:
- An open source protocol format for metrics
- A set of data collection tools
- Originally developed by SoundCloud
- Become de facto standard for monitoring in cloud-native environments
Jenkins monitoring architecture with Prometheus:
Jenkins (serveur Prometheus)
↓ expose /prometheus
Prometheus (client/collecteur)
↓ stocke les métriques time-series
Grafana / Kibana (optionnel)
↓ dashboards et visualisations
Tools required:
- Prometheus plugin for Jenkins (exposes the
/prometheusendpoint) - Prometheus server in Docker (collects and stores metrics)
- Docker on host machine
4.2 Demonstration: Configuring Prometheus
Step 1: Install Prometheus plugin in Jenkins
Navigate to Manage Jenkins → Plugins → Available plugins → Search for Prometheus → Install.
Step 2: Create the Prometheus volume and configuration file
# Créer le dossier volume
mkdir C:/volumes/prom
Create the prometheus.yaml file:
# C:/volumes/prom/prometheus.yaml
# Configuration Prometheus pour surveiller Jenkins
# YAML sensible aux espaces - modifier avec précaution
global:
scrape_interval: 15s # Collecte des métriques toutes les 15 secondes
evaluation_interval: 15s # Évaluation des règles toutes les 15 secondes
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'jenkins'
# Surcharger le chemin par défaut (/metrics) avec le chemin Jenkins
metrics_path: '/prometheus'
static_configs:
# host.docker.internal pointe vers la machine hôte depuis l'intérieur du conteneur
# Remplacer 2113 par votre port Jenkins local
- targets: ['host.docker.internal:2113']
Important about
host.docker.internal: This special URL points to the host machine from inside a Docker container. Replace with the IP of the host machine on Linux if this DNS resolution does not work.
Step 3: Start the Prometheus container
docker run \
--name prometheus \
-p 9090:9090 \
-v C:/volumes/prom/prometheus.yaml:/etc/prometheus/prometheus.yaml \
prom/prometheus
Parameter explanation:
--name prometheus: Container name-p 9090:9090: Publishing port 9090 (Prometheus web interface)-v .../prometheus.yaml:/etc/prometheus/prometheus.yaml: Mounting the config file
Step 4: Verify that Prometheus collects Jenkins
Open http://localhost:9090 → Prometheus is accessible.
Navigate to Status → Targets to verify that job jenkins is in UP state.
4.3 Demonstration: Measuring the impact of a plugin with Prometheus
Scenario
We will introduce a new infrastructure plugin and measure its impact on disk usage per job — an important metric at Globomantics because disk space directly impacts backup and bandwidth costs.
Step 1: Install CloudBees Disk Usage plugin
The CloudBees Disk Usage Simple plugin (cloudbees-disk-usage-simple) adds disk usage metrics to the Prometheus endpoint.
After installation, wait for data to percolate to Prometheus:
- Manage Jenkins → Disk Usage → Wait for initial scan to complete
Step 2: Reference measurement (baseline)
In Prometheus, query:
default_jenkins_job_usage_bytes
Result before new plugin:
- Only the “Plugin Management” job has disk usage: 215,000 bytes (215K)
- Other jobs (not yet executed): 0 bytes
Cross-reference check: Jenkins Dashboard → Jobs → Properties of the “Plugin Management” job folder → Size: ~215K ✓
Step 3: Installing the new plugin and new measurement
Install a new plugin that may increase disk usage. Restart builds. Prometheus query after:
# Utilisation disque par job
default_jenkins_job_usage_bytes
# Comparer avant/après
default_jenkins_job_usage_bytes{job="plugin-management"}
Analysis: Compare the value before and after the introduction of the new plugin. A significant increase per job justifies a cost/benefit evaluation of the plugin.
Other Jenkins metrics available through Prometheus
# Nombre de builds en cours
default_jenkins_builds_running_build_duration_milliseconds_summary
# Santé générale de Jenkins
default_jenkins_health_check_score
# Nombre de jobs
default_jenkins_job_count_value
# Durée des builds
default_jenkins_builds_last_build_duration_milliseconds
# Utilisation de l'espace workspace
default_jenkins_node_free_space_bytes
4.4 Demonstration: Working with Jenkins logs
Jenkins Debugging Fundamentals
Your first source for debugging as a Jenkins operator should always be Jenkins logs.
Navigation: Manage Jenkins → System Log → All Jenkins Logs
Key information in logs
1. Plugins present but disabled: The startup log contains lines acknowledging plugins that are installed but disabled:
[...] Plugin ws-cleanup is disabled
[...] Plugin bitbucket is disabled
2. Executing init.groovy.d scripts: The exact execution sequence of the startup scripts is visible in the logs:
2024-01-15 08:53:10.123 INFO o.h.g.d.impl.StartupTrigger: Executing init.groovy.d/disable-bitbucket-plugins.groovy
Plugin désactivé : Bitbucket Branch Source Plugin
Plugin désactivé : Bitbucket Push and Pull Request Plugin
2024-01-15 08:53:36.456 INFO jenkins.InitReactorRunner$1: Completed initialization
Timing analysis: The script started at 08:53:10, initialization finished at 08:53:36 → 26 seconds for this script.
3. Plugin loading errors: When a plugin fails to load (incompatible version, missing dependency, etc.):
WARNING: Failed to initialize plugin: some-plugin
java.lang.NoSuchMethodError: hudson.model.AbstractBuild.getSomething()...
Where to look depending on the problem
| Problem | Where to look |
|---|---|
| Plugin not loading | System Log → startup |
| Plugin behaves badly in build | Build Log (Console Build Output) |
| Plugin configuration problem | System Log → after modification |
| Performance issue | Prometheus + System Log |
Custom logging from your Groovy scripts
In your automation scripts, use the Jenkins logging rather than println for better filtering capabilities:
import java.util.logging.Logger
def logger = Logger.getLogger("init-scripts")
// Différents niveaux de log
logger.info("Démarrage de la gestion des plugins Bitbucket")
logger.warning("Plugin ${pluginKey} introuvable")
logger.severe("Erreur critique lors de l'installation du plugin")
// println va dans le log Jenkins aussi, mais sans niveau de log
println "Message dans les logs Jenkins"
4.5 Best practices for Jenkins plugins
These practices are the result of many years of production experience with Jenkins.
1. Limit who can edit plugins
This is the most fundamental security concept: limiting privileges to the people who need them.
- Editing a plugin should always have a second look
- If a developer needs a new plugin: an admin should check the impact on existing builds
- Disable, delete or update plugins: reserved for admins who have the overview
Implementation: Use the Role-Based Authorization Strategy plugin to define specific roles.
2. Back up your Jenkins configuration
Backup options:
# Option 1 : Backup manuel du volume Docker
cp -r C:/volumes/jenkins/ C:/volumes/jenkins-backup-$(date +%Y%m%d)/
# Option 2 : Via le plugin ThinBackup
# Manage Jenkins → ThinBackup → Backup Now
# Option 3 : Versionner la configuration (JCasC)
# Jenkins Configuration as Code - plus avancé
ThinBackup Plugin: Formal and dedicated solution for Jenkins backups.
JCasC (Jenkins Configuration as Code): Allows you to specify the entire Jenkins configuration in the form of YAML code, versionable in Git. Considered overkill for most Jenkins operators, but very powerful for large teams.
Minimum objective: To be able to return to yesterday’s configuration in the event of a problem this morning.
3. Do not write your own plugins (unless truly necessary)
The strength of Jenkins lies in its configurability, automatability and customization via Groovy. Plugins solve problem classes related to a particular domain.
Case where a custom plugin is justified:
- You are a company that publicly maintains a particular SDK
- You need to integrate a fully proprietary system that is not supported
- You have really exhausted all the alternatives in Groovy
Otherwise: Groovy in the Script Console can solve 95% of customization needs without the complexity of developing, maintaining and distributing a Java plugin.
4. Treat security warnings seriously
Jenkins displays visual indicators for plugins with known security advisories. These warnings come directly from update-center.json.
Recommended process:
- Identify warnings during regular reviews (weekly/monthly)
- Consult the advisory (URL provided in the warning)
- Assess the risk (vulnerability type, exploitability)
- Schedule update or deactivation based on criticality
5. Plugin Update Policy
Recommendation: update plugins frequently and on a scheduled basis:
// Script Groovy pour identifier les plugins avec des mises à jour disponibles
import jenkins.model.Jenkins
import hudson.PluginWrapper
def pm = Jenkins.instance.pluginManager
def uc = Jenkins.instance.updateCenter
// Forcer la vérification des updates
uc.updateAllSites()
def updatable = pm.plugins.findAll { plugin ->
def update = uc.getPlugin(plugin.shortName)
update != null && update.version != plugin.version
}
println "Plugins avec des mises à jour disponibles : ${updatable.size()}"
updatable.each { plugin ->
def update = uc.getPlugin(plugin.shortName)
println "${plugin.displayName.padRight(50)} ${plugin.version} → ${update.version}"
}
5. Maintain plugin security and compatibility
5.1 Plugin Recommendations
Absolute security impact of plugins
When installing a plugin, there are very few things it can’t do. It will have absolute access to your Jenkins data, except for things that are hashed like passwords.
But the dangerous data (build history, configuration, non-hashed credentials, secrets in clear text in the logs) is perfectly accessible.
Use only the official Jenkins feed
Unlike other package managers (npm, pypi, nuget) where legitimate alternative feeds sometimes exist, there is no equivalent reason for Jenkins.
✅ Official feed : https://updates.jenkins.io/
❌ Feeds non officiels : risque élevé
⚠️ Installation manuelle de fichiers .hpi : possible mais risqué
Only acceptable exception: Configure a proxy that is placed between your Jenkins and the official feed (for restricted network environments).
Navigation to change the Update Site: Manage Jenkins → Plugins → Advanced settings → Update Site URL
Least Privilege Principle
On Jenkins users:
Développeur standard → Déclencher des builds, voir les logs
Lead développeur → Créer/modifier des jobs dans son scope
Administrateur Jenkins → Gérer les plugins, la configuration système
On the plugins themselves: When a plugin exposes its own permissions configuration, deploy the least privileged principal.
On the Jenkins identity: Run Jenkins under a system user identity which has the minimum necessary privileges (not root/SYSTEM, not local admin).
Distrust of inactive plugins
If a plugin has not had a release for more than a year:
- Check if the project is still active (GitHub Issues, latest commits)
- Evaluate if there is an actively maintained alternative
- If no alternative: regularly monitor security advisories for this plugin
RBAC with the Role-Based Authorization Strategy Plugin
// Vérification programmatique de la configuration RBAC
import jenkins.model.Jenkins
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy
def strategy = Jenkins.instance.authorizationStrategy
if (strategy instanceof RoleBasedAuthorizationStrategy) {
println "RBAC actif"
// Lister les rôles configurés
def roles = strategy.doGetAllRoles("globalRoles")
roles.each { role -> println "Rôle : ${role}" }
} else {
println "ATTENTION : RBAC n'est pas activé !"
}
5.2 Demonstration: Working with Jenkins Security Advisories
Where do Security Advisories come from?
Throughout the demonstrations, warnings appeared in the Jenkins interface next to certain plugins. These are Security Advisories — warnings of vulnerabilities discovered in specific versions of plugins.
This data comes directly from update-center.json, in the warnings section (towards the end of the file).
Advisory Example: Structs Plugin
Advisory visible in the interface:
Structs Plugin: Exposure of secrets through system log
Search in update-center.json:
When searching for structs: in JSON (with colons to avoid false positives — plugins that depend on structs):
"structs": {
"name": "structs",
"version": "324.va_f5d6774f3a_d",
"title": "Structs Plugin",
"url": "https://updates.jenkins.io/download/plugins/structs/324.va_f5d6774f3a_d/structs.hpi",
"developers": [
{
"name": "Kohsuke Kawaguchi",
"developerId": "kohsuke"
}
]
}
Notable point: Kohsuke Kawaguchi is the creator of Jenkins. Structs is an infrastructure plugin very closely linked to Jenkins itself — maintained by its original creator. This is the epitome of the “third-ish party” plugin nuance.
Find advisory in JSON
The advisory is not in the plugin section itself, but in the warnings section (at the end of the JSON):
"warnings": [
{
"id": "SECURITY-XXXX",
"message": "Exposure of secrets through system log",
"name": "structs",
"type": "plugin",
"url": "https://www.jenkins.io/security/advisory/...",
"versions": [
{
"lastVersion": "324.va_f5d6774f3a_d",
"pattern": "3[0-1][0-9]\\..*|32[0-4]\\..*"
}
]
}
]
Structure of a warning:
id: Unique identifier of the advisory (SECURITY-XXXX)message: Short description of the vulnerabilityname: Key of the plugin concernedtype:pluginorcoreurl: Link to the full advisoryversions: Affected version patterns (regex) withlastVersionindicating the corrected version
Programmatic Advisory Analysis
// Script pour lister tous les plugins installés avec des advisories de sécurité
import jenkins.model.Jenkins
def pm = Jenkins.instance.pluginManager
def uc = Jenkins.instance.updateCenter
println "=== PLUGINS AVEC ADVISORIES DE SÉCURITÉ ==="
println ""
pm.plugins.each { plugin ->
def ucPlugin = uc.getPlugin(plugin.shortName)
if (ucPlugin != null && ucPlugin.hasWarnings()) {
println "⚠️ ${plugin.displayName} (${plugin.shortName})"
println " Version installée : ${plugin.version}"
println " Version disponible : ${ucPlugin.version}"
println ""
}
}
5.3 Demonstration: Script to lock a plugin version
Concept: Pin (lock) of a plugin version
The goal is to fix a plugin to a specific version and maintain that version proactively, until a conscious decision to change.
Difference with the whitelist:
- The whitelist is exhaustive and makes set comparisons
- The pin concerns a subset of plugins where the exact version matters
Why an earlier version may be acceptable: In general, running an earlier version of a plugin is compatible with later versions of Jenkins. Compatibility goes down but does not necessarily go up.
Jenkins Registry URL Convention
By examining the plugin URLs in the update-center.json, a clear pattern emerges:
https://updates.jenkins.io/download/plugins/<NOM_PLUGIN>/<VERSION>/<NOM_PLUGIN>.hpi
Concrete examples:
https://updates.jenkins.io/download/plugins/git/4.11.4/git.hpi
https://updates.jenkins.io/download/plugins/dotnet-sdk/1.3.9/dotnet-sdk.hpi
https://updates.jenkins.io/download/plugins/pipeline-stage-view/2.28/pipeline-stage-view.hpi
https://updates.jenkins.io/download/plugins/prometheus/2.0.10/prometheus.hpi
This convention is fundamental for the locking script: it allows you to construct the download URL of a specific version without having to scrape the registry.
The HPI → JPI process
When Jenkins installs a plugin:
- Download
plugin.hpi - The extract in the
plugin/folder - Rename
plugin.hpitoplugin.jpi
This renaming allows Jenkins to distinguish a .hpi file (new version to be integrated) from a .jpi file (current version already integrated). Our script must take this convention into account.
Version lock script
// Script à exécuter dans la Script Console Jenkins
// ou dans un Pipeline (avec les imports appropriés)
import jenkins.model.Jenkins
import hudson.PluginWrapper
import java.net.URL
// =========================================================
// CONFIGURATION : définir les plugins à verrouiller
// Format : [ "clé-du-plugin": "version-cible" ]
// =========================================================
def pluginsToPin = [
"dotnet-sdk": "1.3.9",
"git": "4.11.4"
]
def pm = Jenkins.instance.pluginManager
def jenkinsHome = Jenkins.instance.rootDir.absolutePath
// Convention d'URL Jenkins pour télécharger une version spécifique
def buildDownloadUrl = { String pluginKey, String version ->
"https://updates.jenkins.io/download/plugins/${pluginKey}/${version}/${pluginKey}.hpi"
}
pluginsToPin.each { pluginKey, targetVersion ->
def installedPlugin = pm.getPlugin(pluginKey)
if (installedPlugin != null && installedPlugin.version == targetVersion) {
println "✅ ${pluginKey} est déjà à la version ${targetVersion}. Rien à faire."
return
}
println "📦 Traitement de ${pluginKey}..."
// Supprimer l'ancienne version si elle existe
if (installedPlugin != null) {
println " Suppression de la version ${installedPlugin.version}..."
// Supprimer le dossier extrait
def pluginFolder = new File("${jenkinsHome}/plugins/${pluginKey}")
if (pluginFolder.exists()) {
pluginFolder.deleteDir()
}
// Supprimer le fichier .jpi courant
def jpiFile = new File("${jenkinsHome}/plugins/${pluginKey}.jpi")
if (jpiFile.exists()) {
jpiFile.delete()
}
// Supprimer le fichier .hpi s'il existe (install en cours)
def hpiFile = new File("${jenkinsHome}/plugins/${pluginKey}.hpi")
if (hpiFile.exists()) {
hpiFile.delete()
}
}
// Télécharger la version cible
def downloadUrl = buildDownloadUrl(pluginKey, targetVersion)
println " Téléchargement depuis : ${downloadUrl}"
def destFile = new File("${jenkinsHome}/plugins/${pluginKey}.hpi")
try {
def connection = new URL(downloadUrl).openConnection()
connection.setConnectTimeout(30000) // 30 secondes timeout
connection.setReadTimeout(60000) // 60 secondes read timeout
destFile.bytes = connection.inputStream.bytes
println " ✅ ${pluginKey} version ${targetVersion} téléchargé avec succès."
println " ⚠️ Redémarrage Jenkins requis pour appliquer le changement."
} catch (Exception e) {
println " ❌ Erreur lors du téléchargement de ${pluginKey} : ${e.message}"
}
}
Jenkins.instance.save()
println ""
println "Script de pin de version terminé."
println "Redémarrer Jenkins pour appliquer tous les changements."
Pipeline version of pin script
pipeline {
agent any
stages {
stage("Pin Plugin Versions") {
steps {
script {
// Lire la liste des pins depuis un fichier (versionné en Git)
// Format du fichier : une entrée par ligne "plugin-key:version"
def pinsFile = readFile('plugin-pins.txt').trim()
def pluginsToPin = [:]
pinsFile.split('\n').each { line ->
if (!line.startsWith('#') && line.contains(':')) {
def parts = line.split(':')
pluginsToPin[parts[0].trim()] = parts[1].trim()
}
}
def pm = Jenkins.instance.pluginManager
def jenkinsHome = Jenkins.instance.rootDir.absolutePath
pluginsToPin.each { pluginKey, targetVersion ->
def installed = pm.getPlugin(pluginKey)
if (installed?.version == targetVersion) {
echo "✅ ${pluginKey} : version ${targetVersion} confirmée."
return
}
echo "Mise à jour de ${pluginKey} vers la version ${targetVersion}..."
// Nettoyer l'ancienne version
['', '.jpi', '.hpi'].each { ext ->
def f = new File("${jenkinsHome}/plugins/${pluginKey}${ext}")
if (f.exists()) {
if (f.isDirectory()) f.deleteDir()
else f.delete()
}
}
// Télécharger la version cible
def url = "https://updates.jenkins.io/download/plugins/${pluginKey}/${targetVersion}/${pluginKey}.hpi"
new File("${jenkinsHome}/plugins/${pluginKey}.hpi").bytes = new URL(url).bytes
echo "✅ ${pluginKey} version ${targetVersion} installée."
}
Jenkins.instance.save()
}
}
}
}
}
File plugin-pins.txt (versioned in Git):
# Plugins avec versions verrouillées
# Format : plugin-key:version
# Modifier ce fichier et pousser pour changer les versions verrouillées
dotnet-sdk:1.3.9
git:4.11.4
# prometheus:2.0.10
5.4 Course Summary
What you learned
Module 1 — The Jenkins plugin model:
- The three categories of plugins (macro, interface, infrastructure)
- Technical architecture (Java, Maven, archetypes, interfaces/abstract classes)
- The internal structure of a
.hpi/.jpifile - Versioning formats and why they matter
- Upward and backward compatibility between plugins and Jenkins
Module 2 — The management interface:
- Full navigation of the Plugin Manager (Updates, Available, Installed, Advanced)
- How JSON registry works (
update-center.json) - Installing, updating and configuring plugins
- State management (enable/disable) via interface AND via file system
Module 3 — Automation with Groovy:
- Introduction to Groovy (dynamic/strict typing, closures, syntax)
- The Jenkins Console Script
- Startup scripts (
init.groovy.d) - The Jenkins CLI (advantages, limitations, token authentication)
- Plugin management pipelines (whitelist enforcement)
Module 4 — Monitoring and troubleshooting:
- Prometheus for Jenkins Metrics
- Measuring the impact of plugins on performance
- Parsing Jenkins logs for debugging
- Operational Best Practices
Module 5 — Security and compatibility:
- Security principles for plugins (official feed only, least privilege, RBAC)
- Reading and interpreting Security Advisories
- Programmatic analysis of
update-center.json - Plugin version lock (pin) script
Suggested next steps
- Develop a Jenkins plugin — The natural next step for deeper understanding
- Course: Automating Jenkins with Groovy — For complete mastery of automation
- Course: Docker Cloud Plugin for Jenkins — The only real way to manage your own Jenkins instances
Resources
- All scripts for this course are available in the Git repository mentioned by the instructor
- Official plugin registry: https://plugins.jenkins.io/
- Update Center JSON: accessible from Manage Jenkins → Plugins → Advanced settings
6. Appendix: Groovy Scripts Quick Reference
Retrieve the list of installed plugins
// Version minimale
Jenkins.instance.pluginManager.plugins
// Version formatée et triée
List<PluginWrapper> plugins = Jenkins.instance.pluginManager.plugins
.sort { it.displayName }
plugins.each { plugin ->
println plugin.displayName.padRight(50) + plugin.version
}
Disable a specific plugin
def pm = Jenkins.instance.pluginManager
def plugin = pm.getPlugin("ws-cleanup")
if (plugin != null && plugin.isEnabled()) {
plugin.disable()
Jenkins.instance.save()
println "Plugin désactivé."
}
Activate a specific plugin
def pm = Jenkins.instance.pluginManager
def plugin = pm.getPlugin("ws-cleanup")
if (plugin != null && !plugin.isEnabled()) {
plugin.enable()
Jenkins.instance.save()
println "Plugin activé."
}
Find plugins by keyword in their long name
def pm = Jenkins.instance.pluginManager
def keyword = "bitbucket" // Modifier selon les besoins
def matchingPlugins = pm.plugins.findAll { plugin ->
def longName = plugin.manifest.getMainAttributes().getValue("Long-Name")
longName != null && longName.toLowerCase().contains(keyword.toLowerCase())
}
matchingPlugins.each { plugin ->
println "${plugin.displayName} (${plugin.shortName}) v${plugin.version} - activé: ${plugin.isEnabled()}"
}
Disable all plugins matching a keyword
import jenkins.model.Jenkins
def keyword = "bitbucket"
def pm = Jenkins.instance.pluginManager
def toDisable = pm.plugins.findAll { plugin ->
def longName = plugin.manifest.getMainAttributes().getValue("Long-Name")
longName != null && longName.toLowerCase().contains(keyword.toLowerCase())
&& plugin.isEnabled()
}
println "Plugins à désactiver : ${toDisable.size()}"
toDisable.each { plugin ->
plugin.disable()
println "Désactivé : ${plugin.displayName}"
}
Jenkins.instance.save()
List plugins with available updates
import jenkins.model.Jenkins
def pm = Jenkins.instance.pluginManager
def uc = Jenkins.instance.updateCenter
uc.updateAllSites()
println "=== Plugins avec des mises à jour disponibles ==="
pm.plugins.sort { it.displayName }.each { plugin ->
def update = uc.getPlugin(plugin.shortName)
if (update != null && update.version != plugin.version) {
println "${plugin.displayName.padRight(50)} ${plugin.version} → ${update.version}"
}
}
List plugins with security advisories
import jenkins.model.Jenkins
def pm = Jenkins.instance.pluginManager
def uc = Jenkins.instance.updateCenter
println "=== Plugins avec advisories de sécurité ==="
pm.plugins.each { plugin ->
def ucPlugin = uc.getPlugin(plugin.shortName)
if (ucPlugin?.hasWarnings()) {
println "⚠️ ${plugin.displayName} (${plugin.shortName}) v${plugin.version}"
}
}
Check the installed version of a specific plugin
def pluginKey = "git"
def plugin = Jenkins.instance.pluginManager.getPlugin(pluginKey)
if (plugin != null) {
println "Plugin : ${plugin.displayName}"
println "Clé : ${plugin.shortName}"
println "Version: ${plugin.version}"
println "Activé : ${plugin.isEnabled()}"
} else {
println "Plugin '${pluginKey}' non installé."
}
Download and install a specific version of a plugin
import jenkins.model.Jenkins
import java.net.URL
def pluginKey = "dotnet-sdk"
def targetVersion = "1.3.9"
def jenkinsHome = Jenkins.instance.rootDir.absolutePath
// Convention URL Jenkins
def downloadUrl = "https://updates.jenkins.io/download/plugins/${pluginKey}/${targetVersion}/${pluginKey}.hpi"
// Nettoyer l'ancienne version
def pm = Jenkins.instance.pluginManager
def existing = pm.getPlugin(pluginKey)
if (existing != null) {
new File("${jenkinsHome}/plugins/${pluginKey}").deleteDir()
new File("${jenkinsHome}/plugins/${pluginKey}.jpi").delete()
}
// Télécharger
def dest = new File("${jenkinsHome}/plugins/${pluginKey}.hpi")
dest.bytes = new URL(downloadUrl).bytes
Jenkins.instance.save()
println "Plugin ${pluginKey} v${targetVersion} installé. Redémarrage requis."
7. Appendix: Docker Commands for Jenkins
Start Jenkins in Docker
# Version avec tag LTS (flottant - déconseillé en production)
docker run \
--name jenkins \
--detach \
--publish 8080:8080 \
--publish 50000:50000 \
--volume jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
# Version avec tag figé (recommandé en production)
docker run \
--name jenkins \
--detach \
--publish 2113:8080 \
--publish 50000:50000 \
--volume C:/volumes/jenkins:/var/jenkins_home \
jenkins/jenkins:2.414.3-lts
Jenkins Container Management
# Arrêter Jenkins
docker stop jenkins
# Démarrer Jenkins
docker start jenkins
# Redémarrer Jenkins
docker restart jenkins
# Voir les logs Jenkins
docker logs -f jenkins
# Ouvrir un shell dans le conteneur
docker exec -it jenkins /bin/bash
Jenkins volume backup
# Backup complet (depuis l'host)
cp -r C:/volumes/jenkins/ C:/volumes/jenkins-backup-$(date +%Y%m%d)/
# Backup via tar depuis l'intérieur du conteneur
docker exec jenkins tar -czf /tmp/jenkins-backup.tar.gz /var/jenkins_home
docker cp jenkins:/tmp/jenkins-backup.tar.gz ./jenkins-backup.tar.gz
8. Appendix: Prometheus.yaml reference file
# prometheus.yaml
# Configuration Prometheus pour surveiller Jenkins
# Attention : ce fichier est sensible aux espaces (YAML strict)
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_timeout: 10s
alerting:
alertmanagers:
- static_configs:
- targets: []
rule_files: []
scrape_configs:
# Prometheus se surveille lui-même
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Jenkins via le plugin Prometheus
- job_name: 'jenkins'
# Chemin personnalisé (défaut Prometheus : /metrics)
metrics_path: '/prometheus'
# Schéma HTTP (ou https si Jenkins est derrière TLS)
scheme: 'http'
static_configs:
# host.docker.internal = machine hôte vue depuis le conteneur Docker
# Remplacer 2113 par votre port Jenkins
- targets: ['host.docker.internal:2113']
# Optionnel : authentification basique si Jenkins est sécurisé
# basic_auth:
# username: 'prometheus-user'
# password: 'votre-token-api'
Start Prometheus in Docker
docker run \
--name prometheus \
--detach \
--publish 9090:9090 \
--volume C:/volumes/prom/prometheus.yaml:/etc/prometheus/prometheus.yaml \
prom/prometheus \
--config.file=/etc/prometheus/prometheus.yaml \
--storage.tsdb.path=/prometheus \
--web.console.libraries=/usr/share/prometheus/console_libraries \
--web.console.templates=/usr/share/prometheus/consoles
Documentation generated from the content of the “Using and Managing Jenkins” training by Chris B. Behrens.
Search Terms
managing · jenkins · ci/cd · git · devops · plugin · plugins · demonstration · script · groovy · prometheus · version · security · management · pipeline · cli · console · install · installed · docker · list · specific · access · advisories