【背景】:部門在搞持續集成,使用jenkins做爲核心調度,要再其基礎上進行二次封裝,因此須要研究下jenkins的api。筆者主要負責搭建平臺,在研究用法也花費了些時間,本文主要作個簡要的記錄,但願能爲各位朋友節省時間。html
【環境】:(默認讀者已經具有了基本的持續集成經驗和jenkins用法)java
1. Jenkins1.455 node
2. 系統Suse
python
3. Tomcat 6.0.37數據庫
4. Java 1.6.0_26json
5. patch-parameter api
【API介紹】
curl
Jenkins提供了html、json、python API,實質都是以http get/post方式調用的。查看http://www.xxx.xxx/jenkins/api/ 便可獲得相應的說明,如圖:svn
【API實踐】post
1.建立
curl -X POST http://www.xxx.xxx/jenkins/createItem?name=JavaStd --user peterguo:peterguo --data-binary "@javastd.config.xml" -H "Content-Type: text/xml"
2.禁用
curl -X POST http://www.xxx.xxx/jenkins/job/JavaStd/disable --user peterguo:peterguo
3.啓用
curl -X POST http://www.xxx.xxx/jenkins/job/JavaStd/enable --user peterguo:peterguo
4.刪除
curl -X POST http://www.xxx.xxx/jenkins/job/JavaStd/doDelete --user peterguo:peterguo
5.獲取項目描述
curl -X GET http://www.xxx.xxx/jenkins/job/JavaStd/description --user peterguo:peterguo
6.獲取配置文件
curl -X GET http://www.xxx.xxx/jenkins/job/JavaStd/config.xml --user peterguo:peterguo
7.觸發SCM檢查
curl -X GET http://www.xxx.xxx/jenkins/job/JavaStd/polling --user peterguo:peterguo
8.普通觸發
curl -X GET http://www.xxx.xxx/jenkins/job/JavaStd/build --user peterguo:peterguo
9.帶參數觸發
curl -X GET "http://www.xxx.xxx/jenkins/job/helloworld-freestyle/buildWithParameters?bAllTest=&Choices=2&strParam=abc" --user peterguo:peterguo
10.帶文件觸發
curl -X POST "http://localhost:8080/job/Demo/buildWithParameters?assertMethod=online" -F "input=@atest.txt"
11.參數和補丁觸發
curl -X POST "http://www.xxx.xxx/jenkins/job/helloworld-freestyle/buildWithParameters?bAllTest=&Choices=2&strParam=abc" --user peterguo:peterguo -F "action=upload" -F "patch.diff=@OtherTest.java.patch"
注:帶補丁觸發須要先安裝補丁插件,並設置項目的補丁參數
【經過Python-curl庫調用】
提供python使用pycurl調用的例子片斷,實際和curl調用同樣,優勢是易整合。
import pycurl url = "http://10.129.145.112:8081/jenkins/job/helloworld-freestyle/config.xml" crl = pycurl.Curl() crl.setopt(pycurl.VERBOSE,1) crl.setopt(pycurl.FOLLOWLOCATION, 1) crl.setopt(pycurl.MAXREDIRS, 5) crl.setopt(pycurl.USERPWD, "peterguo:peterguo") crl.setopt(pycurl.CONNECTTIMEOUT, 60) crl.setopt(pycurl.TIMEOUT, 300) crl.setopt(pycurl.HTTPPROXYTUNNEL,1) crl.fp = StringIO.StringIO() crl.setopt(pycurl.URL, url) crl.setopt(crl.WRITEFUNCTION, crl.fp.write) crl.perform() ret = crl.fp.getvalue()
【經過Python-jenkinsapi庫調用】
這裏給出代碼
from jenkinsapi.jenkins import Jenkins class CJenkinsAPI(): ''' 均採用同步設置超時機制 建立項目:輸入:configid planid 建立任務:輸入:configid planid 返回:返回碼 msg buildid 額外動做:不寫SQL 查詢任務:輸入:configid planid taskid 返回:返回碼 msg buildid 額外動做:結束更新SQL(包括成功或失敗),未結束則不處理 終止任務:輸入:configid planid taskid 返回:返回碼 msg buildid 額外動做:終止成功寫SQL ''' __doc__ = '''Usage: \t\tCJenkinsAPI.createProject\t\tCJenkinsAPI.triggerBuild\t\t''' _strConfigTemplatePath = "" _strConfigDataPath = "" def __init__(self): import pycurl pass def __del__(self): pass @staticmethod def createProject(nPlanId, strConfigId): ''' Return:返回碼/返回信息 先根據配置文件生成項目 ''' #用於測試 nPlanId = 14 strConfigId = "D1057406" #返回 nRet, strMsg, nBuild = 0, "", 0 #配置文件模版 strConfigTemplate = CJenkinsAPI._strConfigTemplatePath + "/config.template.xml" #用planID和配置ID做爲項目名 strProjectName = "P%d-%s" % (nPlanId, strConfigId ) #訪問數據庫拿到構建節點IP和SVN strBuildNodeIP = "" strProjectSVN = "" oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") #SVN從t_build_plan中取 lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_build_plan", ["f_svn"], " where f_plan_id='%d' " % nPlanId) strProjectSVN = lSelectRet[0]["f_svn"] #配置信息從t_ci_config中取 lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_ci_config", ["f_node_ip", "f_run_param"], " where f_config_id='%s' " % strConfigId) strBuildNodeIP = lSelectRet[0]["f_node_ip"] strRunParam = lSelectRet[0]["f_run_param"] oProxy.close() strNodeFlag = {True:"", False:"slave_"}["10.129.145.112" in strBuildNodeIP] dReplaceInfo = {"PROJ_DESC" : "This is generate by Ci-plat, with plan_id[%d] and config_id[%s]" % (nPlanId, strConfigId),\ "SVN_URL" : strProjectSVN.replace("http://tc-svn.tencent.com", "svn://172.27.198.49:8081"),\ "BUILD_NODE_IP" : strNodeFlag + strBuildNodeIP,\ "BUILD_SCRIPT" : '''sh build.sh %s ''' % strRunParam, \ "JUNIT_TEST_PATH" :"target/surefire-reports/*.xml", \ "COVERAGE_PATH" :"target/site/cobertura/", \ } #利用模版生成配置文件 oConf = CCiConfig(strConfigTemplate, dReplaceInfo) strCurConfigXml = CJenkinsAPI._strConfigDataPath + "/" + strProjectName + ".config.xml" oConf.dump2file(strCurConfigXml) strCommand = 'curl -X POST http://10.129.145.112:8081/jenkins/createItem?name=%s --user peterguo:peterguo --data-binary "@%s" -H "Content-Type: text/xml"' % (strProjectName, strCurConfigXml) nRet = os.system(strCommand) strMsg = {True:"SucceedCreate,Url:", False:"FailedCreate,Url:"}[nRet==0] print "%d|%s|%d" % (nRet, tran2UTF8(strMsg)+"[http://10.129.145.112:8081/jenkins/job/%s]" % strProjectName, nBuild) @staticmethod def triggerBuild(nPlanId, strConfigId): ''' Return: 觸發前先更新配置文件,使用遠程腳本 觸發前獲取要出發的編號 ''' #返回 nRet, strMsg, nBuild = 0, "", 0 #配置文件模版 strConfigTemplate = CJenkinsAPI._strConfigTemplatePath + "/config.template.xml" #用planID和配置ID做爲項目名 strProjectName = "P%d-%s" % (nPlanId, strConfigId ) #訪問數據庫拿到構建節點IP和SVN strBuildNodeIP = "" strProjectSVN = "" oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") #SVN從t_build_plan中取 lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_build_plan", ["f_svn"], " where f_plan_id='%d' " % nPlanId) strProjectSVN = lSelectRet[0]["f_svn"] #配置信息從t_ci_config中取 lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_ci_config", ["f_node_ip", "f_run_param"], " where f_config_id='%s' " % strConfigId) strBuildNodeIP = lSelectRet[0]["f_node_ip"] strRunParam = lSelectRet[0]["f_run_param"] oProxy.close() strNodeFlag = {True:"", False:"slave_"}["10.129.145.112" in strBuildNodeIP] dReplaceInfo = {"PROJ_DESC" : "This is generate by Ci-plat, with plan_id[%d] and config_id[%s]" % (nPlanId, strConfigId),\ "SVN_URL" : strProjectSVN.replace("http://tc-svn.tencent.com", "svn://172.27.198.49:8081"),\ "BUILD_NODE_IP" : strNodeFlag + strBuildNodeIP,\ "BUILD_SCRIPT" : '''sh build.sh %s ''' % strRunParam, \ "JUNIT_TEST_PATH" :"target/surefire-reports/*.xml", \ "COVERAGE_PATH" :"target/site/cobertura/", \ } #利用模版生成配置文件 oConf = CCiConfig(strConfigTemplate, dReplaceInfo) strCurConfigXml = CJenkinsAPI._strConfigDataPath + "/" + strProjectName + ".config.xml" oConf.dump2file(strCurConfigXml) #更新配置文件 strCommand = 'curl -X POST http://10.129.145.112:8081/jenkins/job/%s/config.xml --user peterguo:peterguo --data-binary "@%s" -H "Content-Type: text/xml"' % (strProjectName, strCurConfigXml) nRet = os.system(strCommand) strMsg += {True:"更新配置成功", False:"更新配置失敗"}[nRet==0] #獲取下一次構建編號 nBuild = Jenkins("http://10.129.145.112:8081/jenkins","peterguo","peterguo")[strProjectName.encode("utf8")].get_next_build_number() #觸發構建 strCommand = 'curl -X POST http://10.129.145.112:8081/jenkins/job/%s/build --user peterguo:peterguo ' % (strProjectName) nRet = os.system(strCommand) strMsg = {True:"SucceedTrigger,Url:", False:"FailedTrigger,Url:"}[nRet==0] print "%d|%s|%d" % (nRet, tran2UTF8(strMsg)+"[http://10.129.145.112:8081/jenkins/job/%s/%d]" % (strProjectName, nBuild), nBuild ) @staticmethod def infoBuild(nPlanId, strConfigId, nTaskId): ''' Return: ''' strProjectName = "P%d-%s" % (nPlanId, strConfigId ) oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") strWhere = " where f_task_id='%d' " % int(nTaskId) lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_build_task", ["f_build_id"], strWhere) oProxy.close() nBuildId = int(lSelectRet[0]["f_build_id"]) oCurBuild = Jenkins("http://10.129.145.112:8081/jenkins","peterguo","peterguo")[strProjectName.encode("utf8")].get_build(nBuildId) bRunning = oCurBuild.is_running() if bRunning == True: print "1|Running|%d" % nBuildId return #最重要更新的數據 dResult2Sql = {} #取測試用例結果的個數信息 if oCurBuild.has_resultset(): dResult = oCurBuild.get_actions() else: dResult = {"failCount":0, "totalCount":0, "skipCount":0} oDeltaDur = oCurBuild.get_duration() oBuildBegin = utc2LocalDatetime(oCurBuild.get_timestamp()) oBuildEnd = oBuildBegin + oDeltaDur dResult2Sql["f_case_fail"] = dResult['failCount'] dResult2Sql["f_case_total"] = dResult['totalCount'] dResult2Sql["f_case_skip"] = dResult['skipCount'] dResult2Sql["f_build_duration"] = "%.3f" % (oDeltaDur.days * 24 * 60 + oDeltaDur.seconds / 60.0) dResult2Sql["f_build_url"] = oCurBuild.baseurl dResult2Sql["f_build_result"] = {True:0, False:1}[oCurBuild.is_good()] dResult2Sql["f_task_status"] = TASK_DONE dResult2Sql["f_build_time"] = oBuildBegin.strftime("%Y-%m-%d %H:%M:%S") dResult2Sql["f_build_end"] = oBuildEnd.strftime("%Y-%m-%d %H:%M:%S") dResult2Sql["f_msg_info"] = tran2GBK("任務完成,收集數據完成") #任務已經完成,須要入庫相關數據,更新相關狀態 oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") strWhere = " where f_task_id='%d' " % int(nTaskId) CSqlProxy.updateValueToDBTable(oProxy.m_oCon, "t_build_task", dResult2Sql.keys(), dResult2Sql.values(), strWhere) oProxy.close() #for item in dResult2Sql.items(): # print item[0], str(item[1]) print "%d|%s|%d" % (0, "SucceedUpdated", nBuildId) @staticmethod def stopBuild(nPlanId, strConfigId, nTaskId): ''' Return: ''' strProjectName = "P%d-%s" % (nPlanId, strConfigId ) oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") strWhere = " where f_task_id='%d' " % int(nTaskId) lSelectRet = CSqlProxy.selectFromTable(oProxy.m_oCon, "t_build_task", ["f_build_id"], strWhere) oProxy.close() nBuildId = int(lSelectRet[0]["f_build_id"]) oCurBuild = Jenkins("http://10.129.145.112:8081/jenkins","peterguo","peterguo")[strProjectName.encode("utf8")].get_build(nBuildId) bRunning = oCurBuild.is_running() if bRunning == False: print "2|AlreadyStopped|%d" % nBuildId return #觸發中止命令 oCurBuild.stop() #等中止 oCurBuild.block_until_complete() #最重要更新的數據 dResult2Sql = {} #取測試用例結果的個數信息 if oCurBuild.has_resultset(): dResult = oCurBuild.get_actions() else: dResult = {"failCount":0, "totalCount":0, "skipCount":0} oDeltaDur = oCurBuild.get_duration() oBuildBegin = utc2LocalDatetime(oCurBuild.get_timestamp()) oBuildEnd = oBuildBegin + oDeltaDur dResult2Sql["f_case_fail"] = dResult['failCount'] dResult2Sql["f_case_total"] = dResult['totalCount'] dResult2Sql["f_case_skip"] = dResult['skipCount'] dResult2Sql["f_build_duration"] = "%.3f" % (oDeltaDur.days * 24 * 60 + oDeltaDur.seconds / 60.0) dResult2Sql["f_build_url"] = oCurBuild.baseurl dResult2Sql["f_build_result"] = {True:0, False:1}[oCurBuild.is_good()] dResult2Sql["f_task_status"] = TASK_ABORTED dResult2Sql["f_build_time"] = oBuildBegin.strftime("%Y-%m-%d %H:%M:%S") dResult2Sql["f_build_end"] = oBuildEnd.strftime("%Y-%m-%d %H:%M:%S") dResult2Sql["f_msg_info"] = tran2GBK("TaskStopped") #任務已經完成,須要入庫相關數據,更新相關狀態 oProxy = CSqlProxy("10.129.145.112", "ci_test", "ci_test", "ci_plat") strWhere = " where f_task_id='%d' " % int(nTaskId) CSqlProxy.updateValueToDBTable(oProxy.m_oCon, "t_build_task", dResult2Sql.keys(), dResult2Sql.values(), strWhere) oProxy.close() #for item in dResult2Sql.items(): # print item[0], str(item[1]) print "%d|%s|%d" % (1, tran2UTF8("SucceedStopped"), nBuildId) @staticmethod def deleteProject(nPlanId, strConfigId): ''' Return:返回碼/返回信息 curl -X POST http://10.129.145.112:8081/jenkins/job/JavaStd/doDelete --user peterguo:peterguo ''' strProjectName = "P%d-%s" % (nPlanId, strConfigId ) strCommand = 'curl -X POST http://10.129.145.112:8081/jenkins/job/%s/doDelete --user peterguo:peterguo' % (strProjectName) CColorPrint.colorPrintStr("CMD:[%s]" % strCommand, "green") nRet = os.system(strCommand) strMsg = {True:"SucceedDeleted", False:"FailedDeleted"}[nRet==0] print nRet, tran2UTF8(strMsg), 0