Jenkins Shared Library 企业复用

验证:未记录
适用版本
未记录
验证范围
尚未登记验证记录。

Shared Library(共享库)把多个 Jenkinsfile 共用的 Pipeline 步骤放进独立 Git 仓库,由使用方按受控版本加载。适合复用测试、报告与制品处理;业务项目仍负责自己的测试脚本和依赖锁文件,生产权限由受控发布环境提供。

调用路径:业务 Jenkinsfile → vars/verifyProject.groovy 的 call → src/ 类、其他 vars/ 全局变量和 Pipeline 步骤。业务 Jenkinsfile 用 @Library('platform-ci') _ 加载库,再调用 verifyProject('go')。

阅读顺序:第 1~5 节完成建库、加载和版本发布;第 6~9 节解释类、CPS、Declarative 与安全边界;最后用第 10~11 节验证和排障。

先记住五条规则#

  • vars/ 暴露可直接调用的全局步骤;src/ 提供需要按包名导入或动态访问的类。
  • @Library 在编译前加载;运行时 library(...) 即使写在文件顶部,也不能支持后续静态 import。
  • Groovy 构造器不经过 CPS 转换,Pipeline 步骤要在对象创建后的普通方法中执行。
  • 跨暂停点保留的状态必须可序列化;@NonCPS 方法不能调用 Pipeline 步骤。
  • Trusted Library 的仓库写权限近似 Jenkins 管理权限;库检索凭据不是业务发布凭据。

1. 建库并固定接口#

独立 Git 仓库可以使用以下结构:

platform-ci/
├── vars/
│   ├── verifyProject.groovy       # 全局步骤 verifyProject(...)
│   ├── verifyProject.txt          # 该步骤的帮助说明
│   └── ciLog.groovy               # 全局变量 ciLog,提供 info(...)
├── src/
│   └── com/example/ci/
│       └── BuildPlan.groovy       # 可 import 的普通 Groovy 类
├── resources/
│   └── com/example/ci/
│       └── notice.txt             # libraryResource 可读取的静态文件
└── README.md
目录 Jenkins 如何识别 适合放什么
vars/ 文件名成为全局调用名;call 方法可写成 verifyProject('go') 面向 Jenkinsfile 的步骤编排;全局变量应保持无状态
src/ 按 package 和类名 import,路径类似 Java 包结构 参数校验、纯逻辑、复用的类
resources/ 用 libraryResource 按相对路径读取 文本模板等静态资源,不会自动出现在 workspace

vars/verifyProject.txt 可写明参数、前置工具和失败语义,例如 verifyProject('go') 要求 Go 工具链、verifyProject('node') 要求 Node.js 与项目的 lint/test 脚本。使用该库的 Pipeline 成功运行后,帮助才可能出现在 Jenkins 的 Global Variable Reference。vars/ 文件名通常采用小写开头的 camelCase。

先写负责选择固定命令的 src/com/example/ci/BuildPlan.groovy,package 与目录对应:

package com.example.ci

class BuildPlan {
  static String commandFor(String kind) {
    switch (kind) {
      case 'go':
        return 'go test -race ./... && go vet ./...'
      case 'node':
        return 'npm ci && npm run lint && npm test'
      default:
        throw new IllegalArgumentException("Unsupported project kind: ${kind}")
    }
  }
}

再写 vars/ciLog.groovy;它提供 ciLog.info(...) 方法,不需要从 src/ 导入:

def info(String message) {
  echo "CI: ${message}"
}

最后写 vars/verifyProject.groovy,把类、另一个全局变量和 Jenkins 内置步骤串起来:

import com.example.ci.BuildPlan

def call(String kind) {
  String command = BuildPlan.commandFor(kind)
  ciLog.info("Verifying ${kind} project")
  timeout(time: 15, unit: 'MINUTES') {
    sh command
  }
}

