0
votes

I have a fairly simple method that uses the NEW Storage API to create a SAS and copy a blob from one container to another.

I am trying to use this to Copy blob BETWEEN STORAGE ACCOUNTS. So I have TWo Storage accounts, with the exact same Containers, and I am trying to copy a blob from the Storage Account's Container to another Storage Account's Container.

I don't know if the SDK is built for that, but it seems like it would be a common scenario.

Some additional information:

  1. I create the token on the Destination Container. Does that token need to be created on both the source and destination? Does it take time to register the token? Do I need to create it for each request, or only once per token "lifetime"?

I should mention a 403 is an Unauthorized Result http error code.

  private static string CreateSharedAccessToken(CloudBlobClient blobClient, string containerName)
    {
        var container = blobClient.GetContainerReference(containerName);
        var blobPermissions = new BlobContainerPermissions();

        // The shared access policy provides read/write access to the container for 10 hours:

        blobPermissions.SharedAccessPolicies.Add("SolutionPolicy", new SharedAccessBlobPolicy()
        {
            // To ensure SAS is valid immediately we don’t set start time 
            // so we can avoid failures caused by small clock differences:

            SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1),
            Permissions = SharedAccessBlobPermissions.Write |
                          SharedAccessBlobPermissions.Read
        });


        blobPermissions.PublicAccess = BlobContainerPublicAccessType.Blob; 
        container.SetPermissions(blobPermissions);

        return container.GetSharedAccessSignature(new SharedAccessBlobPolicy(), "SolutionPolicy");

    }

Down the line I use this token to call a copy operation, which returns a 403:

  var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);
            destBlob.StartCopyFromBlob(uri);

My version of Azure.Storage is 2.1.0.2.

Here is the full copy method in case that helps:

    private static void CopyBlobs(
        CloudBlobContainer srcContainer, string blobToken,
        CloudBlobContainer destContainer)
    {
        var srcBlobList
            = srcContainer.ListBlobs(string.Empty, true, BlobListingDetails.All); // set to none in prod (4perf)

        //// get the SAS token to use for all blobs 
        //string token = srcContainer.GetSharedAccessSignature(
        //    new SharedAccessBlobPolicy(), "SolutionPolicy");

        bool pendingCopy = true;

        foreach (var src in srcBlobList)
        {
            var srcBlob = src as ICloudBlob;

            // Determine BlobType:

            ICloudBlob destBlob;
            if (srcBlob.Properties.BlobType == BlobType.BlockBlob)
            {
                destBlob = destContainer.GetBlockBlobReference(srcBlob.Name);
            }
            else
            {
                destBlob = destContainer.GetPageBlobReference(srcBlob.Name);
            }

            // Determine Copy State:

            if (destBlob.CopyState != null)
            {
                switch (destBlob.CopyState.Status)
                {
                    case CopyStatus.Failed:
                        log.Info(destBlob.CopyState);
                        break;

                    case CopyStatus.Aborted:
                        log.Info(destBlob.CopyState);
                        pendingCopy = true;
                        destBlob.StartCopyFromBlob(destBlob.CopyState.Source);
                        return;

                    case CopyStatus.Pending:
                        log.Info(destBlob.CopyState);
                        pendingCopy = true;
                        break;
                }
            }


            // copy using only Policy ID:
            var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);
            destBlob.StartCopyFromBlob(uri);


            //// copy using src blob as SAS
            //var source = new Uri(srcBlob.Uri.AbsoluteUri + token);
            //destBlob.StartCopyFromBlob(source);

        }
    }

And finally the account and client (vetted) code:

       var credentials = new StorageCredentials("BAR", "FOO");
        var account = new CloudStorageAccount(credentials, true);
        var blobClient = account.CreateCloudBlobClient();
        var sasToken = CreateSharedAccessToken(blobClient, "content");

When I use a REST client this seems to work... any ideas?

3
How much time is occurring between your creation of the SAS and the use of the URL for the copy?MikeWo
It happens instantly; I call the SAS method and miliseconds later call the StartCopyFromBlob() api call. Could it have to do with encoding the uri? I have seen reports that REST clients work but the Azure SDK fails, but have seen no solutions.Shawn J. Molloy
I just realized the problem is that it is going between two different Accounts - I think this means I need creds for both accounts (The Destination and Source)? I'm not sure how the SAS will work across blob content servers..Shawn J. Molloy

3 Answers

1
votes

Consider also this problem:

var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);

Probably you are calling the "ToString" method of Uri that produce a "Human redable" version of the url. If the blobToken contain special caracters like for example "+" this will cause a token malformed error on the storage server that will refuse to give you the access.

Use this instead:

String uri = srcBlob.Uri.AbsoluteUri + blobToken;
0
votes

Shared Access Tokens are not required for this task. I ended up with two accounts and it works fine:

        var accountSrc = new CloudStorageAccount(credsSrc, true);
        var accountDest = new CloudStorageAccount(credsSrc, true);

        var blobClientSrc = accountSrc.CreateCloudBlobClient();
        var blobClientDest = accountDest.CreateCloudBlobClient();


        // Set permissions on the container.
        var permissions = new BlobContainerPermissions {PublicAccess = BlobContainerPublicAccessType.Blob};
        srcContainer.SetPermissions(permissions);
        destContainer.SetPermissions(permissions);


   //grab the blob
        var sourceBlob = srcContainer.GetBlockBlobReference("FOO");
        var destinationBlob = destContainer.GetBlockBlobReference("BAR");

        //create a new blob
        destinationBlob.StartCopyFromBlob(sourceBlob);
0
votes

Since both CloudStorageAccount objects point to the same account, copying without a SAS token would work just fine as you also mentioned.

On the other hand, you need either a publicly accessible blob or a SAS token to copy from another account. So what you tried was correct, but you established a container-level access policy, which can take up to 30 seconds to take effect as also documented in MSDN. During this interval, a SAS token that is associated with the stored access policy will fail with status code 403 (Forbidden), until the access policy becomes active.

One more thing that I would like to point is; when you call Get*BlobReference to create a new blob object, the CopyState property will not be populated until you do a GET/HEAD operation such as FetchAttributes.