CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Kyle Baley - The Coding Hillbilly

"We are stuck with technology when what we really want is just stuff that works" -- Douglas Adams

Automated releases (with a touch of deployments)

One of the things I espoused in my recent presentation on Brownfield applications is automating your releases and your deployments for continuous integration. And being a good-natured hillbilly who has been a little lax on technical content of late, I will now turn your attention to that very topic.

Note: Even though I mention this in the context of a Brownfield application, you would do the same thing to set it up with your Greenfield application as well.

First off, we should define a release and a deployment because they sound pretty similar.

A release is a packaged version of the application. That is, it is some artifact or group of artifacts that *could* be deployed if so desired. A deployment is the actual act of deploying a release.

A couple of notes on the definitions:

  • There doesn't need to be a one-to-one mapping 'twixt releases and deployments. You can create releases all the live long day and not deploy any of them.
  • The reverse isn't true. You can't create a deployment without a release. Because, there ain't nuthin' to deploy, Jed!

There are a few schools of thought on when to create releases and what to do with them. Which is another way of saying don't take what I'm about to say as gospel. This is how I've implemented it on my current project which has a development team consisting of The Coding Hillbilly 'n his alter-egos, Code Coverage Nazi and Guerrilla Refactorer. It's not without its faults which I'll talk about in the next post on deployments.

So with my requisite disclaimer out of the way, I create a release on every build. I.E. Every time I check in code, a new release is built. Wackiness? Maybe, but that's where the the Coding Hillbilly alias gives me a free pass. My advice is so easy to dismiss as the ramblings of a hick that when I eventually do say something useful, it's all the more meaningful.

