Skip to content

Commit 8e76ad7

Browse files
gabynevadaclaude
andauthored
fix(dapr): defer parameter resolution to prevent startup failures (#959)
Fix startup failures when Dapr components reference parameters or value providers that don't have values at initialization time (e.g., user-provided secrets via dashboard). Previously, secret parameters were resolved immediately during the lifecycle hook execution, causing failures when parameter values weren't available yet. Now, value providers are stored and resolved later during environment setup when all parameter values are available. Changes: - Store secret value providers instead of resolving them immediately - Defer resolution to EnvironmentCallbackAnnotation execution - Add tests verifying parameters are stored as IValueProvider instances 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent e933788 commit 8e76ad7

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

src/CommunityToolkit.Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
8181

8282
var componentReferenceAnnotations = daprSidecar.Annotations.OfType<DaprComponentReferenceAnnotation>();
8383

84-
var secrets = new Dictionary<string, string>();
84+
var secretValueProviders = new Dictionary<string, IValueProvider>();
8585
var endpointEnvironmentVars = new Dictionary<string, IValueProvider>();
8686
var hasValueProviders = false;
8787

@@ -97,17 +97,17 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
9797
}
9898
}
9999

100-
// Check if there are any secrets that need to be added to the secret store
100+
// Collect secret value providers to be resolved later when environment is set up
101101
if (componentReferenceAnnotation.Component.TryGetAnnotationsOfType<DaprComponentSecretAnnotation>(out var secretAnnotations))
102102
{
103103
foreach (var secretAnnotation in secretAnnotations)
104104
{
105-
secrets[secretAnnotation.Key] = (await secretAnnotation.Value.GetValueAsync(cancellationToken))!;
105+
secretValueProviders[secretAnnotation.Key] = secretAnnotation.Value;
106106
}
107107
}
108108

109109
// If we have any secrets or value providers, ensure the secret store path is added
110-
if ((secrets.Count > 0 || hasValueProviders) && onDemandResourcesPaths.TryGetValue("secretstore", out var secretStorePath))
110+
if ((secretValueProviders.Count > 0 || hasValueProviders) && onDemandResourcesPaths.TryGetValue("secretstore", out var secretStorePath))
111111
{
112112
string onDemandResourcesPathDirectory = Path.GetDirectoryName(secretStorePath)!;
113113
if (onDemandResourcesPathDirectory is not null)
@@ -136,13 +136,15 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
136136
}
137137
}
138138

139-
if (secrets.Count > 0 || endpointEnvironmentVars.Count > 0)
139+
if (secretValueProviders.Count > 0 || endpointEnvironmentVars.Count > 0)
140140
{
141141
daprSidecar.Annotations.Add(new EnvironmentCallbackAnnotation(async context =>
142142
{
143-
foreach (var secret in secrets)
143+
// Resolve secrets when environment is being set up (when parameter values are available)
144+
foreach (var (secretKey, secretValueProvider) in secretValueProviders)
144145
{
145-
context.EnvironmentVariables.TryAdd(secret.Key, secret.Value);
146+
var secretValue = await secretValueProvider.GetValueAsync(context.CancellationToken);
147+
context.EnvironmentVariables.TryAdd(secretKey, secretValue ?? string.Empty);
146148
}
147149

148150
// Add value provider references

tests/CommunityToolkit.Aspire.Hosting.Dapr.Tests/ResourceBuilderExtensionsTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Aspire.Hosting;
22
using Aspire.Hosting.ApplicationModel;
33
using Aspire.Hosting.Utils;
4+
using Microsoft.Extensions.DependencyInjection;
45
using System.Runtime.CompilerServices;
56
using Xunit;
67

@@ -146,6 +147,61 @@ public void WithReferenceOnSidecarCorrectlyAttachesDaprComponents()
146147
Assert.DoesNotContain(project.Resource.Annotations, a => a is DaprComponentReferenceAnnotation);
147148
}
148149

150+
[Fact]
151+
public void SecretParameterValueStoredAsProviderNotResolvedValue()
152+
{
153+
// Arrange
154+
// When a secret parameter is defined without an explicit value,
155+
// it should be stored as an IValueProvider, not resolved immediately
156+
var builder = DistributedApplication.CreateBuilder();
157+
158+
// Create a parameter without an explicit value (simulating user input via dashboard)
159+
var secretParam = builder.AddParameter("secretPassword", secret: true);
160+
161+
// Add a Dapr component that uses the secret parameter
162+
var pubsub = builder.AddDaprPubSub("pubsub")
163+
.WithMetadata("password", secretParam.Resource);
164+
165+
// Act - Get the component resource and verify annotations
166+
var componentResource = Assert.Single(builder.Resources.OfType<DaprComponentResource>());
167+
168+
// Assert - Verify that secret annotation exists with the IValueProvider
169+
Assert.True(componentResource.TryGetAnnotationsOfType<DaprComponentSecretAnnotation>(out var secretAnnotations));
170+
var secretAnnotation = Assert.Single(secretAnnotations);
171+
172+
// The key point: the Value should be an IValueProvider (ParameterResource), not a resolved string
173+
Assert.NotNull(secretAnnotation.Value);
174+
Assert.IsAssignableFrom<IValueProvider>(secretAnnotation.Value);
175+
176+
// Verify the key contains the parameter name
177+
Assert.Equal("secretPassword", secretAnnotation.Key);
178+
}
179+
180+
[Fact]
181+
public void ValueProviderStoredInComponentAnnotation()
182+
{
183+
// Arrange
184+
// This test verifies that value providers (like endpoint references)
185+
// are stored in annotations, not resolved immediately
186+
var builder = DistributedApplication.CreateBuilder();
187+
188+
var redis = builder.AddRedis("redis");
189+
var pubsub = builder.AddDaprPubSub("pubsub")
190+
.WithMetadata("redisHost", redis.GetEndpoint("tcp"));
191+
192+
// Act - Get the component and verify it has the value provider annotation
193+
var componentResource = Assert.Single(builder.Resources.OfType<DaprComponentResource>());
194+
195+
// Assert - Verify that the component has the value provider annotation with IValueProvider
196+
Assert.True(componentResource.TryGetAnnotationsOfType<DaprComponentValueProviderAnnotation>(out var valueProviderAnnotations));
197+
var annotation = Assert.Single(valueProviderAnnotations);
198+
199+
// Verify that the ValueProvider is stored, not a resolved value
200+
Assert.NotNull(annotation.ValueProvider);
201+
Assert.IsAssignableFrom<IValueProvider>(annotation.ValueProvider);
202+
Assert.Equal("redisHost", annotation.MetadataName);
203+
}
204+
149205
// Test helper class that implements IValueProvider
150206
private class TestValueProvider : global::Aspire.Hosting.ApplicationModel.IValueProvider
151207
{

0 commit comments

Comments
 (0)