jenkins对接、jenkins-rest

https://www.bilibili.com/video/BV1RqNRz5Eo6

Jenkins是一款常见的构建管理工具,配置好后操作也很简单,只需去控制台找到对应的项目,再输入分支名即可

如果每次只发个位数的项目到也还好,一个个进去点嘛。但如果一次要发几十个项目呢?这就很费时费力了。好在Jenkins提供了rest接口,可以通过接口来进行批量构建

一、功能描述

目的:使用 Jenkins-rest,多线程批量构建项目

构建项目,需要2个参数

  • jobName, 对应jenkins上的名称
  • 构建参数格式Map<String, List<String>>,主要是分支名,如果还有其它的也可以加进去

构建的代码

java 复制代码
IntegerResponse response = jobsApi.buildWithParameters(null, jobName, buildProperties);

构建完之后肯定是要获取到构建结果的

  1. 构建后返回一个 queueId
  2. 通过 queueId,获取 buildNumber
  3. 通过 buildNumber 获取BuildInfo,BuildInfo里有我们想要的结果参数

二、代码实现

因为涉及到公司的代码,这里只给出核心的流程,业务参数就不给出了

java 复制代码
private void build(JenkinsBuildRequest request) {
    logger.info("jenkins构建开始:JenkinsBuildRequest:{}", request);
    JenkinsBuildInfo jenkinsBuildInfo = initJenkinsBuildInfo(request);

    try (JenkinsClient client = JenkinsClient.builder().endPoint(jenkinsUrl).credentials(jenkinsCredentials).build()) {
        JobsApi jobsApi = client.api().jobsApi();
        QueueApi queueApi = client.api().queueApi();

        Map<String, List<String>> buildProperties = buildProperties(request);
        String jobName = request.generateJobName();
        IntegerResponse response = jobsApi.buildWithParameters(null, jobName, buildProperties);

        if (!response.errors().isEmpty()) {
            logger.error("jenkins构建失败: planId:{}, jobName:{}, responseError:{}", request.getPlanId(), jobName, response.errors());
            jenkinsBuildInfo.setStatus(JenkinsStatusEnum.FAILURE.getValue());
            updateBuildInfo(request, jenkinsBuildInfo);
            return;
        }

        int queueId = response.value();
        jenkinsBuildInfo.setQueueId(queueId);
        // 有queueId 没有number的时候就是构建中
        jenkinsBuildInfo.setStatus(JenkinsStatusEnum.BUILDING.getValue());
        updateBuildInfo(request, jenkinsBuildInfo);

        JenkinsStatusEnum status = pollBuildStatusAndSetBuildInfo(queueId, jobName, jobsApi, queueApi, jenkinsBuildInfo);
        jenkinsBuildInfo.setStatus(status.getValue());
        updateBuildInfo(request, jenkinsBuildInfo);
        logger.info("jenkins构建成功: planId:{}, jobName:{}", request.getPlanId(), jobName);
    } catch (Exception e) {
        logger.error("jenkins构建失败: JenkinsBuildRequest:{}", request, e);
        jenkinsBuildInfo.setStatus(JenkinsStatusEnum.FAILURE.getValue());
        updateBuildInfo(request, jenkinsBuildInfo);
    }
}

/**
 * 轮询获取Jenkins的构建状态
 */