BuildPlan 是 src/ 类,所以要 import com.example.ci.BuildPlan;ciLog 是 vars/ 全局变量,直接按名称调用,不要写 import ciLog。timeout、sh 是 Jenkins 的 Pipeline 步骤。BuildPlan 不接受任意 shell 字符串,避免把未经校验的 Jenkinsfile 参数拼进命令;工具版本由 Agent 镜像控制。

src/ 类适合优先写成不依赖 Jenkins 步骤的纯逻辑。如果类确实要调用 sh 或 echo,需从调用方显式传入脚本上下文,例如 new Helper(this),再在类中调用 steps.sh(...);保存跨 Pipeline 暂停点的实例状态时还要考虑 Serializable。简单编排放在 vars/ 更容易维护。

2. 注册受限库#

  1. 创建 Jenkins Folder(任务目录),在目录设置中注册 Pipeline Library,名称 platform-ci。
  2. 选择 Modern SCM → Git,填写只读仓库地址和只读凭据。
  3. Default version 填审核通过的完整 Git commit SHA,关闭允许任务覆盖版本,关闭隐式加载。
  4. 目录库运行于 Groovy sandbox(受限执行环境)。若没有目录库设置,核对 Pipeline: Shared Groovy Libraries 与 Folders 插件。

platform-ci 是 Jenkins 注册的库名,不是 Groovy package 名,也不要求与 Git 仓库名相同。关闭隐式加载后,业务 Jenkinsfile 必须显式写 @Library('platform-ci') _;关闭版本覆盖后,业务任务不能用 @Library('platform-ci@其他版本') _ 自行选择新提交。@Library 在 Jenkinsfile 编译前加载库,所以能在文件顶部 import src/ 中的类。

全局 Trusted Library 可绕过沙箱,其仓库写权限近似 Jenkins 管理权限;不要为了消除审批提示就改成受信任库。官方说明

3. @Library 的 _、版本和 Git 地址#

@Library('platform-ci') _ 中的 _ 是什么#

Groovy 的 @Library 是注解,必须附着在一个语法元素上。只想使用 vars/ 提供的 verifyProject(...)、ciLog.info(...) 等全局变量时,文件顶部通常没有需要 import 的类,就在注解后写一个 _ 作为占位:

@Library('platform-ci') _
// 后面直接使用 vars/verifyProject.groovy 暴露的名字
verifyProject('go')

_ 不是库对象、参数或特殊的 Jenkins 步骤,不会调用任何函数。若下一行要导入 src/ 类,可把注解直接附在 import 上,此时不用 _:

@Library('platform-ci')
import com.example.ci.BuildPlan

不能把两种写法理解为“有 _ 才能用 vars/,没有 _ 才能用 src/”:两者都会加载同一个库;差别只是注解附着的位置。单独写一行 @Library('platform-ci') 而后面没有 import 等可附着元素,不是完整写法。上面 verifyProject('go') 是调用示意;Declarative Pipeline 的完整放置方式见下一节。

已注册库:默认版本、指定版本、多个库#

写法 结果 前提
@Library('platform-ci') _ 加载 Jenkins 注册库的 Default version 已注册名为 platform-ci 的库
@Library('platform-ci@main') _ 请求 Git 分支 main 该库允许覆盖默认版本,且 SCM 能解析此版本
@Library('platform-ci@v1.2.0') _ 请求标签或其他 SCM ref 同上
@Library(['platform-ci', 'another-lib@v2']) _ 一次加载多个库 两个库均已注册;显式版本需允许覆盖

@ 后面是库版本,可以是分支、标签或 Git commit SHA,并不是 Git 仓库 URL。当前文档的企业示例将 Default version 固定到审核过的 SHA,且关闭“Allow default version to be overridden”,所以业务 Jenkinsfile 应使用 @Library('platform-ci') _;上表中的 @main/@v1.2.0 在该配置下不能自行切换。若未设置 Default version,则引用时必须显式给版本。

