0

I'm using Azurite to run tests locally about some functionality that uploads file to an Azure Blob Storage. I'm running it by using docker compose, and I would like to run it in a non-default port for the tests. The configuration I came up with is the following:

  storage:
    image: mcr.microsoft.com/azure-storage/azurite
    environment:
      - AZURITE_ACCOUNTS=account:QUJDRA==
    ports:
      - "10020:10000"

I'm using the following configuration to register the BlobServiceClient service in Asp.Net Core:

services.AddAzureClients(builder =>
{
    builder.AddBlobServiceClient(
        new Uri("http://localhost:10020/account"),
        new StorageSharedKeyCredential("account", "QUJDRA=="));
});

And the code that uploads files is as follows:

public async Task<string> UploadFile(BlobServiceClient blobServiceClient, Stream file)
{
    var blobContainerClient = blobServiceClient.GetBlobContainerClient("container");
    await blobContainerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer);

    var blobClient = blobContainerClient.GetBlobClient("blob");
    await blobClient.UploadAsync(file);

    return blobClient.Uri.ToString();
}

If I run this configuration in the default port (10000), it all works as expected, and I get the following logs from the Azurite container:

storage-1  | 172.21.0.1 - - [20/Jan/2023:11:02:35 +0000] "PUT /account/container?restype=container HTTP/1.1" 409 -
storage-1  | 172.21.0.1 - - [20/Jan/2023:11:02:37 +0000] "PUT /account/container/blob?comp=block&blockid=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1" 201 -
storage-1  | 172.21.0.1 - - [20/Jan/2023:11:02:37 +0000] "PUT /account/container/blob?comp=blocklist HTTP/1.1" 201 -

However, if I try to run it in the non-default port (10020), the line in which the file is uploaded await blobClient.UploadAsync(file) produces the following exception:

Azure.RequestFailedException : Service request failed.
Status: 400 (Bad Request)

storage-1  | 172.25.0.1 - - [20/Jan/2023:11:18:43 +0000] "PUT /account/container?restype=container HTTP/1.1" 201 -
storage-1  | 172.25.0.1 - - [20/Jan/2023:11:18:44 +0000] "PUT /account/blob?comp=block&blockid=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1" 400 -

If you look closely to the second line of the logs, which corresponds to the upload of the file, in this case the url is missing the /container part after the name of the account. I guess that's the reason for the 400 error.

Why is it that a change in the port is changing the url in this way? Is there any configuration that I'm missing?

  • Please check [the official blog](https://techcommunity.microsoft.com/t5/azure-paas-blog/troubleshooting-azurite-and-azure-storage-emulator-issues/ba-p/3296302) and [try to get the debug logs](https://hub.docker.com/_/microsoft-azure-storage-azurite). – Jason Pan Jan 26 '23 at 09:56
  • It seems that the issue is related to how the BlobServiceClient and the BlobContainerClient are being constructed. The BlobServiceClient URL should include the storage account name, while the BlobContainerClient URL should include the storage account name and the container name. – Sourav Feb 03 '23 at 11:14

1 Answers1

0

The issue comes does to how the BlobContainerClient.GetBlobClient() attempts to determine the account name from the URI in Azure.Storage.Blobs (12.14.1) and Azure.Storage.Common (12.13.0).

The GetBlobClient method creates an instance of BlobUriBuilder internally, receiving the container client's URI. It then attempts to deconstruct the URI but as the placement of the account name in Azure Storage Containers (before the domain) and Azurite (first segment of the path) differ, the port is used to determine how this should be done.

The whitelisted ports to determine if it is an Azurite instance are:

10000, 10001, 10002, 10003, 10004,
10100, 10101, 10102, 10103, 10104,
11000, 11001, 11002, 11003, 11004,
11100, 11101, 11102, 11103, 11104

I have not found any documentation that exposes this beyond the source code.


Source: BlobUriBuilder constructor calls uri.IsHostIPEndPointStyle()

...
   if (uri.IsHostIPEndPointStyle())
   {
       _isPathStyleUri = true;
       var accountEndIndex = path.IndexOf("/", StringComparison.InvariantCulture);
   
       // Slash not found; path has account name & no container name
       if (accountEndIndex == -1)
       {
           AccountName = path;
           startIndex = path.Length;
       }
       else
       {
           AccountName = path.Substring(0, accountEndIndex);
           startIndex = accountEndIndex + 1;
       }
   }
   else
   {
       AccountName = uri.GetAccountNameFromDomain(Constants.Blob.UriSubDomain) ?? string.Empty;
   }
...

Source: which references Constants.Sas.PathStylePorts

   // See remarks at https://docs.microsoft.com/en-us/dotnet/api/system.net.ipaddress.tryparse?view=netframework-4.7.2
   /// <summary>
   /// Check to see if Uri is using IP Endpoint style.
   /// </summary>
   /// <param name="uri">The Uri.</param>
   /// <returns>True if using IP Endpoint style.</returns>
   public static bool IsHostIPEndPointStyle(this Uri uri) =>
       (!string.IsNullOrEmpty(uri.Host) &&
       uri.Host.IndexOf(".", StringComparison.InvariantCulture) >= 0 &&
       IPAddress.TryParse(uri.Host, out _)) ||
       Constants.Sas.PathStylePorts.Contains(uri.Port);

Source: for the whitelisted ports

   /// <summary>
   /// List of ports used for path style addressing.
   /// Copied from Microsoft.Azure.Storage.Core.Util
   /// </summary>
   internal static readonly int[] PathStylePorts = { 10000, 10001, 10002, 10003, 10004, 10100, 10101, 10102, 10103, 10104, 11000, 11001, 11002, 11003, 11004, 11100, 11101, 11102, 11103, 11104 };
EtherZa
  • 1
  • 1
  • 1