Skip to content
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

Model Dapr sidecar as an Aspire resource #1604

Merged
merged 7 commits into from
Jan 10, 2024
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
22 changes: 11 additions & 11 deletions samples/dapr/AppHost/aspire-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
}
}
},
"servicea-dapr": {
"type": "dapr.v0",
"dapr": {
"application": "servicea",
"appId": "servicea",
"components": [
"statestore",
"pubsub"
]
}
},
"serviceb": {
"type": "project.v0",
"path": "../ServiceB/DaprServiceB.csproj",
Expand All @@ -52,17 +63,6 @@
}
}
},
"servicea-dapr": {
"type": "dapr.v0",
"dapr": {
"application": "servicea",
"appId": "servicea",
"components": [
"statestore",
"pubsub"
]
}
},
"serviceb-dapr": {
"type": "dapr.v0",
"dapr": {
Expand Down
59 changes: 33 additions & 26 deletions src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
continue;
}

var sidecarOptions = daprAnnotation.Options;
var daprSidecar = daprAnnotation.Sidecar;

var sidecarOptionsAnnotation = daprSidecar.Annotations.OfType<DaprSidecarOptionsAnnotation>().LastOrDefault();

var sidecarOptions = sidecarOptionsAnnotation?.Options;

[return: NotNullIfNotNull(nameof(path))]
string? NormalizePath(string? path)
Expand Down Expand Up @@ -95,6 +99,8 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
var daprMetricsPortArg = (string? port) => ModelNamedArg("--metrics-port", port);
var daprProfilePortArg = (string? port) => ModelNamedArg("--profile-port", port);

var appId = sidecarOptions?.AppId ?? resource.Name;

var daprCommandLine =
CommandLineBuilder
.Create(
Expand All @@ -106,7 +112,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
ModelNamedArg("--app-health-probe-interval", sidecarOptions?.AppHealthProbeInterval),
ModelNamedArg("--app-health-probe-timeout", sidecarOptions?.AppHealthProbeTimeout),
ModelNamedArg("--app-health-threshold", sidecarOptions?.AppHealthThreshold),
ModelNamedArg("--app-id", sidecarOptions?.AppId),
ModelNamedArg("--app-id", appId),
ModelNamedArg("--app-max-concurrency", sidecarOptions?.AppMaxConcurrency),
ModelNamedArg("--app-protocol", sidecarOptions?.AppProtocol),
ModelNamedArg("--config", NormalizePath(sidecarOptions?.Config)),
Expand All @@ -125,13 +131,8 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
ModelNamedArg("--unix-domain-socket", sidecarOptions?.UnixDomainSocket),
PostOptionsArgs(Args(sidecarOptions?.Command)));

if (!(sidecarOptions?.AppId is { } appId))
{
throw new DistributedApplicationException("AppId is required for Dapr sidecar executable.");
}

var daprSideCarResourceName = $"{resource.Name}-dapr";
var daprSideCar = new ExecutableResource(daprSideCarResourceName, fileName, appHostDirectory, daprCommandLine.Arguments.ToArray());
var daprCliResourceName = $"{daprSidecar.Name}-cli";
var daprCli = new ExecutableResource(daprCliResourceName, fileName, appHostDirectory, daprCommandLine.Arguments.ToArray());

resource.Annotations.Add(
new EnvironmentCallbackAnnotation(
Expand All @@ -146,32 +147,32 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
{
// By default, the Dapr sidecar will listen on localhost, which is not accessible from the container.

var grpcEndpoint = $"http://localhost:{{{{- portFor \"{daprSideCarResourceName}_grpc\" -}}}}";
var httpEndpoint = $"http://localhost:{{{{- portFor \"{daprSideCarResourceName}_http\" -}}}}";
var grpcEndpoint = $"http://localhost:{{{{- portFor \"{daprCliResourceName}_grpc\" -}}}}";
var httpEndpoint = $"http://localhost:{{{{- portFor \"{daprCliResourceName}_http\" -}}}}";

context.EnvironmentVariables.TryAdd("DAPR_GRPC_ENDPOINT", HostNameResolver.ReplaceLocalhostWithContainerHost(grpcEndpoint, _configuration));
context.EnvironmentVariables.TryAdd("DAPR_HTTP_ENDPOINT", HostNameResolver.ReplaceLocalhostWithContainerHost(httpEndpoint, _configuration));
}

context.EnvironmentVariables.TryAdd("DAPR_GRPC_PORT", $"{{{{- portFor \"{daprSideCarResourceName}_grpc\" -}}}}");
context.EnvironmentVariables.TryAdd("DAPR_HTTP_PORT", $"{{{{- portFor \"{daprSideCarResourceName}_http\" -}}}}");
context.EnvironmentVariables.TryAdd("DAPR_GRPC_PORT", $"{{{{- portFor \"{daprCliResourceName}_grpc\" -}}}}");
context.EnvironmentVariables.TryAdd("DAPR_HTTP_PORT", $"{{{{- portFor \"{daprCliResourceName}_http\" -}}}}");
}));