先选加载方式:位置和类访问能力#

方式 能否写在 Jenkinsfile 顶部 Git 仓库从哪里来 src/ 类如何使用
@Library('platform-ci') _ 可以,编译前加载 Jenkins 已注册库的 SCM 配置 主要用于 vars/;要静态导入类,直接使用下一行的注解加 import 写法
@Library('platform-ci') 下一行 import com.example.ci.BuildPlan 可以,注解附在 import 上 Jenkins 已注册库的 SCM 配置 直接用 BuildPlan 类型、new BuildPlan(...)、静态方法等
library('platform-ci') Scripted Pipeline 可作为顶层运行语句;Declarative 放在 script {} Jenkins 已注册库的 SCM 配置 已编译,不能再写静态 import;从返回值按完整包名动态访问
library(identifier: ..., retriever: modernSCM(...)) Scripted Pipeline 可作为顶层运行语句;Declarative 放在 script {} Jenkinsfile 中的 remote、credentialsId 和版本 同上,从返回值动态访问;可不预先注册库
配置了 Load implicitly 的库 Jenkinsfile 可不写加载语句 Jenkins 已注册库的 SCM 配置 编译前已加载,可静态 import;是否启用由管理员配置

“写在文件顶部”不等于“编译前加载”:@Library 是编译期注解,library(...) 是运行时步骤。普通 Groovy import 在 Jenkinsfile 执行前就由编译器处理,因此不能靠前一行的运行时 library(...) 让后面的 import 生效。

某些可选插件能提供特殊格式的自动共享库名称,例如 GitHub Groovy Libraries 的 github.com/owner/repo;那是插件提供的专门检索器,并不表示任意 Git URL 都能放进 @Library(...)。私有企业 Git 仓库使用已注册库,或使用下面明确填写 remote 与 credentialsId 的动态检索方式。

直接写 Git 地址和凭据 ID:运行时 library 步骤#

@Library('https://git.example.com/platform-ci.git@main') _ 不是“直接从该 URL 加载”的写法:@Library 参数先按库名查找 Jenkins 已注册的配置。需要在 Jenkinsfile 中提供 Git 地址、版本和检索凭据时,使用动态 library 步骤。下面是 Declarative Pipeline 的完整写法:

pipeline {
  agent { label 'linux-test' }
  stages {
    stage('Verify') {
      steps {
        checkout scm
        script {
          def loaded = library(
            identifier: 'example-ci@main',
            retriever: modernSCM(
              [$class: 'GitSCMSource',
               remote: 'git@git.example.com:platform/platform-ci.git',
               credentialsId: 'platform-ci-git-readonly']
            )
          )
          echo loaded.com.example.ci.BuildPlan.commandFor('go')
          verifyProject('go')
        }
      }
    }
  }
}
字段 本例作用 对应位置
identifier: 'example-ci@main' 此次动态加载的库名和 Git 版本 example-ci 是本次调用名,与上文已注册的 platform-ci 区分;main 是检索的分支,可改为受控标签或提交
retriever: modernSCM(...) 选择共享库的源码检索方式 Jenkins 的 Modern SCM 检索器
$class: 'GitSCMSource' 使用 Git SCM Source 需要相应 Git/SCM 插件支持
remote: 'git@git.example.com:platform/platform-ci.git' 共享库的 Git 仓库地址 不是业务项目 checkout scm 的仓库地址
credentialsId: 'platform-ci-git-readonly' 用来读取该私有 Git 仓库的 Jenkins 凭据 ID Jenkins Credentials 中已创建、此 Job 可使用的只读 SSH 私钥凭据;传的是 ID,不是密钥正文

loaded.com.example.ci.BuildPlan.commandFor('go') 是动态访问类的静态方法,不是 Groovy import。上例既调用了 src/ 类,也调用了 vars/verifyProject.groovy 暴露的全局步骤。credentialsId 只用于拉取共享库源码,不会自动把凭据传给业务 shell。业务发布/API 凭据应另外按最小范围绑定,见凭据与权限。

