Azure Management API

4 minute read

by Peter Daukintis

Disclaimer – the code shown below is not of high quality, does not have any error handling and is shown for educational purposes only.

Having hosted an experimental application on the Azure CTP since PDC ‘08 I decided it was time that I stopped putting myself through the process of having to manually upload changes via the Azure Developer Portal. Luckily for me, the Azure Team accelerated my progress by releasing the Azure Management API (see http://blogs.msdn.com/windowsazure/archive/2009/09/17/introducing-the-windows-azure-service-management-api.aspx for an introduction and the docs can be found here http://msdn.microsoft.com/en-us/library/ee460799.aspx).

My current build system uses CruiseControl.NET and when I kick off a ‘deployment’ build currently I have an MSBuild task that copies the deployment files (.csdef and .cspkg) to Azure Storage. This was an interim step that allows me to keep backups in the cloud and also to be able to select and deploy them easily via the Developer Portal. So, the next step was to develop another MSBuild task which accessed the new management APIs to enable a deploy. (An alternative to this would be to take the approach outlined here http://blogs.msdn.com/domgreen/archive/2009/09/29/deploying-to-the-cloud-as-part-of-your-daily-build.aspx which uses the csmanage.exe command line tool provided by the Azure Team).

Creating an MSBuild task is very straightforward and is documented here http://msdn.microsoft.com/en-us/library/t9883dzc.aspx and there are plenty of other online resources describing the process.

So, on to the code; this is the outline of the MSBuild task, it uses the AzureAccount class to do all of the real work:

public classDeployToAzureTask : Task
{
    [Required]
    public stringPackageUri { get; set; }

    [Required]
    public stringConfigUri { get; set; }
   
    public override bool Execute()
    {
        AzureAccount acct = newAzureAccount("cert.cer", "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");

        if(Uri.IsWellFormedUriString(PackageUri, UriKind.Absolute) == true&&
            Uri.IsWellFormedUriString(ConfigUri, UriKind.Absolute))
        {
            acct.Upgrade("MyServiceName", "staging", newUri(PackageUri), newUri(ConfigUri));
            return true;
        }

        return false;
    }
}

 

Some things need to be explained:

The “cert.cer” string is the path location of a self-signed certificate which can be created using IIS or generated using the makecert command line tool as follows:

makecert -r -pe -a sha1 -n "CN=Windows Azure Authentication Certificate" -ss My -len 2048 -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" -sy 24 mycert.cer

and uploaded to your Azure account using the Developer Portal Account tab:

APICert  

The constructor for the AzureAccount class takes a local path to your self-signed certificate and also your Azure account subscription ID (also on the Account tab of the Azure Developer Portal).

The Upgrade call takes the name of your service, a string which is either ‘staging’ or ‘production’ and uri’s to the input packages (since I have these uploaded to Azure Storage).

So, now to the important part…

The upgrade call will replace an existing running hosted service with your new one (when upgrading the staging server this leaves the url exactly the same as before i.e. with the same guid which can be useful if you don’t want to keep publishing the deployed url).

public string Upgrade(stringserviceName, stringdeploymentSlot, UripackageURI, UricfgURI)
{
    // Download the cfg file to convert to base 64..
  
WebClient wc = newWebClient();
    stringtempfile = System.IO.Path.GetTempFileName();
    wc.DownloadFile(cfgURI, tempfile);

    byte[] bytes = File.ReadAllBytes(tempfile);
    stringbase64Str = Convert.ToBase64String(bytes);

    if(base64Str != null)
    {
        stringlbl = DateTime.Now.ToString("MM-dd-yyyyTHH-mm-ss");
        System.Text.UTF8Encodingenc = newSystem.Text.UTF8Encoding();
        byte[] lblBytes = enc.GetBytes(lbl);
        lbl = Convert.ToBase64String(lblBytes);

        XNamespace ns = XNamespace.Get("http://schemas.microsoft.com/windowsazure");
        XNamespace nsi = XNamespace.Get("http://www.w3.org/2001/XMLSchema-instance");

        XDocument doc = newXDocument(newXDeclaration("1.0", "utf-8", "no"),
                    newXElement(ns + "UpgradeDeployment",
                    newXElement(ns + "Mode", "auto"),
                    newXElement(ns + "PackageUrl", packageURI.ToString()),
                    newXElement("RoleToUpgrade", newXAttribute(nsi + "nil", "true")),
                    newXElement(ns + "Configuration", base64Str),
                    newXElement(ns + "DeploymentLabel", lbl)));

        Uriuri = newUri(_baseUri, _subscriptionId + "/services/hostedservices/"+ serviceName +
            "/deploymentslots/"+ deploymentSlot.ToLower() + "/action=upgrade");

        CreateHttpRequest(uri, "POST", _cert, _timeout, doc);
    }

    return null;
}
 

So, this function downloads the configuration file from storage and converts it’s contents to base64 as required by the API. Then it bundles all of the inputs up into xml format to be posted to the upgrade api which is documented here http://msdn.microsoft.com/en-us/library/ee460793.aspx. When I implemented this exactly as stated in the documentation I couldn’t get the call to succeed so I used Fiddler to check a similar request made by using csmanage.exe and modified the data I was posting based on this. So, there may be some minor inconsistencies between my code and the documentation but this code works at the time of posting.

One thing to note is that if you have Fiddler running on your machine when these API calls are made they won’t work as Fiddler won’t know to use your client certificate when it forwards your request. I guess there is a way to configure Fiddler to allow the calls to be made with the correct certificate.

The final piece of code required to get this all working is to create the request and check the response:

public HttpWebRequest CreateHttpRequest(Uriuri, stringhttpMethod,
    X509Certificate2accessCertificate, TimeSpan timeout, XDocument postData)
{
    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("x-ms-version", "2009-08-08");
    request.ContentType = "application/xml";
    if(httpMethod.ToLower() == "post")
    {
        PostWrapper wrapper = newPostWrapper();
        wrapper.postData = postData.ToString();
        wrapper.Request = request;
        request.BeginGetRequestStream(newAsyncCallback(ReadCallback), wrapper);
    }
    returnrequest;
}

private voidReadCallback(IAsyncResult asynchronousResult)
{
    PostWrapper data = (PostWrapper)asynchronousResult.AsyncState;
    HttpWebRequest request = data.Request;

    // End the operation.
  
StreampostStream = request.EndGetRequestStream(asynchronousResult);

    stringsData = data.postData.ToString();
    System.Text.UTF8Encodingenc = newSystem.Text.UTF8Encoding();
    byte[] stringBytes = enc.GetBytes(sData);
   
    postStream.Write(stringBytes, 0, stringBytes.Length);
    postStream.Close();

    request.BeginGetResponse(newAsyncCallback(ResponseCallback), request);
}

private voidResponseCallback(IAsyncResult asynchronousResult)
{
    HttpWebRequest req = asynchronousResult.AsyncState asHttpWebRequest;
    if(req != null)
    {
        WebResponse resp = req.EndGetResponse(asynchronousResult);
        //resp.
      
stringtrackingId = resp.Headers.Get("x-ms-request-id");

        startPollingOperation(trackingId);
    }
}
 

The response received in the ResponseCallback method returns an ID string which you can then use to track the status of the operation. I haven’t got this bit working yet so haven’t provided a definition for startPollingOperation. This is not necessary for my build task as I am just happy to start the operation rolling. I am also developing a wpf application that uses this code and I will probably update it to call other parts of the API as I need them.

Technorati Tags: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Windows Live Tags: Azure,Management,Disclaimer,code,error,Developer,Portal,Team,archive,introduction,library,system,CruiseControl,deployment,MSBuild,task,files,Storage,APIs,tool,AzureAccount,DeployToAzureTask,PackageUri,ConfigUri,Execute,cert,XXXXXXXX,XXXX,XXXXXXXXXXXX,Absolute,Upgrade,MyServiceName,Some,path,location,self,certificate,Authentication,Microsoft,Cryptographic,Provider,account,subscription,production,packages,server,Download,WebClient,GetTempFileName,DownloadFile,File,ReadAllBytes,Convert,DateTime,Text,GetBytes,XNamespace,XMLSchema,instance,XDocument,XDeclaration,XElement,UpgradeDeployment,Mode,PackageUrl,RoleToUpgrade,XAttribute,Configuration,DeploymentLabel,_baseUri,_subscriptionId,services,ToLower,action,CreateHttpRequest,POST,_cert,_timeout,contents,documentation,Fiddler,data,machine,client,piece,response,HttpWebRequest,TimeSpan,timeout,Create,TotalMilliseconds,ReadWriteTimeout,Method,ClientCertificates,Headers,version,ContentType,PostWrapper,wrapper,Request,BeginGetRequestStream,AsyncCallback,ReadCallback,IAsyncResult,AsyncState,operation,Stream,EndGetRequestStream,Write,Length,Close,BeginGetResponse,ResponseCallback,WebResponse,EndGetResponse,status,haven,definition,purposes,backups,inconsistencies,blogs,msdn,windowsazure,aspx,csmanage,acct,makecert,serviceName,deploymentSlot,cfgURI,tempfile,byte,bytes,lblBytes,httpMethod,accessCertificate,postData,asynchronousResult,postStream,sData,stringBytes,resp,trackingId,startPollingOperation

</div>

Categories:

Updated:

Comments