Building and Using a Jenkins Shared Library

Abbreviations

Abbreviation Full name Meaning here
CI / CD Continuous Integration / Continuous Delivery The build-test-deploy automation a shared library standardises
DSL Domain-Specific Language The small, readable syntax a library exposes to Jenkinsfiles (buildApp { ... })
CPS Continuation-Passing Style How Jenkins rewrites Pipeline Groovy so a build can pause, survive a restart and resume
SCM Source Code Management The Git repository Jenkins loads the library from
IDE Integrated Development Environment Editor support such as completion inside DSL closures
API Application Programming Interface The public steps a library promises not to break
JVM Java Virtual Machine The single process in which all builds on a controller share classes

1. The short answer

A Jenkins Shared Library is a Git repository of Pipeline Groovy code that many Jenkinsfiles load and call. Instead of copy-pasting the same 80 lines of "log in to the registry, install, test, build, push" into every repository, each Jenkinsfile shrinks to a few readable calls, and the logic lives --- versioned, reviewed and tested --- in one place.

A well-structured library has three layers:

Layer Location Job
DSL entry point vars/<step>.groovy Becomes a global step (buildApp). Thin: wire up config, delegate.
Configuration object src/.../<Step>Config.groovy Holds the user's settings, separate from behaviour.
Implementation class src/.../<Step>.groovy Does the work by calling real Jenkins steps (sh, withCredentials, ...).

The rest of this article builds one such step, buildApp, end to end.

2. Repository layout

text 复制代码
jenkins-shared-library/
├── vars/                       # global steps: file name = step name
│   ├── buildApp.groovy
│   └── buildApp.txt            # help text shown in the Pipeline Syntax page
├── src/                        # ordinary Groovy classes on the classpath
│   └── com/example/jenkins/
│       └── build/
│           ├── BuildApp.groovy
│           └── BuildAppConfig.groovy
├── resources/                  # non-Groovy files, read with libraryResource
│   └── com/example/jenkins/build/settings.xml
├── test/                       # unit tests (JenkinsPipelineUnit)
└── README.md
  • vars/ --- each foo.groovy becomes a global variable foo available in every Pipeline that loads the library. A call method makes foo(...) / foo { ... } work; other methods are reached as foo.bar(...).
  • src/ --- standard Java-style package directories, added to the classpath. Import them with import com.example....
  • resources/ --- loaded as text with libraryResource 'com/example/jenkins/build/settings.xml', handy for config templates and scripts.

3. How the pieces fit

#mermaid-svg-uw1bJjAi4Pjhfzju{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uw1bJjAi4Pjhfzju .error-icon{fill:#552222;}#mermaid-svg-uw1bJjAi4Pjhfzju .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uw1bJjAi4Pjhfzju .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .marker.cross{stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uw1bJjAi4Pjhfzju p{margin:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label text{fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label span{color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label span p{background-color:transparent;}#mermaid-svg-uw1bJjAi4Pjhfzju .label text,#mermaid-svg-uw1bJjAi4Pjhfzju span{fill:#333;color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .node rect,#mermaid-svg-uw1bJjAi4Pjhfzju .node circle,#mermaid-svg-uw1bJjAi4Pjhfzju .node ellipse,#mermaid-svg-uw1bJjAi4Pjhfzju .node polygon,#mermaid-svg-uw1bJjAi4Pjhfzju .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .rough-node .label text,#mermaid-svg-uw1bJjAi4Pjhfzju .node .label text,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label,#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label{text-anchor:middle;}#mermaid-svg-uw1bJjAi4Pjhfzju .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .rough-node .label,#mermaid-svg-uw1bJjAi4Pjhfzju .node .label,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label,#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label{text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .node.clickable{cursor:pointer;}#mermaid-svg-uw1bJjAi4Pjhfzju .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .arrowheadPath{fill:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uw1bJjAi4Pjhfzju .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster text{fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster span{color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uw1bJjAi4Pjhfzju .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju rect.text{fill:none;stroke-width:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape p,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label rect,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uw1bJjAi4Pjhfzju .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uw1bJjAi4Pjhfzju :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Jenkinsfile

@Library('jenkins-shared-library') _
vars/buildApp.groovy

(DSL entry point)
BuildAppConfig

(settings)
BuildApp

(implementation)
Pipeline steps

withCredentials, withEnv, sh
Build / test / deploy

