Harin Sandhoo's Blog

November 19, 2009

Using the Service Management APIs to Automate Azure Deployments

Filed under: Azure — Harin @ 11:27 am

With the latest CTP, Microsoft announced the a ServiceManagement API that can be used to automate builds (at least to your staging environment).  Note:  Screenshots are coming soon.

Here’s a simple walkthrough on how to create a certificate for use with the Azure Service Management APIs.

Create a self signed certificate

  1. Open IIS 7
  2. Features -> Server Certificates
  3. Create Self-Signed Certificate
  4. Enter a friendly name and click ok.
  5. Export the certificate
    1. Start Certificate Manager by clicking on Start -> Run, and entering certmgr.msc
    2. Find your certificate (Mine was located in ‘Trusted Root Certification Authorities’
    3. Export the certificate

i.      Right click on -> All Tasks -> Export, click next after the welcome screen.

ii.      Do not export the private key, and click next.

iii.      Select DER encoded binary X.509 (.CER)

iv.      Select a file location

v.      Click on Finish

Associate the certificate with Azure

  1. Go to the https://windows.azure.com portal and sign in with your account credentials
  2. Navigate to the Account Tab a nd click on ‘Manage My API Certificates’
  3. Click on Browse, select the certificate that was exported previously, and then click upload.
  4. The certificate should appear in the Installed Certificates list
  5. Start using the Management Service API.
    1. Here is sample code from MSDN
HttpWebRequest CreateHttpRequest(Uri uri, string httpMethod, X509Certificate2 accessCertificate, TimeSpan timeout)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Timeout = (int)timeout.TotalMilliseconds;
request.ReadWriteTimeout = (int)timeout.TotalMilliseconds * 100;
request.Method = httpMethod;
request.ClientCertificates.Add(accessCertificate);
request.Headers.Add(Constants.VersionHeader, Constants.VersionTarget);
request.ContentType = Constants.ContentTypeXml;
return request;
}

Note:  If you are calling this code from a machine where the certificate was not originally generated, then you may need to export the certificate with the private key and install it on that server.  For example, if you are automating builds using TFS, then export the certificate with the private key, and import it on the build server.

To automate deployments with TFS the general steps are:

  1. Create and associate a management certificate as outlined above.
  2. Make sure the build server has the certificate installed with the private key.
  3. Create a build for your Azure solution in TFS.
  4. Run cspack.exe to package your deployment package on the build output to package the deployment.
  5. Use the BlobStorage REST APIs / provided StorageClient to upload the package into blob storage.
private static string SendFileToContainer(BlobContainer deploymentContainer, string filePath, string versionName)
{
string retVal = string.Empty;
FileInfo info = new FileInfo(filePath);
string newFileName = DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + versionName + "_" + info.Name;
BlobProperties blobProp = new BlobProperties(newFileName);
blobProp.Metadata = new System.Collections.Specialized.NameValueCollection();
blobProp.Metadata.Add("version", versionName);
if (deploymentContainer.CreateBlob(blobProp, new BlobContents(File.ReadAllBytes(filePath)), true))
{
retVal = deploymentContainer.ContainerUri.AbsoluteUri + "/" + newFileName;
}
return retVal;
}

Finally, use the ServiceManagement REST APIs to perform the deployment.  Here’s some more code that may help.

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
//using AIS.Azure.ServiceManagementWrapper.Constants;
//using AIS.Azure.ServiceManagementWrapper.HelperObjects;
using System.Security.Cryptography.X509Certificates;
namespace AIS.Azure.ServiceManagementWrapper
{
namespace HelperObjects
{
public class HostedService
{
public class HostedServiceProperties
{
public HostedServiceProperties() { }
public string Description { get; set; }
public string AffinityGroup { get; set; }
public string Location { get; set; }
public string Label { get; set; }
}

public class HostedServiceDeploymentRoleInstance
{
public string RoleName { get; set; }
public string InstanceName { get; set; }
public string InstanceStatus { get; set; }
}

public class HostedServiceDeployment
{
public string Name { get; set; }
public string DeploymentSlot { get; set; }
public string PrivateID { get; set; }
public string Status { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Configuration { get; set; }
public IEnumerable<HostedServiceDeploymentRoleInstance> RoleInstances { get; set; }
public string UpgradeDomainCount { get; set; }
}
public HostedService() { }

public string Url { get; set; }
public string ServiceName { get; set; }
public HostedServiceProperties Properties { get; set; }
public IEnumerable<HostedServiceDeployment> Deployments { get; set; }

public override string ToString()
{
StringBuilder blder = new StringBuilder();
blder.AppendLine("ServiceName: " + ServiceName);
blder.AppendLine("Url: " + Url);
blder.AppendLine("Description: " + Properties.Description);
blder.AppendLine("AffinityGroup: " + Properties.AffinityGroup);
blder.AppendLine("Location: " + Properties.Location);
blder.AppendLine("Label: " + Properties.Label);
blder.AppendLine("Deployments:");
foreach (var deployment in Deployments)
{
blder.AppendLine("Deployment:");
blder.AppendLine("Name:\t\t" + deployment.Name);
blder.AppendLine("DeploymentSlot:\t" + deployment.DeploymentSlot);
blder.AppendLine("PrivateID:\t" + deployment.PrivateID);
blder.AppendLine("Status:\t\t" + deployment.Status);
blder.AppendLine("Label:\t\t" + deployment.Label);
blder.AppendLine("\t\tUrl:\t\t" + deployment.Url);
blder.AppendLine("Configuration:\t" + deployment.Configuration);
blder.AppendLine("UpgradeDomainCount:\t" + deployment.UpgradeDomainCount);
foreach (var roleinstance in deployment.RoleInstances)
{
blder.AppendLine("RoleInstance:");
blder.AppendLine("RoleName:\t" + roleinstance.RoleName);
blder.AppendLine("InstanceName:\t" + roleinstance.InstanceName);
blder.AppendLine("InstanceStatus:\t" + roleinstance.InstanceStatus);
}
}
return blder.ToString();
}

}

namespace Constants
{

class RequestTypes
{
public const string GET = "GET";
public const string POST = "POST";
public const string DELETE = "DELETE";
public const string LIST = "LIST";
}

class Headers
{
public const string x_ms_version_header = "x-ms-version";
public const string x_ms_version_value = "2009-10-01";
public const string x_ms_request_id_header = "x-ms-request-id";
}

class ContentTypes
{
public const string XML = "xml";
}

class HostedServices
{
public const string Namespace = "http://schemas.microsoft.com/windowsazure";
public const string ListHostedServices = "https://management.core.windows.net/{0}/services/hostedservices";
public const string GetHostedServiceProperties = "https://management.core.windows.net/{0}/services/hostedservices/{1}?embed-detail={2}";
public const string UpgradeDeploymentByDeploymentSlot = "https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}/?comp=upgrade";
public const string UpgradeDeploymentByDeploymentName = "https://management.core.windows.net/{0}/services/hostedservices/{1}/deployments/{2}/?comp=upgrade";
}
}

class ServiceManager
{
public enum DeploymentSlots { Staging, Production};
public enum Mode {Auto, Manual};
private static XNamespace ns = "http://schemas.microsoft.com/windowsazure";

private ServiceManager()
{
}

private static HttpWebRequest CreateHttpRequest(string requestUri, string httpMethod, X509Certificate accessCertificate, TimeSpan timeout)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
request.Timeout = (int)timeout.TotalMilliseconds;
request.ReadWriteTimeout = (int)timeout.TotalMilliseconds * 100;
request.Method = httpMethod;
request.ClientCertificates.Add(accessCertificate);
request.Headers.Add(Constants.Headers.x_ms_version_header,
Constants.Headers.x_ms_version_value);
request.ContentType = Constants.ContentTypes.XML;
return request;
}

private static string ValueOrEmpty(XElement element)
{
return (element != null) ? element.Value : string.Empty;
}

private static XElement ProcessResponse(HttpWebRequest request)
{
XElement retVal = null;
using (WebResponse resp = request.GetResponse())
{
using (StreamReader respStream = new StreamReader(resp.GetResponseStream()))
{
string responseText = respStream.ReadToEnd();
retVal = XDocument.Parse(responseText).Root;
}
}
return retVal;
}

/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static IEnumerable<HostedService> ListHostedServices(string subscriptionId, string certificatePath, double msTimeout)
{
string strUrl = string.Format(Constants.HostedServices.ListHostedServices, subscriptionId);

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.GET, cert, TimeSpan.FromMilliseconds(msTimeout));

var response = ProcessResponse(req);

var serviceList = from svc in response.Descendants(ns + "HostedService")
select new { Url = svc.Element(ns + "Url").Value, ServiceName = svc.Element(ns + "ServiceName").Value };

IEnumerable<HostedService> retVal = new List<HostedService>();
// Populate the service by just getting the values
foreach (var service in serviceList)
{
retVal = retVal.Concat<HostedService>(GetHostedServiceProperties(subscriptionId, service.ServiceName, true, certificatePath, msTimeout));
}
return retVal;
}
/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="serviceName"></param>
/// <param name="embedDetail"></param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static IEnumerable<HostedService> GetHostedServiceProperties(string subscriptionId, string serviceName, bool embedDetail, string certificatePath, double msTimeout)
{
string strUrl = string.Format(Constants.HostedServices.GetHostedServiceProperties, subscriptionId, serviceName, embedDetail.ToString().ToLower());

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.GET, cert, TimeSpan.FromMilliseconds(msTimeout));

XElement response = ProcessResponse(req);

var serviceList = from svc in response.DescendantsAndSelf(ns + "HostedService")
select new HostedService()
{
Url = svc.Element(ns + "Url").Value,
ServiceName = svc.Element(ns + "ServiceName").Value,
Properties = new HostedService.HostedServiceProperties()
{
Description = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Description")),
AffinityGroup = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "AffinityGroup")),
Location = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Location")),
Label = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Label"))
},
Deployments = from deployment in svc.Element(ns + "Deployments").Elements(ns + "Deployment")
select new HelperObjects.HostedService.HostedServiceDeployment()
{
Name = ValueOrEmpty(deployment.Element(ns + "Name")),
DeploymentSlot = ValueOrEmpty(deployment.Element(ns + "DeploymentSlot")),
PrivateID = ValueOrEmpty(deployment.Element(ns + "PrivateID")),
Status = ValueOrEmpty(deployment.Element(ns + "Status")),
Label = ValueOrEmpty(deployment.Element(ns + "Label")),
Url = ValueOrEmpty(deployment.Element(ns + "Url")),
Configuration = ValueOrEmpty(deployment.Element(ns + "Configuration")),
RoleInstances = from roleinstance in deployment.Element(ns + "RoleInstanceList").Elements(ns + "RoleInstance")
select new HelperObjects.HostedService.HostedServiceDeploymentRoleInstance()
{
RoleName = ValueOrEmpty(roleinstance.Element(ns + "RoleName")),
InstanceName = ValueOrEmpty(roleinstance.Element(ns + "InstanceName")),
InstanceStatus = ValueOrEmpty(roleinstance.Element(ns + "InstanceStatus"))
},
UpgradeDomainCount = ValueOrEmpty(deployment.Element(ns + "UpgradeDomainCount"))
}
};
return serviceList;
}
/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="serviceName"></param>
/// <param name="deploymentSlot"></param>
/// <param name="mode"></param>
/// <param name="packageUrl"></param>
/// <param name="configuration"></param>
/// <param name="label"></param>
/// <param name="roleNameToUpgrade">Optional.  Pass null or String.Empty if none.</param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static string UpgradeDeploymentByDeploymentSlot(string subscriptionId,
string serviceName,
DeploymentSlots deploymentSlot,
Mode mode,
string packageUrl,
string configuration,
string label,
string roleNameToUpgrade,
string certificatePath,
double msTimeout)
{

string strUrl = string.Format(Constants.HostedServices.UpgradeDeploymentByDeploymentSlot, subscriptionId, serviceName, Enum.GetName(typeof(DeploymentSlots),deploymentSlot).ToLower());

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.POST, cert, TimeSpan.FromMilliseconds(msTimeout));
using (XmlTextWriter requestBodyStream = new XmlTextWriter(req.GetRequestStream(), Encoding.UTF8))
{
//requestBodyStream.Write();
XNamespace azureNS = (string)Constants.HostedServices.Namespace.Clone();
XDocument doc = new XDocument(
new XDeclaration("1.0","utf-8", "yes"),
new XElement( azureNS + "UpgradeDeployment",
new XElement(azureNS + "Mode", Enum.GetName(typeof(Mode), mode).ToLower()),
new XElement(azureNS + "PackageUrl", packageUrl),
new XElement(azureNS + "Configuration", configuration),
new XElement(azureNS + "Label", Convert.ToBase64String( Encoding.UTF8.GetBytes(label)))
));

if (!string.IsNullOrEmpty(roleNameToUpgrade))
{
doc.Root.Element(azureNS + "Label").AddAfterSelf(new XElement(azureNS + "RoleToUpgrade", roleNameToUpgrade));
}

doc.Save(requestBodyStream);
}
req.ContentType = "application/xml";

string retVal = string.Empty;
using (WebResponse response = req.GetResponse())
{
try
{
retVal = response.Headers[Constants.Headers.x_ms_request_id_header];
}
catch(NullReferenceException ex)
{
// The request header was not found
retVal = string.Empty;
}
}

return retVal;
}

}
}

Create a free website or blog at WordPress.com.