Navigation

Search

Categories

 

On this page

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 8
This Year: 2
This Month: 0
This Week: 0
Comments: 6

Sign In
Pick a theme:

 Wednesday, December 05, 2007
Wednesday, December 05, 2007 12:14:16 PM (GMT Standard Time, UTC+00:00) ( )

If there’s one thing that you should ask your self across everything you do it’s “How can we test this”. Deployments are often overlooked as so infrequent that it doesn’t need to be fully tested or automated. I’ll show you how to setup a simple promotion system using CruiseControl.Net and NAnt.

Here are the symtoms that you need to improve how your deploying your applications.

  1. You can’t remember if you deployed some fix or feature and have to check to see if you did.
  2. You don’t remember when you actually deployed but remember it was around march last year.
  3. You’re the only person that knows how to deploy.
  4. You’ve accidently deployed a the application built in debug.

What you’ll need

  1. Download and install CruiseControl.Net
  2. Read thru the last article where we covered versioning with NAnt and CruiseControl.Net

You can download the code from google code at

http://sleepoverrated.googlecode.com/svn/

If you have a Subversion Client like TortoiseSVN then you can just check out all the code.

The Enviroments

BK02Enviroments

We’ll be using three server enviroments here, each one could be a web server or a windows form or anything but today it’ll just be a command line application. The Application prints its version number and the enviroment name from a config file. Hopefully after we’re thru here you can scale this system out to whatever your needs are.

NAnt Configuration

We will need two builds one for Current (our Continuous Integration) and one for Development. The only difference is that Current is compiled in debug and development is compiled in release mode. Its good practise to start compiling in debug on your Continuous Integration enviroment so that you can generate Code Coverage reports using NCover.

The only promotion we’re showing here is for staging since you should really seperate your promotion to live into another process to prevent accidental promotions.

buildknowledge.build

This is a variation of the last articles build file.

The main differences are:

  1. There’s a debug property so we can set the compile mode
  2. the expand.template.file lets us generate a config file by replacing tokens in config\app.config.template
  3. deploy.copy lets us copy a whole directory of development to staging
  4. include buildfile lets us add more tasks specific to our different enviroments in the second build file

The actually targets we’ll be running are in the next NAnt file so these are just lower level tasks

<?xml version="1.0" encoding="utf-8"?>
<!--EXTERNAL_PROPERTIES: target.dir;source.dir;AppConfig.EnviromentName;AppConfig.Directory-->
<project name="BuildKnowledge" default="current">    
    
    <!-- add tasks in from the enviroments build file -->
    <include buildfile="buildknowledge.enviroments.build"/>
    
    <!-- initialize properties -->
    <property name="debug" value="false" />
    <property name="target.dir" value="build" />
    <property name="source.dir" value="" />
    
    <target name="init" description="clean out build dirs">
        <delete dir="build" />
        <mkdir dir="build" />    
        <delete dir="${target.dir}" />
        <mkdir dir="${target.dir}" />    
    </target>
    
    <target name="asminfo" description="generating the AssemblyInfo.cs">
        <if test="${property::exists('CCNetLabel') == false}">
            <property name="CCNetLabel" value="0.0.0.1" />
        </if>
    
        <asminfo output="build\AssemblyInfo.cs" language="CSharp">
            <imports>
                <import namespace="System" />
                <import namespace="System.Reflection" />
                <import namespace="System.EnterpriseServices" />
                <import namespace="System.Runtime.InteropServices" />
            </imports>
            <attributes>
                <attribute type="ComVisibleAttribute" value="false" />
                <attribute type="CLSCompliantAttribute" value="true" />
                <attribute type="AssemblyVersionAttribute" value="${CCNetLabel}" />
                <attribute type="AssemblyTitleAttribute" value="Build knowledge - Hello World" />
                <attribute type="ApplicationNameAttribute" value="BuildKnowledge" />
            </attributes>
            <references>
                <include name="System.EnterpriseServices.dll" />
            </references>            
        </asminfo>
    </target>
        
    <target name="compile" depends="init,asminfo" description="compiling all the code in the src dir">
        <csc output="${target.dir}\${project::get-name()}.exe" target="exe" debug="${debug}">
            <sources basedir="src">
                <include name="**/*.cs" />
                <include name="../build/AssemblyInfo.cs" />
                <exclude name="**/AssemblyInfo.cs" />
            </sources>
        </csc>    
    </target>
    
    <target name="deploy.copy" description="copy promotion of one build to the next enviroment">
        <if test="${directory::exists(target.dir) == false}">
            <mkdir dir="${target.dir}" />
        </if>
        <copy todir="${target.dir}" overwrite="true">
            <fileset basedir="${source.dir}">
                <include name="**/*" />
                <exclude name="*.config" />
            </fileset>
        </copy>
    </target>
    
    <target name="load.settings" description="load all the enviroment settings">
        <if test="${file::exists('config\' + properties)}">
          <echo message="Loading ${properties}" />
          <include buildfile="config\${properties}" />
        </if>
    </target>
    
    <target name="expand.template.file" description="replace the tokens in the template file">
        <copy file="config\${target}.template" tofile="config\${target}" overwrite="true" >
            <filterchain>
                <replacetokens>    
                    <token key="AppConfig.EnviromentName" value="${AppConfig.EnviromentName}" />
                </replacetokens>
            </filterchain>
        </copy>    
    </target>
    
    <target name="deploy.config" description="create the config file and deploy it">
        <property name="target" value="app.config" />
        <call target="expand.template.file" />
        <copy file="config\${target}" tofile="${AppConfig.Directory}\${project::get-name()}.exe.config" />
    </target>
