Skip to content
Merged
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 @@ -81,7 +81,7 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken

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

var secrets = new Dictionary<string, string>();
var secretValueProviders = new Dictionary<string, IValueProvider>();
var endpointEnvironmentVars = new Dictionary<string, IValueProvider>();
var hasValueProviders = false;

Expand All @@ -97,17 +97,17 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
}
}

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

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

if (secrets.Count > 0 || endpointEnvironmentVars.Count > 0)
if (secretValueProviders.Count > 0 || endpointEnvironmentVars.Count > 0)
{
daprSidecar.Annotations.Add(new EnvironmentCallbackAnnotation(async context =>
{
foreach (var secret in secrets)
// Resolve secrets when environment is being set up (when parameter values are available)
foreach (var (secretKey, secretValueProvider) in secretValueProviders)
{
context.EnvironmentVariables.TryAdd(secret.Key, secret.Value);
var secretValue = await secretValueProvider.GetValueAsync(context.CancellationToken);
context.EnvironmentVariables.TryAdd(secretKey, secretValue ?? string.Empty);
}

// Add value provider references
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
using System.Runtime.CompilerServices;
using Xunit;

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

[Fact]
public void SecretParameterValueStoredAsProviderNotResolvedValue()
{
// Arrange
// When a secret parameter is defined without an explicit value,
// it should be stored as an IValueProvider, not resolved immediately
var builder = DistributedApplication.CreateBuilder();

// Create a parameter without an explicit value (simulating user input via dashboard)
var secretParam = builder.AddParameter("secretPassword", secret: true);

// Add a Dapr component that uses the secret parameter
var pubsub = builder.AddDaprPubSub("pubsub")
.WithMetadata("password", secretParam.Resource);

// Act - Get the component resource and verify annotations
var componentResource = Assert.Single(builder.Resources.OfType<DaprComponentResource>());

// Assert - Verify that secret annotation exists with the IValueProvider
Assert.True(componentResource.TryGetAnnotationsOfType<DaprComponentSecretAnnotation>(out var secretAnnotations));
var secretAnnotation = Assert.Single(secretAnnotations);

// The key point: the Value should be an IValueProvider (ParameterResource), not a resolved string
Assert.NotNull(secretAnnotation.Value);
Assert.IsAssignableFrom<IValueProvider>(secretAnnotation.Value);

// Verify the key contains the parameter name
Assert.Equal("secretPassword", secretAnnotation.Key);
}

[Fact]
public void ValueProviderStoredInComponentAnnotation()
{
// Arrange
// This test verifies that value providers (like endpoint references)
// are stored in annotations, not resolved immediately
var builder = DistributedApplication.CreateBuilder();

var redis = builder.AddRedis("redis");
var pubsub = builder.AddDaprPubSub("pubsub")
.WithMetadata("redisHost", redis.GetEndpoint("tcp"));

// Act - Get the component and verify it has the value provider annotation
var componentResource = Assert.Single(builder.Resources.OfType<DaprComponentResource>());

// Assert - Verify that the component has the value provider annotation with IValueProvider
Assert.True(componentResource.TryGetAnnotationsOfType<DaprComponentValueProviderAnnotation>(out var valueProviderAnnotations));
var annotation = Assert.Single(valueProviderAnnotations);

// Verify that the ValueProvider is stored, not a resolved value
Assert.NotNull(annotation.ValueProvider);
Assert.IsAssignableFrom<IValueProvider>(annotation.ValueProvider);
Assert.Equal("redisHost", annotation.MetadataName);
}

// Test helper class that implements IValueProvider
private class TestValueProvider : global::Aspire.Hosting.ApplicationModel.IValueProvider
{
Expand Down
Loading