Jenkins持续集成学习-Windows环境进行.Net开发3

Jenkins持续集成学习-Windows环境进行.Net开发3


目录

Jenkins持续集成学习-Windows环境进行.Net开发1
Jenkins持续集成学习-Windows环境进行.Net开发2
Jenkins持续集成学习-Windows环境进行.Net开发3
Jenkins持续集成学习-Windows环境进行.Net开发4
Jenkins持续集成学习-搭建jenkins问题汇总

前言

在前面两篇文章介绍了关于持续集成的完整主流程。

30.png

目标

在上一篇文章中我们完成了主流程的持续集成,但是提交代码仍然需要手动点击构建,本篇文章就来探究如何做到SVN代码提交后自动构建。

优化nuget包生成流程

在开始之前我需要解决上一篇文章理解有误的一个问题。
在上一章我们将单元测试的不稳定错误等级设置为1。

2.27.PNG

当我添加多个失败的单元测试时,我发现1次单元测试失败错误等级就会加1,我增加了一共11个失败的单元测试,因此单元测试失败返回值为11。

4.PNG
因此上次的逻辑就行不通了,编译的时候自动创建nuget包,不稳定版本删除nuget包,这样只能将错误等级设置的非常大。比如int.Max,否则失败会导致删除脚本不执行。
因此我们有两种选择:

1. 编译的时候自动创建nuget包, 单元测试将不稳定的ERRORLEVEL设置的非常大,单元测试失败都可以认为是不稳定,然后自动删除nuget包。
2. 编译的时候不自动创建nuget包,单元测试通过后再调用脚本创建nuget包。

我们优化一下使用第二种方法生成nuget包。

我们将项目中自动生成nuget包的勾去除
3.PNG

或者我们修改csprojGeneratePackageOnBuild节点值,改为false,则编译的时候也不会自动创建nuget包。
5.PNG

然后我们修改单元测试ERRORLEVEL,单元测试失败了就不再执行后续Build的流程,在单元测试成功时创建Nuget包。
6.png

通过nuget pack csproj文件名 -Properties Configuration=Release -OutputDirectory 输出文件夹命令创建nuget包

需要加-Properties Configuration=Release参数。使用pack创建包的时候会先进行编译,若没有指定Release在默认会生成Debug版本
需要添加-OutputDirectory XXXX参数,否则默认会保存到项目的根目录。
同时我们删除了ERRORLEVEL,只要单元测试失败都算失败,这样就不会执行报创建了。

17:53:29 Results (nunit3) saved as TestResult.xml
17:53:29 
17:53:29 D:\Program Files (x86)\Jenkins\workspace\unittest>exit 0 
17:53:29 [unittest] $ cmd /c call C:\WINDOWS\TEMP\jenkins3052083372263337733.bat
17:53:29 
17:53:29 D:\Program Files (x86)\Jenkins\workspace\unittest>E:\开发工具\VS开发工具\VS插件\nuget.exe pack Jenkins.Core/Jenkins.Core.csproj -Properties Configuration=Release -OutputDirectory Jenkins.Core\bin\Release 
17:53:29 正在尝试从“Jenkins.Core.csproj”生成程序包。
17:53:29 MSBuild auto-detection: using msbuild version '15.9.21.664' from 'D:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\bin'.
17:53:31 正在打包“D:\Program Files (x86)\Jenkins\workspace\unittest\Jenkins.Core\bin\Release\net45”中的文件。
17:53:31 警告: NU5115: 未指定 Description。正在使用“Description”。
17:53:31 Successfully created package 'D:\Program Files (x86)\Jenkins\workspace\unittest\Jenkins.Core\bin\Release\Jenkins.Core.0.5.0.nupkg'.
17:53:32 
17:53:32 D:\Program Files (x86)\Jenkins\workspace\unittest>exit 0 
17:53:33 Recording NUnit tests results
17:53:33 Starting Publish Nuget packages publication
17:53:33 [unittest] $ E:\开发工具\VS开发工具\VS插件\NuGet.exe push Jenkins.Core\bin\Release\Jenkins.Core.0.5.0.nupkg ******** -Source http://127.0.0.1:10080/nuget -NonInteractive
17:53:33 Pushing Jenkins.Core.0.5.0.nupkg to 'http://127.0.0.1:10080/nuget'...
17:53:34   PUT http://127.0.0.1:10080/nuget/
17:53:35   Created http://127.0.0.1:10080/nuget/ 981ms
17:53:35 Your package was pushed.
17:53:35 Ended Publish Nuget packages publication
17:53:35 [WS-CLEANUP] Deleting project workspace...
17:53:35 [WS-CLEANUP] Skipped based on build state SUCCESS
17:53:35 Finished: SUCCESS