</project>

buildknowledge.enviroments.build

Here’s our enviroments, each one will used by CruiseControl.Net as a different project. We have a file for each enviroment with all the settings we’ll be using. This lets you quickly change the connectionstring but today all we’re using it for is the the name of the Enviroment and its directory.

  1. Current – debug compile, generate config
  2. Development – release compile, generate config
  3. Staging – copy development, generate config
<?xml version="1.0" encoding="utf-8"?>
<project name="BuildKnowledgeEnviroments">    
    
    <target name="current" description="starting Continous Integration build">        
        <property name="properties" value="current.enviroment.xml" />
        <call target ="load.settings" />

        <property name="debug" value="true" />
        <property name="target.dir" value="${AppConfig.Directory}" />
        <call target="compile" />
        
        <call target="deploy.config" />
    </target>

    <target name="development" description="starting Development build">        
        <property name="properties" value="development.enviroment.xml" />
        <call target="load.settings" />
        
        <property name="debug" value="false" />
        <property name="target.dir" value="${AppConfig.Directory}" />
        <call target="compile" />

        <call target="deploy.config" />
    </target>
    
    <target name="staging" description="starting Staging promotion">
        <property name="properties" value="development.enviroment.xml" />
        <call target="load.settings" />
        <property name="source.dir" value="${AppConfig.Directory}" />

        <property name="properties" value="staging.enviroment.xml" />
        <call target ="load.settings" />
        <property name="target.dir" value="${AppConfig.Directory}" />

        <call target="deploy.copy" />
        
        <call target="deploy.config" />
    </target>
</project>

app.config.template

This is where we’re generating out app.config from, the @AppConfig.EnviromentName@ is the token that we’re looking for in the expand.template.file task.

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="EnviromentName" value="@AppConfig.EnviromentName@"/>
    </appSettings>
</configuration>

development.enviroment.xml

With the load.settings task we load in our enviroment settings files here’s the one for the development enviroment. It gives us the properties ${AppConfig.EnviromentName} and ${AppConfig.Directory} which you’ll see being used in the NAnt files.

<?xml version="1.0"?>
<properties>
    <property name="AppConfig.EnviromentName" value="Development" />
    <property name="AppConfig.Directory" value="deploy\development" />
</properties>

CruiseControl.Net Configuration

