Jenkins shared library: Why a shared library - Part 1

Jenkins shared library: Why a shared library - Part 1

Building Jenkins Shared Library

~5 min read

Most of my projects have a Jenkinsfile, and over time they've started to look alike. The finance system, the inventory system and the identity system each carry their own copy of a Sync blog stage - the one that publishes the project's blog/*.md posts to Ghost. The three copies are the same block of Groovy: a when { branch 'main' } guard, a catchError that marks the stage UNSTABLE instead of failing the build, two Ghost credentials, and a small shell function that runs the blog-sync tool at a pinned release tag, first to sync content and then to schedule it.

Copying a stage into a new project is quick. The cost shows up later. When I bump the pinned blog-sync version, that's one edit per repository, and it's easy to miss one. When I fix a mistake in the shell snippet, the same applies. The copies drift, and nothing tells me they have.

A Jenkins shared library is the standard answer: a separate Git repository that Jenkins loads into every pipeline that asks for it, so shared logic lives in one place and the Jenkinsfiles shrink to a call.

The three folders

A library has a fixed layout, and each folder has one job:

| Folder | What goes there | How it's used | |---|---|---| | vars/ | Steps and whole-pipeline templates | Called directly: sayHello() | | src/ | Groovy classes and helpers | import them | | resources/ | Templates and scripts bundled with the library | libraryResource('...') |

The file name in vars/ is the step name, so vars/syncBlog.groovy becomes syncBlog(...) in a Jenkinsfile. Classes in src/ follow normal Groovy package rules: the folder path must match the package line. Files in resources/ are read as strings.

Starting from a scaffold

I didn't start with the blog stage. The first commit after the docs was a small scaffold with one working example of each folder, so that I'd be extending something I'd seen run rather than something I'd only read about:

  • sayHello, the simplest possible step.
  • standardPipeline, a whole declarative pipeline (checkout, build, test) configured by a map, so a team's Jenkinsfile can be three lines.
  • notifyBuild, a step that combines a class from src/ with a template from resources/.

Two things in the docs I wrote alongside it are worth repeating here, because they're the ones that bite later:

  • Release discipline. A project that loads the library from main gets every change immediately, which is exactly what you don't want for a change to something every pipeline uses. The answer is to tag releases and let important pipelines pin a version: @Library('bb-jenkins-shared-lib@v1.0.0') _.
  • Classes in src/ must be Serializable. Pipelines pause and resume by serialising their state, and an object that can't be serialised across a step fails with NotSerializableException.

What's next

The scaffold works, but its examples are placeholders. The next post replaces one with the real thing: pulling the Sync blog stage out of the Jenkinsfiles and into the library.


Next: Extracting the Sync blog stage → · Back to series index