on the agent

This diagram answers: when a Jenkinsfile calls buildApp, which file does what?

4. The configuration object

Settings live in a plain class, kept apart from the code that acts on them:

groovy 复制代码
// src/com/example/jenkins/build/BuildAppConfig.groovy
package com.example.jenkins.build

class BuildAppConfig implements Serializable {
    String credentialsId
    String registryUrl

    void validate(def script) {
        if (!credentialsId) script.error('buildApp: credentialsId is required')
        if (!registryUrl)   script.error('buildApp: registryUrl is required')
    }
}

Why Serializable? Pipeline Groovy runs under CPS (Continuation-Passing Style): at every step boundary Jenkins may serialise the whole program state to disk so the build can survive a controller restart. Any object still referenced at that point --- including this config --- must be serialisable, or the build fails with NotSerializableException.

5. The implementation class

The implementation receives the Pipeline script object so it can call Jenkins steps on it. Classes in src/ do not see sh or withCredentials on their own --- those are methods of the running Pipeline script.

groovy 复制代码
// src/com/example/jenkins/build/BuildApp.groovy
package com.example.jenkins.build

class BuildApp implements Serializable {
    private final def script
    private final BuildAppConfig config

    BuildApp(def script, BuildAppConfig config) {
        this.script = script
        this.config = config
    }

    void execute(Closure body) {
        config.validate(script)

        script.withCredentials([
            script.usernamePassword(
                credentialsId: config.credentialsId,
                usernameVariable: 'REGISTRY_USER',
                passwordVariable: 'REGISTRY_PASSWORD'
            )
        ]) {
            script.withEnv(["PACKAGE_REGISTRY=${config.registryUrl}"]) {
                body()
            }
        }
    }
}

The caller's closure runs inside the credentials and environment blocks, so its sh commands see REGISTRY_USER, REGISTRY_PASSWORD and PACKAGE_REGISTRY. withCredentials also masks the secret values in the build log.

6. The DSL entry point in vars/

The vars/ file is the public face. Keep it thin: find the configuration, build the implementation, delegate.

groovy 复制代码
// vars/buildApp.groovy
import com.example.jenkins.build.BuildApp
import com.example.jenkins.build.BuildAppConfig

// buildApp.configure { credentialsId = '...'; registryUrl = '...' }
def configure(@DelegatesTo(BuildAppConfig) Closure closure) {
    closure.delegate = config()
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure()
}

// buildApp { sh '...' }
def call(Closure body) {
    new BuildApp(this, config()).execute(body)
}

private BuildAppConfig config() {
    if (!binding.hasVariable('__buildAppConfig')) {
        binding.setVariable('__buildAppConfig', new BuildAppConfig())
    }
    binding.getVariable('__buildAppConfig')
}

Three details carry the design:

  1. Closure.DELEGATE_FIRST --- inside configure { credentialsId = 'x' }, the bare name credentialsId is resolved against the delegate (the config object) before anything else. That is what turns a closure into a configuration block.
  2. @DelegatesTo(BuildAppConfig) --- no runtime effect; it tells the IDE (Integrated Development Environment) what the delegate is, so completion and type checking work inside the block.
  3. Passing this --- in a vars/ script, this is a Pipeline script object, so BuildApp can call script.sh, script.withCredentials and script.error through it.

Why the configuration is kept in the binding

configure and call are two separate calls, so the settings must survive from one to the other. A vars/ script is instantiated once per build (a per-build singleton), and its binding lives as long as that build. Storing the config there means:

  • buildApp.configure { ... } followed later by buildApp { ... } in the same build shares the same settings;
  • two builds running at the same time never see each other's settings.

Do not use static fields for this. Static state lives in the library's class loader, outside any single build, and concurrent builds would overwrite each other's credentials ID or registry URL.

Help text

vars/buildApp.txt (plain text or HTML) appears under Pipeline Syntax → Global Variables Reference once the library has been loaded by a build. Treat it as the user documentation of the step.

7. Making the library available

Register the library once in Manage Jenkins → System → Global Trusted Pipeline Libraries (or on a folder, for a team-scoped library):

Setting Recommendation
Name jenkins-shared-library --- the name Jenkinsfiles refer to
Default version A tag or a stable branch (v1.4.0, main)
Allow default version to be overridden On, so a Jenkinsfile can pin or test a version
Load implicitly Usually off --- explicit @Library makes the dependency visible
Retrieval method Modern SCM → Git, pointing at the library repository

