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() 获取整体负载统计
相关推荐
像我这样帅的人丶你还10 小时前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩10 小时前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构
tntxia11 小时前
Mybatis的日志输入
java
亦暖筑序12 小时前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式
用户2986985301415 小时前
Java 实现 Word 文档加密与权限解除
java·后端
Yeats_Liao16 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构
未秃头的程序猿16 小时前
告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍
java·后端·面试
鹤望兰67517 小时前
字节跳动国际支付-后端开发-三面面经
java
Flittly17 小时前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
RainCity17 小时前
Java Swing 自定义组件库分享(十二)
java·笔记·后端