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

Prioritize retrieval of environment variables from IConfiguration instead of directly #1339

Closed
Closed
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
116 changes: 110 additions & 6 deletions src/Dapr.Actors.AspNetCore/ActorsServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,9 +11,13 @@
// limitations under the License.
// ------------------------------------------------------------------------

#nullable enable

using System;
using Dapr;
using Dapr.Actors.Client;
using Dapr.Actors.Runtime;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand All @@ -30,12 +34,9 @@ public static class ActorsServiceCollectionExtensions
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" />.</param>
/// <param name="configure">A delegate used to configure actor options and register actor types.</param>
public static void AddActors(this IServiceCollection services, Action<ActorRuntimeOptions> configure)
public static void AddActors(this IServiceCollection? services, Action<ActorRuntimeOptions>? configure)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
ArgumentNullException.ThrowIfNull(services, nameof(services));

// Routing and health checks are required dependencies.
services.AddRouting();
Expand All @@ -45,6 +46,16 @@ public static void AddActors(this IServiceCollection services, Action<ActorRunti
services.TryAddSingleton<ActorRuntime>(s =>
{
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;

//Replace the HttpEndpoint with an endpoint prioritizing IConfiguration
var configuration = s.GetService<IConfiguration>();
options.HttpEndpoint = options.HttpEndpoint != "http://127.0.0.1:3500"
? GetHttpEndpoint(configuration)
: options.HttpEndpoint;
options.DaprApiToken = string.IsNullOrWhiteSpace(options.DaprApiToken)
? GetApiToken(configuration)
: options.DaprApiToken;

var loggerFactory = s.GetRequiredService<ILoggerFactory>();
var activatorFactory = s.GetRequiredService<ActorActivatorFactory>();
var proxyFactory = s.GetRequiredService<IActorProxyFactory>();
Expand All @@ -54,6 +65,16 @@ public static void AddActors(this IServiceCollection services, Action<ActorRunti
services.TryAddSingleton<IActorProxyFactory>(s =>
{
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;

//Replace the HttpEndpoint with an endpoint prioritizing IConfiguration
var configuration = s.GetService<IConfiguration>();
options.HttpEndpoint = options.HttpEndpoint != "http://127.0.0.1:3500"
? GetHttpEndpoint(configuration)
: options.HttpEndpoint;
options.DaprApiToken = string.IsNullOrWhiteSpace(options.DaprApiToken)
? GetApiToken(configuration)
: options.DaprApiToken;

var factory = new ActorProxyFactory()
{
DefaultOptions =
Expand All @@ -72,5 +93,88 @@ public static void AddActors(this IServiceCollection services, Action<ActorRunti
services.Configure<ActorRuntimeOptions>(configure);
}
}

/// <summary>
/// Retrieves the Dapr API token using a failover approach starting with an optional <see cref="IConfiguration"/>
/// instance, then trying to pull from the well-known environment variable name and then opting for an empty string
/// as a default value.
/// </summary>
/// <returns>The Dapr API token.</returns>
private static string GetApiToken(IConfiguration? configuration) => GetResourceValue(configuration, DaprDefaults.DaprApiTokenName);

/// <summary>
/// Builds the Dapr gRPC endpoint using the value from the IConfiguration, if available, then falling back
/// to the value in the environment variable(s) and finally otherwise using the default value (an empty string).
/// </summary>
/// <remarks>
/// Marked as internal for testing purposes.
/// </remarks>
/// <param name="configuration">An injected instance of the <see cref="IConfiguration"/>.</param>
/// <returns>The built gRPC endpoint.</returns>
private static string GetHttpEndpoint(IConfiguration? configuration)
{
//Prioritize pulling from IConfiguration with a fallback from pulling from the environment variable directly
var httpEndpoint = GetResourceValue(configuration, DaprDefaults.DaprHttpEndpointName);
var httpPort = GetResourceValue(configuration, DaprDefaults.DaprHttpPortName);
int? parsedGrpcPort = string.IsNullOrWhiteSpace(httpPort) ? null : int.Parse(httpPort);

var endpoint = BuildEndpoint(httpEndpoint, parsedGrpcPort);
return string.IsNullOrWhiteSpace(endpoint) ? $"http://localhost:{DaprDefaults.DefaultHttpPort}/" : endpoint;
}

/// <summary>
/// Retrieves the specified value prioritizing pulling it from <see cref="IConfiguration"/>, falling back
/// to an environment variable, and using an empty string as a default.
/// </summary>
/// <param name="configuration">An instance of an <see cref="IConfiguration"/>.</param>
/// <param name="name">The name of the value to retrieve.</param>
/// <returns>The value of the resource.</returns>
private static string GetResourceValue(IConfiguration? configuration, string name)
{
//Attempt to retrieve first from the configuration
var configurationValue = configuration?.GetValue<string?>(name);
if (configurationValue is not null)
return configurationValue;

//Fall back to the environment variable with the same name or default to an empty string
var envVar = Environment.GetEnvironmentVariable(name);
return envVar ?? string.Empty;
}

/// <summary>
/// Builds the endpoint provided an optional endpoint and optional port.
/// </summary>
/// <remarks>
/// Marked as internal for testing purposes.
/// </remarks>
/// <param name="endpoint">The endpoint.</param>
/// <param name="endpointPort">The port</param>
/// <returns>A constructed endpoint value.</returns>
internal static string BuildEndpoint(string? endpoint, int? endpointPort)
{
if (string.IsNullOrWhiteSpace(endpoint) && endpointPort is null)
return string.Empty;

var endpointBuilder = new UriBuilder();
if (!string.IsNullOrWhiteSpace(endpoint))
{
//Extract the scheme, host and port from the endpoint
var uri = new Uri(endpoint);
endpointBuilder.Scheme = uri.Scheme;
endpointBuilder.Host = uri.Host;
endpointBuilder.Port = uri.Port;

//Update the port if provided separately
if (endpointPort is not null)
endpointBuilder.Port = (int)endpointPort;
}
else if (string.IsNullOrWhiteSpace(endpoint) && endpointPort is not null)
{
endpointBuilder.Host = "localhost";
endpointBuilder.Port = (int)endpointPort;
}

return endpointBuilder.ToString();
}
}
}
Loading
Loading