Implementing Team City for .NET Projects, Part 5: Deployments

August 9th, 2009

My apologies to all those who have been waiting for the deployment scenario – I appreciate your patience.  My approach to deployments has been a moving target – as it is starting to firm up a bit, I thought  I would write down some thoughts.

For more information on this series, please see the introductory post. In the previous post, I discussed build scripts as a way of extending the functionality of TeamCity.  If you downloaded the sample solution, you could see the tasks and structure of the NAnt and Rake scripts.  Since then, I have fully embraced Rake as my default build script engine; therefore, my script examples in this post are Rake scripts.

Don’t forget to listen to Elegant Code Cast #28 , where Chris Brandsma and I had the privilege of speaking with Jim Wierich, the father of Rake.

I have modeled the Rake scripts after the Fluent NHibernate and FubuMVC scripts.  I am also very interested in rake-dotnet from Pete Mounce.  Once rake-dotnet has support for NUnit and MSpec, I will be migrating my scripts to use it as a base library.  (Pete, I am actually planning on a patch for both – you know…in my spare time.)  Rake-dotnet is definitely worth checking out, especially if you use xUnit as your test library.

The Deployment Process

Here is a representation of my preferred folder structure for a solution:

before build

The following tasks are completed on each check-in:

  • MSBuild is called to clean and build both debug and release versions of the projects in the solution
  • Tests are run
  • An archive is created of each site and/or binaries (both debug and release versions), build scripts, and possibly SQL scripts

A post-build event is run on each web application project to pre-compile the site into a specific directory for each compilation type. My post build event looks like this:

%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe 
–nologo -errorstack -f -u -c -p $(ProjectDir) -v temp
$(SolutionDir)\Build\results\PrecompiledWeb\$(ConfigurationName)\$(TargetName)

The post-build event is also a good place to add any plug-in type binaries not directly depended on by your site, but included in your solution. Each site also has a set of configuration sources for each environment up the chain (nightly, test, staging, production)

After the build, I have added several folders and archive files to the build directory:

after build 

The archive files are stored in subsequent builds.  I never have to rebuild my solution for each platform.  Here is an example artifact path definition:

image

For the nightly build artifacts, I have the following definitions:

build/*=>Build
build/results/Debug.zip=>Sites
build/results/Release.zip=>Sites
build/results/DebugBinaries.zip=>Binaries
build/results/ReleaseBinaries.zip=>Binaries

For subsequent builds, the definitions change slightly:

build/**/*=>build
sites/**/*=>sites
binaries/**/*=>binaries

When the nightly build is run:

  • The check-in build is run, if it has pending changes
  • The desired (debug or release) site is unzipped and pushed to the nightly build site
  • The configuration files for the site are overwritten with the nightly build environment files (See script below) You may update the configuration files before or after pushing your site, depending on your preference)
  • Artifacts of the sites, binaries, and build scripts are stored with the build on TeamCity
  • Smoke test the sites (see script below)
    When a subsequent environment is run:
  • Clean all files before build (checkmark on the VCS configuration page for the build)
  • The artifacts from the dependent build are retrieved (configured from the dependencies page of the build configuration).
    image
  • The build tools are retrieved from source control using an edit checkout rule (+:lib)
    Whereas the compile-check and nightly build configurations get the entire folder structure from version control, the subsequent builds only retrieve the lib directory.image
  • The existing site is archived
  • The new site is unzipped and pushed
  • The configuration files for the site are overwritten with the specific environment files
  • Artifacts of the sites, binaries, and build scripts are stored with the build on TeamCity
  • Smoke test the sites
    The idea is to feed up the chain of deployments the artifacts needed to complete the next deployment.  It takes a little trial and error to get it right, but the benefits are worth it.  Our current builds at Unity Media Group take 2 to 3 minutes for the compile check (including test runs and zipping files), then approximately 45 seconds to deploy the site on the internal network.

What I have not automated yet (but would like to):

  • Reconfiguring the sites to point to a maintenance page while deploying
  • Running the SQL schema compare and integration or run update scripts
    – I have run compare/integration scripts previously, but they tended to be more hassle than running them manually.  Still looking for better ways to do this, though.
  • If building binaries for use by other projects, update those projects with the new binaries
  • Run data scrubbing scripts (for creating known default environments for test or demonstration sites)

      Rake File Snippets

    To update the configuration files, I define the following method in my helper file:

 # UPDATING CONFIGURATION FILES 
def copyToDirectory(zip_file, website_dir, config_type)
throw("ZipFile does not exist!", zip_file) unless File.exist?(zip_file)
seven_zip = SevenZip.new :ziparchive => zip_file, :directory=>website_dir
seven_zip.unzip

# WEB_PROJECTS: list of web application projects with a Config directory
# => WEB_PROJECTS = ['ElegantCode.Example.Administration',
# 'ElegantCode.Example.Client',
# 'ElegantCode.Example.Services']
WEB_PROJECTS.each do |site|
puts "##teamcity[progressMessage 'Updating application settings for #{site}']"
list=FileList.new("#{website_dir}/#{site}/Config/*-#{config_type}.config")
list.each do |file|
target = file.gsub("-#{config_type}",'')
puts "##teamcity[progressMessage 'Copying #{file.to_s} to #{target.to_s}']"
cp file,target
end
end
end

And this is how I use it in the rakefile:

# USAGE:
zip_file = File.expand_path(File.join("..","Sites", 'Debug.zip'))
website_dir = File.join('Z:', 'AdventureMVC')
copyToDirectory zip_file, website_dir, 'sandbox'

Similarly, to smoke test the site, I have the following class in my helper file:

# SMOKE TEST 
class PreJIT
attr_reader :sites

def self.compile(sites)
sites.each do |site|
puts "##teamcity[progressMessage 'Pre-jitting #{site}']"
open (site)
end
end
end

And this is how I use it:

# USAGE:
sites = ['http://test.example.com/admin', 'http://test.example.com']
PreJIT.compile(sites)

If the site  has a 40x/50x error, the build will fail.

There is much more to the rake scripts; however, you should peruse the rake files from Fluent NHibernate or FubuMVC for more ideas on writing your own Rake scripts.  You can incorporate rake-dotnet into your rake environment, as well.  Finally, you can accomplish the same tasks using MSBuild or NAnt, if you feel more confident in those environments.

Until next time..

Fluent NHibernate

FubuMVC

rake-dotnet

TeamCity

Rake


  • Pingback: Dew Drop – August 10, 2009 | Alvin Ashcraft's Morning Dew

  • http://elegantcode.com Richard Cirerol

    From the CodeProgression.com comments…

    Paul Davis said…
    RE: Reconfiguring the sites to point to a maintenance page while deploying….

    Can you copy in a app_offline.htm file?

    http://weblogs.asp.net/scottgu/archive/2005/10/06/426755.aspx

    AUGUST 11, 2009 5:39 PM

  • http://www.elegantcode.com Ryan Kelley

    Richard,

    Thanks for posting this, I was sitting on the edge of my chair about 3 months ago waiting for this! However, I was able to get my TeamCity / Rake stuff going full speed ahead, doing internal deployment of web app and SQL Compare scripts all automatic. I would love to compare some notes with you. I am especially interested in how you are doing your deployment to the server.