如果使用 Scripted Pipeline,library(...) 可以写在 Jenkinsfile 的顶层、node 前面。它依然是运行时加载,并不会让文件顶部的静态 import 生效:

def loaded = library(
  identifier: 'example-ci@main',
  retriever: modernSCM(
    [$class: 'GitSCMSource',
     remote: 'git@git.example.com:platform/platform-ci.git',
     credentialsId: 'platform-ci-git-readonly']
  )
)

node('linux-test') {
  checkout scm
  echo loaded.com.example.ci.BuildPlan.commandFor('go')
  def runner = loaded.com.example.ci.BuildRunner.new(this)
  runner.verify('go')
}

顶层 library 负责加载共享库,node 才分配 Agent 和 workspace。这里的 BuildRunner.new(this) 动态实例化第 6 节的类,this 是当前 Pipeline 脚本上下文;同一个库的 verifyProject('go') 全局步骤也可在 node 内调用。Declarative Jenkinsfile 以 pipeline { ... } 为主体,不能把 library(...) 当作普通步骤放在它前面;在 Declarative 中使用前一个 script { ... } 示例。企业生产任务若要求统一固定版本和集中审查,继续使用上节已注册并锁定版本的目录库。

如果要调用动态库 src/ 类的构造器,可以把 .new(...) 当作动态代理上的构造方法;下面假设 other-ci 是另一个已注册、允许选择 main 的库,且包含第 6 节的 BuildRunner 类。代码应放在 Scripted Pipeline 的 node { ... } 或 Declarative 的 script { ... } 内:

def loaded = library('other-ci@main')
def runner = loaded.com.example.ci.BuildRunner.new(this)
runner.verify('go')

这里的 this 是当前 Pipeline 脚本上下文,让 BuildRunner 能调用 Pipeline 步骤。动态类不能写成 BuildRunner runner 这样的静态类型声明;同一个库名在一次运行中也不能反复加载不同版本。

参考:Jenkins Shared Libraries:Using libraries、Loading libraries dynamically、Dynamic retrieval。

4. 在真实应用验证#

把下列 Jenkinsfile 放入已有 Go 服务仓库:

@Library('platform-ci') _
pipeline {
  agent { label 'linux-test' }
  options { skipDefaultCheckout(true); timestamps() }
  stages {
    stage('Verify') {
      steps {
        deleteDir()
        checkout scm
        verifyProject('go')
        script {
          ciLog.info('Verification finished')
        }
      }
    }
  }
  post { always { deleteDir() } }
}

verifyProject('go') 直接调用 vars/verifyProject.groovy 的 call;后者调用 BuildPlan.commandFor('go')、ciLog.info(...) 和 sh。Node.js 服务改成 verifyProject('node'),仓库必须提供实际 lint、test 脚本。ciLog.info(...) 属于对象方法调用,在 Declarative Pipeline 的 steps 中需要放进 script { ... },而 verifyProject('go') 这样的全局步骤调用可直接写在 steps 中。

若 Jenkinsfile 本身需要使用 src/ 类,可把 @Library 注解放在 import 上:

@Library('platform-ci')
import com.example.ci.BuildPlan

pipeline {
  agent any
  stages {
    stage('Show command') {
      steps {
        script {
          echo BuildPlan.commandFor('go')
        }
      }
    }
  }
}

这是独立的导入示例;实际构建仍应调用 verifyProject('go'),让超时与日志逻辑一同生效。vars/verifyProject.groovy 不用 import;src/com/example/ci/BuildPlan.groovy 则使用包名导入。不要把 vars/ 全局变量当成普通静态类导入。

共享库中的 resources/com/example/ci/notice.txt 可在 vars/ 步骤里这样读取:

