Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Apr 1, 2024
1 parent a3b62f3 commit 1f5cecb
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 28 deletions.
14 changes: 11 additions & 3 deletions src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -780,11 +780,19 @@ private async Task<List<KeyValuePair<string, string>>> GetDashboardEnvironmentVa
KeyValuePair.Create(DashboardConfigNames.ResourceServiceUrlName.EnvVarName, resourceServiceUrl),
KeyValuePair.Create(DashboardConfigNames.DashboardOtlpUrlName.EnvVarName, otlpEndpointUrl),
KeyValuePair.Create(DashboardConfigNames.ResourceServiceAuthModeName.EnvVarName, "Unsecured"),
KeyValuePair.Create(DashboardConfigNames.DashboardFrontendAuthModeName.EnvVarName, "BrowserToken"),
KeyValuePair.Create(DashboardConfigNames.DashboardFrontendBrowserTokenName.EnvVarName, configuration["AppHost:BrowserToken"]!),
};

if (configuration["AppHost:OtlpApiKey"] is { } otlpApiKey)
if (configuration["AppHost:BrowserToken"] is { Length: > 0 } browserToken)
{
env.Add(KeyValuePair.Create(DashboardConfigNames.DashboardFrontendAuthModeName.EnvVarName, "BrowserToken"));
env.Add(KeyValuePair.Create(DashboardConfigNames.DashboardFrontendBrowserTokenName.EnvVarName, browserToken));
}
else
{
env.Add(KeyValuePair.Create(DashboardConfigNames.DashboardFrontendAuthModeName.EnvVarName, "Unsecured"));
}

if (configuration["AppHost:OtlpApiKey"] is { Length: > 0 } otlpApiKey)
{
env.Add(KeyValuePair.Create(DashboardConfigNames.DashboardOtlpAuthModeName.EnvVarName, "ApiKey"));
env.Add(KeyValuePair.Create(DashboardConfigNames.DashboardOtlpPrimaryApiKeyName.EnvVarName, otlpApiKey));
Expand Down
22 changes: 12 additions & 10 deletions src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ public class DistributedApplicationBuilder : IDistributedApplicationBuilder
private const string BuilderConstructingEventName = "DistributedApplicationBuilderConstructing";
private const string BuilderConstructedEventName = "DistributedApplicationBuilderConstructed";

private const string DisableOtlpApiKeyAuthKey = "DOTNET_DISABLE_OTLP_API_KEY_AUTH";

private readonly HostApplicationBuilder _innerBuilder;

/// <inheritdoc />
Expand Down Expand Up @@ -87,25 +85,29 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
// Make the app host directory available to the application via configuration
["AppHost:Directory"] = AppHostDirectory
});
if (!IsOtlpApiKeyAuthDisabled(_innerBuilder.Configuration))

if (!options.DisableDashboard && !IsDashboardUnsecured(_innerBuilder.Configuration))
{
// Set a random API key for the OTLP exporter.
// Passed to apps as a standard OTEL attribute to include in OTLP requests and the dashboard to validate.
_innerBuilder.Configuration.AddInMemoryCollection(
new Dictionary<string, string?>
{
["AppHost:OtlpApiKey"] = Guid.NewGuid().ToString()
["AppHost:OtlpApiKey"] = TokenGenerator.GetToken()
}
);
}
if (!options.DisableDashboard)
{

if (_innerBuilder.Configuration[KnownConfigNames.DashboardFrontendBrowserToken] is not { Length: > 0 } browserToken)
{
browserToken = TokenGenerator.GetToken();
}

// Set a random API key for the OTLP exporter.
// Passed to apps as a standard OTEL attribute to include in OTLP requests and the dashboard to validate.
_innerBuilder.Configuration.AddInMemoryCollection(
new Dictionary<string, string?>
{
["AppHost:BrowserToken"] = Guid.NewGuid().ToString()
["AppHost:BrowserToken"] = browserToken
}
);
}
Expand Down Expand Up @@ -158,9 +160,9 @@ private void MapTransportOptionsFromCustomKeys(TransportOptions options)
}
}

private static bool IsOtlpApiKeyAuthDisabled(IConfiguration configuration)
private static bool IsDashboardUnsecured(IConfiguration configuration)
{
return configuration.GetBool(DisableOtlpApiKeyAuthKey) ?? false;
return configuration.GetBool(KnownConfigNames.DashboardUnsecuredAllowAnonymous) ?? false;
}

private void ConfigurePublishingOptions(DistributedApplicationOptions options)
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Hosting/DistributedApplicationLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public Task StartAsync(CancellationToken cancellationToken)

