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


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

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


一、功能描述


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


构建项目,需要2个参数

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

在这里插入图片描述


构建的代码

IntegerResponse response = jobsApi.buildWithParameters(null, jobName, buildProperties);

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

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

二、代码实现


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

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个项目,我的线程配置如下

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

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

  • https://jenkins.xxxxx.com/
  • admin:123456

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

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() 获取整体负载统计
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