Why create a release on every check-in? Easy, because I can. It's easy to create them so why not do so every time you can. That way, if the client says, "show me what you have" you can give him the absolute latest and, one might assume, greatest. What's the downside to having a release on each check-in? Wasted disk space? Give me half a day off and go buy yourself something nice with the savings. (Yes, I'm ignoring a whole lot of internal politics. Such is the benefit of developing on your own build environment.)

That's enough theory. Here's the NAnt task that creates my releases, with all subtasks merged into one. A summary of properties used follows:

     <target name="release">
        <property name="current.release.dir" value="${releases.dir}\${project.fullversion}" />
        <delete dir="${current.release.dir}" failonerror="false" />
        <mkdir dir="${current.release.dir}" />
        <copy todir="${current.release.dir}">
            <fileset basedir="${app.src.dir}\Trilogy.Gunton.Web">
                <include name="Content\*.*" />
                <include name="**\*.master" />
                <include name="bin\*.*" />
                <include name="**\*.aspx" />
                <include name="**\*.asax" />
                <include name="**\*.config" />
            </fileset>
        </copy>
        <copy file="${config.dir}\nhibernate-default.cfg.xml.template" tofile="${current.release.dir}\nhibernate-default.cfg.xml">
            <filterchain>
                <replacetokens>
                    <token key="SERVER" value="${database.server}" />
                    <token key="DBNAME" value="${database.name.test}" />
                    <token key="SHOW_SQL" value="${debug.show_sql}" />
                    <token key="DATA_ACCESS_ASSEMBLY" value="${data.access.assembly}" />
                </replacetokens>
            </filterchain>
        </copy>
        <delete dir="${latest.release.dir}" failonerror="false" />
        <mkdir dir="${latest.release.dir}" />
        <copy todir="${latest.release.dir}">
            <fileset basedir="${current.release.dir}">
                <include name="**\*.*" />
            </fileset>
        </copy>
    </target>
releases.dir Path to the releases folder. e.g. C:\projects\LineageOrganizer\releases
project.fullversion Full version number of the project. This is generated partially using the CCNetLabel
current.release.dir Combines releases.dir and project.fullversion. e.g. C:\projects\LineageOrganizer\releases\1.0.31
app.src.dir Path to the source code for the application. e.g. C:\projects\LineageOrganizer\src\app
config.dir Path to a folder that contains templates that vary from environment to environment. e.g. C:\projects\LineageOrganizer\config
database.server Name of the database server you want to use for this environment
database.name.test Name of the test database you are using for this environment.
data.access.assembly Name of the data.access.assembly to be used for NHibernate
latest.release.dir Path to the latest release folder. e.g. C:\projects\LineageOrganizer\releases\Latest

Some these will become clear when I go through the build file. I won't add "I hope" because I GUARANTEE lucidity*.

If you look closely at the tasks in this target, it does one of three things:

  • Deletes folders
  • Makes folders
  • Copies files

That's all a release is in my case. Elsewhere in my build file, I'm already building the application for testing purposes and as it so happens, I'm building it in the same way I would release it. This is admittedly a little dicey here because you may not want to do that. For example, you may want to compile with a different configuration for your releases. But it's not a pain point for me yet so the decision can be put off.

So, in order, here is what is happening (more explanation follows):

  1. Get the path to the new release we are building (based on the current version number). This uses the CCNetLabel property as described by Donald Belcham, whom I should have referenced long ago in this post because most of these ideas come from conversations with him.
  2. Delete the folder if it exists (it usually won't) and create/re-create it
  3. Copy the contents of the web application to the release folder
  4. Copy the NHibernate configuration template to the release folder and replace its tokens with values appropriate for the test environment.
  5. Delete the folder containing the latest release and re-create it. This is NOT the folder labeled with the previous release number. It is a subfolder in the releases folder that is physically named Latest. It will always exist already except for the first time you run this target (or if you physically delete it)
  6. Copy the contents of the current release folder to the latest release folder

Note on #3. We copy only the files we need to run the application. No code files and no project files here, bub!

Note on #4. The only reason I am creating a separate NHibernate file (rather than using the one already in the app folder) is because I want my releases to go against a different database than they do in the normal, everyday testing of the application. That database gets blown away and re-created every time I run my integration tests and I don't want that affecting any testing the QA department is doing on the latest deployment. So the database name is ${database.name.test} rather than ${database.name.dev}. Note that everything here is running on the same server so I don't need to differentiate the server names.

Note on #5. Why have a separate folder for the latest release that is simply a copy of another folder? That's the subject of the next post where I go into the deployment target.

Kyle the Anticipatory

* I remind you that there are no refunds

 



Comments

Pages tagged "name" said:

Pingback from  Pages tagged "name"

# March 1, 2008 4:19 PM

2 Links Today (2008-03-02) said:

Pingback from  2 Links Today (2008-03-02)

# March 2, 2008 10:18 AM

Christopher Steen said:

Link Listing - March 2, 2008

# March 3, 2008 2:20 AM

Christopher Steen said:

ASP.NET Elegant navigation bar using CSS [Via: Antonio ] Sharepoint Silverlight in SharePoint [Via:...

# March 3, 2008 2:20 AM

Automated deployments - Kyle Baley - The Coding Hillbilly said:

Pingback from  Automated deployments - Kyle Baley - The Coding Hillbilly

# March 3, 2008 9:13 AM

alberto said:

Why do you use CCNET build number to version the release instead of the revision number. I've seen many people doing that, but I cannot see the benefits.

# March 3, 2008 3:40 PM

Kyle Baley said:

@alberto

Which revision number do you refer to? The reason I like to use the CCNET build number is because the build file generates the AssemblyInfo.cs file used to version the application and I display that somewhere in the app itself. That way, if users report bugs, I can tell which version they're using.

I'll admit this is usually overkill in most environments because the deployments are generally pretty controlled. But imagine a scenario where users navigate to a page listing the last x releases and can deploy any one of them themselves, usually picking the latest one.

# March 3, 2008 5:34 PM

alberto said:

I was referring to the revision number in your scm (usually svn). That way if the user reports a bug, you immediately know which revision correspond to that version. You don't get that directly from using your ccnet build number, do you?

# March 3, 2008 6:07 PM

Kyle Baley said:

I imagine ccnet can get that somehow but for all intents and purposes, the ccnet build number is the one I want. It's the most visible to me (via the dashboard) and applies to the entire application rather than individual files.

But you made me think of something important I forgot to incorporate. I'm not labelling/tagging the repository after creating the release. An important step if I ever want to go back to see what caused a problem in a previous version.

# March 4, 2008 10:21 AM

alberto said:

In svn, unlike cvs, the revision number refers to the whole application, not the file. I think it is better to have your build versioned, 1.2.3.X, where X is your revision number. That way you know which source code corresponds to that release and you don't even need to tag the release (although I usually do that, anyway). But I don't really care what build it was. If you use ccnet, you then definitely need to tag the repository after the build number to keep that build-to-code relationship.

PS: You can read about this very same problem in the comments of Donald Belcham's post you linked. I was there too. ;)

# March 4, 2008 1:10 PM

nant test the value of a property said:

Pingback from  nant test the value of a property

# May 22, 2008 7:01 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!