在 《【Hello CC.NET】CC.NET 實現自動化集成》 的 HellowWorld 中經實現:
1.獲取源碼
2.編譯項目
3.集成測試
4.Ftp發布項目
5.創建安裝包
6.郵件通知
在方案落地的過程中,FTP 上傳開發自測環境(或測試環境)后,仍然需要人手來修改相關配置(比如 Web.config)。
假設項目的 Web.config 配置如下:
<appSettings> <add key="OrgDB" value="xxx_dev_db"/> <add key="BusinessDB" value="xxx_DEV_DB"/> <add key="RedisCache" value="192.168.10.111:6379"/> <add key="SSOService" value="http://192.168.10.111/DEV/V1.1.9/SSOService.svc"/> <add key="Coder" value="蔡海華"/> </appSettings>
在發布到測試環境之前,我們需要把 _dev_db 替換為 _test_db,192.168.10.111:6379 替換為 192.168.10.112:6379 ,http://192.168.10.111/DEV/V1.1.9/SSOService.svc 替換為 http://192.168.10.111/Test/V1.1.9/SSOService.svc(忽略大小寫)。
一、用 bat 腳本實現文件拷貝和web.config內容替換
1.文件拷貝
為了不影響舊的環境,我們新建一個文件夾 PackageDirectory 來保存修改后的文件。
文件拷貝用批處理來實現:新建一個 txt 文件,輸入以下內容,另存為 DFCopier.bat(directories & files copier),copyfrom 和 copyto 由參數方式傳入。
@echo off set from=%1 set to=%2 ::rd /q/s "%to%"&&md "%to%" dir /a /b %to%|findstr .>nul 2>nul && goto havefiles || goto nofiles :havefiles rem echo 有文件 goto end :nofiles rem echo 沒有文件 XCOPY %from% %to% /c/q/e/r :end
2.文件內容替換
新建一個 txt 文件,輸入以下內容,另存為 FCReplacer.bat(file content replacer),directory , file, from, to 由參數方式傳入。
@echo off set dir=%1 set file=%2 set from=%3 set to=%4 setlocal enabledelayedexpansion pushd %dir% for /f "tokens=1,2* delims=:" %%i in ('findstr /n ".*" %file%') do ( set txt=%%j if "!txt!" == "" ( echo.>>%dir%%file%.tmp ) else ( echo !txt:%from%=%to%!>>%dir%%file%.tmp ) ) move /y %dir%%file%.tmp %file% :end
3.配置 ccnet.config
在 tasks 節點中添加以下兩個子節點。假設一個最簡單的場景:替換數據庫命名 xxx_dev_db 為 xxx_test_db。
<!-- 復制文件到 Package 目錄 --> <exec> <executable>C:\CC.NET\Server\DFCopier.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PublishDirectory C:\CC.NET\Server\Test\PackageDirectory</buildArgs> </exec> <!-- 替換 Package 目錄下的 WebConfig 文件 --> <exec> <executable>C:\CC.NET\Server\FCReplacer.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PackageDirectory\WcfService Web.config _dev_db _test_db</buildArgs> </exec>
ftp 和 package 部分修改如下:
<!--ftp發布Wcf服務到開發環境--> <ftp> <serverName>127.0.0.1</serverName> <userName>admin</userName> <password>admin</password> <action>UploadFolder</action> <ftpFolderName></ftpFolderName> <localFolderName>C:\CC.NET\Server\Test\PackageDirectory\WcfService</localFolderName> <recursiveCopy>true</recursiveCopy> <timeDifference>1</timeDifference> </ftp> <package> <name>Lib.sln</name> <compression>9</compression> <manifest type="defaultManifestGenerator" /> <packageList> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\*.svc" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\*.Release.config" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\bin\*.dll" targetFolder="WcfService\bin" /> <!--<packageFolder sourceFolder="C:\CC.NET\Server\Test\PublishDirectory\WcfService" targetFolder="WcfService" fileFilter="*.*" flatten="false" includeSubFolders="false" />--> </packageList> </package>
4.測試
Forcebuild 得到以下結果(只截取了 DFCopier.bat 和 FCReplacer.bar 相關的部分):
打開 Web.config 文件卻發現中文會變成亂碼。
二、自己實現文件內容替換功能
批處理的方式在中文面前顯得有點蒼白無力,另外一些更復雜的場景是無法簡單地 replace 來解決。一番 Google 折騰后我決定自己寫一個 replacer,支持正則來處理可能出現的復雜需求。
1.實現 replacer
新建一個項目,命名為 FCReplacer。實現的代碼 100 行不到。編譯得到 FCReplacer.exe
class Program { static void Main(string[] args) { var file = GetFileName(args); var replacers = GetReplaers(args); if (file == null || replacers == null || replacers.Count() == 0) return; var txt = File.ReadAllText(file); Console.WriteLine(string.Format("Replace content of file({0})", file)); foreach (var replacer in replacers) { Console.WriteLine(string.Format("Replace {0} to {1}", replacer.From, replacer.To)); txt = replacer.Replace(txt); } File.WriteAllText(file, txt); } static string GetFileName(string[] args) { string filename = null; var fileRegex = new Regex("^/file=(=?.*)$", RegexOptions.Compiled); foreach (var arg in args) { var match = fileRegex.Match(arg); if (match.Groups.Count == 2) { filename = match.Groups[1].Value.Trim(); break; } } return filename; } static IEnumerable<Replacer> GetReplaers(string[] args) { var list = new List<Replacer>(); var replacerRegex = new Regex("^/from=(=?.*)/to=(=?.*)$", RegexOptions.Compiled); foreach (var arg in args) { var match = replacerRegex.Match(arg); if (match.Groups.Count == 3) { var from = match.Groups[1].Value.Trim(); var to = match.Groups[2].Value.Trim(); list.Add(new Replacer() { From = from, To = to }); } } return list; } class Replacer { public string From { get; set; } public string To { get; set; } public string Replace(string txt, RegexOptions regexOption = RegexOptions.IgnoreCase) { txt = Regex.Replace(txt, this.From, this.To, regexOption); return txt; } } }
2.配置 ccnet.config
把文件替換的批處理部分改為 FCReplacer.exe。替換的邏輯通過參數方式傳入。假設三個更復雜的場景:
(1) 替換數據庫名 xxx_dev_db 為 xxx_test_db
(2)替換 redis 緩存服務的地址 192.168.10.111:PORT 為 192.168.10.112:PORT,端口設置不變
(3)替換 SSO 服務地址,版本號不變
<!-- 復制文件到 Package 目錄 --> <exec> <executable>C:\CC.NET\Server\DFCopier.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PublishDirectory C:\CC.NET\Server\Test\PackageDirectory</buildArgs> </exec> <!-- 替換 Package 目錄下的 WebConfig 文件 --> <!--<exec> <executable>C:\CC.NET\Server\FCReplacer.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PackageDirectory\WcfService Web.config _dev_db _test_db</buildArgs> </exec>--> <exec> <executable>C:\CC.NET\Server\FCReplacer.exe</executable> <buildArgs> /file=C:\CC.NET\Server\Test\PackageDirectory\WcfService\Web.config /from=_dev_db/to=_test_db /from=http://192.168.10.111/Dev/V(=?\d+(.\d+)*)/SSOService.svc/to=http://192.168.10.111/Test/V$1/SSOService.svc </buildArgs> </exec>
3.測試
Forcebuild 得到以下結果(只截取了 DFCopier.bat 和 FCReplacer.exe 相關的部分):
打開 Web.config 文件,三個替換邏輯都已經實現,中文也不再是亂碼。
完整的 ccnet.config 配置如下:
<cruisecontrol xmlns:cb="urn:ccnet.config.builder"> <project name="Lib.Sln"> <!--標簽--> <labeller type="dateLabeller"/> <artifactDirectory>C:\CC.NET\Server\Test\ArtifactDirectory</artifactDirectory> <!--項目的目錄--> <workingDirectory >C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <!--自動構建結果的查看地址--> <webURL>http://vw-caihaihua/CC/server/local/project/Lib.Sln/ViewProjectReport.aspx</webURL> <!--自動運行時間間隔--> <triggers> <!--源碼修改觸發--> <intervalTrigger seconds="10" buildCondition="IfModificationExists " /> <!--每日構建--> <scheduleTrigger time="19:00" buildCondition="ForceBuild"> <weekDays> <!--<weekDay>Sunday</weekDay>--> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> <!--<weekDay>Saturday</weekDay>--> </weekDays> </scheduleTrigger> </triggers> <!--對源碼修改延遲處理時間間隔--> <modificationDelaySeconds>30</modificationDelaySeconds> <maxSourceControlRetries>5</maxSourceControlRetries> <!--源代碼管理(SVN)--> <sourcecontrol type="svn"> <trunkUrl>https://vw-caihaihua/svn/Test/trunk/</trunkUrl> <executable>C:\Program Files (x86)\VisualSVN Server\bin\svn.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\</workingDirectory> <username>ci</username> <password>123456</password> </sourcecontrol> <tasks> <!--<devenv> <solutionfile>C:\CC.NET\Server\Test\WorkingDirectory\Lib.sln</solutionfile> <configuration>Debug</configuration> </devenv>--> <!--清理解決方案--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <buildArgs>/t:clean /t:rebuild /p:configuration=debug</buildArgs> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <projectFile>Lib.sln</projectFile> <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,C:\Program Files (x86)\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger> </msbuild> <!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:LibTest\bin\Debug\LibTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec> <!--發布Wcf服務到本機--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\WcfService</workingDirectory> <projectFile>WcfService.csproj</projectFile> <buildArgs> /t:ResolveReferences;Compile /t:_CopyWebApplication /p:Configuration=Release /p:WebProjectOutputDir=C:\CC.NET\Server\Test\PublishDirectory\WcfService /p:OutputPath=C:\CC.NET\Server\Test\PublishDirectory\WcfService\bin </buildArgs> </msbuild> <!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:WcfServiceTest\bin\Debug\WcfServiceTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec> <!--啟動 Asp.NET Development Server--> <!--<exec> <executable>C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE</executable> <buildArgs>/port:9999 /path:C:\CC.NET\Server\Test\PublishDirectory\WcfService</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec>-->
<!--ADDED START--> <!-- 復制文件到 Package 目錄 --> <exec> <executable>C:\CC.NET\Server\DFCopier.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PublishDirectory C:\CC.NET\Server\Test\PackageDirectory</buildArgs> </exec> <!-- 替換 Package 目錄下的 WebConfig 文件 --> <!--<exec> <executable>C:\CC.NET\Server\FCReplacer.bat</executable> <buildArgs>C:\CC.NET\Server\Test\PackageDirectory\WcfService Web.config _dev_db _test_db</buildArgs> </exec>--> <exec> <executable>C:\CC.NET\Server\FCReplacer.exe</executable> <buildArgs> /file=C:\CC.NET\Server\Test\PackageDirectory\WcfService\Web.config /from=_dev_db/to=_test_db /from=http://192.168.10.111/Dev/V(=?\d+(.\d+)*)/SSOService.svc/to=http://192.168.10.111/Test/V$1/SSOService.svc a </buildArgs> </exec>
<!--ADDED END-->
<!--ftp發布Wcf服務到開發環境--> <ftp> <serverName>127.0.0.1</serverName> <userName>admin</userName> <password>admin</password> <action>UploadFolder</action> <ftpFolderName></ftpFolderName> <localFolderName>C:\CC.NET\Server\Test\PackageDirectory\WcfService</localFolderName> <recursiveCopy>true</recursiveCopy> <timeDifference>1</timeDifference> </ftp> <package> <name>Lib.sln</name> <compression>9</compression> <manifest type="defaultManifestGenerator" /> <packageList> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\*.svc" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\*.Release.config" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PackageDirectory\WcfService\bin\*.dll" targetFolder="WcfService\bin" /> <!--<packageFolder sourceFolder="C:\CC.NET\Server\Test\PublishDirectory\WcfService" targetFolder="WcfService" fileFilter="*.*" flatten="false" includeSubFolders="false" />--> </packageList> </package> </tasks> <state type="state" directory="C:\CC.NET\server\CCState"/> <publishers> <!--標簽備份(如果成功)--> <buildpublisher> <sourceDir>C:\CC.NET\Server\Test\WorkingDirectory</sourceDir> <publishDir>C:\CC.NET\Server\Test\HistoryVersion</publishDir> </buildpublisher> <modificationHistory/> <statistics/> <!--郵件通知--> <email mailhost="smtp.live.com" mailport="25" mailhostUsername="ci@XXXCompany.com" mailhostPassword="******" from="ci@XXXCompany.com" useSSL="TRUE" includeDetails="true"> <!--郵件標題配置--> <subjectPrefix>[CI@XXXCompany]</subjectPrefix> <subjectSettings> <!-- Success/Broken/StillBroken/Fixed/Exception--> <subject buildResult="Success" value="${CCNetProject} Build Successful: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Fixed" value="${CCNetProject} Build Fixed: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Broken" value="${CCNetProject} Broke: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="StillBroken" value="${CCNetProject} Still Broken: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Exception" value="${CCNetProject} In Exception: Please check status of network / sourcecontrol.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> </subjectSettings> <!--收件人配置--> <converters> <regexConverter find="$" replace="@XXXCompany.com"/> </converters> <modifierNotificationTypes> <notificationType>Failed</notificationType> <notificationType>Fixed</notificationType> </modifierNotificationTypes> <users> <user group="leader" name="ci.XXXCompany" address="ci@XXXCompany.com"/> <user group="developer" name="harvey.choi" address="harvey.choi@XXXCompany.com"/> <user group="tester" name="jolin" address="jolin.tang@XXXCompany.com"/> </users> <groups> <group name="leader"> <notifications> <!--Always/Success/Change/Fixed/Failed --> <notificationType>Change</notificationType> <notificationType>Exception</notificationType> </notifications> </group> <group name="developer"> <notifications> <notificationType>Success</notificationType> <notificationType>Fixed</notificationType> <notificationType>Failed</notificationType> </notifications> </group> <group name="tester"> <notifications> <notificationType>Fixed</notificationType> </notifications> </group> </groups> </email> <xmllogger/> </publishers> </project> </cruisecontrol>
到這里,一個簡單場景的自動化發布部分已經實現。TODO 列表里仍須解決的幾個主要問題:
1.Build 成功,人工測試未通過時,如何一鍵回滾
2.數據庫的更新和回滾如何實現自動化