def note = libraryResource('com/example/ci/notice.txt').trim()
echo note
// 若外部工具需要文件,再写入当前 workspace:
writeFile file: 'notice.txt', text: note

libraryResource 返回文件文本;资源不会自动复制到业务仓库 workspace。不要把凭据放入共享库的静态资源。运行时 library 'platform-ci' 也能动态加载非隐式库,但发生在 Jenkinsfile 编译之后,不能随后用普通 import 静态引用该库的 src/ 类;固定版本的流水线使用上面的 @Library 更直接。

先验证成功,再提交一个失败的业务单元测试,流水线必须失败;未知项目类型也必须明确失败。还要检查 Agent 缺少工具时的失败信息、vars/verifyProject.txt 帮助显示以及库升级后 Go/Node 两类项目的兼容性。

5. 发布与回退#

先在测试 Folder 将默认提交指向候选版本,跑 Go 和 Node 两类真实项目;验证通过再修改生产 Folder 固定提交。保留上一提交号,失败恢复版本配置并重跑。库默认版本、目录权限与仓库保护规则也纳入变更审计。

验收:应用维护者不能覆盖库版本,库修改不能直接进入生产;破坏性接口变更按新版本逐团队迁移。

6. src/ 类如何调用 Pipeline 步骤#

vars/ 脚本可直接使用 sh、echo、timeout 等步骤;src/ 中的普通类没有这些隐式方法。要让类执行步骤,应把调用方的脚本上下文显式传入,便于看出依赖和测试:

Pipeline 方法会经过 CPS 转换,Groovy 构造器不会。不要在构造器中调用 Pipeline 步骤,也不要间接调用会执行这些步骤的方法。

Jenkins 会对 Pipeline 方法做 CPS 转换,以便在步骤暂停时保存执行状态,并在 Controller 重启后恢复。sh、stage、timeout 等步骤都在这条执行路径上。构造器没有经过该转换;从构造器进入 CPS 方法可能触发 expected to call ...<init> but wound up catching ... 之类的方法不匹配,执行结果也可能异常。构造器只保存参数,创建对象后再调用普通方法执行步骤。

错误与正确写法的差别:

// 错误:构造器直接调用 Pipeline 步骤
class BadRunner {
  BadRunner(steps) {
    steps.sh('make test')
  }
}

// 正确:先构造,再从普通方法调用步骤
class Runner implements Serializable {
  def steps
  Runner(steps) { this.steps = steps }
  void runTests() { steps.sh('make test') }
}
// 在有 Agent 的 Pipeline 步骤中:new Runner(this).runTests()

下面的 BuildRunner 是同一正确模式的完整版本,包含命令选择、日志和超时:

// src/com/example/ci/BuildRunner.groovy
package com.example.ci

class BuildRunner implements Serializable {
  private final def steps

  BuildRunner(steps) {
    this.steps = steps
  }

  void verify(String kind) {
    String command = BuildPlan.commandFor(kind)
    steps.echo("Verifying ${kind}")
    steps.timeout(time: 15, unit: 'MINUTES') {
      steps.sh(command)
    }
  }
}
// vars/verifyWithRunner.groovy
import com.example.ci.BuildRunner

def call(String kind) {
  new BuildRunner(this).verify(kind)
}

Jenkinsfile 加载库后可直接写 verifyWithRunner('go')。this 是 vars/ 脚本上下文;类通过 steps.echo、steps.sh 调用 Pipeline 步骤。跨暂停点保存状态的类及其字段必须能安全序列化。BuildRunner 因此实现 Serializable;简单场景仍用第 1 节的 vars/verifyProject.groovy,不必为了封装而多建一个类。

src/ 类若只做纯计算,可像 BuildPlan 一样使用普通静态方法,无须传 this。如果要读 env.BRANCH_NAME、params 等构建上下文,优先由 vars/ 将所需的字符串/布尔值传给类;不要让纯逻辑类暗中依赖 Jenkins 的全局绑定。import 加载的是类,不会把 src/ 类变成可直接调用的 Pipeline 步骤。