The next step is in the ccnet.config we have a different project for each enviroment and the things to notice here are

  1. Both Development and Staging are using the Remote Project Labeller to take the version number from the previous build enviroment. So Development uses Currents version number and Staging uses Developments version number. We use some trickery here because actually its still a local project but its using the tcp connection to talk to itself.
  2. Each project is using the same build file but its pointing at a different target from our enviroments file.
<cruisecontrol>
    <project name="BuildKnowledge Current">
        <tasks>
            <nant>
              <executable>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds\tools\nant\bin\NAnt.exe</executable>
              <baseDirectory>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds</baseDirectory>
              <nologo>false</nologo>
              <buildFile>buildknowledge.build</buildFile>
              <targetList>
                <target>current</target>
              </targetList>
              <buildTimeoutSeconds>1200</buildTimeoutSeconds>
            </nant>
        </tasks>
        <labeller type="iterationlabeller">
            <prefix>1.0</prefix>
            <duration>2</duration>
            <releaseStartDate>2007/11/24</releaseStartDate>
            <separator>.</separator>
        </labeller>            
    </project>
    
    <project name="BuildKnowledge Development">
        <tasks>
            <nant>
              <executable>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds\tools\nant\bin\NAnt.exe</executable>
              <baseDirectory>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds</baseDirectory>
              <nologo>false</nologo>
              <buildFile>buildknowledge.build</buildFile>
              <targetList>
                <target>development</target>
              </targetList>
              <buildTimeoutSeconds>1200</buildTimeoutSeconds>
            </nant>
        </tasks>
        <labeller type="remoteProjectLabeller">
            <project>BuildKnowledge Current</project>
            <serverUri>tcp://localhost:21234/CruiseManager.rem</serverUri>
        </labeller>            
    </project>    
    
    <project name="BuildKnowledge Staging">
        <tasks>
            <nant>
              <executable>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds\tools\nant\bin\NAnt.exe</executable>
              <baseDirectory>C:\code\sleepoverrated\02_Build_Knowledge-Promoting_Builds</baseDirectory>
              <nologo>false</nologo>
              <buildFile>buildknowledge.build</buildFile>
              <targetList>
                <target>staging</target>
              </targetList>
              <buildTimeoutSeconds>1200</buildTimeoutSeconds>
            </nant>
        </tasks>
        <labeller type="remoteProjectLabeller">
            <project>BuildKnowledge Development</project>
            <serverUri>tcp://localhost:21234/CruiseManager.rem</serverUri>
        </labeller>                
    </project>        
</cruisecontrol>

See it in action

Now lets see how well this works when we’re actually using it.

Building Current

since we’re not using the sourcecontrol block in CruiseControl.Net we’ll have to forcebuild the Current project but you should set it up to build everytime you check something in.

So select Current and start a force build and the version numbers start to increase just like last time.

BK02CCTray02

Checking the directory deploy\current you can see the new files we just generated. You’ll notice that here we have a pdb file because we compiled in debug.

BK02Files00

Now if we run this build we can see that its got the correct version number and its using the correct configuration file that we generated.

BK02Run01

Building Development

Now when we do a force build on Development we build a new release from code. If we were under source control it would be the latest version so there is the possibility of building while the code is being updated. If that happens you could always just rebuild development again. You could use the modificationDelaySeconds element on the project block in ccnet.config to setup a delay on building.

BK02CCTray03

If we look in the deploy\development directory we can quickly see that we’ve built in release mode because we’re missing the pdb.

BK02Files01

Running the app we can see that its using the right configuration file.

BK02Run02

Building Staging

Finally we do a force build on staging and that will copy over the last build of development and generate the config file. There’s also been some builds on Current but that won’t affect our promotion to staging. Another thing to notice is that you have the Last Build Time to quickly check the last time you deployed to staging. If you use the CruiseControl.Net web dashboard you can even see the log of all the promotions you’ve done, that way you never need to wonder what versions where or if someone did a promotion while you were off sick.

BK02CCTray04

Running the app we can see that its using the right configuration file.

BK02Run03

Comments [0] | | # 
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview