Thursday, August 5, 2010

Continous Installation : Forcing multiple builds at once

It's been about 2 years since I've set up CCNet for Continuous Installation at work, and it works like a charm. Programs we install with CCNet are at time of writing :Off course some programs also require a database, so the database updating is also done as a part of the installation :-).

But now another need has surfaced : Program Dependencies.
We make software for the local government (City hall level), but this is all kinds of software : finance, registry of births, deaths and marriages, construction requests and so on. What we do NOT want is that multiple programs need to handle the state of a person (married, divorced, ...). There is 1 program that handles this kind of stuff, and others can interface with it. For the moment these interfaces are done via WCF services. So there is no 'real' reference like with a function library, but more like with an interface library. And so far this works great.

For example when a new version of lets say Bookkeeping is needed, I may also need to install or update PeopleManagement, because of the new dependency between Bookkeeping and PeopleManagement. Now as long as it is only 1 dependency, I could just schedule them after each other, but there are programs with 5 or more dependencies :-(
And doing it by hand also means that I (or a collegue) may forget to update a dependency, which is not good.

There is the ForceBuild Publisher, but this has a big problem, it just launches the projects with the Fire and Forget strategy. Meaning that I do not have control of the exact order of execution, I have no control what to do when a dependency fails, ...

Luckily the code of that publisher is a good starting point, the only things I need to add is to wait for the outcome, before returning control to CCNet-core and foresee a property to fail the task if the forced build failed.

The current code of the ForceBuild is as follows :
public void Run(IIntegrationResult result)
{
if (IntegrationStatus != result.Status) return;
factory.GetCruiseManager(ServerUri).ForceBuild(Project, BuildForcerName);
}
With the adjustments, the code is :
public void Run(IIntegrationResult result)
{
//get cruisemanager
var cm = factory.GetCruiseManager(ServerUri);
var cpi = GetCurrentProjectInfo(cm);

//get last known build date of project to be forced
var lastBuildDate = cpi.LastBuildDate;
var compareBuildDate = lastBuildDate;

//force build of wanted project
cm.ForceBuild(ProjectName, buildForcerName);

//keep waiting until build is done
while (lastBuildDate.Equals(compareBuildDate))
{
System.Threading.Thread.Sleep(500);
cpi = GetCurrentProjectInfo(cm);
compareBuildDate = cpi.LastBuildDate;
}

if (FailIfForceBuildFaled && cpi.BuildStatus != IntegrationStatus.Success)
{
throw new CruiseControlException("Force Build Failed of : " + ProjectName);
}
}

private ProjectStatus GetCurrentProjectInfo(ICruiseManager cm)
{
var previousBuildInfos = cm.GetCruiseServerSnapshot();
ProjectStatus result = null;

foreach (ProjectStatus ps in previousBuildInfos.ProjectStatuses)
{
if (ps.Name == ProjectName)
{
result = ps;
continue;
}
}

return result;
}


Not much change in code, but a very great increase in the ease of installing our software. Now I just schedule this project, iso many others :-)