Intermediate

Using and Managing Jenkins

Navigate to Manage Jenkins → Plugins → Available plugins → Search for Prometheus → Install.

**Scenario: Globomantics, a fictional organization of which you are the Jenkins administrator.


Table of Contents

  1. Understand the Jenkins plugins model
  1. Working with the Jenkins plugin management interface
  1. Automate Jenkins plugins
  1. Monitor and troubleshoot plugins
  1. Maintain plugin security and compatibility

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:

CategoryDescriptionExamples
Macro pluginsTake a simple user command and expand it into a series of console commandsBuild Automation Plugins (Maven, Gradle, MSBuild)
Plugin interfaceEdit Jenkins UIDashboard View, Blue Ocean
Plugin infrastructureProvide basic capabilities used by other pluginsCredentials 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:

  1. Downloading the .hpi file to the plugins/ folder
  2. Extract to a folder with the same name as the plugin key
  3. 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:

FormatExampleDescription
Semantic Versioning (SemVer)4.11.4, 2.3.0, 1.4MAJOR.MINOR.PATCH — Sometimes only two components (implicitly patch .0)
Build number + hash3.0.2-120.v5a_9591aa_dff3Artifact build number + original commit hash
SemVer + hash4.11.4.1-preview.13c1f8aHybrid 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

ComponentIncrementMeaning
MAJOR (3.0.0)Public interface removed or modified in an incompatible mannerExisting client code will break
MINOR (2.3.0)New features added in a backwards compatible wayExisting client code still working
PATCH (2.2.1)Bug fixes without API changesExisting client code still working

Practical rule: When we increment the MAJOR, we reset MINOR and PATCH to zero. For example: 2.11.23.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

  1. 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)
  2. This dependency existed for a while, but no one had updated it because the old one worked perfectly
  3. The Jenkins installation process automatically updated this dependency to the new version
  4. In the new version, a public interface had been removed
  5. The developer had only incremented the MINOR (not the MAJOR), incorrectly reporting that it was backwards compatible
  6. Jenkins considered auto-update safe (minor increment)
  7. 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:

TabDescription
UpdatesInstalled plugins for which a new version is available
Available pluginsPlugins from the official registry that are not yet installed
Installed pluginsPlugins currently installed on this instance
Advanced settingsAdvanced 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 is 42crunch-security-audit
  • warnings: 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:

  1. Jenkins uploads the .hpi file to plugins/
  2. Extract it in a folder with the same name as the plugin key
  3. Rename the file with the extension .jpi
  4. 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:

  1. Check the installed version corresponds to the latest available version
  2. Consult the releases page of the plugin (link from the plugin home page)
  3. 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:

  1. Installed or not installed — An uninstalled plugin has no impact on configuration, security or builds
  2. 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:

  1. The simplified CLI approach — Good for simple cases, documented in many articles
  2. 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:

  1. Sort plugins by display name
  2. Align columns with padRight(50) (right padding up to 50 characters)
  3. 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 plugin
  • Plugin-Version: Plugin version
  • Jenkins-Version: Minimum Jenkins version required
  • Plugin-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:

  1. Security: The access level of a pipeline is too high for the controller
  2. 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

MethodWhen runsAdvantagesDisadvantages
CLIAnytimeEasy integration with external toolsRequires token auth, limited in functionality
init.groovy.dWhen starting JenkinsAutomatic, without interventionOnly on startup (instances rarely restarted)
PipelineScheduleable (cron), manually triggeredFlexible, versionable in Git, scheduleableSecurity 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

  1. This pipeline must be executed on agent any (the controller), not on a specific agent
  2. Accessing Jenkins.instance from a pipeline requires script approval in Manage Jenkins → In-process Script Approval
  3. 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 /prometheus endpoint)
  • 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

ProblemWhere to look
Plugin not loadingSystem Log → startup
Plugin behaves badly in buildBuild Log (Console Build Output)
Plugin configuration problemSystem Log → after modification
Performance issuePrometheus + 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:

  1. Identify warnings during regular reviews (weekly/monthly)
  2. Consult the advisory (URL provided in the warning)
  3. Assess the risk (vulnerability type, exploitability)
  4. 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 vulnerability
  • name: Key of the plugin concerned
  • type: plugin or core
  • url: Link to the full advisory
  • versions: Affected version patterns (regex) with lastVersion indicating 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:

  1. Download plugin.hpi
  2. The extract in the plugin/ folder
  3. Rename plugin.hpi to plugin.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/.jpi file
  • 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

  1. Develop a Jenkins plugin — The natural next step for deeper understanding
  2. Course: Automating Jenkins with Groovy — For complete mastery of automation
  3. 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

Interested in this course?

Contact us to book it or get a custom training plan for your team.