Trust levels matter. A global library runs outside the Groovy sandbox and can call any Jenkins API; a folder-level library runs inside the sandbox. Protect the global library's repository like production code: required reviews, protected branches, tagged releases.

8. Loading it from a Jenkinsfile

groovy 复制代码
@Library('jenkins-shared-library') _            // default version
@Library('jenkins-shared-library@v1.4.0') _     // pinned to a tag
@Library('jenkins-shared-library@feature/x') _  // try a branch before release

The trailing _ is not a typo: a Groovy annotation must decorate something , and _ is a throwaway target when you only need the global steps from vars/. If you also want to import classes from src/, put the annotation on the import instead:

groovy 复制代码
@Library('jenkins-shared-library') import com.example.jenkins.build.BuildAppConfig

The library step loads a library dynamically at run time, for example when the version comes from a parameter:

groovy 复制代码
library "jenkins-shared-library@${params.LIB_VERSION}"

9. A complete Jenkinsfile

groovy 复制代码
@Library('jenkins-shared-library@v1.4.0') _

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                script {
                    buildApp.configure {
                        credentialsId = 'registry-credentials'
                        registryUrl   = 'https://packages.example.com/'
                    }
                }

                buildApp {
                    sh '''
                        set -eu
                        package-manager install
                        package-manager test
                        package-manager build
                    '''
                }
            }
        }
    }
}

Why is configure in a script {} block? Declarative Pipeline allows steps in steps {} --- and a global variable's call method counts as a step, so buildApp { ... } is fine. But buildApp.configure { ... } is a method call on an object, which Declarative rejects outside script {}. A scripted Pipeline (node { ... }) has no such restriction.

10. What happens at run time

Jenkins steps BuildApp BuildAppConfig vars/buildApp Jenkinsfile Jenkins steps BuildApp BuildAppConfig vars/buildApp Jenkinsfile #mermaid-svg-vsFxxDBN7KeTTA8s{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vsFxxDBN7KeTTA8s .error-icon{fill:#552222;}#mermaid-svg-vsFxxDBN7KeTTA8s .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vsFxxDBN7KeTTA8s .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vsFxxDBN7KeTTA8s .marker.cross{stroke:#333333;}#mermaid-svg-vsFxxDBN7KeTTA8s svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vsFxxDBN7KeTTA8s p{margin:0;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vsFxxDBN7KeTTA8s .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .sequenceNumber{fill:white;}#mermaid-svg-vsFxxDBN7KeTTA8s #sequencenumber{fill:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageText{fill:#333;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s .labelText,#mermaid-svg-vsFxxDBN7KeTTA8s .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .loopText,#mermaid-svg-vsFxxDBN7KeTTA8s .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vsFxxDBN7KeTTA8s .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vsFxxDBN7KeTTA8s .noteText,#mermaid-svg-vsFxxDBN7KeTTA8s .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .actorPopupMenu{position:absolute;}#mermaid-svg-vsFxxDBN7KeTTA8s .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-man circle,#mermaid-svg-vsFxxDBN7KeTTA8s line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vsFxxDBN7KeTTA8s :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} buildApp.configure { ... } create, store in binding run closure with config as delegate buildApp { sh ... } fetch from binding new BuildApp(this, config) validate() withCredentials + withEnv run caller's closure (sh ...) closure finished step returns

This diagram answers: in what order do the two calls touch the config, the implementation and Jenkins?

11. CPS pitfalls worth knowing

Most "it works in plain Groovy but not in Jenkins" bugs come from CPS:

Symptom Cause Fix
NotSerializableException A non-serialisable object (a Matcher, an iterator, a JSON parser result) is alive across a step boundary Make your classes Serializable; keep such objects in local scope inside a @NonCPS method
A collection method silently processes only the first element Some Groovy closures (each, collect, sort with a closure) behave oddly under CPS in older versions Prefer a plain for loop, or move the pure-data logic into a @NonCPS method
@NonCPS method fails when calling sh @NonCPS methods run as normal Groovy and cannot call Pipeline steps Use @NonCPS only for pure computation; call steps from normal methods
RejectedAccessException Code in a sandboxed (folder-level) library or Jenkinsfile calls an unapproved method Move the code into a trusted global library, or get the signature approved

