TFS 2010 构建质量变更事件上的自动部署(使用 Microsoft Web Deploy)





5.00/5 (3投票s)
本文解释了在 TFS 2010 的构建质量变更事件上使用 Microsoft Web deploy 进行构建的自动部署。
必备组件
- .NET Framework 2.0 SP1 或更高版本
- Web 部署工具
- IIS 7.0
最初的设想?
我曾与一个开发团队合作,致力于自动化部署生成。我们决定采用 Microsoft 的 Web 部署工具,在生成成功完成后,直接将我们的 Web 应用程序和 Web 服务部署到远程服务器上的 IIS。
在讨论部署过程时,我们的一位团队成员对生成质量表示担忧。他提到,如果我们的生成质量不符合预期,但生成仍然成功,应用程序就会被部署,而在这种情况下,我们不希望生成被部署。这里的预期,我指的是项目中的所有单元测试用例都应通过,并且代码覆盖率应高于项目设定的最低值。
我们同意了他的观点,并决定只有当生成质量更改为“准备部署”并且满足所有预期时,才部署该生成。
如何实现?
开始之前,请确保已安装 Web Deploy 2.0,并在部署服务器上启动了 Web Deployment Agent Service 和 Web Management Service。
为了在生成成功后自动部署,我们必须在生成定义的生成参数中传递以下参数。如图 1 所示。
/p:DeployOnBuild=True /p:Configuration=Debug /p:DeployTarget=MSDeployPublish
/p:MSDeployPublishMethod=RemoteAgent /p:MsDeployServiceUrl=MachineName
/p:DeployIisAppPath=TestSite/MySite /p:UserName=UserName /p:Password=Password
当我们排队生成时,上述生成参数将被传递给 Microsoft Build,Microsoft Build 将使用这些参数调用 Microsoft Web Deploy,将应用程序部署到 IIS。
但是我们的需求是在生成质量变更时进行部署,所以只需从上述参数列表中删除名为 /p:DeployTarget=MSDeployPublish
的参数。这实际上使 Microsoft Build 能够创建一个部署包,但它不会将其部署到服务器。正如我们稍后在主题中看到的,我们需要省略其他参数才能在生成质量变更时进行部署。这是第一步的结束。
现在,我们将开始我们的主要工作。正如我们所知,我们必须在生成质量变更时部署生成,无论如何,我们都需要订阅 Microsoft Team Foundation Server 2010 (TFS 2010) 的生成质量变更事件。
Microsoft Team Foundation Server 2010 中有一个有趣的功能,在文档中很难找到,那就是你可以编写 TFS 2010 的事件订阅程序,它将在 TFS 2010 的上下文中执行。这意味着你现在不需要使用 TFS 客户端对象模型来回溯到 TFS 服务器并获取所需信息。由于你现在正在 TFS 的上下文中执行事件订阅程序,所有信息都将对你可用。
所以,让我们看看我们是如何为生成质量变更事件编写事件订阅程序的。
要编写一个事件订阅程序来处理 TFS 2010 事件,我们必须实现 `ISubscriber` 接口,该接口位于 `Microsoft.TeamFoundation.Framework.Server` 中。当我们实现 `ISubscriber` 接口时,有一个名为 `ProcessEvent` 的方法,实际上我们必须在那里编写我们的部署逻辑。为了进一步解释,请看下面的代码。
public string Name
{
get
{
return "BuildQualityChangedNotificationEventHandler";
}
}
public SubscriberPriority Priority
{
get
{
return SubscriberPriority.High;
}
}
`Process` Event 方法将实际在部署服务器上执行部署。当 Web Deployment 工具创建一个包含部署内容的包时,该内容包括配置文件、HTML 文件、ASP.NET 文件等,它还会创建一个以“deploy.cmd”文本结尾的 cmd 文件。当我们通过命令行执行此文件时,它会调用 Microsoft Web deploy 将包的内容部署到部署服务器的 IIS 中。用于身份验证的机器名称和其他信息将从执行“*.deploy.cmd”文件时传递的命令行参数中获取。因此,在 `ProcessEvent` 方法中,我们实际上是通过生成定义对象检索在生成定义中的 Microsoft Build 参数中传递的参数。一旦我们有了参数,我们将把这些参数传递给“*.deploy.cmd”文件,并使用 `ProcessStartInfo` 和 `Process` 类执行命令,如下面的代码所示。
为了处理生成质量变更事件,我们必须使用 `BuildQualityChangedNotificationEvent` 类,该类存在于 `Microsoft.TeamFoundation.Build.Server` 程序集中。`BuildQualityChangedNotificationEvent` 对象将包含有关生成质量和生成定义的信息。
“*.deploy.cmd“文件将位于生成的目标位置。我们将从 `BuildQualityChangedNotificationEvent` 类中的生成定义属性获取生成的目标位置。因此,使用生成的目标位置,我们将找到“*.deploy.cmd“文件并执行它。
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext,
NotificationType notificationType, object notificationEventArgs, out int statusCode,
out string statusMessage, out ExceptionPropertyCollection properties)
{
BuildQualityChangedNotificationEvent buildEvent =
notificationEventArgs as BuildQualityChangedNotificationEvent;
if (notificationType == NotificationType.Notification &&
buildEvent != null && buildEvent.Build.Status == BuildStatus.Succeeded)
{
if (buildEvent.NewValue.Equals("Ready for Deployment",
StringComparison.OrdinalIgnoreCase))
{
DirectoryInfo directory = new DirectoryInfo(buildEvent.Build.DropLocation);
FileInfo[] deployFiles = directory.GetFiles("*.deploy.cmd",
SearchOption.AllDirectories);
XmlDocument doc = new XmlDocument();
doc.LoadXml(buildEvent.Build.Definition.ProcessParameters);
string msDeployParameters = GetMSDeployParameters(doc);
if (deployFiles.Length > 0)
{
ProcessStartInfo processStartInfo = null;
for (int count = 0; count < deployFiles.Length; count++)
{
processStartInfo = new ProcessStartInfo("cmd.exe",
@"/C " + deployFiles[count].FullName + msDeployParameters);
processStartInfo.CreateNoWindow = false;
processStartInfo.UseShellExecute = false;
Process process = Process.Start(processStartInfo);
}
}
}
}
statusCode = 1;
statusMessage = "Deployed";
properties = null;
return EventNotificationStatus.ActionApproved;
}
private string GetMSDeployParameters(XmlDocument xmlDoc)
{
StringBuilder msDeployParameters = new StringBuilder(@" /Y");
string[] msBuildArgumentsArr = GetMSBuildArguments(xmlDoc);
if (msBuildArgumentsArr != null && msBuildArgumentsArr.Length > 0)
{
for (int count = 0; count < msBuildArgumentsArr.Length; count++)
{
string[] msBuildArgsKeyValueArr = msBuildArgumentsArr[count].Split('=');
if (msBuildArgsKeyValueArr.Length > 1)
{
if (msBuildArgsKeyValueArr[0].Equals
("MsDeployServiceUrl", StringComparison.OrdinalIgnoreCase))
{
msDeployParameters.Append(@" /M:" + msBuildArgsKeyValueArr[1]);
}
if (msBuildArgsKeyValueArr[0].Equals
("UserName", StringComparison.OrdinalIgnoreCase))
{
msDeployParameters.Append(@" /U:" + msBuildArgsKeyValueArr[1]);
}
if (msBuildArgsKeyValueArr[0].Equals
("Password", StringComparison.OrdinalIgnoreCase))
{
msDeployParameters.Append(@" /P:" + msBuildArgsKeyValueArr[1]);
}
}
}
}
return msDeployParameters.ToString();
}
private string[] GetMSBuildArguments(XmlDocument xmlDoc)
{
string[] msBuildArgumentsArr = null;
string msBuildArguments = string.Empty;
XmlNode msBuildArgumentNode = GetMSBuildArgumentNode(xmlDoc);
if (msBuildArgumentNode != null)
{
msBuildArguments = msBuildArgumentNode.InnerText;
if (!string.IsNullOrEmpty(msBuildArguments))
{
msBuildArgumentsArr = msBuildArguments.Split(new string[]
{ "/p:" }, StringSplitOptions.RemoveEmptyEntries);
}
}
return msBuildArgumentsArr;
}
private XmlNode GetMSBuildArgumentNode(XmlDocument xmlDoc)
{
XmlNode msBuildArgumentNode = null;
XmlNodeList xmlNodeList = xmlDoc.GetElementsByTagName
("String", @"http://schemas.microsoft.com/winfx/2006/xaml");
if (xmlNodeList.Count > 0)
{
foreach (XmlNode xmlNode in xmlNodeList)
{
XmlNode attributeNode = xmlNode.Attributes.GetNamedItem
("Key", @"http://schemas.microsoft.com/winfx/2006/xaml");
if (attributeNode.Value.Equals
("MSBuildArguments", StringComparison.OrdinalIgnoreCase))
{
msBuildArgumentNode = xmlNode;
break;
}
}
}
return msBuildArgumentNode;
}
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(BuildQualityChangedNotificationEvent) };
}
只需将上面提供的代码复制到你的类中并进行编译。要成功编译,你需要添加三个程序集:`Microsoft.TeamFoundation.Common`、`Microsoft.TeamFoundation.Build.Server` 和 `Microsoft.TeamFoundation.Framework.Server`。`Microsoft.TeamFoundation.Build.Server` 和 `Microsoft.TeamFoundation.Framework.Server` 程序集将在路径 *C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin* 中找到。
现在你有了编译好的程序集,它可以处理 TFS 2010 的生成质量变更事件,但 TFS 2010 如何知道在哪里查找这个程序集呢?如我们所知,TFS 2010 是一个 Web 服务的集合,所以你必须将这个程序集放在 *C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins* 文件夹中。TFS 2010 将从这个位置获取该程序集并相应地执行事件。对 `Plugins` 文件夹的任何更改都会重启 TFS,所以一旦你的程序集被放入 `plugins` 文件夹,它就会自动加载。如果它没有被加载,那么只需重启 IIS,它就会被加载。
现在我们一切就绪,只需排队一个新的生成并将其生成质量更改为“准备部署”,我们的 Web 应用程序就会部署到部署服务器上。
结论
下载文章附带的压缩项目并进行编译。编译后,将 DLL 复制到 *C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins* 文件夹。重启 IIS,你就能在生成质量变更时自动部署了。
一切顺利。 :)