daprSideCar.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "grpc", port: sidecarOptions?.DaprGrpcPort));
daprSideCar.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "http", port: sidecarOptions?.DaprHttpPort));
daprSideCar.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "metrics", port: sidecarOptions?.MetricsPort));
daprCli.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "grpc", port: sidecarOptions?.DaprGrpcPort));
daprCli.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "http", port: sidecarOptions?.DaprHttpPort));
daprCli.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "metrics", port: sidecarOptions?.MetricsPort));
if (sidecarOptions?.EnableProfiling == true)
{
daprSideCar.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "profile", port: sidecarOptions?.ProfilePort));
daprCli.Annotations.Add(new EndpointAnnotation(ProtocolType.Tcp, name: "profile", port: sidecarOptions?.ProfilePort));
}

// NOTE: Telemetry is enabled by default.
if (this._options.EnableTelemetry != false)
{
OtlpConfigurationExtensions.AddOtlpEnvironment(daprSideCar, _configuration, _environment);
OtlpConfigurationExtensions.AddOtlpEnvironment(daprCli, _configuration, _environment);
}

daprSideCar.Annotations.Add(
daprCli.Annotations.Add(
new ExecutableArgsCallbackAnnotation(
updatedArgs =>
{
Expand All @@ -185,16 +186,22 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
}
}

updatedArgs.AddRange(daprGrpcPortArg($"{{{{- portForServing \"{daprSideCarResourceName}_grpc\" -}}}}")());
updatedArgs.AddRange(daprHttpPortArg($"{{{{- portForServing \"{daprSideCarResourceName}_http\" -}}}}")());
updatedArgs.AddRange(daprMetricsPortArg($"{{{{- portForServing \"{daprSideCarResourceName}_metrics\" -}}}}")());
updatedArgs.AddRange(daprGrpcPortArg($"{{{{- portForServing \"{daprCliResourceName}_grpc\" -}}}}")());
updatedArgs.AddRange(daprHttpPortArg($"{{{{- portForServing \"{daprCliResourceName}_http\" -}}}}")());
updatedArgs.AddRange(daprMetricsPortArg($"{{{{- portForServing \"{daprCliResourceName}_metrics\" -}}}}")());
if (sidecarOptions?.EnableProfiling == true)
{
updatedArgs.AddRange(daprProfilePortArg($"{{{{- portForServing \"{daprSideCarResourceName}_profile\" -}}}}")());
updatedArgs.AddRange(daprProfilePortArg($"{{{{- portForServing \"{daprCliResourceName}_profile\" -}}}}")());
}
}));

daprSideCar.Annotations.Add(
// Apply environment variables to the CLI...
daprCli.Annotations.AddRange(daprSidecar.Annotations.OfType<EnvironmentCallbackAnnotation>());

// The CLI is an artifact of a local run, so it should not be published...
daprCli.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore);

daprSidecar.Annotations.Add(
new ManifestPublishingCallbackAnnotation(
context =>
{
Expand All @@ -207,7 +214,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
context.Writer.TryWriteNumber("appHealthProbeInterval", sidecarOptions?.AppHealthProbeInterval);
context.Writer.TryWriteNumber("appHealthProbeTimeout", sidecarOptions?.AppHealthProbeTimeout);
context.Writer.TryWriteNumber("appHealthThreshold", sidecarOptions?.AppHealthThreshold);
context.Writer.TryWriteString("appId", sidecarOptions?.AppId);
context.Writer.TryWriteString("appId", appId);
context.Writer.TryWriteNumber("appMaxConcurrency", sidecarOptions?.AppMaxConcurrency);
context.Writer.TryWriteNumber("appPort", sidecarOptions?.AppPort);
context.Writer.TryWriteString("appProtocol", sidecarOptions?.AppProtocol);
Expand All @@ -234,7 +241,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
context.Writer.WriteEndObject();
}));

sideCars.Add(daprSideCar);
sideCars.Add(daprCli);
}

