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}nantVitreo.Nant.dll" />
     <iisappmap vdirname="${deploy.vdir}" extension=".mvc"
                executable="C:WINDOWSMicrosoft.NETFrameworkv2.0.50727aspnet_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:projectsLineageOrganizerreleasesLatest
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:projectsLineageOrganizertools

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:dataccnetTrilogy.Gunton</workingDirectory>
  <triggers />
  <tasks>
    <nant>
      <executable>c:dataccnetTrilogy.Guntontoolsnantnant.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