Moving to a deploy system based on Tfs Build

Now that I’m able to deploy to a remote machine a web application thanks to a customized build workflow it is time to move to a real scenario. I’ve blogged about two distinct tasks

  1. executing arbitrary code with a tfs build
  2. deploy an application to a remote server with a custom tfs workflow

Now I want to move to a real scenario:executing the deploy when build quality of a specific build changes to ready for deployment. To accomplish this task you first need to understand how to register for event subscription in TFS, thanks to the bisubscribe.exe tool. Subscription is made through webservices.

image

You register to TFS event submitting ad url where your service is listening, and whenever the event fires, TFS calls your service. To find all events you can register to, you can simply browse the service

http://win-y4onzs094up:8080/tfs/services/v1.0/registration.asmx

Then click GetRegistrationEntries, and invoke the service. The result value is a long XML where you can find the eventType node that specifies the type of events you can register to. Since I want to be notified when a build changes quality, I need to register to BuildStatusChangeEvent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- <EventType>
<Name>BuildStatusChangeEvent</Name>
<Schema><?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="BuildStatusChangeEvent" nillable="true" type="BuildStatusChangeEvent" />
<xs:complexType name="BuildStatusChangeEvent">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="TeamFoundationServerUrl" type="xs:anyURI" />
<xs:element minOccurs="0" maxOccurs="1" name="TeamProject" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Title" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Subscriber" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Id" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Url" type="xs:anyURI" />
<xs:element minOccurs="0" maxOccurs="1" name="TimeZone" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="TimeZoneOffset" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="ChangedTime" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="StatusChange" type="Change" />
<xs:element minOccurs="0" maxOccurs="1" name="ChangedBy" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Change">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="FieldName" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="OldValue" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="NewValue" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
</Schema>
</EventType>

The command I issue to the tfs service to register my service for changing of BuildStatusChangeEvent is the following one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
C:\Program Files\Microsoft Team Foundation Server 2010\Tools>
BisSubscribe.exe
/EventType BuildStatusChangeEvent
/address http://10.0.0.2/Bisubscribe.Test/BuildMachine.svc
/collection http://win-y4onzs094up:8080/tfs/DefaultCollection
 
BisSubscribe - Team Foundation Server BisSubscribe Tool
Copyright (c) Microsoft Corporation.  All rights reserved.
 
TF50001:  Created or found an existing subscription. The subscription ID is 2.

You need simply to specify: EventType, address and collection and you are done. Now for those ones that have no idea on how to implement the BuildMachine.svc service here is the details.

First of all check this post that explains in detail how to use WCF to create a service that is compatible with TFS subscription system, then it is matters of minute because the service needs only a function called Notify. First of all I created service interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace Bisubscribe.Test.Services
{
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
public interface INotificationService
{
 
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify")]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
 
}
}

Then the concrete class to simply dump content of the messages to verify that the event system is called.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace Bisubscribe.Test.Services
{
public class BuildMachine : INotificationService
{
private ILogger Logger { get; set; }
 
public BuildMachine(ILogger logger)
{
Logger = logger;
}
 
public void Notify(string eventXml, string tfsIdentityXml)
{
Logger.Debug("Called eventXML:\n{0}",  eventXml );
Logger.Debug("Called tfsIdentityXml:\n{0}", tfsIdentityXml);
}
}
}

Since I’m using castle log4net integration and castle wcf integration I’m able to declare dependency to a ILogger interface to simply log every information that the server receives. Then I create a simple web application and insert the BuildMachine.Svc file with this content

1
2
3
<%   1:  @ServiceHost Service="Bisubscribe.Test.Services.BuildMachine"
Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory, Castle.Facilities.WcfIntegration"
%>

I’m using the Castle WcfIntegration facility so I need to register the class in the castle config file, and finally, the only really critical step, is creating the right configuration for WCF to expose the service with SOAP 1.2 as described in this article. Now I changed the build quality of one of the build and check log file to verify if service is called. Here is the result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
2010-08-07 11:14:39,666 [7] DEBUG Bisubscribe.Test.Services.BuildMachine [(null)] - Called eventXML:
<?xml version="1.0" encoding="utf-16"?><BuildStatusChangeEvent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BuildUri>vstfs:///Build/Build/34</BuildUri>
<TeamFoundationServerUrl>http://win-y4onzs094up:8080/tfs/DefaultCollection</TeamFoundationServerUrl>
<TeamProject>WebDeploy</TeamProject>
<Title>WebDeploy Build Demo_20100709.7 Quality Changed To Initial Test Passed</Title>
<Subscriber>WIN-Y4ONZS094UP\Administrator</Subscriber>
<Id>Demo_20100709.7</Id>
<Url>http://win-y4onzs094up:8080/tfs/web/build.aspx?pcguid=ab718f2c-b4ab-499e-98c1-0a9766e3ddf6&amp;builduri=vstfs:///Build/Build/34</Url>
<TimeZone>Pacific Daylight Time</TimeZone>
<TimeZoneOffset>-07:00:00</TimeZoneOffset>
<ChangedTime>8/7/2010 2:12:37 AM</ChangedTime>
<StatusChange>
<FieldName>Quality</FieldName>
<OldValue>Ready for Deployment</OldValue>
<NewValue>Initial Test Passed</NewValue>
</StatusChange>
<ChangedBy>WIN-Y4ONZS094UP\gianmaria.ricci</ChangedBy>
</BuildStatusChangeEvent>

This is simple XML that can be parsed with Linq2SQL, you can find the id of the build and in StatusChange node you are notified of the field name that changes (Quality) the old and new value and this is enough information to implement the requested functionality.

Next step is triggering the execution of the remote build script to deploy the right build into the test server.

alk.

Sample code is here.