Chris Surfleet's MentalReboot

Code insights tinged with a spot of insanity

Over the last couple of years, we’ve been working slowly through the Continuous Deployment evolution; we started continuously integrating, and then began continuously deploying to test, and then to staging. It’s been hugely liberating but the inevitable next step was pretty daunting: can we get any closer to continuous deployments to live? Putting aside the ideal of a deployment on each checkin for a now I decided the first step should be getting a TFS build which can do the deployment.

This is the first in a 3-part series in which I will go through a complete FTP deployment solution, which will be able to push any configuration of your web application to a remote server and will be fully configurable from a TFS Build Definition. For this first part I will go over how to build the FTP component, while the later posts will cover first a custom TFS workflow and then some custom configuration management.

Why a Team Build Activity?

There are FTP tasks available in MsBuild, so it would be totally possible to get your application FTP’d from your build file. Why not? Well I think that we should have a separation of concerns here, your csproj file handles building and constructing your product, while Team Build handles deployment. This then allows you to control all your deployment setup in each Build Definition.

(A side note here, this leaves the question of where you handle configuration such as web.config transformation, personally I do this from Team Build but it’s a grey area.)

If you can’t be bothered working through the tutorial (shame on you!) you can get the finished product here: FtpDeployment.zip (57.53 kb)

WinSCP

This solution makes use of the WinSCP utility, so you will need to install it first on your build server and dev boxes. This install includes a COM object we can send our FTP commands to.

Project Setup

OK, create yourself a new class library project and add references to the following assemblies, and ensure each has Copy Local set to false:

  • Microsoft.Build.Framework
  • Microsoft.Build.Utilities.v4.0
  • Microsoft.TeamFoundation
  • Microsoft.TeamFoundation.Build.Client
  • Microsoft.TeamFoundation.Build.Workflow (In C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\)
  • Microsoft.TeamFoundation.TestImpact.BuildIntegration (Also in PrivateAssemblies)
  • Microsoft.TeamFoundation.TestImpact.Client (In C:\Windows\assembly\GAC_MSIL\
    Microsoft.TeamFoundation.TestImpact.Client\10.0.0.0__b03f5f7f11d50a3a\)
  • Microsoft.TeamFoundation.VersionControl.Client
  • Microsoft.TeamFoundation.VersionControl.Common
  • Microsoft.TeamFoundation.WorkItemTracking.Client
  • System.Activities
  • System.Activities.Presentation

FTP Code

Now create a new class called FtpSynchronizer to hold the code we will use to perform the FTP. Add the following method to it:

public bool PublishWebsite(string ftpSite, string ftpUserName,
            string ftpPassword, string ftpFolderPath, string localFolder,
            string winScpLocation = @"C:\Program Files\WinSCP\winscp.com")
{
  using (Process winScp = new Process())
  {
    winScp.StartInfo.FileName = winScpLocation;
    winScp.StartInfo.RedirectStandardInput = true;
    winScp.StartInfo.UseShellExecute = false;
    winScp.StartInfo.CreateNoWindow = true;
    winScp.Start();

    winScp.StandardInput.WriteLine("option batch abort");
    winScp.StandardInput.WriteLine("option confirm off");
    winScp.StandardInput.WriteLine("open ftp://" + ftpUserName + ":" +
                  ftpPassword + "@" + ftpSite);
    winScp.StandardInput.WriteLine("synchronize remote " + localFolder +
                  " /" + ftpFolderPath);
    winScp.StandardInput.WriteLine("exit");

    winScp.StandardInput.Close();

    winScp.WaitForExit();

    return (winScp.ExitCode == 0);
  }
}

The first block is firing up WinSCP in the background and preparing it for commands, it’s the second block we are more interested in. This simply opens up a batch-capable connection to an FTP site and sends a “synchronize remote” command. This will copy any newer files from the local folder to the FTP server. It will not delete files on the server which are not on the client, this is the safer behaviour but could be a gotcha in certain circumstances.

After firing the commands we wait for WinSCP to do its stuff, and then return true if everything was successful, or false if not. This isn’t ideal, and I’ll post soon on how to add better error handling to this component, but it is workable in a basic scenario.

At this point, test your class. Go set yourself up a unit test project and create some tests against your FTP site. Done? Brill!

App_offline.htm

This code works, but it would be nice if users of the application weren’t shown an ugly 404 page if they try to access the site while its being updated. Lets use an app_offline file!

In the FtpSynchronize class add the following at the beginning of the PublishWebsite method:

string appOfflinePath = Path.Combine(Path.GetTempPath(), "app_offline.htm");

using (StreamWriter stream = File.CreateText(appOfflinePath))
{
  stream.Write("<html><body><h1>Application currently undergoing maintenance, " +
      "check back in a few minutes.</h1></body></html>");
  stream.Close();
}

 

Obviously you could make the page contents nicer if you like. Then you simply need to upload it before your synchronization, and remove it after, so surround the “synchronize remote” line like this:

