Saturday, August 19, 2023

Enumerate Azure Storage Accounts (New)

In April 2021 I posted an article titled Enumerate Azure Storage Accounts which explained how to enumerate all of the storage accounts in an Azure subscription, then drill down into all the containers and blobs, and tables and rows. This sort of code can be used as the basis of some useful custom reporting tools.

Unfortunately, the old code uses deprecated classes, so after a few concentrated hours of study and suffering I found modern replacement code. The code linked below is a skeleton of the modern way to enumerate storage accounts and their contents. For more details see the Azure SDK Samples.

➤ Note that I've used the environment variables credentials, which looks a bit clumsy in the sample. Look for documentation on classes derived from TokenCredential and pick one that suits your needs.

An example C# console command that uses the new Azure sdk libraries can be downloaded from here:

SubscriptionReaderSample.cs

The .cs file has been renamed as a .txt file to avoid security blocks.

Wednesday, August 16, 2023

Azure Table 'batch' (transaction) operations

To bulk insert rows in the old Azure Table Storage API you would create a TableBatchOperation class and fill it with TableOperations, then call ExecuteBatchAsync. The batch could contain different operations, but bulk inserts were my most common need.

The batch related classes do not exist in the new Table API, and finding the replacement code was a dreadful chore. I didn't know the expression batch had been replaced with transaction, so my searches for "batch" produced endless useless old results and discussions. After searching until my fingers bled I stumbled across a hint that batch processing as was now transaction processing. Useful search results now started to arrive, but it took more time to finally find some definitive sample code that actually worked. The best summary page I found was here:

azure-sdk-for-net Transactional Batches

My sanity check code in LINQPad looks like this:

async Task Main()
{
	var tsclient = new TableServiceClient("YOUR STORAGE CONNECT STRING");
	var tclient = tsclient.GetTableClient("TestTable1");
	await tclient.CreateIfNotExistsAsync().Dump();
	var trans = new List<TableTransactionAction>>();
	var rows = Enumerable.Range(1, 10).Select(i => new MockRow()
	{
		Id = i,
		Name = $"Name for {i}"
	} ).ToArray();
	trans.AddRange(rows.Select(r => new TableTransactionAction(TableTransactionActionType.Add, r)));
	await tclient.SubmitTransactionAsync(trans).Dump();
}

class MockRow : ITableEntity
{
	public string PartitionKey { get; set; } = "P1";
	public string RowKey { get; set; } = Guid.NewGuid().ToString("N");
	public DateTimeOffset? Timestamp { get; set; }
	public ETag ETag { get; set; }
	public int Id { get; set; }
	public string Name { get; set; }
}

Transaction success returns status 202 and a list of sub-responses with status 204. I deliberately caused an error while inserting a transaction of 5 rows, by setting the second RowKey with invalid characters. As advertised, no rows are inserted and a TableTransactionFailedException is thrown with a detailed message like this:

2:The 'RowKey' parameter of value 'Bad\Key' is out of range.
RequestId:c10901e2-e002-009a-7aba-cf9069000000
Time:2023-08-15T20:55:37.4158811Z
 The index of the entity that caused the error can be found in FailedTransactionActionIndex.
Status: 400 (Bad Request)
ErrorCode: OutOfRangeInput
Additional Information:
FailedEntity: 2