SVN怎麼觸發Jenkins自動構建

通常,有幾種方式可以在SVN倉庫發生改變時觸發Jenkins進行構建。第一種是,Jenkins主動輪詢SVN倉庫;第二種是,在SVN客戶端(如TortoiseSVN)創建客戶端hooks來觸發構建;第三種是,在SVN服務器端,創建倉庫hooks來觸發構建。而我所要介紹的就是這第三種。

 

在http://svnbook.red-bean.com/en/1.5/svn.reposadmin.create.html#svn.reposadmin.create.hooks這裏可以找到如何創建倉庫hooks的指導說明。

現在假設,我有一個倉庫名爲"auto_everything",在SVN管理頁面的版本庫列表中也可以看到:

登錄到SVN服務器上,找到該倉庫所在目錄(可以用find命令搜一下):

在該倉庫目錄下,有一個hooks目錄,默認情況下該目錄下有一些模板文件(.tmpl文件):

SVN的倉庫hooks的原理是這樣的:當你對該倉庫執行一些操作時,比如創建一個新的revision版本或修改一個未版本化的屬性,就會觸發hooks目錄下的相應程序。操作的事件(或時間節點)是跟程序文件名相對應的。舉個例子,post-commit,代表,你往該倉庫提交完成後,如果此時hooks目錄下有一個叫做post-commit的程序文件的話,該程序就會被觸發執行。該程序文件可以是任意可執行的程序,比如shell腳本、python腳本、二進制程序等,它需要有可執行權限。

 

SVN在執行該hooks文件時,同時會傳入三個參數:該倉庫的路徑(REPOS)、修訂版本號(REV)、事務名稱(TXN_NAME,這個不知道什麼鬼,不過也用不上)。在https://wiki.jenkins.io/display/JENKINS/Subversion+Plugin這裏可以找到利用post-commit hook來觸發Jenkins執行的示例腳本。

#!/bin/sh

REPOS="$1"                                         # 本倉庫的目錄路徑

REV="$2"                                            # 版本修訂號

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重試,應該可以修復這個問題。

 

 

考慮到上面總總情況,實際生產環境中,我建議按照如下操作步驟進行。

1、 首先,在Jenkins中,不要允許匿名用戶有讀取權限。相反,我們可以登錄進去,在用戶的設置界面("用戶"→ 選擇某個用戶 →"設置"),找到用戶的API Token並記下來,我們後面在程序中可以通過該token來訪問Jenkins。你使用的是哪個用戶的token,程序就具有那個用戶的訪問權限。

2、 保持跨站點請求僞造防護爲啓用。在"系統管理"→"全局安全配置"中,可以設置:

3、 對於需要被post-commit hook所觸發的job,要啓用它的"Poll SCM"選項:

4、 在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進行自動構建的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章