diff --git a/Dapr.PluggableComponents.Complete.sln b/Dapr.PluggableComponents.Complete.sln index b06cf97..e793979 100644 --- a/Dapr.PluggableComponents.Complete.sln +++ b/Dapr.PluggableComponents.Complete.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBindingSample", "sam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.PluggableComponents.Tests", "src\Dapr.PluggableComponents.Tests\Dapr.PluggableComponents.Tests.csproj", "{C9027B0B-A589-4E92-AB32-34B8961C479B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalEnvSecretStoreSample", "samples\LocalEnvSecretStoreSample\LocalEnvSecretStoreSample.csproj", "{FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,10 @@ Global {C9027B0B-A589-4E92-AB32-34B8961C479B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9027B0B-A589-4E92-AB32-34B8961C479B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9027B0B-A589-4E92-AB32-34B8961C479B}.Release|Any CPU.Build.0 = Release|Any CPU + {FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {E54270BE-EF83-47BE-B29C-29BC89701099} = {8E8203A7-A0B9-4F48-9CD4-DE9A0D3B73FB} @@ -69,5 +75,6 @@ Global {A6565BA8-E05C-4B4E-A908-C4B86232F9AB} = {6F4E950F-E4CD-4FA4-BA3D-528F0022C03B} {BCE0E3E4-60D9-4947-9E1A-300640BE5FBF} = {6F4E950F-E4CD-4FA4-BA3D-528F0022C03B} {C9027B0B-A589-4E92-AB32-34B8961C479B} = {8E8203A7-A0B9-4F48-9CD4-DE9A0D3B73FB} + {FB79E8DE-3AA9-47E0-9EA6-627DCAFC6068} = {6F4E950F-E4CD-4FA4-BA3D-528F0022C03B} EndGlobalSection EndGlobal diff --git a/samples/LocalEnvSecretStoreSample/LocalEnvSecretStoreSample.csproj b/samples/LocalEnvSecretStoreSample/LocalEnvSecretStoreSample.csproj new file mode 100644 index 0000000..928a1c5 --- /dev/null +++ b/samples/LocalEnvSecretStoreSample/LocalEnvSecretStoreSample.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/samples/LocalEnvSecretStoreSample/Program.cs b/samples/LocalEnvSecretStoreSample/Program.cs new file mode 100644 index 0000000..931645d --- /dev/null +++ b/samples/LocalEnvSecretStoreSample/Program.cs @@ -0,0 +1,18 @@ +using Dapr.PluggableComponents; +using LocalEnvSecretStoreSample.Services; + +var app = DaprPluggableComponentsApplication.Create(); + +app.RegisterService( + "local.env-pluggable", + serviceBuilder => + { + serviceBuilder.RegisterSecretStore( + context => + { + Console.WriteLine("Creating secret store for instance '{0}' on socket '{1}'...", context.InstanceId, context.SocketPath); + return new LocalEnvSecretStore(context.ServiceProvider.GetRequiredService>()); + }); + }); + +app.Run(); diff --git a/samples/LocalEnvSecretStoreSample/Services/LocalEnvSecretStore.cs b/samples/LocalEnvSecretStoreSample/Services/LocalEnvSecretStore.cs new file mode 100644 index 0000000..eba5788 --- /dev/null +++ b/samples/LocalEnvSecretStoreSample/Services/LocalEnvSecretStore.cs @@ -0,0 +1,74 @@ +using System.Collections; +using Dapr.PluggableComponents.Components; +using Dapr.PluggableComponents.Components.SecretStore; + +namespace LocalEnvSecretStoreSample.Services; + +internal sealed class LocalEnvSecretStore : ISecretStore +{ + private readonly ILogger logger; + + public LocalEnvSecretStore(ILogger logger) + { + this.logger = logger; + } + + #region ISecretStore Members + + public Task GetAsync(SecretStoreGetRequest request, CancellationToken cancellationToken = default) + { + this.logger.LogInformation("Get request for secret {key}", request.Key); + + return Task.FromResult( + new SecretStoreGetResponse + { + Secrets = new Dictionary + { + { request.Key, Environment.GetEnvironmentVariable(request.Key) ?? String.Empty } + } + }); + } + + public Task BulkGetAsync(SecretStoreBulkGetRequest request, CancellationToken cancellationToken = default) + { + this.logger.LogInformation("Get request for all secrets"); + + return Task.FromResult( + new SecretStoreBulkGetResponse + { + Keys = + Environment + .GetEnvironmentVariables() + .ToDictionary() + .ToDictionary( + kvp => kvp.Key, + kvp => new SecretStoreGetResponse + { + Secrets = new Dictionary + { + { kvp.Key, kvp.Value ?? String.Empty } + } + }) + }); + } + + public Task InitAsync(MetadataRequest request, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + #endregion +} + +internal static class DictionaryExtensions +{ + public static IEnumerable> ToDictionary(this IDictionary dictionary) + { + var enumerator = dictionary.GetEnumerator(); + + while (enumerator.MoveNext()) + { + yield return new KeyValuePair((TKey)enumerator.Key, (TValue?)enumerator.Value); + } + } +} diff --git a/samples/LocalEnvSecretStoreSample/appsettings.Development.json b/samples/LocalEnvSecretStoreSample/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/samples/LocalEnvSecretStoreSample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/LocalEnvSecretStoreSample/appsettings.json b/samples/LocalEnvSecretStoreSample/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/samples/LocalEnvSecretStoreSample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Dapr.PluggableComponents.AspNetCore/DaprPluggableComponentsServiceBuilder.cs b/src/Dapr.PluggableComponents.AspNetCore/DaprPluggableComponentsServiceBuilder.cs index 0d2bdbb..7e524e2 100644 --- a/src/Dapr.PluggableComponents.AspNetCore/DaprPluggableComponentsServiceBuilder.cs +++ b/src/Dapr.PluggableComponents.AspNetCore/DaprPluggableComponentsServiceBuilder.cs @@ -14,6 +14,7 @@ using Dapr.PluggableComponents.Adaptors; using Dapr.PluggableComponents.Components.Bindings; using Dapr.PluggableComponents.Components.PubSub; +using Dapr.PluggableComponents.Components.SecretStore; using Dapr.PluggableComponents.Components.StateStore; namespace Dapr.PluggableComponents; @@ -181,6 +182,45 @@ public DaprPluggableComponentsServiceBuilder RegisterStateStore(Com #endregion + #region Secret Store Registration + + /// + /// Registers a singleton secret store with this service. + /// + /// The type of secret store to register. + /// The current instance. + /// + /// A single instance of the secret store will be created to service all configured Dapr components. + /// + /// Only a single secret store type can be associated with a given service. + /// + public DaprPluggableComponentsServiceBuilder RegisterSecretStore() where TSecretStore : class, ISecretStore + { + this.AddComponent(); + return this; + } + + /// + /// Registers a secret store with this service. + /// + /// The type of secret store to register. + /// A factory method called when creating new secret store instances. + /// The current instance. + /// + /// The factory method will be called once for each configured Dapr component; the returned instance will be + /// associated with that Dapr component and methods invoked when the component receives requests. + /// + /// Only a single secret store type can be associated with a given service. + /// + public DaprPluggableComponentsServiceBuilder RegisterSecretStore(ComponentProviderDelegate secretStoreFactory) + where TSecretStore : class, ISecretStore + { + this.AddComponent(secretStoreFactory); + return this; + } + + #endregion + private void AddComponent() where TComponentType : class where TComponentImpl : class, TComponentType @@ -236,4 +276,9 @@ private void AddRelatedStateStoreServices() where TStateStore : cla this.AddRelatedService(); } } + + private void AddRelatedSecretStoreServices() where TSecretStore : class + { + this.AddRelatedService(); + } } diff --git a/src/Dapr.PluggableComponents.Protos/Dapr.PluggableComponents.Protos.csproj b/src/Dapr.PluggableComponents.Protos/Dapr.PluggableComponents.Protos.csproj index 6986646..9b68e40 100644 --- a/src/Dapr.PluggableComponents.Protos/Dapr.PluggableComponents.Protos.csproj +++ b/src/Dapr.PluggableComponents.Protos/Dapr.PluggableComponents.Protos.csproj @@ -1,8 +1,8 @@ - + v1 - v1.11.0 + v1.14.4 https://raw.githubusercontent.com/dapr/dapr/$(ProtosTag)/dapr/proto/components/$(ProtosVersion) $(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\Protos $(ProtosRootDir)\dapr\proto\components\$(ProtosVersion) @@ -13,6 +13,7 @@ + @@ -36,12 +37,8 @@ - - + + - + \ No newline at end of file diff --git a/src/Dapr.PluggableComponents.Tests/Adaptors/AdaptorFixture.cs b/src/Dapr.PluggableComponents.Tests/Adaptors/AdaptorFixture.cs index aa516d8..f8bcd3b 100644 --- a/src/Dapr.PluggableComponents.Tests/Adaptors/AdaptorFixture.cs +++ b/src/Dapr.PluggableComponents.Tests/Adaptors/AdaptorFixture.cs @@ -14,6 +14,7 @@ using Dapr.PluggableComponents.Components; using Dapr.PluggableComponents.Components.Bindings; using Dapr.PluggableComponents.Components.PubSub; +using Dapr.PluggableComponents.Components.SecretStore; using Dapr.PluggableComponents.Components.StateStore; using Grpc.Core; using Microsoft.Extensions.Logging; @@ -75,6 +76,11 @@ public static AdaptorFixture CreateStateStore(IS return new AdaptorFixture((logger, componentProvider) => new StateStoreAdaptor(logger, componentProvider), mockComponent); } + public static AdaptorFixture CreateSecretStore(ISecretStore? mockComponent = null) + { + return new AdaptorFixture((logger, componentProvider) => new SecretStoreAdaptor(logger, componentProvider), mockComponent); + } + public static async Task TestInitAsync( Func> adaptorFactory, Func, Client.Autogen.Grpc.v1.MetadataRequest, Task> initCall) diff --git a/src/Dapr.PluggableComponents.Tests/Adaptors/SecretStoreAdaptorTests.cs b/src/Dapr.PluggableComponents.Tests/Adaptors/SecretStoreAdaptorTests.cs new file mode 100644 index 0000000..34360d1 --- /dev/null +++ b/src/Dapr.PluggableComponents.Tests/Adaptors/SecretStoreAdaptorTests.cs @@ -0,0 +1,137 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Client.Autogen.Grpc.v1; +using Dapr.PluggableComponents.Adaptors; +using Dapr.PluggableComponents.Components.SecretStore; +using NSubstitute; +using Xunit; + +namespace Dapr.PluggableComponents.Tests.Adaptors; + +public sealed class SecretStoreAdaptorTests +{ + [Fact] + public Task InitTests() + { + return AdaptorFixture.TestInitAsync( + () => AdaptorFixture.CreateSecretStore(), + (fixture, metadataRequest) => fixture.Adaptor.Init(new Proto.Components.V1.SecretStoreInitRequest { Metadata = metadataRequest }, fixture.Context)); + } + + [Fact] + public Task PingTests() + { + return AdaptorFixture.TestPingAsync( + AdaptorFixture.CreateSecretStore, + fixture => fixture.Adaptor.Ping(new PingRequest(), fixture.Context)); + } + + [Fact] + public async Task GetSecret() + { + using var fixture = AdaptorFixture.CreateSecretStore(Substitute.For()); + + string key = "key"; + + string key1 = "key1"; + string key2 = "key2"; + + string value1 = "value1"; + string value2 = "value2"; + + fixture.MockComponent + .GetAsync(Arg.Any(), Arg.Any()) + .Returns( + new SecretStoreGetResponse + { + Secrets = new Dictionary + { + { key1, value1 }, + { key2, value2 } + } + }); + + var getRequest = new Proto.Components.V1.GetSecretRequest(); + + getRequest.Key = key; + + var response = await fixture.Adaptor.Get( + getRequest, + fixture.Context); + + Assert.Contains(response.Data, item => item.Key == key1 && item.Value == value1); + Assert.Contains(response.Data, item => item.Key == key2 && item.Value == value2); + + await fixture + .MockComponent + .Received(1) + .GetAsync(Arg.Is( + requests => requests.Key == key), + Arg.Is(cancellationToken => cancellationToken == fixture.Context.CancellationToken)); + } + + [Fact] + public async Task GetBulkSecrets() + { + using var fixture = AdaptorFixture.CreateSecretStore(Substitute.For()); + + string key = "key"; + + string key1 = "key1"; + string key2 = "key2"; + + string value1 = "value1"; + string value2 = "value2"; + + fixture.MockComponent + .BulkGetAsync(Arg.Any(), Arg.Any()) + .Returns( + new SecretStoreBulkGetResponse + { + Keys = new Dictionary + { + { + key, + new SecretStoreGetResponse + { + Secrets = new Dictionary + { + { key1, value1 }, + { key2, value2 } + } + } + } + } + }); + + var getRequest = new Proto.Components.V1.BulkGetSecretRequest(); + + var response = await fixture.Adaptor.BulkGet( + getRequest, + fixture.Context); + + Assert.Contains(response.Data, item => item.Key == key); + + var secretResponse = response.Data[key]; + + Assert.Contains(secretResponse.Secrets, item => item.Key == key1 && item.Value == value1); + Assert.Contains(secretResponse.Secrets, item => item.Key == key2 && item.Value == value2); + + await fixture + .MockComponent + .Received(1) + .BulkGetAsync(Arg.Any(), + Arg.Is(cancellationToken => cancellationToken == fixture.Context.CancellationToken)); + } +} diff --git a/src/Dapr.PluggableComponents/Adaptors/SecretStoreAdaptor.cs b/src/Dapr.PluggableComponents/Adaptors/SecretStoreAdaptor.cs new file mode 100644 index 0000000..b4ed315 --- /dev/null +++ b/src/Dapr.PluggableComponents/Adaptors/SecretStoreAdaptor.cs @@ -0,0 +1,116 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Client.Autogen.Grpc.v1; +using Dapr.PluggableComponents.Components; +using Dapr.PluggableComponents.Components.SecretStore; +using Dapr.Proto.Components.V1; +using Grpc.Core; +using Microsoft.Extensions.Logging; +using static Dapr.Proto.Components.V1.SecretStore; + +namespace Dapr.PluggableComponents.Adaptors; + +/// +/// Represents the gRPC protocol adaptor for a state store Dapr Pluggable Component. +/// +/// +/// An instances of this class is created for every request made to the component. +/// +public class SecretStoreAdaptor : SecretStoreBase +{ + private readonly ILogger logger; + private readonly IDaprPluggableComponentProvider componentProvider; + + /// + /// Creates a new instance of the class. + /// + /// A logger used for internal purposes. + /// A means to obtain the Dapr Pluggable Component associated with this adapter instance. + /// If any parameter is null. + public SecretStoreAdaptor(ILogger logger, IDaprPluggableComponentProvider componentProvider) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.componentProvider = componentProvider ?? throw new ArgumentNullException(nameof(componentProvider)); + } + + /// + public override async Task Features(FeaturesRequest request, ServerCallContext context) + { + this.logger.LogDebug("Features request"); + + var response = new FeaturesResponse(); + + if (this.GetSecretStore(context) is IPluggableComponentFeatures features) + { + var featuresResponse = await features.GetFeaturesAsync(context.CancellationToken); + + response.Features.AddRange(featuresResponse); + } + + return response; + } + + /// + public override async Task Get(GetSecretRequest request, ServerCallContext context) + { + this.logger.LogDebug("Get request for key {key}", request.Key); + + var response = await this.GetSecretStore(context).GetAsync( + SecretStoreGetRequest.FromGetRequest(request), + context.CancellationToken); + + return SecretStoreGetResponse.ToGetResponse(response); + } + + /// + public override async Task BulkGet(BulkGetSecretRequest request, ServerCallContext context) + { + this.logger.LogDebug("Bulk get request for secret"); + + var secretStore = this.GetSecretStore(context); + var response = await secretStore.BulkGetAsync(SecretStoreBulkGetRequest.FromGetRequest(request), context.CancellationToken); + return SecretStoreBulkGetResponse.ToBulkGetResponse(response); + } + + /// + public async override Task Init(Proto.Components.V1.SecretStoreInitRequest request, ServerCallContext context) + { + this.logger.LogDebug("Init request"); + + await this.GetSecretStore(context).InitAsync( + Components.MetadataRequest.FromMetadataRequest(request.Metadata), + context.CancellationToken); + + return new SecretStoreInitResponse(); + } + + /// + public override async Task Ping(PingRequest request, ServerCallContext context) + { + this.logger.LogDebug("Ping request"); + + if (this.GetSecretStore(context) is IPluggableComponentLiveness ping) + { + await ping.PingAsync(context.CancellationToken); + } + + return new PingResponse(); + } + + private ISecretStore GetSecretStore(ServerCallContext context) + { + return this.componentProvider.GetComponent(context); + } +} + diff --git a/src/Dapr.PluggableComponents/Components/SecretStore/ISecretStore.cs b/src/Dapr.PluggableComponents/Components/SecretStore/ISecretStore.cs new file mode 100644 index 0000000..4a8ae7e --- /dev/null +++ b/src/Dapr.PluggableComponents/Components/SecretStore/ISecretStore.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.PluggableComponents.Components.SecretStore; + +/// +/// Represents a secret store Dapr Pluggable Component. +/// +public interface ISecretStore : IPluggableComponent +{ + /// + /// Called to get secret. + /// + /// Properties related to the secret to be retrieved. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation, resulting in the retrieved secret, if any. + Task GetAsync(SecretStoreGetRequest request, CancellationToken cancellationToken = default); + + /// + /// Called to get bulk secret. + /// + /// Properties related to the secret to be retrieved. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation, resulting in the retrieved secret, if any. + Task BulkGetAsync(SecretStoreBulkGetRequest request, CancellationToken cancellationToken = default); + +} diff --git a/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetRequest.cs b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetRequest.cs new file mode 100644 index 0000000..58cdb58 --- /dev/null +++ b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetRequest.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Proto.Components.V1; + +namespace Dapr.PluggableComponents.Components.SecretStore; + + +/// +/// Represents properties associated with a request to retrieve all secrets from a secret store. +/// +public sealed record SecretStoreBulkGetRequest +{ + /// + /// Gets the metadata associated with the request. + /// + public IReadOnlyDictionary Metadata { get; init; } = new Dictionary(); + + internal static SecretStoreBulkGetRequest FromGetRequest(BulkGetSecretRequest request) + { + return new SecretStoreBulkGetRequest() + { + Metadata = request.Metadata + }; + } +} diff --git a/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetResponse.cs b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetResponse.cs new file mode 100644 index 0000000..af3dd89 --- /dev/null +++ b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreBulkGetResponse.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Proto.Components.V1; + +namespace Dapr.PluggableComponents.Components.SecretStore; + +/// +/// Represents properties associated with a response to retrieving all secrets from a secret store. +/// +public sealed record SecretStoreBulkGetResponse +{ + /// + /// Gets the groups of secrets. + /// + public IReadOnlyDictionary Keys { get; init; } = new Dictionary(); + + internal static BulkGetSecretResponse ToBulkGetResponse(SecretStoreBulkGetResponse response) + { + BulkGetSecretResponse grpcResponse = new(); + + foreach (var item in response.Keys) + { + SecretResponse secretResp = new(); + + foreach (var sec in item.Value.Secrets) + { + secretResp.Secrets.Add(sec.Key, sec.Value); + } + + grpcResponse.Data.Add(item.Key, secretResp); + } + + return grpcResponse; + } +} + diff --git a/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetRequest.cs b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetRequest.cs new file mode 100644 index 0000000..f73d94a --- /dev/null +++ b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetRequest.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Proto.Components.V1; + +namespace Dapr.PluggableComponents.Components.SecretStore; + +/// +/// Represents properties associated with a request to retrieve a secret from a secret store. +/// +/// The key for the secrets that should be retrieved. +public sealed record SecretStoreGetRequest(string Key) +{ + /// + /// Gets the metadata associated with the request. + /// + public IReadOnlyDictionary Metadata { get; init; } = new Dictionary(); + + internal static SecretStoreGetRequest FromGetRequest(GetSecretRequest request) + { + return new SecretStoreGetRequest(request.Key) + { + Metadata = request.Metadata + }; + } +} + diff --git a/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetResponse.cs b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetResponse.cs new file mode 100644 index 0000000..adad4f4 --- /dev/null +++ b/src/Dapr.PluggableComponents/Components/SecretStore/SecretStoreGetResponse.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 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. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.PluggableComponents.Utilities; +using Dapr.Proto.Components.V1; + +namespace Dapr.PluggableComponents.Components.SecretStore; + +/// +/// Represents properties associated with a response to retrieving secret from a secret store. +/// +public sealed record SecretStoreGetResponse +{ + /// + /// Gets the secrets. + /// + public IReadOnlyDictionary Secrets { get; init; } = new Dictionary(); + + internal static GetSecretResponse ToGetResponse(SecretStoreGetResponse response) + { + var grpcResponse = new GetSecretResponse(); + + // NOTE: in case of not found, you should not return any error. + + if (response != null) + { + grpcResponse.Data.Add(response.Secrets); + } + + return grpcResponse; + } +}