public Task StartedAsync(CancellationToken cancellationToken)
{
if (distributedApplicationOptions.DashboardEnabled)
if (distributedApplicationOptions.DashboardEnabled && configuration["AppHost:BrowserToken"] is { Length: > 0 } browserToken)
{
LoggingHelpers.WriteDashboardUrl(logger, configuration["ASPNETCORE_URLS"], configuration["AppHost:BrowserToken"]);
LoggingHelpers.WriteDashboardUrl(logger, configuration["ASPNETCORE_URLS"], browserToken);
}

if (executionContext.IsRunMode)
Expand Down
10 changes: 6 additions & 4 deletions src/Shared/KnownConfigNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ namespace Aspire.Hosting;

internal static class KnownConfigNames
{
public static string AspNetCoreUrls = "ASPNETCORE_URLS";
public static string AllowUnsecuredTransport = "ASPIRE_ALLOW_UNSECURED_TRANSPORT";
public static string DashboardOtlpEndpointUrl = "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL";
public static string ResourceServiceEndpointUrl = "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL";
public const string AspNetCoreUrls = "ASPNETCORE_URLS";
public const string AllowUnsecuredTransport = "ASPIRE_ALLOW_UNSECURED_TRANSPORT";
public const string DashboardOtlpEndpointUrl = "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL";
public const string DashboardFrontendBrowserToken = "DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN";
public const string DashboardUnsecuredAllowAnonymous = "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS";
public const string ResourceServiceEndpointUrl = "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL";
}
52 changes: 51 additions & 1 deletion tests/Aspire.Hosting.Tests/Dcp/ApplicationExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,54 @@ public async Task RunApplicationAsync_NoResources_DashboardStarted()
Assert.Equal("aspire-dashboard", dashboard.Metadata.Name);
}

[Fact]
public async Task RunApplicationAsync_AuthConfigured_EnvVarsPresent()
{
// Arrange
var distributedAppModel = new DistributedApplicationModel(new ResourceCollection());
var kubernetesService = new MockKubernetesService();

var appExecutor = CreateAppExecutor(distributedAppModel, kubernetesService: kubernetesService);

// Act
await appExecutor.RunApplicationAsync();

// Assert
var dashboard = Assert.IsType<Executable>(Assert.Single(kubernetesService.CreatedResources));
Assert.NotNull(dashboard.Spec.Env);

Assert.Equal("BrowserToken", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardFrontendAuthModeName.EnvVarName).Value);
Assert.Equal("TestBrowserToken!", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardFrontendBrowserTokenName.EnvVarName).Value);

Assert.Equal("ApiKey", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardOtlpAuthModeName.EnvVarName).Value);
Assert.Equal("TestOtlpApiKey!", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardOtlpPrimaryApiKeyName.EnvVarName).Value);
}

[Fact]
public async Task RunApplicationAsync_AuthRemoved_EnvVarsUnsecured()
{
// Arrange
var distributedAppModel = new DistributedApplicationModel(new ResourceCollection());
var kubernetesService = new MockKubernetesService();
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string, string?>
{
["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] = "http://localhost"
});

var appExecutor = CreateAppExecutor(distributedAppModel, configuration: builder.Build(), kubernetesService: kubernetesService);

// Act
await appExecutor.RunApplicationAsync();

// Assert
var dashboard = Assert.IsType<Executable>(Assert.Single(kubernetesService.CreatedResources));
Assert.NotNull(dashboard.Spec.Env);

Assert.Equal("Unsecured", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardFrontendAuthModeName.EnvVarName).Value);
Assert.Equal("Unsecured", dashboard.Spec.Env.Single(e => e.Name == DashboardConfigNames.DashboardOtlpAuthModeName.EnvVarName).Value);
}

[Fact]
public async Task ContainersArePassedOtelServiceName()
{
Expand Down Expand Up @@ -63,7 +111,9 @@ private static ApplicationExecutor CreateAppExecutor(
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string, string?>
{
["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] = "http://localhost"
["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] = "http://localhost",
["AppHost:BrowserToken"] = "TestBrowserToken!",
["AppHost:OtlpApiKey"] = "TestOtlpApiKey!"
});

configuration = builder.Build();
Expand Down
93 changes: 85 additions & 8 deletions tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,6 @@ public async Task SpecifyingEnvPortInEndpointFlowsToEnv()
var nodeApp = await KubernetesHelper.GetResourceByNameAsync<Executable>(kubernetes, "nodeapp", r => r.Status?.EffectiveEnv is not null, token);
Assert.NotNull(nodeApp);

string? GetEnv(IEnumerable<EnvVar>? envVars, string name)
{
Assert.NotNull(envVars);
return Assert.Single(envVars.Where(e => e.Name == name)).Value;
};

Assert.Equal("redis:latest", redisContainer.Spec.Image);
Assert.Equal("{{- portForServing \"redis0\" }}", GetEnv(redisContainer.Spec.Env, "REDIS_PORT"));
Assert.Equal("6379", GetEnv(redisContainer.Status!.EffectiveEnv, "REDIS_PORT"));
Expand All @@ -293,6 +287,89 @@ public async Task SpecifyingEnvPortInEndpointFlowsToEnv()
Assert.NotEqual(0, int.Parse(nodeAppPortValue, CultureInfo.InvariantCulture));

await app.StopAsync();

static string? GetEnv(IEnumerable<EnvVar>? envVars, string name)
{
Assert.NotNull(envVars);
return Assert.Single(envVars.Where(e => e.Name == name)).Value;
}
}

[LocalOnlyFact("docker")]
public async Task StartAsync_DashboardAuthConfig_PassedToDashboardProcess()
{
var browserToken = "ThisIsATestToken";
var args = new string[] {
"ASPNETCORE_URLS=http://localhost:0",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL=http://localhost:0",
$"DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN={browserToken}"
};
using var testProgram = CreateTestProgram(args: args, disableDashboard: false);

testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(_testOutputHelper));

await using var app = testProgram.Build();

var kubernetes = app.Services.GetRequiredService<IKubernetesService>();

await app.StartAsync();

using var cts = new CancellationTokenSource(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10));
var token = cts.Token;

var aspireDashboard = await KubernetesHelper.GetResourceByNameAsync<Executable>(kubernetes, "aspire-dashboard", r => r.Status?.EffectiveEnv is not null, token);
Assert.NotNull(aspireDashboard);

Assert.Equal("BrowserToken", GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__FRONTEND__AUTHMODE"));
Assert.Equal("ThisIsATestToken", GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__FRONTEND__BROWSERTOKEN"));

Assert.Equal("ApiKey", GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__OTLP__AUTHMODE"));
var keyBytes = Convert.FromHexString(GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__OTLP__PRIMARYAPIKEY")!);
Assert.Equal(16, keyBytes.Length);

await app.StopAsync();

static string? GetEnv(IEnumerable<EnvVar>? envVars, string name)
{
Assert.NotNull(envVars);
return Assert.Single(envVars.Where(e => e.Name == name)).Value;
}
}

[LocalOnlyFact("docker")]
public async Task StartAsync_UnsecuredAllowAnonymous_PassedToDashboardProcess()
{
var args = new string[] {
"ASPNETCORE_URLS=http://localhost:0",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL=http://localhost:0",
"DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true"
};
using var testProgram = CreateTestProgram(args: args, disableDashboard: false);

testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(_testOutputHelper));

await using var app = testProgram.Build();

var kubernetes = app.Services.GetRequiredService<IKubernetesService>();

await app.StartAsync();

using var cts = new CancellationTokenSource(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10));
var token = cts.Token;

var aspireDashboard = await KubernetesHelper.GetResourceByNameAsync<Executable>(kubernetes, "aspire-dashboard", r => r.Status?.EffectiveEnv is not null, token);
Assert.NotNull(aspireDashboard);

Assert.Equal("Unsecured", GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__FRONTEND__AUTHMODE"));
Assert.Equal("Unsecured", GetEnv(aspireDashboard.Spec.Env, "DASHBOARD__OTLP__AUTHMODE"));

await app.StopAsync();

static string? GetEnv(IEnumerable<EnvVar>? envVars, string name)
{
Assert.NotNull(envVars);
return Assert.Single(envVars.Where(e => e.Name == name)).Value;
}
}

[LocalOnlyFact("docker")]
Expand Down Expand Up @@ -716,6 +793,6 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appMode
}
}

private static TestProgram CreateTestProgram(string[]? args = null, bool includeIntegrationServices = false, bool includeNodeApp = false) =>
TestProgram.Create<DistributedApplicationTests>(args, includeIntegrationServices: includeIntegrationServices, includeNodeApp: includeNodeApp);
private static TestProgram CreateTestProgram(string[]? args = null, bool includeIntegrationServices = false, bool includeNodeApp = false, bool disableDashboard = true) =>
TestProgram.Create<DistributedApplicationTests>(args, includeIntegrationServices: includeIntegrationServices, includeNodeApp: includeNodeApp, disableDashboard: disableDashboard);
}

0 comments on commit 1f5cecb

Please sign in to comment.