此时流程优化如下

28.PNG

graph LR 编译程序集 --> 编译单元测试程序集 编译单元测试程序集 --> |通过| 执行单元测试 编译单元测试程序集 --> |不通过| 失败 执行单元测试 --> |通过| 创建nuget包 创建nuget包 --> 上传nuget包 执行单元测试 --> |不通过| 失败 上传nuget包 --> 清理编译文件夹 失败 --> 清理编译文件夹

自动触发构建

SVN自动触发构建一共有3种方式。

  1. 分别为Jenkins定时轮询触发。
  2. SVN客户端创建钩子触发。
  3. SVN服务器端创建钩子触发。

Jenkins定时轮询触发

Jenkins定时轮询触发是使用Jenkins 轮询SCM功能定时检查SVN是否有变更触发构建。

Jenkins的轮询SCM的说明上提到该功能需要扫描整个Jenkins工作区并验证,操作性能要求比较高。我们依然验证一下这个功能。

在配置Build Triggers选项中勾选轮询SCM,在Schedule输入 * * * * *表示每分钟轮询一次,即代码提交后1分钟触发构建。
1.png

设置完之后我们提交代码就会自动构建了。相比手动构建,自动构建左边菜单栏会显示轮询日志,右边会显示由SCM变更启动,表明是轮询SCM触发的构建。

2.png

SVN客户端钩子触发

SVN客户端钩子触发是在本地提交的时候执行本地的Post-Commit钩子,通过这个钩子执行脚本使用http请求调用jenkins的远程构建接口。

配置

生成用户授权Token

系统配置-管理用户-用户-配置API TOKEN点击生成新的Token按钮,创建一个token。我们需要根据这个token来获取权限。
10.png

增加项目授权token

在项目的配置中修改Build Triggers,勾选Trigger builds remotely支持触发远程构建。在Authentication Token输入一个自定义的串,我们可以使用JENKINS_URL/job/JOB_NAME/build?token=TOKEN_NAME来远程构建项目。比如我们当前项目可以使用http://127.0.0.1:8080/job/unittest/build?token=123远程构建
11.PNG

创建客户端钩子脚本

20.PNG

graph LR SVN提交代码 --> 触发代码提交后钩子 触发代码提交后钩子 --> 执行本地脚本

创建一个bat脚本。命名为post-commit-unittest.bat,我们在这个脚本里写入参数,将真正执行通知的脚本分离出来,这就可以重用了。

SET CSCRIPT=%windir%\system32\cscript.exe
SET VBSCRIPT=F:\Repositories\JenkinsTest\hooks\post-commit-hook-jenkins.vbs
SET JENKINS=http://127.0.0.1:8080/
SET JOBNAME="unittest"
SET TOKEN="123"
REM AUTHORIZATION: Set to "" for anonymous acceess
REM AUTHORIZATION: Set to encoded Base64 string, generated from "user_id:api_token" 
REM                found on Jenkins under "user/configure/API token"
REM                User needs "Job/Read" permission on Jenkins
REM AUTHORIZATION=base64(test:1184023ac835f44484f52316235a033db8)
SET AUTHORIZATION="dGVzdDoxMTg0MDIzYWM4MzVmNDQ0ODRmNTIzMTYyMzVhMDMzZGI4"
"%CSCRIPT%" "%VBSCRIPT%" %JENKINS% %JOBNAME% %TOKEN% %AUTHORIZATION%

SVN调用脚本会传入3个参数

