dotnet / aspire

Tools, templates, and packages to accelerate building observable, production-ready apps
https://learn.microsoft.com/dotnet/aspire
MIT License
3.95k stars 485 forks source link

Using WithDaprSidecar it's not possible to set WithEnvironment #5089

Open mzj opened 4 months ago

mzj commented 4 months ago

Is there an existing issue for this?

Describe the bug

Following https://github.com/dotnet/aspire/pull/1604, when I try to call WithEnvironment(...), I'm getting the following error message:

Error (active) CS0311 The type 'Aspire.Hosting.Dapr.IDaprSidecarResource' cannot be used as type parameter 'T' in the generic type or method 'ResourceBuilderExtensions.WithEnvironment(IResourceBuilder, string, string?)'. There is no implicit reference conversion from 'Aspire.Hosting.Dapr.IDaprSidecarResource' to 'Aspire.Hosting.ApplicationModel.IResourceWithEnvironment'.

I am using .NET 8 with the following Aspire/Dapr packages:

Expected Behavior

I expected the method call to work with the final result of adding the environment variable (APP_API_TOKEN) to the Dapr sidecar.

Code in question:

builder.AddProject<Projects.DaprServiceA>("servicea")
       .WithDaprSidecar(
                 sidecarBuilder =>
                 {
                     sidecarBuilder
                         .WithOptions(new DaprSidecarOptions { AppId = "identity-api-dapr" })
                         .WithEnvironment("APP_API_TOKEN", "somesecrettoken");
                 }
       )

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version info

.NET SDK: Version: 8.0.302 Commit: ef14e02af8 Workload version: 8.0.300-manifests.5273bb1c MSBuild version: 17.10.4+10fbfbf2e

Anything else?

No response

bit365 commented 4 months ago

I also encountered the same problem!

It is hoped that WithEnvironment and WithReference methods will be provided on sidecarBuilder. The former provides environment variables to Sidecar, and the latter can inject the endpoint of any resource (for example, rabbitmq connection string) as an environment variable. In this way, Dapr's configuration file can associate the corresponding configuration with the environment variable. For example: var rabbitmq = builder.AddRabbitMQ("rabbitmq") gets a rabbitmq resource. The connection string of the resource needs to be used in Dapr to configure the PubSub component. Dapr itself provides retrieval of configuration items from environment variables.For some reason, the preview version a long time ago was fine, but now the official version is not working.

The previous solution was here, but it is no longer valid.

https://github.com/dotnet/aspire/pull/1604

bit365 commented 4 months ago

This way, while it is possible to set environment variables for the sidecar, it is not very elegant and beautiful.

builder.AddProject<Projects.DaprServiceA>("servicea")
       .WithDaprSidecar(
                 sidecarBuilder =>
                 {
                     sidecarBuilder
                         .WithOptions(new DaprSidecarOptions { AppId = "identity-api-dapr" })
                         .WithAnnotation("APP_API_TOKEN", ()=>"somesecrettoken");
                 }
       )
mzj commented 4 months ago

This way, while it is possible to set environment variables for the sidecar, it is not very elegant and beautiful.

builder.AddProject<Projects.DaprServiceA>("servicea")
       .WithDaprSidecar(
                 sidecarBuilder =>
                 {
                     sidecarBuilder
                         .WithOptions(new DaprSidecarOptions { AppId = "identity-api-dapr" })
                         .WithAnnotation("APP_API_TOKEN", ()=>"somesecrettoken");
                 }
       )

Thanks, bit365, but I'm getting the following error message when I try calling WithAnnotation(...) as suggested:

Cannot convert lambda expression to type 'ResourceAnnotationMutationBehavior' because it is not a delegate type

These are the only two definitions of the WithAnnotation method that I found, and neither of them takes a lambda as the second argument:

IResourceBuilder WithAnnotation(ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation, new() => WithAnnotation(new TAnnotation(), behavior);

IResourceBuilder WithAnnotation(TAnnotation annotation, ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation;

bit365 commented 4 months ago

这样,虽然可以为 sidecar 设置环境变量,但它不是很优雅和美观。

builder.AddProject<Projects.DaprServiceA>("servicea")
       .WithDaprSidecar(
                 sidecarBuilder =>
                 {
                     sidecarBuilder
                         .WithOptions(new DaprSidecarOptions { AppId = "identity-api-dapr" })
                         .WithAnnotation("APP_API_TOKEN", ()=>"somesecrettoken");
                 }
       )

谢谢,bit365,但是当我尝试按照建议调用WithAnnotation(...)时,我收到以下错误消息:

无法将 lambda 表达式转换为类型“ResourceAnnotationMutationBehavior”,因为它不是委托类型

这是我找到的 WithAnnotation 方法的仅有的两个定义,它们都没有将 lambda 作为第二个参数:

IResourceBuilder WithAnnotation(ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation, new() => WithAnnotation(new TAnnotation(), behavior); IResourceBuilder WithAnnotation(TAnnotation annotation, ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation;

Sorry, I wrote it wrong. If I write it like this, it can work normally in Aspire 8.0.100. I hope it can help you.

builder.WithAnnotation(new EnvironmentCallbackAnnotation("APP_API_TOKEN", () => "somesecrettoken"))
bit365 commented 4 months ago

I wrote an extension method to configure environment variables and connection strings to the Dapr sidecar. I hope it will be helpful to you. As a temporary solution, please give me some pointers. I hope the Aspire team can solve this problem as soon as possible.

public static class DaprSidecarResourceBuilderExtensions
{
    private const string ConnectionStringEnvironmentName = "ConnectionStrings__";

    public static IResourceBuilder<IDaprSidecarResource> WithReference(this IResourceBuilder<IDaprSidecarResource> builder, IResourceBuilder<IResourceWithConnectionString> component, string? connectionName = null, bool optional = false)
    {
        connectionName ??= component.Resource.Name;

        builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
        {
            var connectionStringName = component.Resource.ConnectionStringEnvironmentVariable ?? $"{ConnectionStringEnvironmentName}{connectionName}";
            context.EnvironmentVariables[connectionStringName] = new ConnectionStringReference(component.Resource, optional);
            return Task.CompletedTask;
        }));

        return builder;
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, string? value)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, () => value ?? string.Empty));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, ReferenceExpression value)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
        {
            context.EnvironmentVariables[name] = value;
            return Task.CompletedTask;
        }));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, Func<string> callback)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, callback));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, Action<EnvironmentCallbackContext> callback)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, Func<EnvironmentCallbackContext, Task> callback)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, EndpointReference endpointReference)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
        {
            context.EnvironmentVariables[name] = endpointReference;
            return Task.CompletedTask;
        }));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, IResourceBuilder<ParameterResource> parameter)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
        {
            context.EnvironmentVariables[name] = parameter.Resource;
            return Task.CompletedTask;
        }));
    }

    public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string envVarName, IResourceBuilder<IResourceWithConnectionString> resource)
    {
        return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
        {
            context.EnvironmentVariables[envVarName] = new ConnectionStringReference(resource.Resource, optional: false);
            return Task.CompletedTask;
        }));
    }
}

