差異處
這裏顯示兩個版本的差異處。
Both sides previous revision 前次修改 下次修改 | 前次修改 | ||
jenkins:restapi:get_artifacts [2016/12/23 22:43] tony |
jenkins:restapi:get_artifacts [2023/06/25 09:48] (目前版本) |
||
---|---|---|---|
行 2: | 行 2: | ||
====== Get Artifacts with RestAPI ====== | ====== Get Artifacts with RestAPI ====== | ||
===== Problem ===== | ===== Problem ===== | ||
- | 原先為了自動安裝測試開發的軟體,我們有隻腳本,會去jenkins抓某個固定位置的最新安裝程式並安裝。然而,開發過程會因為新功能或修bug等原因產生branch;原本的腳本並無法根據branch去下載安裝程式,也因此花了些時間去研究並解決這個問題。主要目的還是為了節省反安裝、下載與反安裝的時間。 | + | 為了節省軟體的反安裝、下載與安裝時間,我們有隻script,會去jenkins抓某個固定位置的最新安裝程式並安裝。然而,開發過程會因為新功能或修bug等原因產生branch;原本的腳本並無法根據branch去下載安裝程式,也因此花了些時間去研究並解決這個問題。 |
===== How to? ===== | ===== How to? ===== | ||
+ | jenkins既然有RestAPI,應該就有辦法讓人可以存取到它專案相關資訊吧? 為了達到我們目的,其中會包含幾個步驟: | ||
+ | - 取得專案build列表。 | ||
+ | - 取得branch吻合的build URL。 | ||
+ | - 取得此build的artifact列表。 | ||
+ | - 過濾想要的artifacts。 | ||
+ | 以專案名稱Example為例,其專案的URL為: | ||
+ | <code> | ||
+ | http://tonylin.idv/job/Example | ||
+ | </code> | ||
+ | ==== 取得專案build列表 ==== | ||
+ | 首先可以透過此URL去搜尋所有build資訊: | ||
+ | <code> | ||
+ | http://tonylin.idv/job/Example/api/json?tree=builds[*] | ||
+ | </code> | ||
+ | 以一個build的結果如下: | ||
+ | <code json> | ||
+ | { | ||
+ | "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": [] | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | </code> | ||
+ | ==== 取得branch吻合的build URL ==== | ||
+ | 由於前一個URL使用[*]是列出所有項目,但其實我們只需要知道branch、build結果、build URL與build number,可以透過以下URL去做query: | ||
+ | <code> | ||
+ | http://tonylin.idv/job/Example/api/json?tree=builds[description,result,displayName,url] | ||
+ | </code> | ||
+ | 三個build的輸出結果如下,從結果內容不難得知個別資訊意義,需注意的是master的description會是null或master(我們使用dscription當branch名稱): | ||
+ | <code> | ||
+ | { | ||
+ | "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/" | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | </code> | ||
+ | ==== 取得此build的artifact列表 ==== | ||
+ | 從前一次query結果,以origin/integration_testing為例,我們需要的build URL為: | ||
+ | <code> | ||
+ | http://tonylin.idv/job/Example/6611/ | ||
+ | </code> | ||
+ | 接著我們可以透過以下URL去query artifacts,我需要的是檔案名稱與檔案下載路徑: | ||
+ | <code> | ||
+ | http://tonylin.idv/job/Example/6611/api/json?tree=artifacts[fileName,relativePath] | ||
+ | </code> | ||
+ | 可以看到結果如下: | ||
+ | <code> | ||
+ | { | ||
+ | "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" | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | </code> | ||
+ | 接著只要根據你需要的檔案名稱做過濾後,將relativePath串到build URL就可以拿到你想要的東西了。 | ||
+ | ===== Sample code ===== | ||
+ | 針對以上過程,我用python寫了一隻範例程式給大家參考: | ||
+ | <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 | ||
+ | </code> | ||
+ | 簡單的測試案例: | ||
+ | <code python> | ||
+ | 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)) | ||
+ | |||
+ | </code> | ||
+ | \\ | ||
+ | 友藏內心獨白: 純粹是為了練python而做的。 | ||
===== Reference ===== | ===== Reference ===== | ||
* [[http://stackoverflow.com/questions/17236710/jenkins-rest-api-using-tree-to-reference-specific-item-in-json-array|透過jenkins restapi with tree取得特定項目]] | * [[http://stackoverflow.com/questions/17236710/jenkins-rest-api-using-tree-to-reference-specific-item-in-json-array|透過jenkins restapi with tree取得特定項目]] |