Problem
為了節省軟體的反安裝、下載與安裝時間,我們有隻script,會去jenkins抓某個固定位置的最新安裝程式並安裝。然而,開發過程會因為新功能或修bug等原因產生branch;原本的腳本並無法根據branch去下載安裝程式,也因此花了些時間去研究並解決這個問題。
How to?
jenkins既然有RestAPI,應該就有辦法讓人可以存取到它專案相關資訊吧? 為了達到我們目的,其中會包含幾個步驟:
- 取得專案build列表。
- 取得branch吻合的build URL。
- 取得此build的artifact列表。
- 過濾想要的artifacts。
以專案名稱Example為例,其專案的URL為:
http://tonylin.idv/job/Example
取得專案build列表
首先可以透過此URL去搜尋所有build資訊:
http://tonylin.idv/job/Example/api/json?tree=builds[*]
以一個build的結果如下:
{ "builds": [ { "actions": [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ], "artifacts": [ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ], "building": false, "description": "origin/integration_testing", "displayName": "#6611", "duration": 375922, "estimatedDuration": 366451, "executor": null, "fullDisplayName": "Example #6611", "id": "6611", "keepLog": true, "number": 6611, "queueId": 335, "result": "SUCCESS", "timestamp": 1481712191068, "url": "http://tonylin.idv/job/Example/6611/", "builtOn": "", "changeSet": {}, "culprits": [ {}, {}, {}, {}, {}, {} ], "fingerprint": [] } ] }
取得branch吻合的build URL
由於前一個URL使用[*]是列出所有項目,但其實我們只需要知道branch、build結果、build URL與build number,可以透過以下URL去做query:
http://tonylin.idv/job/Example/api/json?tree=builds[description,result,displayName,url]
三個build的輸出結果如下,從結果內容不難得知個別資訊意義,需注意的是master的description會是null或master(我們使用dscription當branch名稱):
{ "builds": [ { "description": "origin/integration_testing", "displayName": "#6611", "result": "SUCCESS", "url": "http://tonylin.idv/job/Example/6611/" }, { "description": "origin/fix_memory_leak", "displayName": "#6610", "result": "SUCCESS", "url": "http://tonylin.idv/job/Example/6610/" }, { "description": null, "displayName": "#6583", "result": "SUCCESS", "url": "http://tonylin.idv/job/Example/6583/" } ] }
取得此build的artifact列表
從前一次query結果,以origin/integration_testing為例,我們需要的build URL為:
http://tonylin.idv/job/Example/6611/
接著我們可以透過以下URL去query artifacts,我需要的是檔案名稱與檔案下載路徑:
http://tonylin.idv/job/Example/6611/api/json?tree=artifacts[fileName,relativePath]
可以看到結果如下:
{ "artifacts": [ { "fileName": "user_guide.pdf", "relativePath": "dist/user_guide.pdf" }, { "fileName": "Example_1.0_build6611_linux_x64.bin", "relativePath": "dist/Example_1.0_build6611_linux_x64.bin" }, { "fileName": "Example_1.0_build6611_windows_x64.exe", "relativePath": "dist/Example_1.0_build6611_windows_x64.exe" } ] }
接著只要根據你需要的檔案名稱做過濾後,將relativePath串到build URL就可以拿到你想要的東西了。
Sample code
針對以上過程,我用python寫了一隻範例程式給大家參考:
import requests import json import platform def find_latest_build(base_url, project_name, branch=None): query_url = base_url + project_name + "/api/json?tree=builds[description,result,displayName,url]" response = requests.get(query_url) if response.status_code != 200: raise RuntimeError("request failed, status code=%d" % response.status_code) json_object = json.loads(response.content) found_build = None for build in json_object["builds"]: if not branch and build["description"] == "master": return build elif build["description"] == branch: return build return None def find_installer(build_url, file_prefix): is_windows = platform.system() == "Windows" ext = ".exe" if is_windows else ".bin" query_url = build_url + "api/json?tree=artifacts[fileName,relativePath]" response = requests.get(query_url) if response.status_code != 200: raise RuntimeError("request failed, status code=%d" % response.status_code) json_object = json.loads(response.content) for artifact in json_object["artifacts"]: file_name = artifact["fileName"] if file_name.startswith(file_prefix) and file_name.endswith(ext): relative_path = artifact["relativePath"] return build_url + "artifact/" + relative_path return None
簡單的測試案例:
from unittest import TestCase from jenkins.utils import find_latest_build from jenkins.utils import find_installer class TestJenkinsUtils(TestCase): base_url = "http://tonylin.idv/job/" project = "Example" installer_prefix = "Example" def test_find_latest_master_build(self): latest_build = find_latest_build(TestJenkinsUtils.base_url, TestJenkinsUtils.project) self.assertTrue(latest_build["url"].startswith(TestJenkinsUtils.base_url)) self.assertIsNone(latest_build["description"]) self.assertIsNotNone(latest_build["displayName"]) self.assertEqual("SUCCESS", latest_build["result"]) def test_find_latest_branch_build(self): branch_name = "origin/integration_testing" latest_build = find_latest_build(TestJenkinsUtils.base_url, TestJenkinsUtils.project, branch_name) self.assertTrue(latest_build["url"].startswith(TestJenkinsUtils.base_url)) self.assertEqual(branch_name, latest_build["description"]) self.assertIsNotNone(latest_build["displayName"]) self.assertEqual("SUCCESS", latest_build["result"]) def test_find_none_build(self): branch_name = "origin/none" latest_build = find_latest_build(TestJenkinsUtils.base_url, TestJenkinsUtils.project, branch_name) self.assertIsNone(latest_build) def test_find_build_with_invalid_url(self): branch_name = "origin/none" try: latest_build = find_latest_build(TestJenkinsUtils.base_url, "InvalidProject", branch_name) except RuntimeError as e: self.assertEqual("request failed, status code=404", e.message) def test_find_installer(self): latest_build = find_latest_build(TestJenkinsUtils.base_url, TestJenkinsUtils.project) installer_url = find_installer(latest_build["url"], TestJenkinsUtils.installer_prefix) self.assertTrue(installer_url.startswith(TestJenkinsUtils.base_url) and installer_url.__contains__(TestJenkinsUtils.installer_prefix))
友藏內心獨白: 純粹是為了練python而做的。
留言
張貼留言