在 Jenkins 里做参数化构建,选项内容如果固定,直接写进 Choice Parameter 就行。但一旦选项来自 Git 分支、远端接口或者上游任务结果,就需要动态生成。这里有几个判断点:选项之间有没有依赖?脚本运行在哪个节点?用户打开页面时读取还是构建启动时读取?想清楚这三个问题,再动手写 Groovy,会少走弯路。
先确定用哪种参数类型
最直接的方式是使用 Active Choices 插件。在参数化构建的定义中,添加一个“Active Choices Parameter”,并在脚本区域编写 Groovy 代码返回一个字符串列表。例如,需要从远程仓库获取分支列表时,可以用 `["PR-1", "PR-2"]` 这样的硬编码列表占位,再通过 `def proc = ["git", "ls-remote"].execute()` 等命令替换为真实数据。需要注意脚本是在 Jenkins master 节点执行,务必保证逻辑轻量,避免导致构建队列阻塞。
这种方式的典型场景是分支选择、环境列表、版本号列表。比如项目只保留最近 5 个 tag,可以用 `git ls-remote --tags` 拿到后做排序和截取。但要注意,脚本是在用户打开参数页时执行,不是点击构建时执行。如果仓库上有大量引用,建议用 `head` 或 `take(20)` 限制返回条数,否则 UI 会明显卡顿。
依赖其他参数时改用 Reactive Parameter
当某个参数的选项必须依赖另一个参数的选择时,应该使用“Active Choices Reactive Parameter”。该参数会监听其他参数的变化,并在用户改变选择时实时刷新选项。你可以用 `delegate` 获取当前已选参数的值,比如 `def target = delegate.get("software_version")`。随后在脚本中根据 target 返回不同的可选项。由于脚本会在 UI 每次渲染时执行,应避免在脚本中做高开销的数据库查询,可以考虑加一层缓存或把查询结果放在 Jenkins 的全局变量中。
我通常会在这种情况下写一个 switch 分支,每个分支对应一组 option。例如软件版本选了不同大版本,后面拉取的分支列表就不同。这里最容易踩的坑是 `delegate.get` 在有些插件版本里返回的是字符串数组,注意先转成 string。另一个方法是把公共选项抽到一个 `Map` 里,脚本只负责查表。
脚本测试和安全沙箱
在将 Groovy 脚本嵌入参数定义前,务必先在“Manage Jenkins > Script Console”中测试。直接粘贴脚本并模拟入参,确认返回值是 List 类型。常见错误是脚本只返回一个字符串,导致选项无法显示。检查时注意 `println` 的输出会被忽略,必须用最后的返回值作为选项集合。如果脚本使用了系统命令或文件 API,可能会触发 Jenkins 的安全沙箱,需要到“In-process Script Approval”中手动批准相关方法,否则脚本会静默失败。
实际测试时,你可以在 Script Console 里这样写:
def software_version = "1.2"
def options = ["1.2.1", "1.2.2"]
return options然后点 Run 看输出。如果返回结果是 `[1.2.1, 1.2.2]` 就是 List,如果是字符串 `[1.2.1, 1.2.2]` 就是没转 List,需要修改。对于文件操作和 `exec()`,先在审批列表里搜一下是否有对应的签名,没有就手动 approve。这个操作需要管理权限,普通用户只能看到脚本失败,不会看到具体原因。
不想装插件时的备选路径
如果你不想装 Active Choices 插件,也可以用文件交互的方式:让一个上游任务把可选值写到固定路径的文件中,每行一个,然后在本项目的“Choice Parameter”里读取这个文件。但有个关键点:参数选项是在用户点“Build with Parameters”时读取的,而不是构建启动后。所以文件必须在你打开参数页之前就已经生成。如果上游任务还没跑,选项就是空的。另一种更可靠的方式是用 Parameterized Trigger 插件,把上游任务生成的选项作为参数传给下游任务,下游的选项就不会依赖文件读取时点了。这个思路适合有明确上下游关系的流水线,但不适合手动触发时临时改参数。
动态脚本的性能边界
动态参数脚本每渲染一次都会执行,所以每次用户打开构建页面都触发一遍。如果脚本里有远程 HTTP 调用或大循环,请求很容易超时。建议把选项数量控制在几十个以内,或者在脚本里用一个简单的 Map 缓存结果,设置几分钟的过期时间。另外,脚本中访问 `new File("/tmp/...")` 或 `Runtime.getRuntime().exec()` 会被沙箱拦截,要提前在审批列表中放行。最好把可能抛异常的地方用 `try...catch` 包住,返回一个描述性的错误选项,比如 `"ERROR: 无法读取分支列表"`,这样用户在界面上就知道不是没有分支,而是脚本报错。
动态参数看起来是一个小功能,但脚本的运行时机和用户感知直接相关。先在小范围 Job 上测试,观察 UI 响应,再逐步推广到其他流水线。如果发现每次打开页面都很慢,优先检查脚本里是否有网络请求或文件读取。保持脚本轻量、返回值可预期,基本不会出大问题。