Here's how I use it

builder.AddProject<Projects.DaprServiceA>("servicea").WithDaprSidecar(options=>
{
    options.WithEnvironment("testkey1","testvalue1")
    .WithReference(rabbitmq);
});
mzj commented 4 months ago

这样,虽然可以为 sidecar 设置环境变量,但它不是很优雅和美观。

builder.AddProject<Projects.DaprServiceA>("servicea")
       .WithDaprSidecar(
                 sidecarBuilder =>
                 {
                     sidecarBuilder
                         .WithOptions(new DaprSidecarOptions { AppId = "identity-api-dapr" })
                         .WithAnnotation("APP_API_TOKEN", ()=>"somesecrettoken");
                 }
       )

谢谢,bit365,但是当我尝试按照建议调用WithAnnotation(...)时,我收到以下错误消息:

无法将 lambda 表达式转换为类型“ResourceAnnotationMutationBehavior”,因为它不是委托类型

这是我找到的 WithAnnotation 方法的仅有的两个定义,它们都没有将 lambda 作为第二个参数:

IResourceBuilder WithAnnotation(ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation, new() => WithAnnotation(new TAnnotation(), behavior); IResourceBuilder WithAnnotation(TAnnotation annotation, ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation;

Sorry, I wrote it wrong. If I write it like this, it can work normally in Aspire 8.0.100. I hope it can help you.

builder.WithAnnotation(new EnvironmentCallbackAnnotation("APP_API_TOKEN", () => "somesecrettoken"))

This did the trick. Thank you!

builder.WithAnnotation(new EnvironmentCallbackAnnotation("APP_API_TOKEN", () => "somesecrettoken"))

harrykimpel commented 1 month ago

I wrote an extension method to configure environment variables and connection strings to the Dapr sidecar. I hope it will be helpful to you. As a temporary solution, please give me some pointers. I hope the Aspire team can solve this problem as soon as possible.

public static class DaprSidecarResourceBuilderExtensions { private const string ConnectionStringEnvironmentName = "ConnectionStrings__";

public static IResourceBuilder<IDaprSidecarResource> WithReference(this IResourceBuilder<IDaprSidecarResource> builder, IResourceBuilder<IResourceWithConnectionString> component, string? connectionName = null, bool optional = false)
{
    connectionName ??= component.Resource.Name;

    builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
    {
        var connectionStringName = component.Resource.ConnectionStringEnvironmentVariable ?? $"{ConnectionStringEnvironmentName}{connectionName}";
        context.EnvironmentVariables[connectionStringName] = new ConnectionStringReference(component.Resource, optional);
        return Task.CompletedTask;
    }));

    return builder;
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, string? value)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, () => value ?? string.Empty));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, ReferenceExpression value)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
    {
        context.EnvironmentVariables[name] = value;
        return Task.CompletedTask;
    }));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, Func<string> callback)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, callback));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, Action<EnvironmentCallbackContext> callback)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, Func<EnvironmentCallbackContext, Task> callback)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, EndpointReference endpointReference)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
    {
        context.EnvironmentVariables[name] = endpointReference;
        return Task.CompletedTask;
    }));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string name, IResourceBuilder<ParameterResource> parameter)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
    {
        context.EnvironmentVariables[name] = parameter.Resource;
        return Task.CompletedTask;
    }));
}

public static IResourceBuilder<IDaprSidecarResource> WithEnvironment(this IResourceBuilder<IDaprSidecarResource> builder, string envVarName, IResourceBuilder<IResourceWithConnectionString> resource)
{
    return builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
    {
        context.EnvironmentVariables[envVarName] = new ConnectionStringReference(resource.Resource, optional: false);
        return Task.CompletedTask;
    }));
}

} Here's how I use it

builder.AddProject("servicea").WithDaprSidecar(options=> { options.WithEnvironment("testkey1","testvalue1") .WithReference(rabbitmq); });

works perfectly fine, thank you for sharing!