Chapter 24Lesson 02~200 minutes

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.

Hands-onModern SCMPinned refsTestingCompatibilityEvidence

Learning objectives

  • Create a disposable local Git Shared Library with stable repository and consumer identities.
  • Use vars, src and libraryResource coherently.
  • 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.

Security: do not convert this lab into a trusted global library merely to avoid sandbox restrictions. The example needs ordinary Pipeline steps only.

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.

Next lesson

Configuration, Design Choices, and Tradeoffs

Compare trust scope, implicit loading, pinning strategies, framework size and release/migration models.

Knowledge check

Answer before revealing the explanation.

1. Why does the lab use an explicit version such as @Library("ch24-lib@v1.0.0")?

2. Why put Pipeline steps behind a vars entry point instead of directly inside a src class?

3. What is the purpose of the deliberately incompatible v2 change?

4. Why is the resource file part of the version contract?

5. What should a test failure prevent?

Official references and version notes

  • Jenkins — Extending with Shared Libraries — trusted/untrusted libraries, vars/src/resources, version selection, Modern SCM, @Library, library and 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.