持續集成:Jenkins API使用方法詳細介紹


持續集成:Jenkins API簡單使用中介紹了使用Python requests 和 jenkinsapi 庫來請求Jenkins API。本文將更全面的介紹Jenkins API以及使用方法。

Jenkins API簡介

我們可以使用jenkins API來獲取jenkins平台相關信息、觸發構建、創建/刪除job等,這些API使得jenkins具備了很強的擴展性,比如集成其它平台。

Jenkins API采用的是REST架構設計風格,支持以下3種方式:

Jenkins的很多頁面都提供了遠程訪問API,你可以在當前頁面URL后添加 /api 查看當前接口說明。

除了在服務器上手動重啟jenkins外,還可以通過API進行重啟(POST請求):

  • 重啟: <Jenkins-Url>/restart,立即重啟
  • 安全重啟: <Jenkins-Url>/safeRestart,Jenkins會在所有正在運行的job完成后再重新啟動。

獲取信息

以下面這個頁面為例:

在URL后面添加 api 返回如下頁面:

XML API

在URL后添加/api/xml,效果如下:

下面介紹過濾xml信息的方法。

1、使用xpath語法

讀取測試結果:.../api/xml?xpath=/*/*[4] 或者 /api/xml?xpath=/*/action[4]

讀取用例總數totalCount:/api/xml?xpath=/*/action/totalCount 或者 /api/xml?xpath=//totalCount

還有更多的xpath語法可以使用,比如:

  • contains方法:xpath=//urlName[contains(text(),'robot')]
  • 后面的鄰居節點following-sibling:xpath=//failCount/following-sibling::*

更詳細的xpath語法介紹可參考 Web自動化測試:xpath & CSS Selector定位

2、exclude語法排除節點

排除action節點:/api/xml?exclude=/*/action

3、depth指定深度

指定深度為1:/api/xml?depth=1

4、tree參數

可以使用tree參數來提取以及組合指定字段:/api/xml?pretty=true&tree=building,displayName,id,url,previousBuild[url]

如果返回多組結果,可以指定范圍(索引從0開始):

  • {M,N}: 從第M個元素到第N個元素
  • {M,}: 從第M個元素到最后一個元素
  • {,N}: 從第1個元素到第N個元素,等價於 {0,N}
  • {N}: 第N個元素,等價於 {N,N+1}

比如下面的頁面:

獲取第1個到第2個元素:.../api/xml?tree=jobs[name]{1,3}

5、組合