1. 当前项目的SVN仓库地址
2. 当前的版本号
3. 事务名称

这里暂时不需要用到。

通过CScript.exe调用执行vbs脚本。

CScript.exe是Windows脚本宿主的一个版本,可以用来从命令行运行脚本。

通知脚本参数说明

1. CSCRIPT:CScript.exe的路径。
2. VBSCRIPT:同时jenkins的脚本路径。
3. JENKINS:jenkins服务地址。
4. JOBNAME:项目名称。
5. TOKEN:项目的Token。
6. AUTHORIZATION:用于授权token。

AUTHORIZATION值为base64(user_id:api_token)

设置钩子

在SVN客户端的设置中找到钩子脚本,点击添加。

7.PNG
设置路径和脚本路径,注意左下角两项勾起来。
8.png

21.PNG

graph LR 获取Jenkins-Crumb-->提交build请求

创建通知脚本

创建一个vbs脚本用于执行通知。

jenkins       = WScript.Arguments.Item(0)
Wscript.Echo "jenkins="&jenkins
jobName  = WScript.Arguments.Item(1)
Wscript.Echo "token="&token
token  = WScript.Arguments.Item(2)
Wscript.Echo "token="&token
authorization = WScript.Arguments.Item(3)
Wscript.Echo "authorization="&authorization

url = jenkins + "crumbIssuer/api/xml?xpath=concat(//crumbRequestField,"":"",//crumb)"
Wscript.Echo "url="&url
Set http = CreateObject("MSXML2.ServerXMLHTTP")
http.open "GET", url, False
http.setRequestHeader "Content-Type", "text/plain;charset=UTF-8"
if not authorization = "" then
  http.setRequestHeader "Authorization", "Basic " + authorization
end if
http.send
crumb = null
if http.status = 200 then
  crumb = split(http.responseText,":")
end if
Wscript.Echo crumb(0)&"="&crumb(1)
url = jenkins + "job/unittest/build?token=" + token
Wscript.Echo url
Set http = CreateObject("MSXML2.ServerXMLHTTP")
http.open "GET", url, False
http.setRequestHeader "Content-Type", "text/plain;charset=UTF-8"
if not authorization = "" then
  http.setRequestHeader "Authorization", "Basic " + authorization
end if
if not isnull(crumb) then 
  http.setRequestHeader crumb(0),crumb(1)
end if
http.send
Wscript.Echo "Status: " & http.status &"Body: " & http.responseText

不同项目使用不同的post-commit.bat的脚本,脚本中设置JOB_NAME和JOB_TOKEN,不同项目最终都是调用上面的这个脚本进行远程构建。

获取Jenkins-Crumb

我们先获取到Jenkins-Crumb获取到防跨域攻击token。通过向JENKINS_URL/crumbIssuer/api/xml发送一个post请求,获取到crumb。
12.PNG

发送的时候我们需要将Authorization加入到http头部。

提交build请求

将获取到的Jenkins-Crumb:XXXXX加入到http头部,通过发送Get请求调用远程构建,触发成功会响应201的状态码。
13.png
14.png

关于远程调用更详细的文档说明可以查看Remote access API

通过上面的设置SVN客户端钩子远程构建就完成了,在项目中可以看到远程构建的标志。

9.PNG

相比SCM轮询,客户端远程构建实时性更高,由于是主动通知,因此代码提交完立刻可以触发远程构建。

SVN服务器钩子触发

服务端钩子与客户端钩子类似,具体区别如下。

服务端与客户端钩子比较

客户端钩子 服务端钩子
脚本位置 客户端post-commit钩子 服务端post-commit钩子
配置 需要在Build Triggers配置中勾选Trigger builds remotely,设置Authentication Token 需要在Build Triggers配置中勾选轮询 SCM
防跨域攻击 支持,需要获取防跨域攻击的token 支持,需要获取防跨域攻击的token
通知方式 通过Remote access API调用主动构建 通过向Subversion Plugin发送请求主动构建
其他要求 需要安装Subversion Plugin插件,同时服务端执行脚本需要一些特殊权限

创建服务端钩子脚本

每个版本库创建后都会自动生成一些文件夹和文件,hooks文件夹内就是存放了服务器端的钩子。我们将我们需要的钩子脚本根据命名规则放入hooks文件夹即可。

24.PNG

windows环境钩子命名规则为钩子名.bat或钩子名.exe,如post-commit.batpost-commit.exe

详情可以查看官方文档Implementing Repository Hooks

创建服务端钩子脚本post-commit.bat

SET REPOS=%1
SET REV=%2
SET CSCRIPT=%windir%\system32\cscript.exe
SET VBSCRIPT=F:\Repositories\JenkinsTest\hooks\post-commit-svn-server.vbs
SET SVNLOOK=D:\Program Files\VisualSVN Server\bin\svnlook.exe
SET JENKINS=http://127.0.0.1:8080/
REM AUTHORIZATION: Set to "" for anonymous acceess
REM AUTHORIZATION: Set to encoded Base64 string, generated from "user_id:api_token" 
REM                found on Jenkins under "user/configure/API token"
REM                User needs "Job/Read" permission on Jenkins
REM AUTHORIZATION=base64(test:1184023ac835f44484f52316235a033db8)
SET AUTHORIZATION="dGVzdDoxMTg0MDIzYWM4MzVmNDQ0ODRmNTIzMTYyMzVhMDMzZGI4"
"%CSCRIPT%" "%VBSCRIPT%" "%REPOS%" "%2" "%SVNLOOK%" %JENKINS% %AUTHORIZATION%

详细的钩子可以到SVN服务管理上找到管理hooks
25.png
26.png
同时我们创建了钩子脚本放入,SVN钩子管理就可以直接读取到我们的脚本。
27.PNG

通知脚本参数说明

1. %1:当前项目的SVN仓库地址。
2. %2:提交后的版本号。
3. CSCRIPT:CScript.exe的路径。
4. VBSCRIPT:同时jenkins的脚本路径。
5. SVNLOOK:svnlook.exe的路径。
6. JENKINS:jenkins服务地址。
7. AUTHORIZATIONN:用于授权token。

svnlook是检验Subversion版本库不同方面的命令行工具。

22.png

graph LR 获取SVN版本库的UUID --> 获取SVN修改项 获取SVN修改项 --> 获取Jenkins-Crumb 获取Jenkins-Crumb-->提交build请求

创建一个vbs脚本用于执行通知。

repos         = WScript.Arguments.Item(0)
Wscript.Echo "repos="&repos
rev           = WScript.Arguments.Item(1)
Wscript.Echo "rev="&rev
svnlook       = WScript.Arguments.Item(2)
Wscript.Echo "svnlook="&svnlook
jenkins       = WScript.Arguments.Item(3)
Wscript.Echo "jenkins="&jenkins
authorization = WScript.Arguments.Item(4)
Wscript.Echo "authorization="&authorization

Set shell = WScript.CreateObject("WScript.Shell")
Set uuidExec = shell.Exec(svnlook & " uuid " & repos)
Do Until uuidExec.StdOut.AtEndOfStream
  uuid = uuidExec.StdOut.ReadLine()
Loop
Wscript.Echo "uuid=" & uuid
Set changedExec = shell.Exec(svnlook & " changed --revision " & rev & " " & repos)
Do Until changedExec.StdOut.AtEndOfStream
  changed = changed + changedExec.StdOut.ReadLine() + Chr(10)
Loop
Wscript.Echo "changed=" & changed
url = jenkins + "crumbIssuer/api/xml?xpath=concat(//crumbRequestField,"":"",//crumb)"
Wscript.Echo "url="&url
Set http = CreateObject("MSXML2.ServerXMLHTTP")
http.open "GET", url, False
http.setRequestHeader "Content-Type", "text/plain;charset=UTF-8"
if not authorization = "" then
  http.setRequestHeader "Authorization", "Basic " + authorization
end if
http.send
crumb = null
if http.status = 200 then
  crumb = split(http.responseText,":")
