2018-10-28

Copy Azure Blob via Rest API

Recently I was requested to work on a data procedure. Basically what I need to do is using SSIS to copy files between different Azure blob storage folders (allow me to use this term, I know there is no such concept in Azure blob. It is just for simplicity :P ).

It is a quite simple task if you can reference Microsoft.WindowsAzure.Storage dll in your package. But in my case the SSIS server doesn't have it. So I decided to use Azure Blob Rest API -- independent to the host environment.

The Azure Blob API's office document gives us a very clear explanation. To call the API, we need to identify the resource, create authorization header, and then issue the web request. But the problem is, I couldn't find any resource to tell you how to create the authorization header for copy. So if you are facing the same problem, continue to section below...

The first step is to create a helper method to generate authorization header, very standard implementation:


        public static String SignThis(String stringToSign, string accountKey, string accountName)
        {
            String signature = string.Empty;
            byte[] unicodeKey = Convert.FromBase64String(accountKey);
            using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
            {
                Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(stringToSign);
                signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
            }

            String authorizationHeader = String.Format(
                  CultureInfo.InvariantCulture,
                  "{0} {1}:{2}",
                  "SharedKey",
                  accountName,
                  signature);

            return authorizationHeader;
        }


The second step is to declare our variables


            string accountKey = "my acccount key";
            string accountName = "my account name";
   
            string srcBlob = "source blob name";
            string destBlob = "destination blob name";
   
            string containerName = "my blob container name";
   
            string method = "PUT";
            DateTime dt = DateTime.UtcNow;
   
            string reqUrl = @"https://" + accountName + ".blob.core.windows.net/" + containerName + "/" + destBlob;
            string copySource = @"https://" + accountName + ".blob.core.windows.net/" + containerName + "/" + srcBlob;
            string canonicalizedHeaders = string.Format("/{0}/{1}/{2}", accountName, containerName, destBlob);



Here is a very important step: we need to create the string for sign. Each line in this string has different meaning, and you can find detailed explanation here. Fortunately we just need to populate mandatory components, like this:


            string StringToSign = String.Format(
                method + "\n"
                + "\n" // content encoding
                + "\n" // content language
                + "\n" // content length
                + "\n" // content md5
                + "\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-copy-source:" + copySource + "\nx-ms-date:" + dt.ToString("R") + "\nx-ms-version:2015-04-05\n"
                + canonicalizedHeaders);


Notice here the version I used is 2015-04-05. Technically you should be able to use any version Microsoft provided, e.g. the latest is 2018-03-28.

Now we have the string, then we only need to sign this string, and issue the web request


            string auth = SignThis(StringToSign, accountKey, accountName);

            Uri reqUri = new Uri(reqUrl);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(reqUri);
            request.Method = method;
            request.Headers.Add("x-ms-date", dt.ToString("R"));
            request.Headers.Add("x-ms-version", "2015-04-05");
            request.Headers.Add("Authorization", auth);
            request.ContentLength = 0;
            request.Headers.Add("x-ms-copy-source", copySource);

            using (var response = request.GetResponse())
            {
                var status = ((HttpWebResponse)response).StatusCode;

                if (status != HttpStatusCode.Accepted)
                {
                    Dts.TaskResult = (int)ScriptResults.Failure;
                }
                else
                {
                    Dts.TaskResult = (int)ScriptResults.Success;
                }
            }


Enjoy :)