可以使用 & 來組合多個表達式,比如:/api/xml?exclude=/*/action&xpath=//fullDisplayName

6、wrapper方法整合多個結果

過濾后可能有多個結果,需要使用wrapper方法來組合,例如:/api/xml?depth=2&xpath=//fullDisplayName&wrapper=jobname

JSON API

JSON API返回json樣式的數據,在URL后添加/api/json。它也支持depth參數,默認為最小深度。

來看一下效果:...3/api/json?pretty=true

JSON API也可以使用tree參數來過濾:/api/json?pretty=true&tree=building,displayName,id,url,previousBuild[url]

請求信息

由於jenkins API采用的是REST架構風格,所以支持REST API的請求方法,比如常用的POST、GET、DELETE等方法。

獲取job的構建Number的API:

  • 獲取最近的job buildNumber: <Jenkins-Url>/job/<Job-Name>/lastBuild/buildNumber
  • 最近完成構建job的buildNumber: <Jenkins-Url>/job/<Job-Name>/lastCompletedBuild/buildNumber
  • 最近失敗job的 buildNumber:<Jenkins-Url>/job/<Job-Name>/lastFailedBuild/buildNumber
  • 最近穩定job的buildNumber:<Jenkins-Url>/job/<Job-Name>/lastStableBuild/buildNumber
  • 最近成功job的buildNumber:<Jenkins-Url>/job/<Job-Name>/lastSuccessfulBuild/buildNumber
  • 最近不成功(除成功以外的狀態)job的buildNumber:<Jenkins-Url>/job/<Job-Name>/lastUnsuccessfulBuild/buildNumber

介紹兩種方式請求api,第一種方法是curl工具,第二種是Python的requests庫。

curl請求

使用如下curl命令發送GET請求:

$ curl -k --silent -L --user 用戶名:API Token JENKINS_URL/job/JOB_NAME

API Token可以在用戶配置中生成。

還是以下面這個頁面為例:

1、解析xml結果

使用xpath過濾robot測試結果:/api/xml?xpath=//action[@_class='hudson.plugins.robot.RobotBuildAction']

shell腳本中,可以對返回的信息進行進一步處理,比如,讀取totalCount:

$ curl -k --silent -L --user admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/view/demo/job/RF-Pipeline-Demo/3/api/xml?xpath=//action[@_class=\'hudson.plugins.robot.RobotBuildAction\'] | tr '<' '\n' | egrep '^totalCount>' | sed 's/.*>//g'
4
$ 

讀取 failCount 和 totalCount:

$ curl -k --silent -L --user admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/view/demo/job/RF-Pipeline-Demo/3/api/xml?xpath=//action[@_class=\'hudson.plugins.robot.RobotBuildAction\'] | tr '<' '\n' | egrep '^totalCount>|^failCount>' | sed 's/>/:/g'  | sed -e '1s/$/,/g' | tr -d '\n'
failCount:0,totalCount:4

在需要讀取多個值的時候,可以一個一個的請求,我的習慣是只請求一次,然后進行二次處理,比如在groovy腳本中:

// curl讀取測試結果
def WORKFLOW_XML = sh ( script: "API_XML=\$(curl -k --silent -L --user admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/view/demo/job/RF-Pipeline-Demo/3/api/xml?xpath=//action[@_class=\'hudson.plugins.robot.RobotBuildAction\'] | tr '<' '\n' | egrep '^totalCount>|^failCount>' | sed 's/>/:/g'  | sed -e '1s/\$/,/g' | tr -d '\n'); echo \${API_XML}", returnStdout: true ).trim();

rf_results = WORKFLOW_XML.split(",");
if (rf_results[0]) {
    for ( res in rf_results ) {
        if (res.contains("failCount")) {
            failCount = res.split("failCount:")[1];
        }
        if (res.contains("totalCount")) {
            totalCount = res.split("totalCount:")[1];
        }
    }
    passCount = totalCount.toInteger() - failCount.toInteger();
    passPercentage = passCount/totalCount.toInteger() * 100;
    passPercentage = Math.round(passPercentage * 100) / 100; // 保留2位小數
    passRatio = passCount + " / " + totalCount;

} else {
    passPercentage = 'null'; 
    passRatio = 'null'; 
}

2、解析json結果

獲取json格式數據和xml類似,json數據的進一步提取可以結合jq命令來使用。這里介紹一個在groovy中處理json數據的方法。

groovy腳本中可以使用readJSON 方法將json字符串反序列化為json對象,然后提取對應的值。

注意:在jenkins pipeline中使用readJSON 方法需要安裝插件 Pipeline Utility Steps Plugin

代碼如下:

def WORKFLOW_JSON = sh ( script: "API_JSON=\$(curl -k --silent -L --user admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/view/demo/job/RF-Pipeline-Demo/3/api/json); echo \${API_JSON}", returnStdout: true ).trim()
def jsonObj = readJSON text: WORKFLOW_JSON;   
def failCount = (jsonObj.actions.failCount - null)[0]; // 去掉list中的null
def totalCount = (jsonObj.actions.totalCount - null)[0]; 

Python requests

介紹第二種請求REST API的工具Python requests庫,舉幾個例子:

獲取build number:

import requests

url = "http://192.168.30.8:8080/job/RF-Pipeline-Demo/lastBuild/buildNumber"
ret = requests.get(url)
print(ret.text)

讀取failCount:

import json
import requests

url = "http://192.168.30.8:8080/view/demo/job/RF-Pipeline-Demo/3/api/json"
ret = requests.get(url)
res = ret.json()
print(res["actions"][3]["failCount"])
print(json.dumps(ret.json(),indent=2))

查看當前job是否正在構建:

def checkBuilding(self,jobname):
    # 查看當前job是否正在構建
    url = "http://192.168.30.8:8080/job/{jobname}/api/json?tree=lastCompletedBuild[number],lastBuild[number]"
    res = requests.get(url)
    res = res.json()
    if res["lastBuild"]["number"] == res["lastCompletedBuild"]["number"]:
        return False
    else:
        print(f"{jobname}正在構建中...")
        return True

觸發構建

通常使用POST請求在觸發構建Job,請求地址為:<jenkins url>/job/<job name>/build

無參數構建

1、curl命令

$ curl -k --silent -L -X POST --USER admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/job/RF-Pipeline-Demo/build

2、Python requests庫

import requests
url = "http://192.168.30.8:8080/job/RF-Pipeline-Demo/build"
username = "admin"
api_token = "11f8790b23a9983c0a218ba125aa855f61"
res = requests.post(url, auth=(username, api_token), verify=False)

參數化構建

對RF-Pipeline-Demo進行參數化構建配置,添加兩個字符串參數param1和param2。

注意:參數化構建功能需要安裝插件 Parameterized Trigger

1、curl命令

# 字符參數
$ curl -k --silent -L -X POST --data param1=1 --data param2=2 --user admin:11f8790b23a9983c0a218ba125aa855f61 http://192.168.30.8:8080/job/RF-Pipeline-Demo/buildWithParameters
$ 

2、Python requests庫

import requests
jenkins_params = {'param1': 'value11',
                  'param2': 'value22'}
url = "http://192.168.30.8:8080/job/RF-Pipeline-Demo/buildWithParameters"
username = "admin"
api_token = "11f8790b23a9983c0a218ba125aa855f61"
res = requests.post(url, auth=(username, api_token), params=jenkins_params, verify=False)

刪除操作

刪除操作使用POST請求:

  • 刪除Job某次構建:<Jenkins-Url>/job/<Job-Name>/<buildNumber>/doDelete
  • 刪除Job:<Jenkins-Url>/job/<Job-Name>/doDelete,POST請求

禁用和啟用Job:

  • 禁用指定job: <Jenkins-Url>/job/<Job-Name>/disable
  • 啟用指定job: <Jenkins-Url>/job/<Job-Name>/enable

前面已經介紹了常見的Jenkins API以及使用方法,Python、Ruby和Java都對jenkins API 進行了封裝,使我們使用起來更加方便。下面簡單介紹Python JenkinsAPI庫的使用方法。

Python API

可以在URL后添加 /api/python 獲取json格式數據:/api/python?pretty=true&tree=building,displayName,id,url,previousBuild[url]

JenkinsAPI, Python-Jenkins, api4jenkins, aiojenkins等Python庫封裝了許多jenkins REST API,下面以JenkinsAPI庫為例。

直接來看代碼:

import datetime
from jenkinsapi.jenkins import Jenkins

class JenkinsApiDemo:
    def __init__(self, job_name, chose='jenkins'):
        self.jenkins_host = '192.168.30.8'
        self.jenkins_port = '8080'
        self.username = 'admin'
        self.pwd = 'admin'
        self.jenkins_url = f"http://{self.jenkins_host}:{self.jenkins_port}"
        self.job_name = job_name
        self.jenkins_server = Jenkins(self.jenkins_url, username=self.username, password=self.pwd, useCrumb=True)

    def GetJenkinsVersion(self):
        print(self.jenkins_server.version)
        return self.jenkins_server.version

    def jobBuild(self):
        if self.jenkins_server.has_job(self.job_name):
            myjob = self.jenkins_server.get_job(self.job_name)
            if not myjob.is_queued_or_running():
                self.jenkins_server.build_job(self.job_name)
    def disableJob(self):
        """Disable a Jenkins job"""
        if self.jenkins_server.has_job(self.job_name):
            job_instance = self.jenkins_server.get_job(self.job_name)
            job_instance.disable()
            print('Job %s Is Enabled ?:%s' % (self.job_name, job_instance.is_enabled()))

    def enableJob(self):
        """Disable a Jenkins job"""
        if self.jenkins_server.has_job(self.job_name):
            job_instance = self.jenkins_server.get_job(self.job_name)
            job_instance.enable()
            print('Job %s Is Enabled ?:%s' % (self.job_name, job_instance.is_enabled()))
            
    def getJobInfo(self):
        if self.jenkins_server.has_job(self.job_name):
            myjob = self.jenkins_server.get_job(self.job_name)
            if not myjob.is_queued_or_running():
                last_buildnumber = myjob.get_last_buildnumber()
                print("last_buildnumber: ", last_buildnumber)

                last_build = myjob.get_build(last_buildnumber)
                # 獲取開始時間
                start_time = last_build.get_timestamp() + datetime.timedelta(hours=8)
                print("start_time: ", start_time)
                print("status: ", last_build.get_status())
                print("build_url: ", last_build.get_build_url())
                print("duration: ", last_build.get_duration())
                print("causes: ", last_build.get_causes()[0]["shortDescription"])
                print("change: ", last_build.get_changeset_items())
                print("console_log: ", last_build.get_console())
            else:
                print(self.job_name + " is running")
        else:
            print("沒有 " + self.job_name + " 這個job")

if __name__ == '__main__':
    jobname = "RF-Pipeline-Demo"
    jk = JenkinsApiDemo(jobname)
    jk.GetJenkinsVersion()
    # jk.jobBuild()
    jk.disableJob()
    jk.enableJob()
    jk.getJobInfo()

執行結果:

2.326
last_buildnumber:  20
start_time:  2022-01-20 18:22:12+00:00
status:  SUCCESS
build_url:  http://192.168.30.8:8080/job/RF-Pipeline-Demo/20/
duration:  0:00:02.620000
causes:  Started by user admin
change:  []
console_log:  Started by user admin
[Pipeline] Start of Pipeline
[Pipeline] node
.............

上述代碼僅展示了部分方法,還可以創建、刪除、復制job,創建、刪除節點,重啟等。

jenkins_server.create_job(jobname, xml)
jenkins_server.copy_job(jobname, newjobname)
jenkins_server.create_node(name)
jenkins_server.delete_job(jobname)
jenkins_server.delete_node(nodename)
jenkins_server.safe_restart()
jenkins_server.shutdown()

更多方法可以查看庫的相關函數。

jenkins API使用示例

舉一個jenkins API使用實例。

1、觸發構建

$ curl -i -L -X POST --USER admin:1142ea7ea6919fadad76b2e0057ec01e17 http://192.168.168.228:8080/view/demo/job/PipelineDemo/build
HTTP/1.1 201 Created
Date: Thu, 15 Dec 2022 07:12:57 GMT
X-Content-Type-Options: nosniff
Location: http://192.168.168.228:8080/queue/item/449/
Content-Length: 0
Server: Jetty(9.4.45.v20220203)

通過響應數據可以知道當前構建的queue number為449。

2、查看執行狀態

查看構建的隊列狀態

$ curl -i -L --user admin:1142ea7ea6919fadad76b2e0057ec01e17 http://192.168.168.228:8080/queue/item/449/api/json
HTTP/1.1 404 Not Found
X-Content-Type-Options: nosniff
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html;charset=iso-8859-1
Content-Length: 473
Server: Jetty(9.4.45.v20220203)

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 404 Not Found</title>
</head>
<body><h2>HTTP ERROR 404 Not Found</h2>
<table>
<tr><th>URI:</th><td>/queue/item/449/api/json</td></tr>
<tr><th>STATUS:</th><td>404</td></tr>
<tr><th>MESSAGE:</th><td>Not Found</td></tr>
<tr><th>SERVLET:</th><td>Stapler</td></tr>
</table>
<hr/><a href="https://eclipse.org/jetty">Powered by Jetty:// 9.4.45.v20220203</a><hr/>

</body>
</html>

返回404表明隊列不存在了,可能是已經執行完了。

3、讀取build number和構建結果

通過queue number讀取對應的build number和構建結果:

$ curl -k -L --user admin:1142ea7ea6919fadad76b2e0057ec01e17 "http://192.168.168.228:8080/job/PipelineDemo/api/xml?tree=builds\[id,number,result,queueId\]&xpath=//build\[queueId=449\]"
<build _class="org.jenkinsci.plugins.workflow.job.WorkflowRun"><id>62</id><number>62</number><queueId>449</queueId><result>SUCCESS</result></build>

可以得到build number為62,result為SUCCESS。

4、讀取構建日志

根據build number讀取構建日志

$ curl -k -L --user admin:1142ea7ea6919fadad76b2e0057ec01e17 http://192.168.168.228:8080/job/PipelineDemo/62/consoleText
Started by user admin
Loading library pipelinelib@1.0
Opening connection to https://192.168.100.21/svn/attrobot-testcase/MX_release/pipelinelib/
Updating https://192.168.100.21/svn/attrobot-testcase/MX_release/pipelinelib/1.0@940 at revision 940
Using sole credentials admin/****** (https://192.168.100.21/svn/) in realm ‘<https://192.168.100.21:443> VisualSVN Server’
At revision 940

No changes for https://192.168.100.21/svn/attrobot-testcase/MX_release/pipelinelib/1.0 since the previous build
Loading library pipelinelib2@1.0
Opening connection to https://192.168.100.21/svn/attrobot-testcase/MX_release/pipelinelib/
Updating https://192.168.100.21/svn/attrobot-testcase/MX_release/pipelinelib/1.0@940 at revision 940
Using sole credentials admin/****** (https://192.168.100.21/svn/) in realm ‘<https://192.168.100.21:443> VisualSVN Server’
At revision 940

[Pipeline] Start of Pipeline
[Pipeline] node
Still waiting to schedule task
Waiting for next available executor on ‘win_98.178’
Running on win_98.178 in D:/jenkins/workspace/PipelineDemo
[Pipeline] {
[Pipeline] ws
Running in D:/jenkins
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (initialize)
[Pipeline] echo
init
[Pipeline] bat

D:\jenkins>echo hello
hello
[Pipeline] script
[Pipeline] {

............

[Pipeline] End of Pipeline
Finished: SUCCESS

在第2步查看執行狀態時,可能隊列還存在:

$ curl -i -L --user admin:1142ea7ea6919fadad76b2e0057ec01e17 http://192.168.168.228:8080/queue/item/453/api/json
HTTP/1.1 200 OK
Date: Thu, 15 Dec 2022 07:34:44 GMT
X-Content-Type-Options: nosniff
X-Jenkins: 2.337
X-Jenkins-Session: 02235fb0
X-Frame-Options: deny
Content-Type: application/json;charset=utf-8
Content-Length: 696
Server: Jetty(9.4.45.v20220203)

{"_class":"hudson.model.Queue$LeftItem","actions":[{"_class":"hudson.model.CauseAction","causes":[{"_class":"hudson.model.Cause$UserIdCause","shortDescription":"Started by user admin","userId":"admin","userName":"admin"}]}],"blocked":false,"buildable":false,"id":453,"inQueueSince":1671089427803,"params":"","stuck":false,"task":{"_class":"org.jenkinsci.plugins.workflow.job.WorkflowJob","name":"PipelineDemo","url":"http://192.168.168.228:8080/job/PipelineDemo/","color":"blue"},"url":"queue/item/453/","why":null,"cancelled":false,"executable":{"_class":"org.jenkinsci.plugins.workflow.job.WorkflowRun","number":64,"url":"http://192.168.168.228:8080/job/PipelineDemo/64/"}}

返回信息中有executable字段:

"executable":{"_class":"org.jenkinsci.plugins.workflow.job.WorkflowRun","number":64,"url":"http://192.168.168.228:8080/job/PipelineDemo/64/"}

通過里面的url(包含了build number)來讀取構建狀態:

C:\Users\DELL>curl -i -L --user admin:1142ea7ea6919fadad76b2e0057ec01e17 http://192.168.168.228:8080/job/PipelineDemo/64/api/json?pretty=true
HTTP/1.1 200 OK
Date: Thu, 15 Dec 2022 07:43:23 GMT
X-Content-Type-Options: nosniff
X-Jenkins: 2.337
X-Jenkins-Session: 02235fb0
X-Frame-Options: deny
Content-Type: application/json;charset=utf-8
Content-Length: 1704
Server: Jetty(9.4.45.v20220203)

{
  "_class" : "org.jenkinsci.plugins.workflow.job.WorkflowRun",
  "actions" : [
    {
      "_class" : "hudson.model.CauseAction",
      "causes" : [
        {
          "_class" : "hudson.model.Cause$UserIdCause",
          "shortDescription" : "Started by user admin",
          "userId" : "admin",
          "userName" : "admin"
        }
      ]
    },
    
...................

    {

    }
  ],
  "artifacts" : [

  ],
  "building" : false,
  "description" : null,
  "displayName" : "#64",
  "duration" : 88477,
  "estimatedDuration" : 54084,
  "executor" : null,
  "fullDisplayName" : "PipelineDemo #64",
  "id" : "64",
  "keepLog" : false,
  "number" : 64,
  "queueId" : 453,
  "result" : "SUCCESS",
  "timestamp" : 1671089435140,
  "url" : "http://192.168.168.228:8080/job/PipelineDemo/64/",
  "changeSets" : [

  ],
  "culprits" : [

  ],
  "nextBuild" : null,
  "previousBuild" : {
    "number" : 63,
    "url" : "http://192.168.168.228:8080/job/PipelineDemo/63/"
  }
}

查看返回信息中的building字段是否為true,為true表示正在執行。為false表示執行完成,可以讀取構建結果,result字段的值。

--THE END--

上德不德,是以有德;下德不失德,是以無德。——《道德經》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM