diff --git a/infra/functions.bicep b/infra/functions.bicep index e86ff08..8b88313 100644 --- a/infra/functions.bicep +++ b/infra/functions.bicep @@ -144,12 +144,8 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { value: imageStorageSettings.folderPath } { - name: '${imageIndexStorageKey}__ConnectionString' - value: imageStorageSettings.connectionString - } - { - name: '${imageIndexStorageKey}__ContainerName' - value: 'index' + name: '${imageIndexStorageKey}__BlobContainerUri' + value: '${functionStorageAccount.properties.primaryEndpoints.blob}index' } ] } diff --git a/infra/main.bicep b/infra/main.bicep index fbd1923..e40f61d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -54,8 +54,7 @@ resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@20 } var imageSettings = { - connectionString: 'DefaultEndpointsProtocol=https;AccountName=${imageStorage.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${imageStorage.listKeys().keys[0].value}' - blobContainerUri: '${imageStorage.properties.primaryEndpoints.blob}/${imageContainerName}' + blobContainerUri: '${imageStorage.properties.primaryEndpoints.blob}${imageContainerName}' folderPath: 'root' } @@ -72,18 +71,17 @@ module functions 'functions.bicep' = { } } -// TODO refactor this. this should only require reading permission. Image INDEX requires more permissions and currently these are in same place // TODO Also this assignment could probably be a separate module etc. -var storageBlobDataOwnerRoleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' +var storageBlobDataReaderRoleDefinitionId = '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1' resource functionAppFunctionBlobStorageAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - scope: imageStorage - name: guid(functions.name, storageBlobDataOwnerRoleDefinitionId, imageStorage.id) + scope: container + name: guid(functions.name, storageBlobDataReaderRoleDefinitionId, container.id) properties: { principalId: functions.outputs.functionAppPrincipalId principalType: 'ServicePrincipal' roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', - storageBlobDataOwnerRoleDefinitionId + storageBlobDataReaderRoleDefinitionId ) } } diff --git a/infra/types.bicep b/infra/types.bicep index 3bd40d9..78e415c 100644 --- a/infra/types.bicep +++ b/infra/types.bicep @@ -8,6 +8,5 @@ type DiscordSettings = { @export() type ImageStorageSettings = { blobContainerUri: string - connectionString: string folderPath: string } diff --git a/src/Common/BlobStorageImageService/BlobStorageImageSourceOptions.cs b/src/Common/BlobStorageImageService/BlobStorageImageSourceOptions.cs index 16a7e32..15047cc 100644 --- a/src/Common/BlobStorageImageService/BlobStorageImageSourceOptions.cs +++ b/src/Common/BlobStorageImageService/BlobStorageImageSourceOptions.cs @@ -17,6 +17,7 @@ public class BlobStorageImageSourceOptions /// /// The name of the container where the images are stored. + /// This is not needed (nor used) if BlobContainerUri is used. /// public required string? ContainerName { get; set; } diff --git a/src/Common/IndexService/BlobStorageIndexStorageService.cs b/src/Common/IndexService/BlobStorageIndexStorageService.cs index e063027..c263bd7 100644 --- a/src/Common/IndexService/BlobStorageIndexStorageService.cs +++ b/src/Common/IndexService/BlobStorageIndexStorageService.cs @@ -2,29 +2,32 @@ using Azure.Storage.Blobs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace DiscordImagePoster.Common.IndexService; public class BlobStorageIndexStorageService : IIndexStorageService { - private ILogger _logger; + private readonly ILogger _logger; + private readonly ImageIndexOptions _options; private readonly BlobContainerClient _blobContainerClient; - private const string IndexBlobName = "index.json"; - public BlobStorageIndexStorageService ( ILogger logger, + IOptions options, [FromKeyedServices(KeyedServiceConstants.ImageIndexBlobContainerClient)] BlobContainerClient blobContainerClient ) { _logger = logger; + _options = options.Value; _blobContainerClient = blobContainerClient; } public async Task GetImageIndexAsync() { - var blobClient = _blobContainerClient.GetBlobClient(IndexBlobName); + _logger.LogTrace("Getting image index from {IndexFileName}", _options.IndexFileName); + var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); var exists = await blobClient.ExistsAsync(); if (!exists) { @@ -38,9 +41,10 @@ public BlobStorageIndexStorageService public async Task UpdateIndexAsync(ImageIndex index) { + _logger.LogTrace("Updating image index to {IndexFileName}", _options.IndexFileName); await _blobContainerClient.CreateIfNotExistsAsync(); var bytes = JsonSerializer.SerializeToUtf8Bytes(index); - var blobClient = _blobContainerClient.GetBlobClient(IndexBlobName); + var blobClient = _blobContainerClient.GetBlobClient(_options.IndexFileName); await blobClient.UploadAsync(new MemoryStream(bytes), true); } } diff --git a/src/Common/IndexService/ImageIndexOptions.cs b/src/Common/IndexService/ImageIndexOptions.cs index 531dee2..e28ca08 100644 --- a/src/Common/IndexService/ImageIndexOptions.cs +++ b/src/Common/IndexService/ImageIndexOptions.cs @@ -11,12 +11,25 @@ public class ImageIndexOptions { /// /// The connection string to the Azure Storage account. + /// Use this only for development. Use managed identity in production. /// public required string ConnectionString { get; set; } /// - /// Container name where the index is stored. + /// The name of the container where the images are stored. + /// This is not needed (nor used) if BlobContainerUri is used. /// - [Required] - public required string ContainerName { get; set; } + public required string? ContainerName { get; set; } + + /// + /// The URI to the container where the images are stored. + /// https://{account_name}.blob.core.windows.net/{container_name} + /// + /// If this is used, the ConnectionString is not needed and managed identity is used + /// + public required string? BlobContainerUri { get; set; } + + /// + /// The path to the index file. Defaults to index.json in root. + public required string IndexFileName { get; set; } = "index.json"; } diff --git a/src/FunctionApp.Isolated/Program.cs b/src/FunctionApp.Isolated/Program.cs index faa4dfc..cc233ff 100644 --- a/src/FunctionApp.Isolated/Program.cs +++ b/src/FunctionApp.Isolated/Program.cs @@ -36,7 +36,6 @@ services.AddKeyedTransient(KeyedServiceConstants.ImageBlobContainerClient, (services, _) => { - //https://{account_name}.blob.core.windows.net/{container_name} var options = services.GetRequiredService>().Value; if (!string.IsNullOrWhiteSpace(options.BlobContainerUri)) { @@ -48,6 +47,10 @@ services.AddKeyedTransient(KeyedServiceConstants.ImageIndexBlobContainerClient, (services, _) => { var options = services.GetRequiredService>().Value; + if (!string.IsNullOrWhiteSpace(options.BlobContainerUri)) + { + return new BlobContainerClient(new Uri(options.BlobContainerUri), new DefaultAzureCredential()); + } return new BlobContainerClient(options.ConnectionString, options.ContainerName); });