SVN怎么触发Jenkins自动构建

一般,有几种方式能够在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进行自动构建的。

相关文章
相关标签/搜索