12. Testing

Do not test a shared library only by pushing a branch and watching a real build. Unit-test it with JenkinsPipelineUnit (com.lesfurets:jenkins-pipeline-unit), which runs the scripts with mocked steps:

groovy 复制代码
// test/BuildAppTest.groovy
import com.lesfurets.jenkins.unit.BasePipelineTest
import org.junit.Before
import org.junit.Test

class BuildAppTest extends BasePipelineTest {
    @Before
    void setUp() {
        super.setUp()
        helper.registerAllowedMethod('usernamePassword', [Map]) { it }
        helper.registerAllowedMethod('withCredentials', [List, Closure]) { l, c -> c() }
        helper.registerAllowedMethod('withEnv', [List, Closure]) { l, c -> c() }
    }

    @Test
    void runsCallerClosureInsideCredentials() {
        def step = loadScript('vars/buildApp.groovy')
        step.configure {
            credentialsId = 'creds'
            registryUrl = 'https://packages.example.com/'
        }
        step.call { sh 'make build' }

        assert helper.callStack.find { it.methodName == 'sh' }
        assert helper.callStack.find { it.methodName == 'withCredentials' }
    }
}

Cover at least:

  • configuration defaults, validation and the error message for missing settings;
  • that the caller's closure runs inside withCredentials and withEnv;
  • the environment variables passed to the closure;
  • failure paths (error is called, nothing runs);
  • that no secret value is passed to echo or written to a file.

Then add one integration check: a throwaway Jenkinsfile that loads the library at @your-branch on a real controller, run before tagging a release.

13. Design guidelines

  • Keep vars/ thin; put logic in src/. Classes are easier to test, reuse and refactor.
  • Separate settings from behaviour. Config objects hold data; implementation classes act.
  • Validate early. Fail with a clear error('buildApp: registryUrl is required'), not a cryptic sh failure ten minutes later.
  • Never hard-code secrets or environment-specific URLs. Take credential IDs as parameters and resolve them with withCredentials.
  • No mutable static state. Builds run concurrently in one JVM.
  • Treat public steps as an API. Jenkinsfiles across the company depend on buildApp's syntax. Do not break it in a minor release; use semantic version tags and a changelog.
  • Pin versions in important pipelines. @Library('lib@v1.4.0') makes a library change an explicit, reviewable bump instead of a surprise.
  • Document every step in its vars/<step>.txt and the README.

14. Conclusion

A Jenkins Shared Library turns copy-pasted Pipeline code into a versioned,workspace/uploads/swagger-ui-integration-spring-boot tested internal product:

  • the vars/ entry point gives Jenkinsfiles a small, readable DSL;
  • the configuration object holds settings for one build, safely in the binding;
  • the implementation class does the work through real Pipeline steps, inside credential and environment scopes.

Add CPS awareness, unit tests with JenkinsPipelineUnit and disciplined versioning, and every repository gets the same reliable build behaviour from a few lines of Jenkinsfile --- with one place to fix it when it needs to change.

References

相关推荐
RuoyiOffice1 天前
KubeSphere 容器云实践:从管理 K8s 到建设企业 PaaS,还需要哪些能力
spring boot·kubernetes·kubesphere·jenkins·paas·微服务部署·ruoyi office
@兽血沸腾4 天前
Windows系统安装Jenkins部署应用至远程Windows服务器操作指南
运维·服务器·jenkins
拖累斯5 天前
Jenkins + Python 全自动跑通「禅道任务/BUG 导出 → 月度开发考核表」
python·jenkins·bug
逐流人5 天前
DevOps 入门实战(一):从 CI/CD 概念到 Jenkins 自动构建部署
运维·云原生·云计算·jenkins·devops
修心光8 天前
记录一次,jenkins打包异常问题
运维·jenkins
后台模板学习8 天前
GitHub Actions 与 Jenkins 在日志监
运维·github·jenkins
X1A0RAN9 天前
Jenkins CI 流水线优化实践:基于提交指纹的任务缓存
缓存·ci/cd·jenkins
北风toto10 天前
Elasticsearch 8.2.3 快照迁移实战:从备份到恢复的完整指南
大数据·elasticsearch·jenkins
biaov15 天前
Jenkins + Docker 搭建前端 CI:前端一键打包部署
前端·docker·jenkins
kaixin_learn_qt_ing18 天前
Jenkins
运维·jenkins