DevOps-7:Jenkins API介绍
Posted 北亮bl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DevOps-7:Jenkins API介绍相关的知识,希望对你有一定的参考价值。
前言:
为什么要使用Jenkins的API?
我在使用Jenkins的过程中,觉得Jenkins的UI还是有不少问题的:
- UI性能差,尤其是有一些任务在构建中时,UI非常卡顿,等个十来秒都正常,极端时甚至会崩溃;
- 权限管理功能薄弱,虽然有
Role-based Authorization Strategy
类似的插件提供了角色管理,但是还是不太好用; - 一些需要人工介入的能力扩展不太方便,比如发布前需要比对数据库表结构并支持人工忽略时,就不好扩展。
如上,因此我还是比较推荐自行开发一个发布系统,自行实现权限能力和一些扩展,通过调用Jenkins的构建API和状态API完成项目的发布和管理操作,包括历史和审计相关能力。
API认证方式说明
Jenkins的API认证支持两种方式,
- 第一种是使用账号密码进行认证,这种请求会报错:
Error 403 No valid crumb was included in the request
,需要在所有请求之前先获取crumb,再把crumb添加在后续请求的Header里,参考:https://stackoverflow.com/questions/44711696/jenkins-403-no-valid-crumb-was-included-in-the-request - 第二种是在Jenkins生成一个Token,在API请求里使用这个Token代替密码,这种不会存在crumb问题,生成用户Token的方法,在Jenkins首页左侧->People->对应用户->Configure->Add new Token
官方API文档介绍
- 安装完成Jenkins之后,直接访问
http://你的Jenkins域名/api
就可以看到API的介绍了,这里会有创建Job和复制Job等API介绍。 - 进入具体的某个job,在url后面直接添加 /api,就可以看到这个job的API介绍,如
http://你的Jenkins域名/job/具体job名/api
这里会有删除Job、启动Job构建、禁用/启用Job等API介绍。
Java调用API说明
网上也有一些封装好的Maven库可以使用,我都是直接用feign去调用,下面就以feign的代码进行举例说明:
feign类基础定义
- 定义一个配置类,返回Jenkins的认证信息(建议使用Jenkins用户token方案)
class MultipartSupportConfig
@Value("$jenkins.username:") // yml里配置的jenkins用户名
private String jenkinsUser;
@Value("$jenkins.password:") // yml里配置的jenkins用户token
private String jenkinsPassword;
@Bean
public Contract feignContract()
return new Contract.Default();
@Bean
public BasicAuthRequestInterceptor basicAuthorizationInterceptor()
return new BasicAuthRequestInterceptor(jenkinsUser, jenkinsPassword);
- 定义feignClient类,指向上面的配置类:
@FeignClient(value = "jenkins", url = "$jenkins.worker.url", configuration = JenkinsFeign.MultipartSupportConfig.class)
public interface JenkinsFeign
// 具体的jenkins API方法定义
常用Jenkins API方法声明
注:通过feign调用Jenkins,不能用RequestMapping注解,不太记得为啥了,当时是出了很多问题,包括JobName的拼接之类,所以最后采用了RequestLine注解实现,你也可以再使用RequestMapping验证看看。
以下是API方法声明:
- crumb头信息获取接口
如果你直接使用Jenkins用户名和密码作为API请求认证方式,那么在所有API调用的Header里,都要有crumb值,这个接口就是用于获取crumb值的(注:不推荐使用)
// 直接请求后续接口会报错: Error 403 No valid crumb was included in the request
// 这个方法就是获取crumb数据的,再在后续请求头里添加 Jenkins-Crumb: xxx
@RequestLine("GET /crumbIssuer/api/json")
String getCrumb();
- 启动Job构建接口,加入队列
此接口用于启动某个job的构建,启动成功后,会在响应header里,返回加入到Jenkins的队列序号信息。
后续需要根据这个队列序号,去查找真正的构建任务编号数据。
注1:如果该Job配置了This project is parameterized
,则必须在方法参数列表里增加这个Job的Parameter值,
如果调用时未提供对应参数,则会用空值传递给Jenkins(旧版本里会报错,说未提供某某参数,我现在的版本2.361.2不会报错,直接用空值启动)
注2:如果该Job无任何参数,请使用API:/job/jenkinsName/build
/**
* 启动job构建
* @param jenkinsName Jenkins的job名,用于url拼接
* @param pack_env job的参数1:构建环境:test/prev/prod
* @param publish_branch job的参数2:使用的git分支名
* @param is_publish job的参数3:是否直接发布
* @param version_desc job的参数4:发布说明
* @return 没有响应body,只有header,如:
* Headers:
* content-length: 0
* date: Fri, 23 Dec 2022 06:05:01 GMT
* location: http://10.100.72.165:8080/queue/item/79/
* server: Jetty(10.0.11)
* x-content-type-options: nosniff
*/
@RequestLine("POST /job/jenkinsName/buildWithParameters")
@Headers("Content-Type: application/x-www-form-urlencoded")
ResponseEntity<String> startDeploy(
@Param("jenkinsName") String jenkinsName,
@Param("pack_env") String pack_env,
@Param("publish_branch") String publish_branch,
@Param("is_publish") String is_publish,
@Param("version_desc") String version_desc
);
- 根据startDeploy方法返回的队列ID,查询真实任务构建ID(buildNum)接口
队列ID表示还在排队,并没有启动,要轮询此接口,得到buildNum再访问后续接口。
/**
* buildWithParameters方法启动构建,得到的只是QueueId,不是Job任务号BuildNum。
* Jenkins会在队列中等待一个可用的线程,再真的启动Job。
* 此方法就是根据QueueId去查询启动的Job任务号。
* 注:需要不断轮询,直到结果出现如下结果为止
* "executable":"_class":"hudson.model.FreeStyleBuild","number":142,
* @param queueId 队列id
* @return json字符串
*/
@RequestLine("GET /queue/item/queueId/api/json")
HashMap<String, Object> queueItem(@Param("queueId") String queueId);
- 根据任务构建ID(buildNum) 查询构建进度文本结果的接口
这个文本结果,用于展示Jenkins工作的中间过程数据。
/**
* 获取某一次构建的进度文本结果信息.
* 注:也可以用/logText/progressiveText接口,或 /consoleText接口,但是缺少时间数据
*
* @param jenkinsName JOB名
* @param buildNum 具体的构建序列号,可以是字符串"lastBuild",表示最后一次构建。
* @param start 从第几个字节开始获取,默认0
* @return 构建过程html数据,用于展示时,建议套上标签<pre> </pre>
*/
@RequestLine("GET /job/jenkinsName/buildNum/logText/progressiveHtml?start=start")
String getBuildProgressive(@Param("jenkinsName") String jenkinsName,
@Param("buildNum") int buildNum,
@Param("start") int start);
- 根据任务构建ID(buildNum) 查询构建是否完成的接口
如果返回值的isBuilding为false,表示构建结束了,去看看成功还是失败吧。
/**
* 获取某一次构建的进展信息。
* 需要轮询,直到结果出现:"building":false,表示构建结束
*
* @param jenkinsName JOB名
* @param number 具体的构建序列号,可以是字符串"lastBuild",表示最后一次构建。
* @return json字符串
*/
@RequestLine("GET /job/jenkinsName/number/api/json?tree=*,executor[*],actions[parameters[*]]")
JenkinsBuildInfo getBuildInfo(@Param("jenkinsName") String jenkinsName,
@Param("number") int number);
一个Jenkins JOB的完整构建代码过程
private final JenkinsFeign jenkinsFeign;
private void startProjectBuild(String jenkinsName)
ResponseEntity response = jenkinsFeign.startDeploy(jenkinsName, "test", "origin/master", "true", "aaa");
String queueId = getQueueId(response);
// 轮询,直到构建启动
int taskNum = getTaskNumber(queueId);
// 轮询,直到构建结束
waitTaskFinish(jenkinsName, taskNum);
// 获取Jenkins响应里的队列ID
private String getQueueId(ResponseEntity response)
String location = response.getHeaders().getFirst("location");
// location参考 http://10.100.72.165:8080/queue/item/37/ 最后一个数字是队列ID
String[] split = location.split("/");
return split[split.length - 1];
// 根据Jenkins的QueueId,循环查找执行中的TaskId,如果任务处于Pending状态时,会返回0
private int getTaskNumber(String queueId)
int times = 0;
while (times < 10)
ThreadHelper.sleep(3000);
HashMap<String, Object> map = jenkinsFeign.queueItem(queueId);
Object objExe = map.get("executable");
if (objExe != null)
Object ret = ((Map<String, Object>) objExe).get("number");
if (ret != null)
return (int) ret;
times++;
return 0;
// 循环等待Jenkins任务构建结束
private String waitTaskFinish(String jenkinsName, int taskNum)
while (true)
ThreadHelper.sleep(10000);
JenkinsBuildInfo info = jenkinsFeign.getBuildInfo(jenkinsName, taskNum);
if (!info.isBuilding())
return info.getResult();
附:我在用的完整FeignClient类定义
import com.zixun.zixunops.builds.jenkins.dtos.JenkinsBuildInfo;
import feign.Contract;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import java.util.HashMap;
@FeignClient(value = "jenkins", url = "$jenkins.worker.url", configuration = JenkinsFeign.MultipartSupportConfig.class)
public interface JenkinsFeign
// 直接请求后续接口会报错: Error 403 No valid crumb was included in the request
// 这个方法就是获取crumb数据的,再在后续请求头里添加 Jenkins-Crumb: xxx
@RequestLine("GET /crumbIssuer/api/json")
String getCrumb();
/**
* 启动job构建
*
* @param jenkinsName Jenkins的job名,用于url拼接
* @param pack_env job的参数1:构建环境:test/prev/prod
* @param publish_branch job的参数2:使用的git分支名
* @param is_publish job的参数3:是否直接发布
* @param version_desc job的参数4:发布说明
* @return 没有响应body,只有header,如:
* Headers:
* content-length: 0
* date: Fri, 23 Dec 2022 06:05:01 GMT
* location: http://10.100.72.165:8080/queue/item/79/
* server: Jetty(10.0.11)
* x-content-type-options: nosniff
*/
@RequestLine("POST /job/jenkinsName/buildWithParameters")
@Headers("Content-Type: application/x-www-form-urlencoded")
ResponseEntity<String> startDeploy(
@Param("jenkinsName") String jenkinsName,
@Param("pack_env") String pack_env,
@Param("publish_branch") String publish_branch,
@Param("is_publish") String is_publish,
@Param("version_desc") String version_desc
);
/**
* buildWithParameters方法启动构建,得到的只是QueueId,不是Job任务号BuildNum。
* Jenkins会在队列中等待一个可用的线程,再真的启动Job。
* 此方法就是根据QueueId去查询启动的Job任务号。
* 注:需要不断轮询,直到结果出现如下结果为止
* "executable":"_class":"hudson.model.FreeStyleBuild","number":142,
*
* @param queueId 队列id
* @return json字符串
*/
@RequestLine("GET /queue/item/queueId/api/json")
HashMap<String, Object> queueItem(@Param("queueId") String queueId);
/**
* 获取某一次构建的进度文本结果信息.
* 注:也可以用/logText/progressiveText接口,或 /consoleText接口,但是缺少时间数据
*
* @param jenkinsName JOB名
* @param buildNum 具体的构建序列号,可以是字符串"lastBuild",表示最后一次构建。
* @param start 从第几个字节开始获取,默认0
* @return 构建过程html数据,用于展示时,建议套上标签<pre> </pre>
*/
@RequestLine("GET /job/jenkinsName/buildNum/logText/progressiveHtml?start=start")
String getBuildProgressive(@Param("jenkinsName") String jenkinsName,
@Param("buildNum") int buildNum,
@Param("start") int start);
/**
* 获取某一次构建的进展信息。
* 需要轮询,直到结果出现:"building":false,表示构建结束
*
* @param jenkinsName JOB名
* @param number 具体的构建序列号,可以是字符串"lastBuild",表示最后一次构建。
* @return json字符串
*/
@RequestLine("GET /job/jenkinsName/number/api/json?tree=*,executor[*],actions[parameters[*]]")
JenkinsBuildInfo getBuildInfo(@Param("jenkinsName") String jenkinsName,
@Param("number") int number);
// 获取job的配置,可以用于备份
@RequestLine("GET /job/jenkinsName/config.xml")
String getJobConfig(@Param("jenkinsName") String jenkinsName);
// 创建job
@RequestLine("POST /createItem?name=jenkinsName")
@Headers("Content-Type: application/xml")
void createJob(@Param("jenkinsName") String jenkinsName, String config);
// 删除job
@RequestLine("POST /job/jenkinsName/doDelete")
void deleteJob(@Param("jenkinsName") String jenkinsName);
// 更新job的配置
@RequestLine("POST /job/jenkinsName/config.xml")
@Headers("Content-Type: application/x-www-form-urlencoded")
void updateJobConfig(@Param("jenkinsName") String jenkinsName, String config);
class MultipartSupportConfig
@Value("$jenkins.worker.username:")
private String jenkinsUser;
@Value("$jenkins.worker.password:")
private String jenkinsPassword;
@Bean
public Contract feignContract()
return new Contract.Default();
@Bean
public BasicAuthRequestInterceptor basicAuthorizationInterceptor()
return new BasicAuthRequestInterceptor(jenkinsUser, jenkinsPassword);
以上是关于DevOps-7:Jenkins API介绍的主要内容,如果未能解决你的问题,请参考以下文章