用Jenkins對GitHub的Pull Request做測試

這一陣子想要對Python project在發出pull request的時候做一些測試
像是pep8的coding style check還是pyflasks等等
原本有考慮TravisCI跟CircleCI,但是我這邊的環境是private network
用的Github也是private repository,基於公司原則考量就放棄了TravisCI跟CircleCI
決定使Jenkins來達到想要的效果,這邊記錄一下構建方法

達到的效果大概如下
當我發起一個pull request的時候,Jenkins會自動針對該PR的內容做檢查
如果PR的內容包含python script的更新,他就替我做pep8的python style check

建構過程如下

1.首先要把GitHub pull request builder plugin裝起來
這個從Jenkins裡[Manage Jenkins] -> [Manage Plugins]就可以簡單裝起來了
但因為我是內網環境,我還得替我的Jenkins架一個Http Proxy才能安裝就是

2.安裝想要拿來測試的工具
這裡我選用了

  • pep8 - 檢查python coding style
  • autopep8 - 一個自動調整scipt符合pep8的工具
  • pyflakes - 可以做一些簡單的邏輯檢查

使用pip把這些套件裝起來

pip install autopep8 pep8 pyflakes

3.GitHub連接準備
為了讓Jenkins可以連接GitHub做測試,我需要準備一個帳號給Jenkins使用
假設有個帳號叫mybot,用下面的指令拿到mybot的API Token
GITHUB_API_URL要依據環境而定
如果是外部公開的GitHub他的位置是https://api.github.com
但如果是內部的private GHE的話,請改成公司內部的api url

curl -u 'mybot' -d '{"scopes":["repo"],"note":"Help example"}' $GITHUB_API_URL/authorizations

之後會還傳如下內容,token的值就是我們拿來給Jenkins通行用的,請好好記住

{
  "scopes": [
    "repo"
  ],
  "token": "your_token",
  "app": {
    "url": "http://developer.github.com/v3/oauth/#oauth-authorizations-api",
    "name": "Help example (API)"
  },
  "url": "https://api.github.com/authorizations/123456",
  "note": "Help example",
  "note_url": null,
  "id": 123456,
}

拿到token之後可以測試看看能不能用 測試方法如下

curl -H "Authorization: token $YOUR_TOKEN"  $GITHUB_API_URL

而再來必須賦予剛剛的帳號對於github project可以操作的權限
進入Github project之後[Settings] → [Collaborators]
把剛剛建立token的帳號加到Collaborators

之後我們就可以透過GitHub API去得知一個Pull Request有什麼檔案被修改 以及對他操作的權限
用GitHub API得到PR裏面異動檔案的範例如下
這裡我只想要py結尾的檔案,而至於github api的用法請參考官方網站說明

curl -H "Authorization: token YOUR_TOKEN"  $GITHUB_API_URL/repos/$OWNER_NAME/$REPO_NAME/pulls/$PR_ID/files |grep filename | awk '{print $2}' |awk -F'"' '{print $2}' | egrep ".py"

上面的指令會回傳類似如下內容,也就是在這次pull request被修改的py檔的路徑

src/main.py
src/mylib/tool.py

4.Jenkins環境設定
從下面進入GitHub Pull Request Builder的設定
[Manage Jenkins] -> [Configure System] -> [GitHub Pull Request Builder]
裏面幾個需要設定的地方如下

  • GitHub Server API URL - 填入github api url的位置,也就是上面提的GITHUB_API_URL
  • Shared secret - 前面創造出來的API Token
  • Credentials - 新增剛剛你創建token的帳號,方便起見直接輸入帳號密碼

上面的東西輸入之後可以跑兩個測試來看看前面的帳號對想測試的github project能不能正常運作
[Test basic connection to GitHub]
[Test Permissions to a Repository]

5.測試用Jenkins Job生成
原則上是一個測試一個job,而在GitHub上顯示的項目也會對應到各個Jenkins job
下圖為例,他是兩個Jenkins job跑的測試,所以在Github上顯示了兩個測試項目

這邊以建立pep8檢查用的job為例,在Jenkins創造一個freestyle的job

在[GitHub project]的地方 填入要被測試project url

