jenkins 构建 job 并获取其状态的实现
leoninew 原创,转载请注明来自博客园
BACKGROUND
使用 jenkins rest api 触发的 job 会先进入任务队列,然后异步执行,而无法直接获取到被触发的 job 构建记录编号。虽然 job 的描述信息中 lastBuild 字段告知了最后的构建记录,但无论是先获取 lastBuild,自增其编号作为下次构建 id,还是请求内等待 lastBuild 更新作为构建记录的做法,都存在若干问题:
- 由于构建任务延迟触发,先触发 job 构建再紧接着获取 lastBuild 的多数情况下将返回历史而非当前的构建记录,不可行。
- 以 lastBuild 编号自增作为下次 job 构建编号的做法不可靠,部分机制如插件可以修改自增步进,见 Changing Jenkins build number: If you have access to the script console (Manage Jenkins -> Script Console), then you can do this following:
Jenkins.instance.getItemByFullName("YourJobName").updateNextBuildNumber(45)
- 在多个调用方同时进行 job 构建时,无法判断谁的构建先触发,出现后续的状态/日志错位。
INVESTIGATION I
虽然 jenkins 在触发 job 构建的请求中仅返回了 201(No content),但 jenkins 提供了内置队列查询接口。另一方面阅读网页和查看 jenkins sdk 的 python 版本 python-jenkins 实现后,得知 jenkins 的 job 构建接口有以下实现细节,其中第 1、3 种情况下,返回的响应会携带 Location 字段,携带了队列编号。
- 无参:POST /job/:job-name/build,无 Content-Type 要求
$ curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:14:36 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/110/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
- 有参:POST /job/:job-name/build,要求表单格式(application/x-www-form-urlencoded),请求消息体有特殊格式要求
- 以 name+value 键值对集合作为请求参数,再进行序列化,形如
{"parameter":[{"name":"branch","value":"test"}]} - 将请求参数转义,以表单格式(application/x-www-form-urlencoded)发送,键为固定值
json。
以 curl 形式调用的命令为
$ curl -i http://localhost:8080/job/rdc-pipline/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==' -d "json=%7B%22parameter%22%3A%5B%7B%22name%22%3A%22branch%22%2C%22value%22%3A%22test%22%7D%5D%7D"
HTTP/1.1 302 Found
Date: Wed, 14 Oct 2020 09:23:36 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/job/rdc-pipline/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
贴出 fiddler 捕获结果
POST http://localhost:8080/job/rdc-pipline/build HTTP/1.1
Host: localhost:8080
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Jenkins-Crumb: d5a1f46c7e02e9633a1d73741a264fa98bc3729e1e4ebdb4974f2a5b4004afb3
Cookie: JSESSIONID.0e0c708f=node018835f7lya5y821r3looclhze104.node0
Content-Length: 93
Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==
Content-Type: application/x-www-form-urlencoded
json=%7B%22parameter%22%3A%5B%7B%22name%22%3A%22branch%22%2C%22value%22%3A%22test%22%7D%5D%7D
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:17:18 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/job/rdc-pipline/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
- 有参:POST /job/:job-name/buildWithParameters?...,无 Content-Type 要求,参数拼接到 QueryString 中,该形式对参数格式没有示例。
以 curl 形式调用的命令为
$ curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 09:26:09 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/98/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
贴出 fiddler 捕获结果
POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A HTTP/1.1
Authorization: basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==
User-Agent: PostmanRuntime/7.26.5
Accept: */*
Postman-Token: 41dda8ba-a376-44f5-b3e5-f59e8fa2fca7
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: JSESSIONID.0e0c708f=node06gr4lsp9c2wg2dg1vlgm9er496.node0
Content-Length: 0
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:23:11 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/118/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
对比和测试两种有参请求,有如下区别
| URL | 状态码 | 其他 |
|---|---|---|
| /job/:job-name/buildWithParameters | 201 | Location 携带队列编号,形如 http://localhost:8080/queue/item/118/ |
| /job/:job-name/build | 201 或 302,规律未知 | Location 仅为 Job 地址,形如 http://localhost:8080/job/rdc-pipline/ |
同时观察第一种方式 jenkins 返回的响应,可以看到出现了相同的队列编号,这表示同时提交的构建任务不会重复入队。编排一系列请求如下,以对入队不同 job 及参数变化的情况进行观察。
curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=B -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
5条 curl 命令执行以下操作
- 触发名为 demo 的 job 构建
- 触发名为 rdc-pipline 的 job 构建,使用参数 branch=A
- 触发名为 demo 的 job 构建
- 触发名为 rdc-pipline 的 job 构建,使用参数 branch=A
- 触发名为 rdc-pipline 的 job 构建,变更参数,使用参数 branch=B

可以看到分别返回了以下 Location
- http://localhost:8080/queue/item/128/
- http://localhost:8080/queue/item/129/
- http://localhost:8080/queue/item/128/
- http://localhost:8080/queue/item/129/
- http://localhost:8080/queue/item/130/
可以看到最后的参数变化生成了两条队列记录,目前为止我们有以下结论:
1. 连续触发的相同 job 构建不会重复入队
job 构建的成本因内内容不同差异很大,但触发 job 构建的成本很小,我们可以轻易地提交大量构建请求,基于以下认知可以理解 jenkins 不进行重复构建的意义:
- 虽然代码可能触发前后的短时间内变化,但这是小概率事件;
- 常规情况下我们并不需要获取多份各自独立的编译结果,将请求入队以节流方式构建是可行的;
所以 jenkins 只会为短时间内重复触发的 job 构建一次。
2. 连续触发的不同 job 构建会各自入队
虽然 jenkins 不会重复构建相同 job,但在多个 job 同时触发构建的时候,执行构建仍是必须的,入队只是前置。
为什么 jenkins 不在队列为空、资源可用的情况下避免入队环节直接构建?这里没有深入调查。
3. 参数变动的相同 job 构建将分别入队
虽然有避免重复构建相同 job 的必要,但是当参数变化时,构建仍是必须的,否则就丢失了请求,这是不能容忍的。
目前所用 sdk 只实现了基于 /job/:job-name/build 的构建接口,因入队后队列编号的意义重大,后续的讨论基于实现和使用 /job/:job-name/buildWithParameters 之上。
4. 允许同时触发构建将有数据错乱的可能
虽然参数相同的构建会被 jenkins 以排重形式处理,但参数不同时,没有区分策略可言
- user1 和 user2 准备发起相同名称但参数不同的 job 构建;
- 无论当前的构建队列是否为空,user1 和 user2 触发的构建都会入队
- 检查 job 信息,无法知晓 lastBuild 或 nextBuildNumber 的归属
participant user1
participant user2
participant jenkins
user1-->>jenkins: {...}
user2-->>jenkins: {...}
jenkins->>jenkins: enqueue and dequeue
user1->>jenkins: which job?
user2->>jenkins: which job?
5. 分布式锁强制使得入队或构建触发串行化不可行
- 强制使构建过程串行化,在计算资源及时间成本上不可接受;
- 强制使入队串行化,意味着 jenkins 的排重机制不生效,将出现构建过程串行化;
这类做法违背了 jenkins 的设计意图,加剧了服务器负载,使得响应时间延长,有导致服务器不可用的风险。
INVESTIGATION II
看起来无法把希望寄托于目前 sdk 提供的 job 信息中 lastBuild 信息上,应在队列上继续调查。阅读到 Check Jenkins job status after triggering a build remotely 后,找到可行方法,其步骤如下:
- 提交 job 构建请求到地址 /job/:job-name/buildWithParameters ,并解析响应中 Location 字段得到队列编号
- 从 /queue/item/:queue-id/api/json 检查队列信息,此时 executable 可能为空
- 间断发起轮询,直到返回的 job 携带非空的 executable
- 使用 job.executable.number 作为构建编号,从 /job/:job-name/:job-id/api/json 获取到 job 状态等。
participant user1
participant user2
participant jenkins
user1->>+jenkins: {...}
jenkins->>-user1: /queue/item/1
user2->>+jenkins: {...}
jenkins->>-user2: /queue/item/2
jenkins->>jenkins: enqueue and dequeue
user1->>+jenkins: which job for /queue/item/1?
jenkins->>-user1: /job/101
user2->>+jenkins: which job for /queue/item/2?
jenkins->>-user2: /job/102
LINQPad 的执行结果如下:

拿到的 job 即使已经出队,也可能没有第1时间被运行,至此状态字段 Result 可能为空,仍然需要刷新其任务状态的后续工作。
FUTHER MORE
虽然可以使用轮询获取到构建编号,但因不是原生手段可能有少许延迟。jenkins 提供了名为 JENKINS-CLI 的交互工具,见管理页 "Tools and Actions" 下的 Jenkins CLI 项,我的本地地址是 http://localhost:8080/cli/
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build --help
java -jar jenkins-cli.jar build JOB [-c] [-f] [-p] [-r N] [-s] [-v] [-w]
-w : Wait until the start of the command (default: false)
其中 job 构建命令支持 -w 参数等待构建触发
If you use the -w option, the command will not return until the build starts and then it will print "Started build #N"
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build demo
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build demo -w
Started demo #53
可以看到返回了 job 编号,然而以 http 形式调用并使用抓包工具检查。

发现请求并未使用其约定的地址 /job/:job-name/build 或 /job/:job-name/buildWithParameters,而是向地址 /cli?remoting=false 发起了请求,正文应是预先约定的特定格式
c
00000007000005build
b
00000006000004demo
9
00000004000002-w
a
00000005020003GBK
c
00000007010005en_US
5
0000000003
在远期开发中,该 sdk 值得深入挖掘。
SUMMARY
这里梳理了本人调查和使用 jenkins rest api 构建 job 并获取状态的过程,并试用了 jenkins cli 发现其并未调用常规 endpoints。到目前为止,jenkins 的 job 状态和日志查询已没有巨大的障碍,但许多实现路径有着各种差别,另行讨论。
leoninew 原创,转载请注明来自博客园
jenkins 构建 job 并获取其状态的实现的更多相关文章
- jenkins构建结果企业微信提醒
每当jenkin在构建之后我们想把构建结果SUCCESS/FAILURE或者其他信息通知给其他人,也许有人会说,不是有邮件提醒吗?但是我这里的环境邮件提醒的话所被通知者并不会第一时间去阅读,所以我们用 ...
- Jenkins获取编译状态
背景:在通过python的API调用Jenkins,启动Jenkins的job任务时,是需要知道Jenkins的编译状态,获取编译状态为 status=server.get_build_info(jo ...
- python回调函数应用-获取jenkins构建结果
需求背景: 现在用jenkins构建自动化测试(2个job),公司现将自动化纳入到发布系统 要求每次构建成功之后,把测试结果发送给发布系统.这就需要先获取jenkins构建的结果,如果构建结束,才能发 ...
- jira 改变issue状态触发jenkins构建/发布
目录 jira中issue状态的改变触发Jenkins构建 jira中定制新的workflow,作为jenkins发布使用流程 大家可以参考我的这个workflow 设置workflow 使用Tran ...
- jenkins+sonar发送结果邮件的状态问题修复
在我的这篇博文中:使用jenkins+sonar进行代码扫描,并发送自定义邮件 邮件的配置为默认的$PROJECT_DEFAULT_SUBJECT 所以发送的邮件标题中的状态是jenkins构建的状态 ...
- 通过jenkins-Python在后台操作Jenkins构建job
最近要开发1个接口,接收到1个指令后自动触发自动化测试,虽然也可以通过shell命令做这一步,但因为目前所有构建自动化的的动作都通过jenkins完成,所以想要尝试能不能用python去控制jenki ...
- Jenkins 实现Gitlab事件自动触发Jenkins构建及钉钉消息推送
实现Gitlab事件自动触发Jenkins构建及钉钉消息推送 实践环境 GitLab Community Edition 12.6.4 Jenkins 2.284 Post build task 1. ...
- Jenkins构建时报错:No Space left on device
Jenkins在自动化构建服务的同时也在消耗服务器的磁盘空间,如果构建的项目个数很多,而Jenkins 服务器磁盘空间又不是非常大的话,每隔一段时间磁盘空间就会爆满导致,就会出现磁盘空间不足无法构建的 ...
- Jenkins构建Android项目持续集成之findbugs的使用
Findbugs简介 关于findbugs的介绍,可以自行百度下,这里贴下百度百科的介绍.findbugs是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题 ...
随机推荐
- 编程体系结构(03):Java集合容器
本文源码:GitHub·点这里 || GitEE·点这里 一.集合容器简介 集合容器是Java开发中最基础API模块,通常用来存储运行时动态创建的元素,基本特点如下: 泛型特点,存储任意类型对象: 动 ...
- do...while 循环
语法 C 语言中 do...while 循环的语法: do { statement(s); }while( condition ); 请注意,条件表达式出现在循环的尾部,所以循环中的 statemen ...
- [Java核心技术]第九章-集合(Java集合框架、具体的集合、映射)
9.1Java集合框架 一些有的没的 可以使用接口类型存放集合的引用.一旦改变了想法,只需要在调用构造函数的地方做一处修改. add方法用于向集合添加元素,如果添加元素确实改变了集合就返回true. ...
- Spring boot中配置HikariCP连接池
# jdbc_config datasourcespring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasourc ...
- DRF序列化、认证、跨域问题
初级 #models.py from django.db import models class User(models.Model): user = models.CharField(max_len ...
- 12.扩展:向量空间模型算法(Vector Space Model)
- Registry 容器镜像服务端细节
引言 通常我们在使用集群或者容器的时候,都会接触到存储在本地的镜像,也或多或少对本地镜像存储有一定的了解.但是服务端的镜像存储细节呢?本文主要介绍容器镜像的服务端存储结构,对于自建镜像服务或是对容器镜 ...
- 【云原生下离在线混部实践系列】深入浅出 Google Borg
Google Borg 是资源调度管理和离在线混部领域的鼻祖,同时也是 Kubernetes 的起源与参照,已成为从业人员首要学习的典范.本文尝试管中窥豹,简单从<Large-scale clu ...
- 记一次Java获取本地摄像头(基于OpenCV)
OpenCV官网下载地址(下载安装后,在安装目录可以找到动态链接库和OpenCv.jar) https://opencv.org/releases/ 安装完成后,这是我的安装目录 maven 依赖(这 ...
- Java随谈(一)魔术数字、常量和枚举
本文适合对 Java 或 C 有一些了解的用户阅读,推荐阅读时间15分钟. 导言 写这个系列的原因? 我曾经听过一种说法,如果不了解Liunx的网络通讯,就很难理解理解Java的IO:如果不知道Jav ...