Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ public static class DaprConfigurationStoreExtension
/// <param name="client">The <see cref="DaprClient"/> used for the request.</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="metadata">Optional metadata sent to the configuration store.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Configuration is loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprConfigurationStore(
this IConfigurationBuilder configurationBuilder,
string store,
IReadOnlyList<string> keys,
DaprClient client,
TimeSpan sidecarWaitTimeout,
IReadOnlyDictionary<string, string>? metadata = default)
IReadOnlyDictionary<string, string>? metadata = default,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(keys, nameof(keys));
Expand All @@ -54,7 +56,8 @@ public static IConfigurationBuilder AddDaprConfigurationStore(
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout,
IsStreaming = false,
Metadata = metadata
Metadata = metadata,
IsOptional = optional
});

return configurationBuilder;
Expand All @@ -71,14 +74,16 @@ public static IConfigurationBuilder AddDaprConfigurationStore(
/// <param name="client">The <see cref="DaprClient"/> used for the request.</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="metadata">Optional metadata sent to the configuration store.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Configuration is loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddStreamingDaprConfigurationStore(
this IConfigurationBuilder configurationBuilder,
string store,
IReadOnlyList<string> keys,
DaprClient client,
TimeSpan sidecarWaitTimeout,
IReadOnlyDictionary<string, string>? metadata = default)
IReadOnlyDictionary<string, string>? metadata = default,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(keys, nameof(keys));
Expand All @@ -91,7 +96,8 @@ public static IConfigurationBuilder AddStreamingDaprConfigurationStore(
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout,
IsStreaming = true,
Metadata = metadata
Metadata = metadata,
IsOptional = optional
});

return configurationBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ namespace Dapr.Extensions.Configuration;
/// </summary>
internal class DaprConfigurationStoreProvider : ConfigurationProvider, IDisposable
{
private string store;
private IReadOnlyList<string> keys;
private DaprClient daprClient;
private TimeSpan sidecarWaitTimeout;
private bool isStreaming;
private IReadOnlyDictionary<string, string>? metadata;
private CancellationTokenSource cts;
private readonly string store;
private readonly IReadOnlyList<string> keys;
private readonly DaprClient daprClient;
private readonly TimeSpan sidecarWaitTimeout;
private readonly bool isStreaming;
private readonly bool isOptional;
private readonly IReadOnlyDictionary<string, string>? metadata;
private readonly CancellationTokenSource cts;
private Task subscribeTask = Task.CompletedTask;

/// <summary>
Expand All @@ -44,30 +45,83 @@ internal class DaprConfigurationStoreProvider : ConfigurationProvider, IDisposab
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="isStreaming">Determines if the source is streaming or not.</param>
/// <param name="metadata">Optional metadata sent to the configuration store.</param>
/// <param name="isOptional">When true, does not block startup waiting for the sidecar.</param>
public DaprConfigurationStoreProvider(
string store,
IReadOnlyList<string> keys,
DaprClient daprClient,
TimeSpan sidecarWaitTimeout,
bool isStreaming = false,
IReadOnlyDictionary<string, string>? metadata = default)
IReadOnlyDictionary<string, string>? metadata = default,
bool isOptional = false)
{
this.store = store;
this.keys = keys;
this.daprClient = daprClient;
this.sidecarWaitTimeout = sidecarWaitTimeout;
this.isStreaming = isStreaming;
this.isOptional = isOptional;
this.metadata = metadata ?? new Dictionary<string, string>();
this.cts = new CancellationTokenSource();
}

public void Dispose()
{
cts.Cancel();
cts.Dispose();
}

/// <inheritdoc/>
public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
public override void Load()
{
if (isOptional)
{
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
_ = Task.Run(() => LoadInBackgroundAsync());
}
else
{
LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
}

private async Task LoadInBackgroundAsync()
{
while (!cts.Token.IsCancellationRequested)
{
try
{
using var tokenSource = new CancellationTokenSource(sidecarWaitTimeout);
using var linked = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token, cts.Token);
await daprClient.WaitForSidecarAsync(linked.Token);

await FetchDataAsync();
OnReload();
return;
}
catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
{
return;
}
catch (OperationCanceledException)
{
// Sidecar wait timed out — retry after delay.
}
catch (DaprException)
{
// Transient Dapr error — retry after delay.
}

try
{
await Task.Delay(sidecarWaitTimeout, cts.Token);
}
catch (OperationCanceledException)
{
return;
}
}
}

