How do you structure Jenkins Shared Libraries using the vars/, src/, and resources/ directories, and what are the tradeoffs of dynamic loading versus version pinning for library consumption?
Quick Answer
Jenkins Shared Libraries use vars/ for pipeline-callable global variables and steps, src/ for Groovy classes with full OOP support, and resources/ for non-Groovy files like templates. Libraries can be loaded dynamically with @Library or pinned to specific versions in Jenkinsfile or global configuration. Version pinning ensures reproducibility but slows adoption of fixes, while dynamic loading provides latest features but risks breaking pipelines on library changes.
Detailed Answer
Think of a company-wide recipe book used by all restaurant kitchens. The vars/ section contains quick recipe cards that any chef can call by name — standardized steps like 'deploy to staging' or 'run security scan.' The src/ section contains the detailed culinary science reference — complex Groovy classes that handle error handling, API clients, and business logic. The resources/ section holds templates, configuration files, and reference materials that recipes can embed. Every kitchen (pipeline) loads the recipe book, and the question of whether to pin to a specific edition or always use the latest is the difference between stability and agility.
In Jenkins Shared Libraries, the vars/ directory contains Groovy scripts that define custom pipeline steps. Each file like vars/deployService.groovy exposes a call() method that pipelines invoke as deployService(). These global variables run in the pipeline's script context with access to steps like sh, echo, and stage. The src/ directory follows Groovy's standard class layout (src/com/company/pipeline/DockerBuilder.groovy) and provides classes that are compiled and added to the pipeline's classpath. These classes support constructors, inheritance, and complex logic but cannot directly call pipeline steps without receiving the script context as a parameter. The resources/ directory holds non-code files accessible via libraryResource() — Dockerfile templates, Helm values templates, or JSON schemas.
Internally, Jenkins loads shared libraries during pipeline initialization. The @Library('[email protected]') annotation in a Jenkinsfile tells Jenkins to check out the library from its configured SCM source at the specified version tag, compile src/ classes, and make vars/ scripts available as pipeline steps. Libraries can be configured globally (available to all pipelines), at the folder level (available to pipelines in that folder), or loaded dynamically with library() step inside the pipeline. Global libraries configured as 'implicit' are loaded automatically without any Jenkinsfile annotation, which is useful for enforcing organizational standards but can break pipelines if the library changes.
At production scale with hundreds of pipelines consuming a shared library, version management becomes critical. Pinning to a Git tag (@2.5.0) guarantees reproducibility — a pipeline that worked last week will work the same way today. But when a security fix or new feature is added to the library, every consuming pipeline must update its version reference. Dynamic loading from a branch (@main) propagates fixes immediately but means a broken commit to the library can cascade across all pipelines simultaneously. The recommended pattern is to pin to tagged releases, use a staging branch for testing library changes across a representative set of pipelines, and automate version bumps through pull requests when a new library version is released.
The non-obvious gotcha is that vars/ scripts and src/ classes run in different CPS (Continuation Passing Style) contexts. Jenkins serializes pipeline state at each step boundary for durability, which means vars/ scripts are CPS-transformed and must avoid non-serializable objects or mark methods with @NonCPS. The src/ classes are not automatically CPS-transformed and can use standard Java libraries freely, but if a src/ class method calls pipeline steps, it must do so through the script context object, and those calls are subject to CPS restrictions. Mixing pipeline-step calls with complex Groovy logic in the same method frequently causes CPS serialization errors that are difficult to diagnose.