end if
Wscript.Echo crumb(0)&"="&crumb(1)
url = jenkins + "subversion/" + uuid + "/notifyCommit?rev=" + rev
Wscript.Echo url
Set http = CreateObject("MSXML2.ServerXMLHTTP")
http.open "POST", url, False
http.setRequestHeader "Content-Type", "text/plain;charset=UTF-8"
if not authorization = "" then
  http.setRequestHeader "Authorization", "Basic " + authorization
end if
if not isnull(crumb) then 
  http.setRequestHeader crumb(0),crumb(1)
end if
http.send changed
if http.status <> 200 then
  Wscript.Echo "Error. HTTP Status: " & http.status & ". Body: " & http.responseText
end if

Windows specific post-commit hook示例使用的是Microsoft.XMLHTTP调用http请求,但是我本机发送会返回403错误,查到一篇msxml3.dll 错误 80070005 拒绝访问换为MSXML2.ServerXMLHTTP发送成功。

获取SVN版本库的UUID

通过svnlook uuid REPOS-PATH获取版本库的唯一UUID

C:\Users\Dm_ca>"D:\Program Files\VisualSVN Server\bin\svnlook.exe" uuid "F:\Repositories\JenkinsTest"
3f64521c-9849-7c44-a469-468730bce0a2

可以看到和SVN版本库的UUID一致
16.png

获取SVN版本改变项

通过svnlook changed --revison REV REPOS-PATH获取版本库某个版本的改变项

C:\Users\Dm_ca>"D:\Program Files\VisualSVN Server\bin\svnlook.exe" changed --revision 50 "F:\Repositories\JenkinsTest"
U   trunk/JenkinsTest.Core/Jenkins.Core.Test/TestClass.cs
获取Jenkins-Crumb

客户端获取Jenkins-Crumb方式一样。

提交build请求

与客户端提交build请求不同,服务端是向http://jenkins-server/subversion/${UUID}/notifyCommit?rev=$REV发送一个post请求。
17.PNG
18.png
服务端构建会显示SCM启动,和jenkins scm不同的是,不需要每分钟定时轮询,而是通过服务端钩子触发任务执行。
19.PNG

三种钩子比较

SCM轮询 客户端钩子 服务端钩子
脚本位置 无脚本 客户端post-commit钩子 服务端post-commit钩子
配置 需要在Build Triggers配置中勾选轮询 SCM,在Schedule配置输入计划规则 需要在Build Triggers配置中勾选Trigger builds remotely,设置Authentication Token 需要在Build Triggers配置中勾选轮询 SCM
防跨域攻击 无需考虑 支持,需要获取防跨域攻击的token 支持,需要获取防跨域攻击的token
通知方式 定时轮询 通过Remote access API调用主动构建 通过向Subversion Plugin发送请求主动构建
时效性 最快代码提交后1分钟触发 立即触发 立即触发
其他要求 需要安装Subversion Plugin插件,同时服务端执行脚本需要一些特殊权限

具体使用哪种方案根据上面表格选择即可。

结语

最终我们的完整持续集成流程图如下图所示

graph LR 获取代码 --> 编码 编码 --> 提交代码 提交代码 --> |自动构建| 编译程序集 编译程序集 --> 编译单元测试程序集 编译单元测试程序集 --> |通过| 执行单元测试 编译单元测试程序集 --> |不通过| 失败 执行单元测试 --> |通过| 创建nuget包 创建nuget包 --> 上传nuget包 执行单元测试 --> |不通过| 失败 上传nuget包 --> 清理编译文件夹 失败 --> 清理编译文件夹 失败 -.-> 获取代码

参考文档

  1. 使用TortoiseSVN的客户端钩子脚本触发Jenkins构建
  2. Jenkins SVN自动构建
  3. SVN怎么触发Jenkins自动构建
  4. msxml3.dll 错误 80070005 拒绝访问
  5. 通过jenkins API去build一个job
  6. Remote access API
  7. Implementing Repository Hooks
  8. Windows specific post-commit hook

本文地址:https://www.cnblogs.com/Jack-Blog/p/10331263.html
作者博客:杰哥很忙
欢迎转载,请在明显位置给出出处及链接

posted @ 2019-01-28 18:12  杰哥很忙  阅读(1760)  评论(0编辑  收藏  举报