【Hello CC.NET】自动化发布时 Web.config 文件维护

《【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.数据库的更新和回滚如何实现自动化

posted @ 2013-08-02 18:30  海华  阅读(2458)  评论(20编辑  收藏  举报