Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Automated deployments

In my last post, I ran on about automated releases. So much so that adding automated deployments at the end seemed like cruel punishment. So instead, I decided to make it a separate post. Still a cruel punishment, but now it is unusually so.

To recap, our automated release target executes on every check-in and creates a subfolder under the releases folder with the version number of the application. Then a releaseable "package" is placed in it. In this case, it’s a web application that is to be deployed.

The target you are about to see follows from this train of thought. And apologies if the formatting looks off. I’ve yet to figure out how to embed XML into a post in LiveWriter or Community Server’s editor so that it uses the right HTML in the browser and the average RSS reader.

<target name="deploy" depends="version">
     <deliisdir vdirname="${deploy.vdir}" failonerror="false" />
     <delete dir="${deploy.dir}" failonerror="false" />
     <mkdir dir="${deploy.dir}" />
     <copy todir="${deploy.dir}" >
         <fileset basedir="${latest.release.dir}">
             <include name="**\*.*" />
         </fileset>
     </copy>
     <property name="database.name" value="${database.name.test}" />
     <call target="drop-database" />
     <call target="create-database" />
     <mkiisdir dirpath="${deploy.dir}" vdirname="${deploy.vdir}" defaultdoc="Default.aspx" />
     <loadtasks assembly="${tools.dir}\nant\Vitreo.Nant.dll" />
     <iisappmap vdirname="${deploy.vdir}" extension=".mvc"
                executable="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll"
                verbs="GET,POST"
                checkfileexists="false" /> 
</target> 

As before, a list of variables:

deploy.vdir The name of the IIS virtual directory we are deploying to
deploy.dir The folder that the virtual directory will point to
latest.release.dir Path to the folder containing the latest release. e.g. C:\projects\LineageOrganizer\releases\Latest
database.name.test The name of the database we are pointing to in the deployed environment (i.e. the Test environment)
tools.dir Path to tools used during the build process. e.g. C:\projects\LineageOrganizer\tools

And again, a brief synopsis of what the target does:

  1. Delete the existing IIS virtual directory if it exists. (The <deliisdir> and <mkiisdir> tasks are included in NAntContrib)
  2. Delete the deploy folder if it exists and re-create it.
  3. Copy the latest release to the deploy folder.
  4. Drop and re-create the test database (the drop-database and create-database targets use the <updateSqlDatabase> task from vincent-vega, which appears to be off-limits today but you can get a copy from CodeCampServer).
  5. Re-create the IIS virtual directory
  6. Create a mapping for the .mvc extension for ASP.NET MVC. (<iisappmap> task is courtesy of Brian Donahue, who I’d recommend adding to your blogroll).

The last step is necessary only if you need to create an IIS mapping, which you do when deploying an ASP.NET MVC application into an IIS 6 environment. Otherwise, you can skip this step.

In the post on releases, I snuck in a teaser on the folder where I store the latest version of the application. In that target, I created a folder with the same name as the application version and released to it. Then I copied the entire contents of this folder to another one called Latest. The reason for this will be clear shortly. First, I need to talk about when this target is executed.

The deployments don’t happen at the same interval as the build. That is, the application is not deployed on every build. This would be pretty chaotic if you re-deployed the application several times a day while the testers were trying to work.

So they have to be on a different schedule or, as in my case, triggered manually. This means setting up a separate CruiseControl.NET project for them. So for our LineageOrganizer application, we would have two CC.NET projects: one for the main build and one to deploy the application. Both use the same build file and both have the same root folder. In fact, the CC.NET project for the deploy is relatively simple:

<project name="LineageOrganizer.deploy">
  <webURL>http://ccnetservername/server/local/project/LineageOrganizer.deploy/ViewProjectReport.aspx</webURL> 
  <workingDirectory>C:\data\ccnet\Trilogy.Gunton</workingDirectory>
  <triggers />
  <tasks>
    <nant>
      <executable>c:\data\ccnet\Trilogy.Gunton\tools\nant\nant.exe</executable>
      <buildFile>Trilogy.Gunton.build</buildFile>
      <targetList>
        <target>deploy</target>
      </targetList>
    </nant>
  </tasks>
</project>

There’s no <sourcecontrol> tag nor are there any merge files to take care of. Just execute the target and that’s it.