winScp.StandardInput.WriteLine("put " + appOfflinePath);
winScp.StandardInput.WriteLine("synchronize remote " + localFolder +
                     " /" + ftpFolderPath);
winScp.StandardInput.WriteLine("rm app_offline.htm");

Done!

Creating the TFS CodeActivity

We’ve got a nice little FTP synchronizer now, but we need to be able to access it from Team Build. For this we’ll create a new class called FtpSynchronizeActivity:

 

[BuildActivity(HostEnvironmentOption.All)]
[BuildExtension(HostEnvironmentOption.All)]
public sealed class FtpSynchronizeActivity : CodeActivity<bool>
{
  protected override bool Execute(CodeActivityContext context)
  {
    throw new NotImplementedException();
  }
}

This will be an activity we can use in a TFS workflow which will return success or failure. (If that made no sense don’t worry, we’ll get to it shortly) We’re going to need some inputs to pass the required info to the FtpSynchronizer so add these above the Execute method:

public InArgument<string> WinScpComLocation { get; set; }

[RequiredArgument]
public InArgument<string> LocalFolder { get; set; }

[RequiredArgument]
public InArgument<string> FtpSite { get; set; }

[RequiredArgument]
public InArgument<string> FtpUserName { get; set; }

[RequiredArgument]
public InArgument<string> FtpPassword { get; set; }

public InArgument<string> FtpFolderPath { get; set; }

These values will be available as properties of the activity in our workflow. All we need to do now is link up the Execute method to the FtpSynchronizer:

protected override bool Execute(CodeActivityContext context)
{
  string winScpLocaton = context.GetValue(WinScpComLocation);
  string localFolder = context.GetValue(LocalFolder);
  string ftpSite = context.GetValue(FtpSite);
  string ftpUserName = context.GetValue(FtpUserName);
  string ftpPassword = context.GetValue(FtpPassword);
  string ftpFolderPath = context.GetValue(FtpFolderPath);

  if (ftpFolderPath == null)
    ftpFolderPath = String.Empty;

  FtpSynchronizer synchronizer = new FtpSynchronizer();
  return String.IsNullOrWhiteSpace(winScpLocaton) ?
    synchronizer.PublishWebsite(ftpSite, ftpUserName, ftpPassword,
                    ftpFolderPath, localFolder) :
    synchronizer.PublishWebsite(ftpSite, ftpUserName, ftpPassword,
                    ftpFolderPath, localFolder, winScpLocaton);
}

Next Steps

All that remains to be done is include the activity in a Team Build workflow. If you've not done this before I'll be covering it in part 2 coming up in a few days. In part 3 we will overcome a couple of problems with configuration and add a bit of extra functionality to the deployment workflow. Watch this space!

Full code for this example is available here: FtpDeployment.zip (57.53 kb)

Did you find this post useful or interesting? I'd be really grateful if you could return the favour by clicking this google link:



Yes yes, I know this is a technology blog, and I promise not to make a habit of pushing charity stuff at you when you just want to know about build systems etc. That said, this particular issue has really had an effect on me, and I would very much like to spread the word.

I do a lot of rock climbing in my (limited) spare time and it has had an overwhelmingly positive effect on my life, one which I think should be shared with and available to everyone. This is a view shared by Samuel Farmer, a black man who grew up in a poor area of Liverpool and later in life became an ourdoors instructor. He has devoted his life to giving disadvantaged children from areas such as those he grew up in the chance to get out into the countryside, challenge themselves and expand their horizons. To this end he set up 'The Hope Project', a charity based in Cornwall, UK.

Unfortunately, over the last few years Sam has been on the receiving end of a stream of racist abuse from a minority of people in his local area who view outsiders – particularly young black outsiders – as unwelcome. This came to a head in January when his outdoors centre was burnt to the ground, causing £70,000 worth of damage. Shockingly, the local police and council do not want to know, and have been almost no real investigations into the arson, despite coverage by news media.

runined

At the beginning of this month, a thread started on the UK’s largest climbing forum and spawned a wonderful grassroots help project for Sam. He has received many parcels of free gear from individuals across the UK to help him get back on his feet. What he really needs now though is money with which to start rebuilding so that he can begin once again helping those with less opportunity in life.

You can read more about his ordeal here: http://tohatchacrow.blogspot.com/2012/02/black-outdoor-instructor-targeted-by.html

The UKClimbing thread is here: http://www.ukclimbing.com/forums/t.php?n=493772

If you are reading this the chances are that you earn a decent wage, and enjoy a pretty good life. Please visit the Hope Project's website and donate just a fraction of what you could and you will be helping not just Sam, but any number of children he will then be able to help on your behalf. I am donating, and I am also pledging the entire advertising revenue of this blog in 2012 to his cause.

You can donate here: http://www.thebeaconofhope.co.uk/

Thanks so much

Chris Surfleet

Did you find this post useful or interesting? I'd be really grateful if you could return the favour by clicking this google link:



Month List

Sign in

Top Programming Sites Vote for me at Fuelmyblog