private async Task LoadAsync()
{
Expand All @@ -77,6 +131,11 @@ private async Task LoadAsync()
await daprClient.WaitForSidecarAsync(tokenSource.Token);
}

await FetchDataAsync();
}

private async Task FetchDataAsync()
{
if (isStreaming)
{
subscribeTask = Task.Run(async () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,16 @@ public class DaprConfigurationStoreSource : IConfigurationSource
/// </summary>
public IReadOnlyDictionary<string, string>? Metadata { get; set; } = default;

/// <summary>
/// Gets or sets a value indicating whether this configuration source is optional.
/// When <c>true</c>, the provider will not block startup waiting for the Dapr sidecar and will
/// instead load configuration in the background once the sidecar becomes available.
/// </summary>
public bool IsOptional { get; set; }

/// <inheritdoc/>
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new DaprConfigurationStoreProvider(Store, Keys, Client, SidecarWaitTimeout, IsStreaming, Metadata);
return new DaprConfigurationStoreProvider(Store, Keys, Client, SidecarWaitTimeout, IsStreaming, Metadata, IsOptional);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ public static class DaprSecretStoreConfigurationExtensions
/// <param name="store">Dapr secret store name.</param>
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">The Dapr client</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client)
DaprClient client,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(secretDescriptors, nameof(secretDescriptors));
Expand All @@ -47,7 +49,8 @@ public static IConfigurationBuilder AddDaprSecretStore(
{
Store = store,
SecretDescriptors = secretDescriptors,
Client = client
Client = client,
IsOptional = optional
});

return configurationBuilder;
Expand All @@ -61,13 +64,15 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">The Dapr client.</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client,
TimeSpan sidecarWaitTimeout)
TimeSpan sidecarWaitTimeout,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(secretDescriptors, nameof(secretDescriptors));
Expand All @@ -78,7 +83,8 @@ public static IConfigurationBuilder AddDaprSecretStore(
Store = store,
SecretDescriptors = secretDescriptors,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
SidecarWaitTimeout = sidecarWaitTimeout,
IsOptional = optional
});

return configurationBuilder;
Expand All @@ -89,14 +95,16 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">The Dapr client</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
IReadOnlyDictionary<string, string>? metadata = null)
IReadOnlyDictionary<string, string>? metadata = null,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
Expand All @@ -105,7 +113,8 @@ public static IConfigurationBuilder AddDaprSecretStore(
{
Store = store,
Metadata = metadata,
Client = client
Client = client,
IsOptional = optional
});

return configurationBuilder;
Expand All @@ -116,16 +125,18 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">The Dapr client</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
TimeSpan sidecarWaitTimeout,
IReadOnlyDictionary<string, string>? metadata = null)
IReadOnlyDictionary<string, string>? metadata = null,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
Expand All @@ -135,7 +146,8 @@ public static IConfigurationBuilder AddDaprSecretStore(
Store = store,
Metadata = metadata,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
SidecarWaitTimeout = sidecarWaitTimeout,
IsOptional = optional
});

return configurationBuilder;
Expand All @@ -146,22 +158,25 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="client">The Dapr client</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
IEnumerable<string>? keyDelimiters)
IEnumerable<string>? keyDelimiters,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));

var source = new DaprSecretStoreConfigurationSource
{
Store = store,
Client = client
Client = client,
IsOptional = optional
};

if (keyDelimiters != null)
Expand All @@ -179,16 +194,18 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="client">The Dapr client</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <param name="optional">When true, does not block startup waiting for the sidecar. Secrets are loaded in the background once the sidecar becomes available.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
IEnumerable<string>? keyDelimiters,
TimeSpan sidecarWaitTimeout)
TimeSpan sidecarWaitTimeout,
bool optional = false)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
Expand All @@ -197,7 +214,8 @@ public static IConfigurationBuilder AddDaprSecretStore(
{
Store = store,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
SidecarWaitTimeout = sidecarWaitTimeout,
IsOptional = optional
};

if (keyDelimiters != null)
Expand All @@ -218,4 +236,4 @@ public static IConfigurationBuilder AddDaprSecretStore(
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(this IConfigurationBuilder configurationBuilder, Action<DaprSecretStoreConfigurationSource> configureSource)
=> configurationBuilder.Add(configureSource);
}
}
Loading
Loading