private JenkinsStatusEnum pollBuildStatusAndSetBuildInfo(int queueId, String jobName, JobsApi jobsApi, QueueApi queueApi, JenkinsBuildInfo jenkinsBuildInfo) throws InterruptedException {

    Integer buildNumber = null;
    // 每次轮询间隔3秒,最多轮询200次,共10分钟
    for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
        // 获取构建编号:如果已经构建结束了调用 queueId 会报错找不到资源,所以获取到buildNumber之后,就不用再去获取 QueueItem
        if (buildNumber == null) {
            QueueItem queueItem = queueApi.queueItem(queueId);
            if (queueItem.executable() != null) {
                buildNumber = queueItem.executable().number();
            }
        }

        // 如果构建编号已确定,获取构建信息并检查构建状态
        if (buildNumber != null) {
            BuildInfo buildInfo = jobsApi.buildInfo("", jobName, buildNumber);

            // 如果构建已完成,设置构建信息并返回状态
            if (!buildInfo.building()) {
                jenkinsBuildInfo.setUrl(buildInfo.url());
                jenkinsBuildInfo.setNumber(buildNumber);
                return "SUCCESS".equals(buildInfo.result()) ? JenkinsStatusEnum.SUCCESS : JenkinsStatusEnum.FAILURE;
            }
        }

        // 每次轮询间隔3秒
        Thread.sleep(POLL_INTERVAL_MS);
    }

    logger.error("jenkins构建失败:轮询超时: jobName:{}, jenkinsBuildInfo:{}", jobName, jenkinsBuildInfo);
    return JenkinsStatusEnum.FAILURE;
}

可根据jenkins上打包的节点,多线程去调用 build 方法。 我们是5个节点,最多60个项目,我的线程配置如下

java 复制代码
@Bean(name = "jenkinsBuildExecutor")
public ThreadPoolExecutor jenkinsBuildExecutor() {
    return new ThreadPoolExecutor(
            4,
            4,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(120)
    );
}

jenkinsUrljenkinsCredentials 就对应访问前缀和帐号密码,格式如下

buildProperties 构建参数,看实际需要什么参数,我这里有一个必填的branch和一个选填的 version

java 复制代码
private Map<String, List<String>> buildProperties(JenkinsBuildRequest request) {
    Map<String, List<String>> params = Maps.newHashMapWithExpectedSize(4);
    params.put("branch", Lists.newArrayList(request.getBranch()));

    if (StringUtils.isNotBlank(request.getVersion())) {
        params.put("version", Lists.newArrayList(request.getVersion()));
    }
    return params;
}

JenkinsBuildInfo 是需要存储这次构建的参数,比如 分支、项目名、操作人、操作时间、构建状态、结果链接什么的

pollBuildStatusAndSetBuildInfo 方法是轮训获取结果的,queueApi.queueItem(queueId) 可能会null异常,所以当拿到 buildNumber 之后就不要再去调用它了

updateBuildInfo 方法是去更新本地的数据库,具体实现看实际业务

三、相关文档

https://cdancy.github.io/jenkins-rest/docs/javadoc/

核心 API 模块

API 类别 API 名称 主要方法 功能描述
JobsApi 任务管理 API build() 触发无参数构建任务
buildWithParameters() 触发带参数的构建任务
buildInfo() 获取构建详细信息
jobInfo() 获取任务详细信息
create() 创建新的 Jenkins 任务
delete() 删除 Jenkins 任务
config() 获取/设置任务配置
disable() 禁用任务
enable() 启用任务
lastBuildNumber() 获取最后构建编号
lastBuildTimestamp() 获取最后构建时间戳
progressiveText() 获取构建进度日志
QueueApi 构建队列 API queueItem() 获取队列项目信息
cancel() 取消队列中的构建
list() 列出队列中的所有项目
SystemApi 系统信息 API systemInfo() 获取 Jenkins 系统信息
PluginManagerApi 插件管理 API installNecessaryPlugins() 安装必要的插件
list() 列出当前安装的插件
StatisticsApi 统计信息 API overallLoad() 获取整体负载统计
相关推荐
爱笑的源码基地4 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
莫逸风6 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
z123456789867 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin5211237 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员8 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus
小钻风33669 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李9 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端
前端双越老师10 小时前
如何以前端视角(非0基础)学 Java ?
java·node.js·全栈
dear_bi_MyOnly10 小时前
【SpringBoot配置文件】
java·spring boot·后端·学习·spring·java-ee·学习方法
做个文艺程序员11 小时前
Linux第22篇:用Docker容器化你的Java SaaS应用:一次构建,随处运行
java·docker·容器