appModel.Resources.AddRange(sideCars);
Expand Down
6 changes: 1 addition & 5 deletions src/Aspire.Hosting.Dapr/DaprSidecarAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ namespace Aspire.Hosting.Dapr;
/// <summary>
/// Indicates that a Dapr sidecar should be started for the associated resource.
/// </summary>
public sealed record DaprSidecarAnnotation : IResourceAnnotation
public sealed record DaprSidecarAnnotation(IDaprSidecarResource Sidecar) : IResourceAnnotation
{
/// <summary>
/// Gets or sets the options used to configured the Dapr sidecar.
/// </summary>
public DaprSidecarOptions? Options { get; init; }
}
13 changes: 13 additions & 0 deletions src/Aspire.Hosting.Dapr/DaprSidecarOptionsAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Dapr;

/// <summary>
/// Indicates the options used to configure a Dapr sidecar.
/// </summary>
public sealed record DaprSidecarOptionsAnnotation(DaprSidecarOptions Options) : IResourceAnnotation
{
}
11 changes: 11 additions & 0 deletions src/Aspire.Hosting.Dapr/DaprSidecarResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Dapr;

/// <summary>
/// Represents a Dapr sidecar resource.
/// </summary>
internal sealed class DaprSidecarResource(string name) : Resource(name), IDaprSidecarResource { }
13 changes: 13 additions & 0 deletions src/Aspire.Hosting.Dapr/IDaprSidecarResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Dapr;

/// <summary>
/// Represents a Dapr sidecar resource.
/// </summary>
public interface IDaprSidecarResource : IResource
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,64 @@ namespace Aspire.Hosting;
public static class IDistributedApplicationResourceBuilderExtensions
{
/// <summary>
/// Ensures that a Dapr sidecar is started for the resource. The default app is the resource name.
/// Ensures that a Dapr sidecar is started for the resource.
/// </summary>
/// <typeparam name="T">The type of the resource.</typeparam>
/// <param name="builder">The resource builder instance.</param>
/// <param name="appId">The ID for the application, used for service discovery.</param>
/// <returns>The resource builder instance.</returns>
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder) where T : IResource
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder, string appId) where T : IResource
{
return builder.WithDaprSidecar(builder.Resource.Name);
return builder.WithDaprSidecar(new DaprSidecarOptions { AppId = appId });
}

/// <summary>
/// Ensures that a Dapr sidecar is started for the resource.
/// </summary>
/// <typeparam name="T">The type of the resource.</typeparam>
/// <param name="builder">The resource builder instance.</param>
/// <param name="appId">The ID for the application, used for service discovery.</param>
/// <param name="options">Options for configuring the Dapr sidecar, if any.</param>
/// <returns>The resource builder instance.</returns>
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder, string appId) where T : IResource
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder, DaprSidecarOptions? options = null) where T : IResource
{
return builder.WithDaprSidecar(new DaprSidecarOptions { AppId = appId });
return builder.WithDaprSidecar(
sidecarBuilder =>
{
if (options is not null)
{
sidecarBuilder.WithOptions(options);
}
});
}

/// <summary>
/// Ensures that a Dapr sidecar is started for the resource.
/// </summary>
/// <typeparam name="T">The type of the resource.</typeparam>
/// <param name="builder">The resource builder instance.</param>
/// <param name="options">Options for configuring the Dapr sidecar, if any.</param>
/// <param name="configureSidecar">A callback that can be use to configure the Dapr sidecar.</param>
/// <returns>The resource builder instance.</returns>
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder, DaprSidecarOptions? options = null) where T : IResource
public static IResourceBuilder<T> WithDaprSidecar<T>(this IResourceBuilder<T> builder, Action<IResourceBuilder<IDaprSidecarResource>> configureSidecar) where T : IResource
{
// Add Dapr is idempoent, so we can call it multiple times.
builder.ApplicationBuilder.AddDapr();
return builder.WithAnnotation(new DaprSidecarAnnotation { Options = options });

var sidecarBuilder = builder.ApplicationBuilder.AddResource(new DaprSidecarResource($"{builder.Resource.Name}-dapr"));

configureSidecar(sidecarBuilder);

return builder.WithAnnotation(new DaprSidecarAnnotation(sidecarBuilder.Resource));
}

/// <summary>
/// Configures a Dapr sidecar with the specified options.
/// </summary>
/// <param name="builder">The Dapr sidecar resource builder instance.</param>
/// <param name="options">Options for configuring the Dapr sidecar.</param>
/// <returns>The Dapr sidecar resource builder instance.</returns>
public static IResourceBuilder<IDaprSidecarResource> WithOptions(this IResourceBuilder<IDaprSidecarResource> builder, DaprSidecarOptions options)
{
return builder.WithAnnotation(new DaprSidecarOptionsAnnotation(options));
}

/// <summary>
Expand Down