做CI/CD相关工具开发的同学应该都遇到过这样的场景:想调试一个对接Jenkins的脚本或页面,但公司的Jenkins服务器有权限限制,或者根本就是一个只有测试环境才有的资源。这时候如果在本地用Node.js搭一个Jenkins的Mock服务,把常用的接口行为模拟出来,开发调试效率会高很多。本文就围绕这个思路,完整讲解如何用Express实现一个可以以假乱真的Jenkins Mock服务,并支持模拟构建产物Image的生成与查询。

一、Jenkins Mock服务需要模拟哪些核心接口
先梳理一下Jenkins的对外API。Jenkins的REST接口围绕几个核心资源展开:Job(任务)、Build(构建)、Queue(队列)和视图。最常用的调用路径包括创建任务、触发构建、查询构建状态、获取构建日志这几个。对于Mock服务来说,不需要实现Jenkins的全部功能,只要把客户端真正会调用的那部分接口覆盖到就可以了。
以一次典型的构建流程为例:客户端先通过 POST /job/任务名/build 触发构建,Jenkins返回一个队列地址;接着轮询 /queue/item/编号/api/json 拿到真实构建号;再通过 /job/任务名/构建号/api/json 查询构建进度;最后用 /job/任务名/构建号/consoleText 拉取控制台日志。把这四步模拟出来,绝大多数Jenkins客户端库都能正常工作。
需要注意的是,Jenkins的接口响应格式是JSON,但字段名比较特殊,比如构建结果字段叫 result,状态为 building 的布尔值表示是否还在构建中。Mock服务必须严格保持这些字段结构,否则客户端解析时容易出错。
二、用Express搭建基础服务与构建状态机
服务骨架用Express搭建即可,依赖非常轻量。核心设计思路是维护一个内存中的任务表和构建表,每次触发构建时创建一个构建记录,并通过一个简单的状态机让它从排队状态逐步流转到成功或失败。
const express = require('express');
const app = express();
app.use(express.json());
// 内存存储:任务表和构建表
const jobs = new Map();
const builds = new Map(); // key: jobName/buildNumber
let queueSeq = 1;
// 创建任务
app.post('/createItem', (req, res) => {
const name = req.query.name;
jobs.set(name, { name, nextBuildNumber: 1 });
res.status(200).end();
});
// 触发构建,进入队列
app.post('/job/:name/build', (req, res) => {
const job = jobs.get(req.params.name);
if (!job) return res.status(404).end();
const queueId = queueSeq++;
const buildNumber = job.nextBuildNumber++;
const build = { jobName: job.name, number: buildNumber,
result: null, building: true, startTime: Date.now() };
builds.set(`${job.name}/${buildNumber}`, build);
// 模拟排队后进入构建
setTimeout(() => { build.building = true; }, 500);
// 模拟构建耗时3秒后结束
setTimeout(() => {
build.building = false;
build.result = 'SUCCESS';
build.duration = Date.now() - build.startTime;
}, 3000);
res.setHeader('Location', `/queue/item/${queueId}/`);
res.status(201).end();
});
app.get('/job/:name/:num/api/json', (req, res) => {
const b = builds.get(`${req.params.name}/${req.params.num}`);
if (!b) return res.status(404).end();
res.json(b);
});
app.listen(8080, () => console.log('Jenkins Mock running on 8080'));上面的代码实现了最核心的状态流转。真实Jenkins中一个构建会经历排队、执行中、结束三个阶段,这里用两个setTimeout来模拟。如果想让Mock服务更灵活,可以加一个环境变量控制构建时长和失败概率,方便测试客户端对失败场景的处理逻辑。
另外一个容易被忽略的细节是HTTP状态码。真实Jenkins触发构建成功返回的是201,并且在响应头中带一个Location字段指向队列项。很多客户端库会依赖这个响应头来追踪构建,Mock服务如果只返回200,这些库就会报错。
三、模拟控制台日志与Image构建产物
控制台日志是调试Jenkins任务时最常看的内容。Mock服务可以用一个日志数组,在构建过程中分阶段推送日志行,客户端拉取 consoleText 时把数组拼成纯文本返回。这样模拟出来的日志有真实的滚动效果,前端页面的日志窗口也能正常展示。
// 构建对象中增加 logs 数组
const build = { logs: [], /* 其他字段 */ };
// 分阶段写日志
const stages = [
'Started by user admin',
'Building in workspace /var/jenkins_workspace/demo',
'Pulling docker image node:18-alpine ...',
'npm install --registry=https://registry.npmjs.org',
'Building image: registry.ippipp.com/demo:latest',
'Pushing image: registry.ippipp.com/demo:latest',
'Finished: SUCCESS'
];
stages.forEach((line, i) => {
setTimeout(() => build.logs.push(line), i * 400);
});
app.get('/job/:name/:num/consoleText', (req, res) => {
const b = builds.get(`${req.params.name}/${req.params.num}`);
if (!b) return res.status(404).end();
res.type('text/plain').send(b.logs.join('\n'));
});关于Image产物的模拟,思路是在构建成功后生成一条虚拟的镜像记录,包含镜像名称、标签、摘要和构建时间。可以额外暴露一个自定义接口 /job/:name/:num/image 返回JSON格式的镜像信息,客户端拿到后就可以做后续的部署验证。如果对接的是真实的镜像仓库查询,还可以配合一个简单的摘要生成函数,模拟镜像的sha256摘要格式,让数据结构完全对齐生产环境。
四、进阶用法与落地建议
要让Mock服务更好用,还有几个可以补充的点。第一是加上Jenkins的CRUMB认证流程,真实Jenkins在开启CSRF保护后需要先请求 /crumbIssuer/api/json 拿到令牌,Mock这个接口能让客户端的认证逻辑也得到测试。第二是支持持久化,把任务和构建数据落到JSON文件或SQLite里,重启服务后数据不丢失,适合长期使用。
第三是把它做成可配置的多场景服务。比如通过配置文件定义每个任务的行为:构建时长、成功概率、日志内容、镜像产物信息等,这样一套Mock服务就能覆盖各种测试场景,包括构建失败、超时、日志异常等边界情况,这对验证客户端的容错能力非常有价值。
最后提醒一点,Mock服务毕竟是Mock,真实Jenkins还有并发构建限制、节点分配、凭证管理等复杂行为,上线前还是要抽时间在真实环境做一轮验证。但作为日常开发和联调工具,这样一个百来行代码的Node.js服务已经能解决大部分痛点,值得花时间打磨一下。