7. CPS、序列化与 @NonCPS#

先把 Pipeline 想成一份可以夹书签的执行清单。执行到耗时的 sh 时,Jenkins 不只要记住“现在在等测试结束”,还要记住“测试结束后该执行哪一行”和“后面还会用到哪些变量”。这样 Controller 重启后,才能从保存的位置继续。

def version = '1.2.3'
echo '开始测试'
sh 'make test'                 // 可能运行很久;Pipeline 在这里等待
echo "测试完成,版本 ${version}" // 恢复后还要知道下一步和 version 的值

普通程序主要靠内存里的调用栈记住执行位置;进程重启后,那份调用栈就没了。Jenkins 把 Jenkinsfile 和共享库的大部分 Groovy 方法改写成可暂停、可保存“接下来做什么”的形式,这种改写叫 CPS(Continuation-Passing Style,续延传递)。它保存的是 Pipeline 的必要执行状态,不是给整个 Controller JVM 或 Agent 进程拍快照;sh 的命令仍在 Agent 上运行。

一句话理解:CPS 让 Pipeline 带着“下一步在哪、还需要什么变量”的书签等待耗时步骤,并在恢复时继续执行。写普通 Jenkinsfile 不需要手工实现 CPS,知道它的边界即可。

由此能推导出两个常见限制:跨暂停点仍要使用的对象必须能被序列化;没有经过 CPS 转换的构造器或 @NonCPS 方法不能调用需要暂停的 Pipeline 步骤。这也是普通 Groovy 在本地能跑、放进 Jenkins Pipeline 却报错的原因。

常见现象 原因 改法
java.io.NotSerializableException 出现在 sh、sleep 等步骤附近 活跃的局部变量或类字段持有不能序列化的对象,如打开的流、正则 Matcher、某些 JSON 解析结果或 Jenkins 内部对象 在进入 Pipeline 步骤前提取成 String、数字、简单 List/Map;及时结束对象作用域;不要把大对象放到类字段
expected to call ... but wound up catching ... CPS 代码和非 CPS 方法/闭包混用 定位日志中的两个方法,简化调用;确有必要时只把纯计算封装进 @NonCPS 方法
构造对象时出现 CPS mismatch 构造器调用了 CPS 转换的方法或 Pipeline 步骤 构造器只赋简单字段,执行步骤放到构造完成后的普通方法
@NonCPS 方法内 sh、node、timeout 行为异常 非 CPS 方法不能调用 CPS Pipeline 步骤 移除 @NonCPS,或把步骤调用移到外层 Pipeline 方法

例如排序遇到 CPS 闭包兼容问题时,可把不调用任何 Pipeline 步骤的小段纯计算放入 @NonCPS,并返回易于序列化的结果:

// src/com/example/ci/Names.groovy
package com.example.ci

import com.cloudbees.groovy.cps.NonCPS

class Names {
  @NonCPS
  static List<String> byLength(List<String> names) {
    names.toSorted { a, b -> a.length() <=> b.length() }
  }
}

不要在 @NonCPS 方法中调用 sh、node、timeout 等 Pipeline 步骤,也不要用它掩盖序列化问题。vars/ 全局变量保持无状态,避免用可变字段缓存构建状态;Controller 重启后这类字段并不可靠。需要处理大量 JSON、HTTP 响应或复杂构建逻辑时,让 Agent 上的脚本/程序处理,只把少量结果返回 Groovy,避免在 Controller 上消耗大量 CPU 和内存。CPS 方法不匹配、Pipeline 最佳实践

8. Declarative Pipeline 与步骤设计边界#

vars/foo.groovy 可以定义 def call(...),让它像步骤一样在 Jenkinsfile 的 steps 中调用。若 vars/foo.groovy 定义的是 def info(...) 等其他方法,Declarative steps 中要通过 script { foo.info(...) } 调用。src/ 类的方法调用也应放进 script。构造器与 Pipeline 步骤的 CPS 限制见第 6 节。

