Skip to content

feat/secretstore #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

pravinpushkar
Copy link

@pravinpushkar pravinpushkar commented Aug 18, 2023

Description

Please explain the changes you've made

Issue reference

We strive to have all PR being opened based on an issue, where the problem or feature have been discussed prior to implementation.

Please reference the issue this PR will close: #43

Checklist

Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • Code compiles correctly
  • Created/updated tests
  • Extended the documentation

Signed-off-by: Pravin Pushkar <[email protected]>
Signed-off-by: Pravin Pushkar <[email protected]>
Signed-off-by: Pravin Pushkar <[email protected]>
@pravinpushkar pravinpushkar changed the title Feat/secretstore feat/secretstore Aug 18, 2023

public sealed record SecretStoreBulkGetResponse
{
//public IReadOnlyDictionary<string, Dictionary<string, string>> responseData = new Dictionary<string, Dictionary<string, string>>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IReadOnlyDictionary<string, IReadonlyDictionary<string, string>> would be the most raw way to expose the bulk data, but I'm not sure if that'd be the best way, as it doesn't easily allow for changes to the API in the future. (E.g. suppose the bulk request returns both values and other metadata for each individual secret). It'd probably be better to expose something like IReadOnlyDictionary<string, GetSecretResponse> Data (which is actually closer to the .proto definition anyway).

I think it's fine to try to simplify some of the .proto APIs where it makes sense (e.g. where it contains unnecessary layers), but we also want to walk that fine line of being true to the .proto as well so that the .NET APIs are still conceptually similar to the other SDKs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use the SecretResponse() constructor from the proto defintion.

/// <param name="request">Properties related to the secret to be retrieved.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the asynchronous operation, resulting in the retrieved secret, if any.</returns>
Task<SecretStoreGetResponse?> GetAsync(SecretStoreGetRequest request, CancellationToken cancellationToken = default);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the Dapr semantics for getting a secret key that doesn't exist? Is that an error or should a "does not exist" value be returned? In the case of state stores, we return null in that case.

Copy link
Author

@pravinpushkar pravinpushkar Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of env secret store we return empty value like {somsecret: ""}. I think it can depend on specific implementation.

@@ -22,7 +23,7 @@
</PropertyGroup>

<ItemGroup>
<Protobuf Include="@(Protos->'$(ProtosComponentsDir)/%(Identity)')" ProtoRoot="$(ProtosRootDir)" GrpcServices="Client,Server" />
<Protobuf Include="@(Protos-&gt;'$(ProtosComponentsDir)/%(Identity)')" ProtoRoot="$(ProtosRootDir)" GrpcServices="Client,Server" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what happened here...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the XML editor is getting too aggressive about escaping characters; -> is what it should be.

Signed-off-by: Pravin Pushkar <[email protected]>
Signed-off-by: Pravin Pushkar <[email protected]>
@pravinpushkar pravinpushkar marked this pull request as ready for review August 22, 2023 17:21
{
public IReadOnlyDictionary<string, string> Data = new Dictionary<string, string>();

internal static BulkGetSecretResponse ToBulkGetResponse(SecretStoreBulkGetResponse? response)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the nullability (?) from the response argument? We would always expect the implementation to give us one, right?


<PropertyGroup>
<ProtosVersion>v1</ProtosVersion>
<ProtosTag>v1.11.0</ProtosTag>
<ProtosTag>master</ProtosTag>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to defer committing this PR until (at least) a v1.12.0 tag exists in dapr/dapr, as we don't want builds picking up whatever happens to be in the master branch at the time.

data = "";
}
Dictionary<string, string> resp = new Dictionary<string, string>();
resp.Add(request.SecretName, data);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C# allows Add() methods to be inlined:

var resp = new Dictionary<string, string>
{
  { request.SecretName, data }
}

}
Dictionary<string, string> resp = new Dictionary<string, string>();
resp.Add(request.SecretName, data);
response = new SecretStoreGetResponse
Copy link
Collaborator

@philliphoff philliphoff Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: var response = new SecretStoreGetResponse allows you to eliminate the nullable response created above.

this.logger.LogInformation("Get request for secret {key}", request.SecretName);

SecretStoreGetResponse? response = null;
string data = Environment.GetEnvironmentVariable(request.SecretName);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should be string? data = ...?

I'd expect a build warning about nullability otherwise (as GetEnvironmentVariable() returns string?).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetEnvironmentVariable returns string when environment variable is not found or set.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetEnvironmentVariable() returns string? (a potentially null string). An alternative would be:

string data = Environment.GetEnvironmentVariable() ?? String.Empty;

...which assigns "" only if the return value is null.

Dictionary<string, string> resp = new Dictionary<string, string>();
foreach (string key in Environment.GetEnvironmentVariables().Keys)
{
resp.Add(key, Environment.GetEnvironmentVariable(key));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetEnvironmentVariables() returns a dictionary of both names and values; there shouldn't be a reason to later call GetEnvironmentVariable() for each variable name.

/// <param name="request">Properties related to the secret to be retrieved.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the asynchronous operation, resulting in the retrieved secret, if any.</returns>
Task<SecretStoreGetResponse> GetAsync(SecretStoreGetRequest request, CancellationToken cancellationToken = default);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to add <remarks> that describe the expected behavior for secrets that don't exist as well as differences between stores that support multiple values per secret and those that don't.

Copy link
Collaborator

@philliphoff philliphoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally on the right track. There's a question for how to represent bulk secret responses, and it'd be helpful to include unit tests for the new components and data types. (I just committed a change that switches tests to NSubstitute from Moq, so be sure to merge in the main branch.)

Signed-off-by: Pravin Pushkar <[email protected]>
foreach (var sec in item.Value.Data)
{
secretResp.Secrets.Add(sec.Key, sec.Value);
grpcResponse.Data.Add(sec.Key, secretResp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only happen once, right, outside the foreach and for the item.Key key?


// NOTE: in case of not found, you should not return any error.

if (response != null)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response should be non-null (by nullability contract). While the implementor could still technically return null at runtime, I'd say that's their problem and not something to explicitly account for.


// NOTE: in case of not found, you should not return any error.

if (response != null)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response should be non-null (by nullability contract). While the implementor could still technically return null at runtime, I'd say that's their problem and not something to explicitly account for.

@philliphoff
Copy link
Collaborator

@pravinpushkar With 1.12 now out the door, we should be able to pick this PR back up, right?

@pravinpushkar
Copy link
Author

@pravinpushkar With 1.12 now out the door, we should be able to pick this PR back up, right?

@philliphoff Yes, I will plan to complete this in coming weeks.

@philliphoff philliphoff requested a review from WhitWaldo October 22, 2024 17:17
Copy link
Collaborator

@WhitWaldo WhitWaldo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several changes. Mostly around naming consistency questions, a potentially unnecessary type and some assignment/nullability nits.

Signed-off-by: Phillip Hoff <[email protected]>
Signed-off-by: Phillip Hoff <[email protected]>
@WhitWaldo
Copy link
Collaborator

@philliphoff Good to merge?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for pluggable secret stores
3 participants