diff --git a/src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs b/src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs index e708ad712..6d86dc046 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretDescriptor.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,8 +21,11 @@ namespace Dapr.Extensions.Configuration public class DaprSecretDescriptor { /// - /// Gets or sets the secret name. + /// The name of the secret to retrieve from the Dapr secret store. /// + /// + /// If the is not specified, this value will also be used as the key to retrieve the secret from the associated source secret store. + /// public string SecretName { get; } /// @@ -31,20 +34,39 @@ public class DaprSecretDescriptor public IReadOnlyDictionary Metadata { get; } /// - /// Secret Descriptor Construcutor + /// A value indicating whether to throw an exception if the secret is not found in the source secret store. + /// + /// + /// Setting this value to will suppress the exception; otherwise, will not. + /// + public bool IsRequired { get; } + + /// + /// The secret key that maps to the to retrieve from the source secret store. + /// + /// + /// Use this property when the does not match the key used to retrieve the secret from the source secret store. + /// + public string SecretKey { get; } + + /// + /// Secret Descriptor Constructor /// - public DaprSecretDescriptor(string secretName) : this(secretName, new Dictionary()) + public DaprSecretDescriptor(string secretName, bool isRequired = true, string secretKey = "") + : this(secretName, new Dictionary(), isRequired, secretKey) { } /// - /// Secret Descriptor Construcutor + /// Secret Descriptor Constructor /// - public DaprSecretDescriptor(string secretName, IReadOnlyDictionary metadata) + public DaprSecretDescriptor(string secretName, IReadOnlyDictionary metadata, bool isRequired = true, string secretKey = "") { SecretName = secretName; Metadata = metadata; + IsRequired = isRequired; + SecretKey = string.IsNullOrEmpty(secretKey) ? secretName : secretKey; } } -} \ No newline at end of file +} diff --git a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs index da5349c30..5991a7dad 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ public DaprSecretStoreConfigurationProvider( bool normalizeKey, IEnumerable secretDescriptors, DaprClient client) : this(store, normalizeKey, null, secretDescriptors, client, DefaultSidecarWaitTimeout) - { + { } /// @@ -181,6 +181,10 @@ private string NormalizeKey(string key) return key; } + /// + /// Loads the configuration by calling the asynchronous LoadAsync method and blocking the calling + /// thread until the operation is completed. + /// public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult(); private async Task LoadAsync() @@ -197,16 +201,34 @@ private async Task LoadAsync() { foreach (var secretDescriptor in secretDescriptors) { - var result = await client.GetSecretAsync(store, secretDescriptor.SecretName, secretDescriptor.Metadata).ConfigureAwait(false); + + Dictionary result; + + try + { + result = await client + .GetSecretAsync(store, secretDescriptor.SecretKey, secretDescriptor.Metadata) + .ConfigureAwait(false); + } + catch (DaprException) + { + if (secretDescriptor.IsRequired) + { + throw; + } + result = new Dictionary(); + } foreach (var key in result.Keys) { if (data.ContainsKey(key)) { - throw new InvalidOperationException($"A duplicate key '{key}' was found in the secret store '{store}'. Please remove any duplicates from your secret store."); + throw new InvalidOperationException( + $"A duplicate key '{key}' was found in the secret store '{store}'. Please remove any duplicates from your secret store."); } - data.Add(normalizeKey ? NormalizeKey(key) : key, result[key]); + data.Add(normalizeKey ? NormalizeKey(secretDescriptor.SecretName) : secretDescriptor.SecretName, + result[key]); } } diff --git a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj index 2e4523582..7d11d5c40 100644 --- a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj +++ b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj @@ -7,6 +7,7 @@ + all diff --git a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs index 488c94983..d35275dd1 100644 --- a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs +++ b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ using FluentAssertions; using Grpc.Net.Client; using Microsoft.Extensions.Configuration; +using Moq; using Xunit; using Autogenerated = Dapr.Client.Autogen.Grpc.v1; @@ -142,23 +143,23 @@ public void AddDaprSecretStore_UsingDescriptors_DuplicateSecret_ReportsError() [Fact] public void LoadSecrets_FromSecretStoreThatReturnsOneValue() { - // Configure Client - var httpClient = new TestHttpClient() + var storeName = "store"; + var secretKey = "secretName"; + var secretValue = "secret"; + + var secretDescriptors = new[] { - Handler = async (entry) => - { - var secrets = new Dictionary() { { "secretName", "secret" } }; - await SendResponseWithSecrets(secrets, entry); - } + new DaprSecretDescriptor(secretKey), }; - var daprClient = new DaprClientBuilder() - .UseHttpClientFactory(() => httpClient) - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) - .Build(); + var daprClient = new Mock(); + + daprClient.Setup(c => c.GetSecretAsync(storeName, secretKey, + It.IsAny>(), default)) + .ReturnsAsync(new Dictionary { { secretKey, secretValue } }); var config = CreateBuilder() - .AddDaprSecretStore("store", new DaprSecretDescriptor[] { new DaprSecretDescriptor("secretName") }, daprClient) + .AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object) .Build(); config["secretName"].Should().Be("secret"); @@ -167,32 +168,127 @@ public void LoadSecrets_FromSecretStoreThatReturnsOneValue() [Fact] public void LoadSecrets_FromSecretStoreThatCanReturnsMultipleValues() { - // Configure Client - var httpClient = new TestHttpClient() + var storeName = "store"; + var firstSecretKey = "first_secret"; + var secondSecretKey = "second_secret"; + var firstSecretValue = "secret1"; + var secondSecretValue = "secret2"; + + var secretDescriptors = new[] { - Handler = async (entry) => - { - var secrets = new Dictionary() { - { "first_secret", "secret1" }, - { "second_secret", "secret2" }}; - await SendResponseWithSecrets(secrets, entry); - } + new DaprSecretDescriptor(firstSecretKey), + new DaprSecretDescriptor(secondSecretKey), + }; + + var daprClient = new Mock(); + + daprClient.Setup(c => c.GetSecretAsync(storeName, firstSecretKey, + It.IsAny>(), default)) + .ReturnsAsync(new Dictionary { { firstSecretKey, firstSecretValue } }); + + daprClient.Setup(c => c.GetSecretAsync(storeName, secondSecretKey, + It.IsAny>(), default)) + .ReturnsAsync(new Dictionary { { secondSecretKey, secondSecretValue } }); + + var config = CreateBuilder() + .AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object) + .Build(); + + config[firstSecretKey].Should().Be(firstSecretValue); + config[secondSecretKey].Should().Be(secondSecretValue); + } + + [Fact] + public void LoadSecrets_FromSecretStoreWithADifferentSecretKeyAndName() + { + var storeName = "store"; + var secretKey = "Microsservice-DatabaseConnStr"; + var secretName = "ConnectionStrings:DatabaseConnStr"; + var secretValue = "secret1"; + + var secretDescriptors = new[] + { + new DaprSecretDescriptor(secretName, new Dictionary(), true, + secretKey) + }; + + var daprClient = new Mock(); + + daprClient.Setup(c => c.GetSecretAsync(storeName, secretKey, + It.IsAny>(), default)) + .ReturnsAsync(new Dictionary { { secretKey, secretValue } }); + + var config = CreateBuilder() + .AddDaprSecretStore(storeName, secretDescriptors, daprClient.Object) + .Build(); + + config[secretName].Should().Be(secretValue); + } + + [Fact] + public void LoadSecrets_FromSecretStoreNotRequiredAndDoesNotExist_ShouldNotThrowException() + { + var storeName = "store"; + var secretName = "ConnectionStrings:DatabaseConnStr"; + + var secretDescriptors = new[] + { + new DaprSecretDescriptor(secretName, new Dictionary(), false) + }; + + var httpClient = new TestHttpClient + { + Handler = async entry => + { + await SendEmptyResponse(entry); + } }; var daprClient = new DaprClientBuilder() .UseHttpClientFactory(() => httpClient) - .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); var config = CreateBuilder() - .AddDaprSecretStore("store", new DaprSecretDescriptor[] { new DaprSecretDescriptor("secretName") }, daprClient) + .AddDaprSecretStore(storeName, secretDescriptors, daprClient) + .Build(); + + config[secretName].Should().BeNull(); + } + + [Fact] + public void LoadSecrets_FromSecretStoreRequiredAndDoesNotExist_ShouldThrowException() + { + var storeName = "store"; + var secretName = "ConnectionStrings:DatabaseConnStr"; + + var secretDescriptors = new[] + { + new DaprSecretDescriptor(secretName, new Dictionary(), true) + }; + + var httpClient = new TestHttpClient + { + Handler = async entry => + { + await SendEmptyResponse(entry); + } + }; + + var daprClient = new DaprClientBuilder() + .UseHttpClientFactory(() => httpClient) + .Build(); + + var ex = Assert.Throws(() => + { + var config = CreateBuilder() + .AddDaprSecretStore(storeName, secretDescriptors, daprClient) .Build(); + }); - config["first_secret"].Should().Be("secret1"); - config["second_secret"].Should().Be("secret2"); + Assert.Contains("Secret", ex.Message); } - //Here + [Fact] public void AddDaprSecretStore_WithoutStore_ReportsError() { @@ -565,7 +661,7 @@ await SendResponseWithSecrets(new Dictionary() ["otherSecretName≡value"] = "secret", }, entry); } - } + } } };