在[Source Code Management]的地方選則Git之後,填入repos的位置,這裡要注意的是有兩個地方要改
點開[Advanced]

  • Refspec - 填入[ +refs/pull/:refs/remotes/origin/pr/ ]代表Jenkins關注的是github的pull-request
  • Branches to build - 填入[ ${sha1} ]

設定[Build Triggers]
把下面兩個選項勾起來
[Build when a change is pushed to GitHub]
[GitHub Pull Request Builder]
要修改的地方有四個

  • GitHub API credentials - 選擇[Jenkins環境設定]時創造的認證
  • Admin list - 前面創造api token時的帳號
  • Crontab line - 改成每分鐘檢查
  • White list - 哪些user的pull-request要被測試

這邊White list的設定要注意,如果發起pull-request的帳號不再裏面
那他的pull-request就不會被測試

設定[Build Triggers]
Configure > Build Triggers > Trigger Setup > Commit Status Context
主要設定測試時候的名字,效果如下
如果我在[Commit Status Context]設定的是Check Python Coding Style
那在Github頁面顯示的就是Check Python Coding Style

設定[Build]
新增一個action [Execute shell]
把所有的python腳本拿出來做pep8的檢查

pr_files=`curl -H "Authorization: token YOUR_TOKEN"  $GITHUB_API_URL/repos/$OWNER_NAME/$REPO_NAME/pulls/$PR_ID/files |grep filename | awk '{print $2}' |awk -F'"' '{print $2}' | egrep ".py"`
for f in  $pr_files
do
   pep8 --show-source --ignore=E501,E251,E121 $f
done

這樣一來就大功告成了
附帶一提,使用--show-source參數可以讓pep8的錯誤清楚一些
同時用--ignore把一些其實不用那麼嚴苛的檢查拿掉 像是一行太長等等

之後其他的測試pyflasks等就能比照辦理了

2016/11/07

前兩天github換了密碼之後,jenkins突然不會作業了

這邊要做兩件事

  1. 更新jenkins上註冊的密碼
  2. 更新API token
  • 更新token 首先去github [Settings] -> [Personal access tokens]把Help example給刪掉 在產生新token更新到jenkins
    $ curl -u 'username' -d '{"scopes":["repo"],"note":"Help example"}'  https://api.github.com/authorizations
    

儘管我更新了jenkins上註冊的密碼依然無用,在做下面兩個測試的時候沒問題
[Test basic connection to GitHub]
[Test Permissions to a Repository]
但是看log會出現如下錯誤

Nov 07, 2016 12:07:01 PM hudson.triggers.Trigger checkTriggers
WARNING: org.jenkinsci.plugins.ghprb.GhprbTrigger.run() failed for hudson.model.FreeStyleProject@7283641a[Check Python Coding Style]
java.lang.Error: org.kohsuke.github.HttpException: Server returned HTTP response code: 401, message: 'Unauthorized' for URL: https://api_url/repos/xxxx/xxxx/pulls?state=open
        at org.kohsuke.github.Requester$PagingIterator.fetch(Requester.java:425)
        at org.kohsuke.github.Requester$PagingIterator.hasNext(Requester.java:392)
        at org.kohsuke.github.PagedIterator.fetch(PagedIterator.java:44)
        at org.kohsuke.github.PagedIterator.hasNext(PagedIterator.java:32)
        at org.kohsuke.github.PagedIterable.asList(PagedIterable.java:42)
        at org.kohsuke.github.GHRepository.getPullRequests(GHRepository.java:627)
        at org.jenkinsci.plugins.ghprb.GhprbRepository.check(GhprbRepository.java:138)
        at org.jenkinsci.plugins.ghprb.GhprbTrigger.run(GhprbTrigger.java:294)
        at hudson.triggers.Trigger.checkTriggers(Trigger.java:266)
        at hudson.triggers.Trigger$Cron.doRun(Trigger.java:214)
        at hudson.triggers.SafeTimerTask.run(SafeTimerTask.java:54)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

看起來是checkTriggers這邊出了問題
儘管從Jenkins設定把密碼更新了,卻沒有反應到既存的job上面
後來先把[Build Triggers]跟[Trigger Setup]的設定清除之後儲存
在重新建立[Build Triggers]跟[Trigger Setup]就解決了這個錯誤,大概是我這一版本Jenkins的bug吧

comments powered by Disqus