Here’s where the concept of a Latest folder comes in. The releases are created based on the application’s version number. And the version number is based partially on the CruiseControl version. But now we have two different CC.NET projects, each with their own version number in CruiseControl. So the latest version for the main build, which is the one that creates the releases, is different than the one that deploys the application. I.E. If we try to get the path to the latest release folder based on the version number within the deploy target, it would create a path based on the deployment CC.NET project, not the original one.

So to get around this, after every release, we copy the release to a statically named folder called Latest. That way, both the build CC.NET project and the deploy CC.NET project know where it is.

Another way to do this would be to write the current version number to a file. This was going to be my first course of action until Donald suggested the static Latest folder which I like better although only because it feels better.

This process is still a work-in-progress for me but it’s worked out reasonably well so far on my one-man team. Though I will admit that the deploy target does fail on occasion if someone is using the app when it is running. Seems to be locking files so that the deploy folder can’t be deleted/re-created. When that becomes annoying enough to fix, you can be sure I’ll pad my blog resume with the solution.

Kyle the Deceptively Simpleww

This entry was posted in Continuous Integration, NAnt. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Rob Thijssen

    I’ve written a small app that monitors TC config folders and pushes changes to git. I wonder if teamcity.codebetter.com would benefit from using this tool to sync config to github so that project maintainers get some more visibility on changes to their build configs? I also requested hosting for the app itself: http://github.com/grenade/teamcity-config-monitor.

  • Kyle Baley

    @Jeff

    It’s getting harder and harder to ignore trying out TeamCity based on tidbits and reviews like this one. It’s relatively high on my todo list. The only thing holding me back is that I simply haven’t been feeling any pain yet with CC.NET.

    @Eric

    I’m liking the look of the project’s direction. It’ll be one to watch.

  • http://hex.lostechies.com Eric Hexter

    The vincent-vega database change management project has been consolidated into the Tarantino project. It is still open source and still on Google Code. You can access it here.

    http://code.google.com/p/tarantino/

    We are working on the docs and sample implementations.

    Eric

  • http://agilology.blogspot.com/ Jeff Tucker

    I’ve been using a similar process to this ever since I first got tired of doing manual updates about four years ago and it’s worked well for me. While I use something like this for now, as soon as our installer is ready I’ll get rid of the NAnt task that I have for deployment and instead use the exec task to run our installer so that it gets tested with continuous integration as well. This also serves as a sort of “unit test” of the installer’s ability to run from a script and do a silent install in multiple sites on the same server (this is critical to our ops people for some reason). We have ignored our installer until the last minute in the past and it has really hurt when we did so I’m hoping that this will solve that problem for us.

    One more thing is I’ve switched to TeamCity from Cruisecontrol. Teamcity keeps build artifacts around for each build so I don’t need version numbered directories for my deployments since you can download directly from the build on TeamCity. It also sets parameters in Nant for you for things like build version, svn revision, build root directory, and whatever else I define. I’ve found it is way easier to configure TeamCity as well so it’s easier for me to make changes to the build when I need to. I would recommend that you try it out.

  • http://andrewmyhre.wordpress.com Andrew Myhre

    Hi Kyle,
    I like your approach here. My deployment projects rebuild the app from the latest source and apply the current build label using the Remote Project Labeller in CCNet. It takes longer to run a deployment build that your setup would, but I don’t mind because it means I can use web deployment projects. The big benefit of using those is that I can keep more of the build configuration in the IDE, which is better for those of my team who are unfamiliar with build scripting and don’t have time to learn it on the job.

  • Kyle Baley

    @Sean

    Actually, you’re absolutely right. I forgot to mention that. In the original version, I wasn’t dropping and re-creating it. But I ran into the problem I mentioned at the end of the post where it wasn’t able to delete the deploy directory because files were locked. I added the deliisdir and mkiisdir in an attempt to fix it and it didn’t work. But I left it in thinking it wasn’t causing any trouble there.

    And app_offline.htm is a good call because of the built-in support for it. Good points all around.

  • http://schambers.lostechies.com Sean Chambers

    I have found that the deliisdir and mkiisdir is a little overkill. There is really no reason to re-create the entire IIS virtual directory on every deploy unless you have major changes to your vdir.

    All I do now is drop in a app_offline.htm into the root, delete everything under the vdir root and then copy my “deployment”, you call it the “latest” folder into the web root, delete the app_offline.htm file and voila! your set.

    I oriignally was deleting the iis dir and re-creating everytime but at the time it was creating more headaches then it was worth.

    Maybe I am missing something here, but I don’t see the benefit of doing that.