差異處
這裏顯示兩個版本的差異處。
jenkins:restapi:get_artifacts [2016/12/25 23:43] tony [Sample code] |
jenkins:restapi:get_artifacts [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | {{tag>jenkins}} | ||
- | ====== Get Artifacts with RestAPI ====== | ||
- | ===== Problem ===== | ||
- | 為了節省軟體的反安裝、下載與安裝時間,我們有隻script,會去jenkins抓某個固定位置的最新安裝程式並安裝。然而,開發過程會因為新功能或修bug等原因產生branch;原本的腳本並無法根據branch去下載安裝程式,也因此花了些時間去研究並解決這個問題。 | ||
- | ===== 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: | ||
- | <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 ===== | ||
- | * [[http://stackoverflow.com/questions/17236710/jenkins-rest-api-using-tree-to-reference-specific-item-in-json-array|透過jenkins restapi with tree取得特定項目]] | ||
- | * [[https://www.cloudbees.com/blog/taming-jenkins-json-api-depth-and-tree|Taming the Jenkins JSON API with Depth and "Tree"]] | ||
- | |||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ | ||
- | |||