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() 获取整体负载统计
相关推荐
希望永不加班12 分钟前
SpringBoot 静态资源访问(图片/JS/CSS)配置详解
java·javascript·css·spring boot·后端
oh LAN28 分钟前
RuoYi-Vue-master:Spring Boot 4.x (JDK 17+) (环境搭建)
java·vue.js·spring boot
ch.ju35 分钟前
Java程序设计(第3版)第二章——java的数据类型:小数
java
Advancer-1 小时前
RedisTemplate 两种序列化实践方案
java·开发语言·redis
java1234_小锋1 小时前
Java高频面试题:MyBatis如何实现动态数据源切换?
java·开发语言·mybatis
墨神谕1 小时前
Java中,为什么要将.java文件编译成,class文件,而不是直接将.java编译成机器码
java·开发语言
Nyarlathotep01131 小时前
并行设计模式(3):Future模式
java·后端
流星雨在线2 小时前
汇总:Tomcat 安装与常用配置
java·tomcat
小冷coding2 小时前
【面试】结合项目整理的场景面试题,覆盖 Java 基础、锁、多线程、数据库、分布式锁 / 事务、消息中间件等核心维度
java·数据库·面试
鬼先生_sir2 小时前
SpringCloud-GateWay网关
java·spring cloud·gateway