ASP.NET Web应用程序(MVC)部署自动化和Subversion

7
我们正在尝试自动化构建流程到我们的分段服务器,但遇到了一个小问题。我们使用VS2010内置的发布功能,提交到Subversion,然后第三方应用程序(Beanstalk)自动拉取更新的文件并将它们FTP到分段服务器。
我们遇到的问题是,我们似乎只有以下选择:
1. (两害相权取其轻)如果我们选择使用“用本地副本替换匹配的文件”,这样做很好,但有一个例外:此选项不会删除从项目中删除的任何文件。这将导致旧文件的垃圾和/或安全问题。
2. 如果我们选择使用“在发布之前删除所有现有文件”,则会删除整个文件夹结构,包括Subversion用于更新跟踪等的.SVN隐藏文件夹。从准确性的角度来看,这似乎是最佳解决方案,但它真正破坏了本地Subversion环境,这是这种自动化的中间人。
我的问题是:是否有一个简单的解决方法,或者我们忽略了完全不同的部署选项(我们不想直接从VS发布到服务器,因为我们想要跟踪部署发生的时间、人员和内容)?我唯一找到的解决方法是在发布之前手动删除文件内容,同时保留文件夹结构,然后使用“用本地副本替换匹配的文件”进行部署。不幸的是,这给“自动化”带来了全新的含义。
有没有关于如何最好地完成此操作的任何想法?

1
正好赶上了。我也在寻找解决方案。你尝试过玩弄“解决方案配置”和“后构建事件”吗? - Sergey Akopov
迄今为止所有的部署都是手动的,这在处理大型变更集时可能非常费力。我简直不敢相信没有更好的选项内置其中,能够在不破坏文件夹的情况下提供准确的构建。 - Keith
4个回答

4
你可能希望考虑使用NAnt或类似工具来自动化任务,例如构建和发布到Subversion。这是我的WebApplication项目的大部分构建文件。对于MVC可能会有所不同。如果是这样,我相信你可以以此为起点。我绝不是NAnt专家,所以可能会有一些缺陷,但这对我绝对有效。
我必须向每个我想要发布的.csproj文件中添加一个PublishToFileSystem目标。其源代码可以在这里找到构建文件也可在Pastebin上找到
<?xml version="1.0"?>
<project name="deploy" default="all">
    <property name="nant.settings.currentframework" value="net-4.0" />  
    <!-- Any of these can be passed through the command line -->
    <property name="sourceDirectory" value="${project::get-base-directory()}" />
    <property name="publishDirectory" value="${sourceDirectory}\build" />
    <property name="MSBuildPath" value="${framework::get-assembly-directory(framework::get-target-framework())}\msbuild.exe" />
    <!-- The build configuration to use when publishing and transforming the web.config file. This is useful when you have multiple environments for which you create builds -->
    <property name="buildConfiguration" value="Release" /> 
    <!-- Set these as needed -->
    <property name="svn.username" value="" />
    <property name="svn.password" value="" />

    <target name="SvnPrep">
        <property name="svn.dir" value="${publishDirectory}\.svn" />
        <property name="svn.update" value="true" readonly="false" />
        <echo>env.svn.path = svn</echo>
        <echo>svn.dir = ${svn.dir}</echo>
        <mkdir dir="${publishDirectory}" unless="${directory::exists(publishDirectory)}" />
        <!-- Check if there's a .svn dir already. If not: checkout, else: update. -->
        <if test="${not directory::exists(svn.dir)}">
            <exec program='svn.exe' workingdir="${publishDirectory}" verbose="true">
                <arg line='co ${svn.builduri} --username ${svn.username} --password ${svn.password} --non-interactive ./' />
            </exec>
            <property name="svn.update" value="false" readonly="false" />
        </if>
        <if test="${svn.update}">
            <exec program='svn.exe' workingdir="${publishDirectory}\" verbose="true">
                <arg line='up --username ${svn.username} --password ${svn.password} --non-interactive --force ./' />
            </exec>
        </if>
        <!-- Force any conflicts to be resolved with the most recent code -->
        <exec program='svn.exe' workingdir="${publishDirectory}\" verbose="true">
            <arg line='resolve --accept theirs-full -R ./' />
        </exec>
    </target>   

    <target name="DeleteFiles">
        <!-- Delete only the files (retain directory structure) in the directory to which you are going to publish/build. NAnt excludes svn directories by default. -->
        <delete includeemptydirs="false">
            <fileset basedir="${publishDirectory}">
                <include name="**/*.*" /> 
            </fileset>
        </delete>
    </target>
    <target name="Publish">
        <!-- I know there's an MSBuild task, I don't know why I didn't use it, but this works. -->
        <!-- Build and publish frontend -->
        <exec program="${MSBuildPath}">
            <arg line='"${sourceDirectory}\YourProject.csproj"' />
            <arg value='"/p:Platform=AnyCPU;Configuration=${buildConfiguration};PublishDestination=${publishDirectory}"' />
            <arg value="/target:PublishToFileSystem" />
        </exec>
        <!-- Transform the correct web.config and copy it to the build folder. PublishToFileSystem doesn't transform the web.config, unfortunately. -->
        <exec program="${MSBuildPath}">
            <arg line='"${sourceDirectory}\YourProject.csproj"' />
            <arg value='"/p:Platform=AnyCPU;Configuration=${buildConfiguration};PublishDestination=${publishDirectory}"' />
            <arg value="/target:TransformWebConfig" />
        </exec>
        <copy file="${sourceDirectory}\YourProject\obj\${buildConfiguration}\TransformWebConfig\transformed\Web.config" tofile="${publishDirectory}\YourProject\web.config" overwrite="true" />     
    </target>

    <target name="SvnCommit">       
        <!-- add any new files -->
        <exec program='svn.exe' workingdir="${publishDirectory}" verbose="true">
            <arg line='add --force .' />
        </exec>
        <!-- delete any missing files, a modification of this https://dev59.com/RnNA5IYBdhLWcg3wKaYx -->
        <!-- When there's nothing to delete it looks like this fails (to NAnt) but it is actually fine, that's why failonerror is false -->     
        <exec program='cmd.exe' workingdir="${publishDirectory}\" verbose="true" failonerror="false" 
            commandline='/C for /f "usebackq tokens=2*" %i in (`svn status ^| findstr /r "^\!"`) do svn del "%i %j"' >
        </exec>
        <exec program='svn.exe' workingdir="${publishDirectory}" verbose="true">
            <arg line='commit -m "Automated commit from build runner"' />
        </exec>
    </target>

    <target name="ShowProperties">
        <script language="C#" prefix="util" >
            <code>
                <![CDATA[
                public static void ScriptMain(Project project) 
                {
                    foreach (DictionaryEntry entry in project.Properties)
                    {
                        Console.WriteLine("{0}={1}", entry.Key, entry.Value);
                    }
                }
                ]]>
            </code>
        </script>
    </target>

    <target name="all">
        <call target="ShowProperties" />
        <call target="SvnPrep" />
        <call target="DeleteFiles" />
        <call target="Publish" />
        <call target="SvnCommit" />
    </target>
