一般,有幾種方式能夠在SVN倉庫發生改變時觸發Jenkins進行構建。第一種是,Jenkins主動輪詢SVN倉庫;第二種是,在SVN客戶端(如TortoiseSVN)建立客戶端hooks來觸發構建;第三種是,在SVN服務器端,建立倉庫hooks來觸發構建。而我所要介紹的就是這第三種。html
在http://svnbook.red-bean.com/en/1.5/svn.reposadmin.create.html#svn.reposadmin.create.hooks這裏能夠找到如何建立倉庫hooks的指導說明。python
如今假設,我有一個倉庫名爲"auto_everything",在SVN管理頁面的版本庫列表中也能夠看到:shell
登陸到SVN服務器上,找到該倉庫所在目錄(能夠用find命令搜一下):json
在該倉庫目錄下,有一個hooks目錄,默認狀況下該目錄下有一些模板文件(.tmpl文件):vim
SVN的倉庫hooks的原理是這樣的:當你對該倉庫執行一些操做時,好比建立一個新的revision版本或修改一個未版本化的屬性,就會觸發hooks目錄下的相應程序。操做的事件(或時間節點)是跟程序文件名相對應的。舉個例子,post-commit,表明,你往該倉庫提交完成後,若是此時hooks目錄下有一個叫作post-commit的程序文件的話,該程序就會被觸發執行。該程序文件能夠是任意可執行的程序,好比shell腳本、python腳本、二進制程序等,它須要有可執行權限。api
SVN在執行該hooks文件時,同時會傳入三個參數:該倉庫的路徑(REPOS)、修訂版本號(REV)、事務名稱(TXN_NAME,這個不知道什麼鬼,不過也用不上)。在https://wiki.jenkins.io/display/JENKINS/Subversion+Plugin這裏能夠找到利用post-commit hook來觸發Jenkins執行的示例腳本。安全
#!/bin/sh服務器 REPOS="$1" # 本倉庫的目錄路徑異步 REV="$2" # 版本修訂號ide UUID=`svnlook uuid $REPOS` /usr/bin/wget \ --header "Content-Type:text/plain;charset=UTF-8" \ --post-data "`svnlook changed --revision $REV $REPOS`" \ --output-document "-" \ --timeout=2 \ http://server/subversion/${UUID}/notifyCommit?rev=$REV |
注一:出於安全考慮,SVN在調用hooks程序時是不傳入任何環境變量的,包括PATH路徑。因此,若是爲了方面書寫命令,咱們咱們須要本身設置PATH路徑:
svn_dir=/home/svn/csvn # SVN安裝目錄 export PATH=$svn_dir/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin |
注二:注意到rev=$REV參數,這是告訴Jenkins簽出該hook程序所報告的那個版本號。若是你的job定義了多個subversion模塊位置,這可能會致使簽出代碼不一致 - 所以那種狀況下就推薦你省去 '?rev=$REV' ,這會使得Jenkins簽出最新版本的代碼。
注三:爲了讓Jenkins中的job能夠被觸發,job須要被顯式地配置爲啓用SCM輪詢才行,未啓用SCM輪詢選項的job將不會被post-commit hook所觸發。下圖爲在job中啓用SCM輪詢的示例:
注四:爲了讓上面的腳本能夠正常工做,Jenkins須要容許匿名用戶有讀取權限。在"系統管理"→"全局安全配置"中,能夠啓用:
固然,一般咱們的Jenkins會有更加嚴格的訪問權限控制,咱們不會這麼作。後面會介紹。
注五:若是Jenkins啓用了跨站點請求僞造防禦(默認啓用)選項,那麼上面的請求會返回一個403錯誤("No valid crumb was included")。在"系統管理"→"全局安全配置"中,能夠看到跨站點請求僞造防禦是否有啓用:
爲了不該問題,請求所需的crumb能夠從URL地址http://server/crumbIssuer/api/xml (或/api/json)獲取到。能夠經過在wget請求中添加相似於下面的代碼來解決:
--header `wget -q --output-document - \ 'http://server/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'` |
注六:因爲wget在給定的超時時間內訪問不成功時,它默認會嘗試最多20次,--timeout=2在一個性能較差的SVN服務器上可能會致使Jenkins掃描倉庫很是屢次,進一步減慢了SVN服務器,而後使用Jenkins不響應。對該問題的可能的解決辦法是增長超時時間,減少重試次數(如--retries=3),或者在wget命令最後面添加2>&1 &來異步地調用wget(所以忽略了全部的通訊錯誤)。
timeout值過低會致使你的commit操做hang住並拋出502錯誤(若是你是在一個proxy後面)或post-commit錯誤。增長超時值,直到你再也不看到wget重試,應該能夠修復這個問題。
考慮到上面總總狀況,實際生產環境中,我建議按照以下操做步驟進行。
一、 首先,在Jenkins中,不要容許匿名用戶有讀取權限。相反,咱們能夠登陸進去,在用戶的設置界面("用戶"→ 選擇某個用戶 →"設置"),找到用戶的API Token並記下來,咱們後面在程序中能夠經過該token來訪問Jenkins。你使用的是哪一個用戶的token,程序就具備那個用戶的訪問權限。
二、 保持跨站點請求僞造防禦爲啓用。在"系統管理"→"全局安全配置"中,能夠設置:
三、 對於須要被post-commit hook所觸發的job,要啓用它的"Poll SCM"選項:
四、 在SVN服務器上,所選倉庫的hooks目錄中,建立程序文件post-commit:
[root@gw ~]# cd /home/svn/csvn/data/repositories/auto_everything/hooks [root@gw hooks]# touch post-commit [root@gw hooks]# chown svn:svn post-commit # 跟SVN的httpd程序的所屬用戶相同 [root@gw hooks]# chmod 755 post-commit # 須要有可執行權限 |
咱們使用python來編寫該程序:
[root@gw hooks]# vim post-commit #!/usr/bin/env python # -*- coding: utf-8 -*-
import urllib2, sys, os, base64, json
# 本倉庫的目錄路徑 repos_path = sys.argv[1] # 版本修訂號 revision = sys.argv[2]
# svnlook命令所在路徑 svnlook = '/home/svn/csvn/bin/svnlook' # Jenkins的地址 baseurl = 'http://172.31.2.8:8080' # Jenkins用戶和用戶的api token user_id = 'admin' api_token = '0e5127a9b050097b14e72b1485cf5e39'
def my_urlopen(url, data=None, header={ }): request = urllib2.Request(url, data) base64string = base64.encodestring('%s:%s' % (user_id, api_token)).replace('\n', '') header['Authorization'] = 'Basic %s' % base64string for key in header: request.add_header(key, header[key])
try: response = urllib2.urlopen(request) except urllib2.URLError as e: print '[Exception URLError]:', e sys.exit(1) else: result = response.read( ) finally: if 'response' in vars( ): response.close( )
return result
def main( ): # 獲取uuid值 command = r'%s uuid %s' % (svnlook, repos_path) with os.popen(command) as f: uuid = f.read( ).strip( )
# 獲取倉庫變動信息 command = r'%s changed --revision %s %s' % (svnlook, revision, repos_path) with os.popen(command) as f: data = f.read( ).strip( )
# 獲取crumb url = r'%s/crumbIssuer/api/json' % baseurl crumb_dict = json.loads(my_urlopen(url))
# 觸發Jenkins url = r'%s/subversion/%s/notifyCommit?rev=%s' % (baseurl, uuid, revision) header={'Content-Type':'text/plain', 'charset':'UTF-8', crumb_dict['crumbRequestField']:crumb_dict['crumb']} my_urlopen(url, data, header)
main( ) |
這樣就設置好了。能夠找一個項目的文件夾,在裏面隨便新建一個問題,而後提交。若是SVN的post-commit有觸發Jenkins進行構建的話,在Jenkins管理頁面中就能夠看到有job在運行了,在Jenkins的系統日誌中也會產生相似下面的信息:
[root@gw ~]# tail -n 100 -f /opt/jenkins/jenkins.log Jun 25, 2018 9:03:17 AM jenkins.scm.impl.subversion.SubversionSCMSource$ListenerImpl onNotify INFO: Received post-commit hook from 7c771039-8f78-4126-8c20-220196ed6f6c for revision 21 on paths [example-pipeline/test.txt] Jun 25, 2018 9:03:17 AM jenkins.scm.impl.subversion.SubversionSCMSource$ListenerImpl onNotify INFO: No subversion consumers for UUID 7c771039-8f78-4126-8c20-220196ed6f6c Jun 25, 2018 9:03:18 AM hudson.triggers.SCMTrigger$Runner run INFO: SCM changes detected in example-pipeline. Triggering #4 Jun 25, 2018 9:03:33 AM org.jenkinsci.plugins.workflow.job.WorkflowRun finish INFO: example-pipeline #4 completed: SUCCESS |
實際生產環境中,一個SVN倉庫可能會存放有多個項目,對應多個Jenkins job。但通過測試,發現沒有任何問題。當咱們將修改提交到SVN倉庫中時,只有那些文件有變更的項目對應的job纔會被觸發。這多是Jenkins有檢查,每一次變更都涉及到哪些文件,跟現有job的SVN源碼路徑是否匹配,若是有匹配到,纔會觸發這個job。不過須要注意的是,不論是新增文件仍是刪除文件,只要項目文件發生變更,都會觸發項目的job進行自動構建的。