Shared Libraries, Global Variables, vars, src, resources, Versioning, Testing, and Pipeline Product Design: Guided Hands-On Workflow and Core Operations
Create a tiny local Shared Library repository, publish two pinned versions, call a vars global plus src helper and resource, exercise a lightweight test path, deliberately break a consumer, then restore compatibility without granting the library unnecessary trust.
Learning objectives
- Create a disposable local Git Shared Library with stable repository and consumer identities.
-
Use
vars,srcandlibraryResourcecoherently. - Load a pinned library version through Modern SCM.
- Test pure helper behavior before Jenkins integration.
- Reproduce and repair a breaking change while preserving failure evidence.
1. Disposable lab topology
Create two local Git repositories: /tmp/ch24-lib and
/tmp/ch24-consumer. Jenkins uses a disposable job
ch24-shared-library-consumer on the normal
low-privilege lab agent. No external credentials or cloud services
are required. The built-in node remains at zero executors.
Baseline: Jenkins 2.568.3 LTS, Java 21, Pipeline:
Groovy Libraries 805.va_fc79344957d, Pipeline: Groovy
4380.v6eb_8378b_9647, Script Security
1422.v06869826dd9b_, Git 5.10.1, Git
Client 6.6.1.
2. Create library v1
Initialize the library repository and add these files. The helper
returns plain serializable data; the vars entry point
owns Pipeline-step calls.
// src/academy/pipeline/Metadata.groovy
package academy.pipeline
class Metadata implements Serializable {
static Map normalize(Map input) {
[service: input.service.toString().trim(),
version: input.version.toString().trim()]
}
}
// vars/buildBanner.groovy
import academy.pipeline.Metadata
def call(Map cfg = [:]) {
def m = Metadata.normalize(cfg)
def defaults = readJSON text: libraryResource('academy/pipeline/defaults.json')
echo "building ${m.service} ${m.version} / policy=${defaults.policy}"
writeFile file: 'evidence/library-api.txt',
text: "api=buildBanner-v1\nservice=${m.service}\nversion=${m.version}\n"
}
{"policy":"lab-only","schema":1}
Place that JSON at
resources/academy/pipeline/defaults.json. If
readJSON is not available in your controller baseline,
parse the tiny resource with a pure helper or use plain text; do not
install an unrelated plugin merely for this lab.
3. Test pure logic before Jenkins
The minimum mandatory test is a plain Groovy/JVM unit test for
Metadata.normalize using the library’s build tool. The
exact framework is less important than preserving a fast test for
the public contract. JenkinsPipelineUnit can be added later as an
optional framework when its current Java/Groovy compatibility is
verified.
assert academy.pipeline.Metadata.normalize(
[service:' api ', version:' 1.2.3 ']
) == [service:'api', version:'1.2.3']
Commit the tested state and create an annotated/protected lab tag
v1.0.0. Record both the tag and
git rev-parse v1.0.0^{commit}.
4. Configure the disposable library
In the disposable lab controller, add a Shared Library named
ch24-lib using Modern SCM → Git,
pointing to the local/bare lab repository. Keep it
untrusted where the UI/context permits and do not
load it implicitly. Set default version v1.0.0 only for
convenience; the consumer will request the version explicitly.
5. Consumer pinned to v1
@Library('ch24-lib@v1.0.0') _
pipeline {
agent { label 'linux-lowtrust' }
options { timestamps() }
stages {
stage('Identity') {
steps {
sh 'mkdir -p evidence && git rev-parse HEAD > evidence/consumer-sha.txt'
}
}
stage('Library API') {
steps {
buildBanner service: 'catalog', version: '1.0.0'
}
}
}
post {
always { archiveArtifacts artifacts: 'evidence/**', allowEmptyArchive: true }
}
}
Also capture the library tag/commit from the library repository or Jenkins checkout evidence. The build is attributable only when both identities are preserved.
6. Introduce an intentional incompatible v2
On a new library branch, change the public entry point to require
component instead of service and fail
clearly when the old parameter is used. Tag it
v2.0.0-broken-lab. Do not overwrite
v1.0.0.
def call(Map cfg = [:]) {
if (!cfg.component) {
error 'buildBanner v2 requires component; service is no longer accepted'
}
// ... normal v2 behavior
}
Run the unchanged consumer against v2. Preserve the build number, stack/error text, consumer SHA, requested version and resolved library commit before correcting anything. This is a contract failure, not an agent retry problem.
7. Publish compatibility deliberately
Create v2.0.1 that accepts both keys for a bounded
migration period and emits a deprecation message without exposing
secrets or changing external systems.
def call(Map cfg = [:]) {
def component = cfg.component ?: cfg.service
if (!component) { error 'component is required' }
if (cfg.service && !cfg.component) {
echo 'DEPRECATION: use component=; service= will be removed in v3'
}
// continue with stable behavior
}
Test v1-style and v2-style consumers, tag the repaired release and document the removal target. Compatibility is now explicit rather than accidental.
8. Challenge: choose the correct layer
A consumer using @Library('ch24-lib@v1.0.0') fails with
“library version not found.” Decide whether to inspect: agent
workspace, library SCM/retriever, consumer build parameters or
external deployment. Correct answer: start with the
library retrieval/SCM identity and prove whether
the configured retriever can resolve that exact tag/ref.
Knowledge check
Answer before revealing the explanation.
1. Why does the lab use an explicit version such as @Library("ch24-lib@v1.0.0")?
It makes the library selection visible in the consumer and lets build evidence map to a stable tag/commit instead of silently following a mutable default.
2. Why put Pipeline steps behind a vars entry point instead of directly inside a src class?
src classes are ordinary Groovy/CPS-transformed library code and should stay easy to test. Passing the script/steps context or keeping Pipeline-step orchestration in vars makes dependencies explicit and reduces hidden coupling.
3. What is the purpose of the deliberately incompatible v2 change?
It proves that a library release can break consumers even when Jenkins and the application repository are unchanged, and forces the learner to diagnose version/API mismatch from evidence rather than retrying blindly.
4. Why is the resource file part of the version contract?
Templates/configuration loaded with libraryResource influence behavior just like Groovy code. The consumer must know which library revision supplied them.
5. What should a test failure prevent?
Publishing or promoting a library version whose public contract is already known to violate supported consumer expectations.
Official references and version notes
-
Jenkins — Extending with Shared Libraries
— trusted/untrusted libraries,
vars/src/resources, version selection, Modern SCM,@Library,libraryand testing guidance. -
Pipeline: Groovy Libraries
— version
805.va_fc79344957d, released 2026-08-27, requiring Jenkins 2.555.1. -
Pipeline: Groovy
— version
4380.v6eb_8378b_9647; Shared Library Groovy is CPS-transformed under the same Pipeline execution model. -
Script Security
— version
1422.v06869826dd9b_; sandbox and approval boundaries remain security-sensitive. -
Git
— version
5.10.1; used by the Modern SCM lab retriever. -
Git Client
— version
6.6.1. - Pipeline Best Practices — avoid overriding built-in Pipeline steps and avoid oversized global-variable files.
- JenkinsPipelineUnit — optional third-party/open-source test framework for Pipeline/library tests; verify the current release and Groovy compatibility before adopting it. The mandatory lab does not require it.
- Pipeline: Deprecated Groovy Libraries — legacy/deprecated plugin; do not use it as the design target for new Shared Libraries.
Version note — 2026-09-17: chapter baseline is
Jenkins 2.568.3 LTS with Java 21. The current Pipeline:
Groovy Libraries release listed above includes security fixes beyond
earlier versions affected by sandbox/file-read/CSRF advisories.
Re-check plugin security advisories and the exact SCM/test-tool
versions before production rollout.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.