也可以把整条 Declarative Pipeline 定义在共享库的某个 vars/*.groovy 文件的 call 中,由 Jenkinsfile 调用,但一次构建只能执行一条 Declarative pipeline { ... }。例如:

// vars/standardPipeline.groovy
def call(Map config) {
  pipeline {
    agent { label 'linux-test' }
    stages {
      stage('Verify') {
        steps {
          checkout scm
          verifyProject(config.kind)
        }
      }
    }
  }
}
// 业务 Jenkinsfile;这里不能再定义第二条 pipeline { ... }
@Library('platform-ci') _
standardPipeline(kind: 'go')

整条流水线模板适合多个项目确实遵守同一阶段和质量门禁时使用;项目有显著差异时,复用较小的 vars/ 步骤更容易看清 Jenkinsfile 的执行路径。无论哪种方式,都不要把用户提供的任意字符串直接拼成 shell 命令。避免把全局变量命名为 sh、echo、timeout 等内置步骤名,也不要把一个 vars/ 文件做成巨大的通用工具箱;小而明确的接口更容易升级和定位错误。共享库定义 Declarative Pipeline、最佳实践

共享库的 src/ 是加载到 Pipeline 的 Groovy 类路径,不等于在 Agent 上安装了第三方工具。避免用 @Grab 在 Controller 中临时下载大量 Java 依赖;需要复杂构建、扫描或网络处理时,把版本固定的可执行工具装到 Agent 镜像,由 sh/bat 调用。第三方库使用说明

9. 安全边界与版本治理#

共享库不是权限系统。vars/ 步骤能调用的 sh、凭据、Agent 和外部服务,仍取决于运行它的 Job 所拥有的权限。审查共享库时重点检查:是否执行了未受控输入、是否访问 Jenkins 内部 API、是否在日志中泄漏秘密、是否把不可信 PR 代码与发布凭据放到同一个执行环境。

场景 应采取的控制
Folder 级库 始终在 Groovy sandbox 中运行;库仓库写权限仍需受控,因为改动会影响该 Folder 下的 Job
全局 Trusted Library 能访问 Jenkins 内部 API,库仓库写权限近似管理权限;只由可信维护者修改,代码审查后再晋级
动态 library 指定 Git URL 允许的仓库、版本和检索凭据应由 Job 权限及代码审查约束;不要把它当成绕过固定库版本策略的普通发布入口
沙箱提示需要 Script Approval 先确认为什么需要该方法;批准签名会影响其他脚本,不要为消除报错而批量批准或改成 Trusted
共享库调用 withCredentials 使用方 Job 必须有权读取该凭据;缩小绑定范围,不把秘密存成 vars/ 字段、静态资源或 Groovy 字符串日志

全局库的 Git 检索凭据 ID 与业务部署凭据 ID 是两回事;前者只用于拉库,后者应在具体发布步骤短时绑定。Trusted Library 也不宜直接调用 Jenkins.getInstance() 等内部 API;需要稳定、受控的 Jenkins 扩展能力时,应考虑实现专门插件。使用固定 commit SHA 可以重现库版本;如果改用可移动分支或标签,同一 Jenkinsfile 在不同时刻可能加载不同代码。记录每次升级的旧 SHA、新 SHA、消费 Job 和回退方式。共享库信任模型、脚本审批

10. 测试库变更:纯逻辑、步骤契约、真实 Job#

共享库代码和业务项目一样需要测试;只用 Jenkinsfile Linter 检查语法,不能证明运行时的库检索、沙箱、CPS、Agent 工具或凭据可用。

  1. 纯逻辑测试:对 BuildPlan.commandFor 的 go、node 和未知类型写断言,保证没有把任意用户输入当成命令。
  2. Pipeline 步骤测试:用 Jenkins Pipeline Unit 等框架模拟 sh、timeout、echo,检查 verifyProject('go') 调用了预期命令并在未知类型失败;模拟器不能替代真实 Jenkins 的 CPS/沙箱验证。
  3. 测试 Folder 集成验证:把候选 commit SHA 注册为测试库版本,运行真实 Go/Node Job,检查库检索凭据、checkout、Agent 工具、报告与失败路径;必要时重启测试 Controller 验证暂停/恢复。
  4. 生产晋级:记录测试结果、候选 SHA 和上一 SHA,修改生产 Folder 的 Default version,先观察少量消费 Job,再扩展;异常时恢复上一 SHA 并重跑。

例如,BuildPlan 的纯逻辑测试可以放在共享库仓库的 test/com/example/ci/BuildPlanTest.groovy,由该仓库的 Groovy 测试构建执行:

package com.example.ci

class BuildPlanTest extends GroovyTestCase {
  void testKnownKinds() {
    assert BuildPlan.commandFor('go') == 'go test -race ./... && go vet ./...'
    assert BuildPlan.commandFor('node') == 'npm ci && npm run lint && npm test'
  }

  void testUnknownKindFails() {
    shouldFail(IllegalArgumentException) {
      BuildPlan.commandFor('shell-from-user')
    }
  }
}

此代码展示测试内容;仓库仍需配置适合其 Groovy/Jenkins 版本的测试依赖和构建命令。涉及 Pipeline 步骤时,参考 Jenkins Pipeline Unit 项目的共享库测试示例;上线前仍须使用目标 Jenkins 版本做集成验证。Pipeline 开发工具

11. 常见报错的定位顺序#

先从构建日志确认失败发生在哪一层:加载共享库 → 编译 Jenkinsfile/类 → 进入 Agent → 执行步骤。不要一看到 MissingMethodException 就把库改成 Trusted。

现象 优先检查
Could not find any definition of libraries ... @Library 中的名称是否与 Jenkins 注册名一致;Job 是否在配置该 Folder 下;是否关闭隐式加载但忘了显式引用
Git 检索失败或 Authentication failed 库的仓库 URL、版本/ref、只读 credentialsId 的类型和可见范围;这与业务仓库的 checkout scm 凭据是两套配置
unable to resolve class com.example... src/ 路径与 package 是否一致;是否在编译前用 @Library 加载;运行时 library 加载后不能静态 import
No such DSL method 'verifyProject' 是否存在 vars/verifyProject.groovy 且大小写一致;库是否成功加载;是否把 call 写成了其他方法名
Declarative 报方法调用不允许 ciLog.info(...)、类方法等对象调用是否放进 script { ... };单个 call 全局步骤可直接放 steps
Scripts not permitted to use ... 沙箱拒绝了具体方法;检查代码能否改用受支持的 Pipeline 步骤,再由管理员评估是否审批签名
NotSerializableException 看异常中的对象类型和最近的暂停步骤;将非序列化对象缩小到纯计算函数内,只让简单结果跨步骤保留
expected to call ... but wound up catching ... 查 CPS mismatch:构造器、@NonCPS、闭包传给非 CPS 方法、GString 内闭包等
sh: command not found 库已加载,问题在所选 Agent 的工具安装和 PATH;库检索凭据与此无关
改了共享库却仍执行旧逻辑 查看构建记录实际加载的库 SHA、Folder Default version、是否引用了可移动分支以及库缓存配置

排障时先复现最小调用,例如只加载库并调用一个 echo 步骤,再逐步加入 src/ 类、资源和业务命令。固定版本策略下,修复代码本身不会改变正在使用的 SHA,必须按第 5 节完成版本晋级。共享库代码运行在 Controller 的 Pipeline 引擎中,而 sh 命令运行在 Agent 上;区分两处日志和文件系统位置,能避免把类加载问题误判为 Agent 问题。

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