diff --git a/src/AzureOpenAIProxy.ApiApp/AzureOpenAIProxy.ApiApp.csproj b/src/AzureOpenAIProxy.ApiApp/AzureOpenAIProxy.ApiApp.csproj index 92c1aae3..554f15c3 100644 --- a/src/AzureOpenAIProxy.ApiApp/AzureOpenAIProxy.ApiApp.csproj +++ b/src/AzureOpenAIProxy.ApiApp/AzureOpenAIProxy.ApiApp.csproj @@ -7,6 +7,7 @@ + diff --git a/src/AzureOpenAIProxy.ApiApp/Configurations/KeyVaultSettings.cs b/src/AzureOpenAIProxy.ApiApp/Configurations/KeyVaultSettings.cs index 80c3a51b..763d861e 100644 --- a/src/AzureOpenAIProxy.ApiApp/Configurations/KeyVaultSettings.cs +++ b/src/AzureOpenAIProxy.ApiApp/Configurations/KeyVaultSettings.cs @@ -16,7 +16,7 @@ public class KeyVaultSettings public string? VaultUri { get; set; } /// - /// Gets or sets the secret name. + /// Gets or sets the secret names. /// - public string? SecretName { get; set; } + public Dictionary SecretNames { get; set; } = []; } \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs b/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs index dc05682b..d4944acf 100644 --- a/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs +++ b/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs @@ -48,7 +48,7 @@ public static IOpenAISettingsBuilder WithKeyVault(this IOpenAISettingsBuilder bu var client = sp.GetService() ?? throw new InvalidOperationException($"{nameof(SecretClient)} service is not registered."); - var value = client.GetSecret(settings.SecretName!).Value.Value; + var value = client.GetSecret(settings.SecretNames[KeyVaultSecretNames.OpenAI]!).Value.Value; var instances = JsonSerializer.Deserialize>(value); diff --git a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs index ae2971bb..0f170517 100644 --- a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs +++ b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Azure.Identity; +using Azure.Data.Tables; +using Azure.Identity; using Azure.Security.KeyVault.Secrets; using AzureOpenAIProxy.ApiApp.Builders; @@ -35,9 +36,9 @@ public static IServiceCollection AddKeyVaultService(this IServiceCollection serv throw new InvalidOperationException($"{nameof(KeyVaultSettings.VaultUri)} is not defined."); } - if (string.IsNullOrWhiteSpace(settings.SecretName) == true) + if (string.IsNullOrWhiteSpace(settings.SecretNames[KeyVaultSecretNames.OpenAI]) == true) { - throw new InvalidOperationException($"{nameof(KeyVaultSettings.SecretName)} is not defined."); + throw new InvalidOperationException($"{nameof(KeyVaultSettings.SecretNames)}.{KeyVaultSecretNames.OpenAI} is not defined."); } var client = new SecretClient(new Uri(settings.VaultUri), new DefaultAzureCredential()); @@ -134,4 +135,36 @@ public static IServiceCollection AddOpenApiService(this IServiceCollection servi return services; } + + /// + /// Adds the TableServiceClient to the services collection. + /// + /// instance. + /// Returns instance. + public static IServiceCollection AddTableStorageService(this IServiceCollection services) + { + services.AddSingleton(sp => { + var configuration = sp.GetService() + ?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registerd."); + + var settings = configuration.GetSection(AzureSettings.Name).GetSection(KeyVaultSettings.Name).Get() + ?? throw new InvalidOperationException($"{nameof(KeyVaultSettings)} could not be retrieved from the configuration."); + + var clientSecret = sp.GetService() + ?? throw new InvalidOperationException($"{nameof(SecretClient)} service is not registered."); + + if (string.IsNullOrWhiteSpace(settings.SecretNames[KeyVaultSecretNames.Storage]) == true) + { + throw new InvalidOperationException($"{nameof(KeyVaultSettings.SecretNames)}.{KeyVaultSecretNames.Storage} is not defined."); + } + + var storageKeyVault = clientSecret.GetSecret(settings.SecretNames[KeyVaultSecretNames.Storage]!); + + var client = new TableServiceClient(storageKeyVault.Value.Value); + + return client; + }); + + return services; + } } diff --git a/src/AzureOpenAIProxy.ApiApp/KeyVaultSecretNames.cs b/src/AzureOpenAIProxy.ApiApp/KeyVaultSecretNames.cs new file mode 100644 index 00000000..a8778adb --- /dev/null +++ b/src/AzureOpenAIProxy.ApiApp/KeyVaultSecretNames.cs @@ -0,0 +1,18 @@ +namespace AzureOpenAIProxy.ApiApp; + +/// +/// This represents the keyvault secret names in appsettings.json +/// +public static class KeyVaultSecretNames +{ + /// + /// Keyvault secret name for OpenAI instance settings + /// + public const string OpenAI = "OpenAI"; + + /// + /// Keyvault secret name for table storage connection string + /// + public const string Storage = "Storage"; + +} \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Program.cs b/src/AzureOpenAIProxy.ApiApp/Program.cs index 4c5a0e55..5bcaf935 100644 --- a/src/AzureOpenAIProxy.ApiApp/Program.cs +++ b/src/AzureOpenAIProxy.ApiApp/Program.cs @@ -16,6 +16,9 @@ // Add OpenAPI service builder.Services.AddOpenApiService(); +// Add TableStorageClient +builder.Services.AddTableStorageService(); + // Add admin services builder.Services.AddAdminEventService(); diff --git a/src/AzureOpenAIProxy.ApiApp/appsettings.json b/src/AzureOpenAIProxy.ApiApp/appsettings.json index 6c89d496..a6725e09 100644 --- a/src/AzureOpenAIProxy.ApiApp/appsettings.json +++ b/src/AzureOpenAIProxy.ApiApp/appsettings.json @@ -25,7 +25,10 @@ }, "KeyVault": { "VaultUri": "https://{{key-vault-name}}.vault.azure.net/", - "SecretName": "azure-openai-instances" + "SecretNames": { + "OpenAI": "azure-openai-instances", + "Storage": "storage-connection-string" + } } }, diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs index 142c1eac..5a77eda8 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs @@ -92,7 +92,7 @@ public void Given_NullOrEmpty_VaultUri_When_Invoked_AddKeyVaultService_Then_It_S var dict = new Dictionary() { { "Azure:KeyVault:VaultUri", vaultUri! }, - { "Azure:KeyVault:SecretName", secretName }, + { "Azure:KeyVault:SecretNames:OpenAI", secretName }, }; #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); @@ -108,16 +108,16 @@ public void Given_NullOrEmpty_VaultUri_When_Invoked_AddKeyVaultService_Then_It_S } [Theory] - [InlineData("http://localhost", default(string))] - [InlineData("http://localhost", "")] - public void Given_NullOrEmpty_SecretName_When_Invoked_AddKeyVaultService_Then_It_Should_Throw_Exception(string vaultUri, string? secretName) + [InlineData("http://localhost", default(string), typeof(KeyNotFoundException))] + [InlineData("http://localhost", "", typeof(InvalidOperationException))] + public void Given_NullOrEmpty_SecretName_When_Invoked_AddKeyVaultService_Then_It_Should_Throw_Exception(string vaultUri, string? secretName, Type exceptionType) { // Arrange var services = new ServiceCollection(); var dict = new Dictionary() { { "Azure:KeyVault:VaultUri", vaultUri }, - { "Azure:KeyVault:SecretName", secretName! }, + { "Azure:KeyVault:SecretNames:OpenAI", secretName! }, }; #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); @@ -129,7 +129,7 @@ public void Given_NullOrEmpty_SecretName_When_Invoked_AddKeyVaultService_Then_It Action action = () => services.BuildServiceProvider().GetService(); // Assert - action.Should().Throw(); + action.Should().Throw().Which.Should().BeOfType(exceptionType); } [Theory] @@ -141,7 +141,7 @@ public void Given_Invalid_VaultUri_When_Invoked_AddKeyVaultService_Then_It_Shoul var dict = new Dictionary() { { "Azure:KeyVault:VaultUri", vaultUri }, - { "Azure:KeyVault:SecretName", secretName }, + { "Azure:KeyVault:SecretNames:OpenAI", secretName }, }; #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); @@ -165,7 +165,7 @@ public void Given_AppSettings_When_Invoked_AddKeyVaultService_Then_It_Should_Ret var dict = new Dictionary() { { "Azure:KeyVault:VaultUri", vaultUri }, - { "Azure:KeyVault:SecretName", secretName }, + { "Azure:KeyVault:SecretNames:OpenAI", secretName }, }; #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); @@ -190,7 +190,7 @@ public void Given_AppSettings_When_Invoked_AddKeyVaultService_Then_It_Should_Ret var dict = new Dictionary() { { "Azure:KeyVault:VaultUri", vaultUri }, - { "Azure:KeyVault:SecretName", secretName }, + { "Azure:KeyVault:SecretNames:OpenAI", secretName }, }; #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();