差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

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取得特定項目]]