</project>

看起来很有前途,我必须在有机会时仔细研究一下。谢谢。 - Keith
开始使用NAnt和其他自动化工具可能需要花费一些时间,但最终它是非常值得的。如果您有任何其他问题,请告诉我它对您的工作效果如何。 - Alexander Pendleton
+1 个好回答!@Alex,为什么要将它放在源代码文件夹内而不是一个单独的svn位置?这样当有人进行svn更新时,它会自动下载,并且您还可以与任何具有源代码访问权限的人共享配置。@Keith,你用过它吗?效果如何?你最终得到了什么不同的结果? - eglasius

0
我们也使用 SVN 进行部署,并遇到了同样的问题。我们的解决方案是为“重大”升级分支项目 - 即添加和删除文件的情况,而不仅仅是修复小错误和进行微调,这些通常可以通过 xcopy 处理。Svn 布局如下:
--project
---production
----20100101
----20100213
[etc, etc]
程序方面来说,它非常简单--如果有足够大的更改,适当地检查构建工件即可。
另一件你可能想尝试的事情,特别是如果你不能轻松地将生产位切换到其他分支,那就是使用一些更高级的东西,比如PowerShell来执行删除文件命令,这可以过滤掉*.svn文件夹。

1
分支的问题在于它为我们希望完全自动化的流程增加了一个手动步骤。感谢您的建议,但这绝对不是我们正在寻找的方向 - 我们宁愿手动删除文件,直到我们能找到更好的解决方案。 - Keith
嗯,考虑到你之前的回答,你可能想尝试一些自动跟踪更改的工具,而不是依赖于到处都有痕迹的工具。Mercurial 是一个不错的选择。 - Wyatt Barnett

0
我会说“幸运的是”,这给自动化带来了全新的意义 :) 您所描述的被称为应用程序发布自动化,有时也称为部署自动化。如果您真的想知道谁做了什么,在哪里,结果如何等等,那么您需要像 Nolio ASAP(http://www.noliosoft.com) 这样的产品。请让我知道这是否有帮助,因为根据您的描述,它似乎是完美匹配。

+Daniel


1
这并不是我们正在寻找的,因为它似乎没有与Subversion集成(至少它没有明确提到)。然而,它看起来是一个非常好的产品,但我们已经有了一个完整的部署解决方案,只是上述的小问题。 - Keith

0
为什么你要将网站发布到Subversion处理的文件夹中呢?
我会直接在SVN处理的文件夹中直接操作文件。只要我提交任何内容,它就会被Beanstalk拉到暂存区。这样,删除的文件总是从repo中删除,您不必担心这个问题。一切都始终保持同步。
如果您觉得这会将太多文件放入暂存区,您仍然可以使用脚本和Visual Studio命令来发布网站。但我不确定Beanstalk如何与此场景集成。我知道CC.net和许多其他替代方案可以做到这一点。

1
首先,我们使用暂存服务器来“暂存”实际部署到生产环境中的预编译代码... 其次,我们使用配置转换来管理在不同环境之间变化的连接字符串和应用程序设置。让Beanstalk直接从SVN拉取当前版本的web.config是行不通的。 - Keith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接