Skip to content
Draft
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 @@ -250,8 +250,8 @@ public static IResourceBuilder<AzureFunctionsProjectResource> WithReference<TSou

private static string CreateDefaultStorageName(this IDistributedApplicationBuilder builder)
{
// Use ProjectNameSha256 for stable naming across deployments regardless of path
var applicationHash = builder.Configuration["AppHost:ProjectNameSha256"]![..5].ToLowerInvariant();
// Use ProjectNameHash for stable naming across deployments regardless of path
var applicationHash = builder.AppHostEnvironment.ProjectNameHash[..5].ToLowerInvariant();
return $"{DefaultAzureFunctionsHostStorageName}{applicationHash}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ public static IResourceBuilder<AzureEnvironmentResource> WithResourceGroup(

private static string CreateDefaultAzureEnvironmentName(this IDistributedApplicationBuilder builder)
{
// Use ProjectNameSha256 for stable naming across deployments
var applicationHash = builder.Configuration["AppHost:ProjectNameSha256"]?[..5].ToLowerInvariant();
// Use ProjectNameHash for stable naming across deployments
var applicationHash = builder.AppHostEnvironment.ProjectNameHash[..5].ToLowerInvariant();
return $"azure{applicationHash}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Azure;
using Azure.Core;
using Azure.ResourceManager.Resources;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand All @@ -20,7 +19,7 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal abstract partial class BaseProvisioningContextProvider(
IInteractionService interactionService,
IOptions<AzureProvisionerOptions> options,
IHostEnvironment environment,
IAppHostEnvironment appHostEnvironment,
ILogger logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
Expand All @@ -33,7 +32,7 @@ internal abstract partial class BaseProvisioningContextProvider(

protected readonly IInteractionService _interactionService = interactionService;
protected readonly AzureProvisionerOptions _options = options.Value;
protected readonly IHostEnvironment _environment = environment;
protected readonly IAppHostEnvironment _appHostEnvironment = appHostEnvironment;
protected readonly ILogger _logger = logger;
protected readonly IArmClientProvider _armClientProvider = armClientProvider;
protected readonly IUserPrincipalProvider _userPrincipalProvider = userPrincipalProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Aspire.Hosting.Azure.Resources;
using Aspire.Hosting.Azure.Utils;
using Aspire.Hosting.Publishing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand All @@ -21,7 +20,7 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal sealed class PublishModeProvisioningContextProvider(
IInteractionService interactionService,
IOptions<AzureProvisionerOptions> options,
IHostEnvironment environment,
IAppHostEnvironment appHostEnvironment,
ILogger<PublishModeProvisioningContextProvider> logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
Expand All @@ -30,7 +29,7 @@ internal sealed class PublishModeProvisioningContextProvider(
IPublishingActivityReporter activityReporter) : BaseProvisioningContextProvider(
interactionService,
options,
environment,
appHostEnvironment,
logger,
armClientProvider,
userPrincipalProvider,
Expand All @@ -48,7 +47,7 @@ protected override string GetDefaultResourceGroupName()

var maxApplicationNameSize = ResourceGroupNameHelpers.MaxResourceGroupNameLength - prefix.Length - 1; // extra '-'

var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_environment.ApplicationName.ToLowerInvariant());
var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_appHostEnvironment.ProjectName.ToLowerInvariant());
if (normalizedApplicationName.Length > maxApplicationNameSize)
{
normalizedApplicationName = normalizedApplicationName[..maxApplicationNameSize];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Text.Json.Nodes;
using Aspire.Hosting.Azure.Resources;
using Aspire.Hosting.Azure.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand All @@ -19,15 +18,15 @@ namespace Aspire.Hosting.Azure.Provisioning.Internal;
internal sealed class RunModeProvisioningContextProvider(
IInteractionService interactionService,
IOptions<AzureProvisionerOptions> options,
IHostEnvironment environment,
IAppHostEnvironment appHostEnvironment,
ILogger<RunModeProvisioningContextProvider> logger,
IArmClientProvider armClientProvider,
IUserPrincipalProvider userPrincipalProvider,
ITokenCredentialProvider tokenCredentialProvider,
DistributedApplicationExecutionContext distributedApplicationExecutionContext) : BaseProvisioningContextProvider(
interactionService,
options,
environment,
appHostEnvironment,
logger,
armClientProvider,
userPrincipalProvider,
Expand All @@ -49,7 +48,7 @@ protected override string GetDefaultResourceGroupName()

var maxApplicationNameSize = ResourceGroupNameHelpers.MaxResourceGroupNameLength - prefix.Length - suffix.Length - 2; // extra '-'s

var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_environment.ApplicationName.ToLowerInvariant());
var normalizedApplicationName = ResourceGroupNameHelpers.NormalizeResourceGroupName(_appHostEnvironment.ProjectName.ToLowerInvariant());
if (normalizedApplicationName.Length > maxApplicationNameSize)
{
normalizedApplicationName = normalizedApplicationName[..maxApplicationNameSize];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ private sealed class Builder(SuspendingDistributedApplicationFactory factory, Di

public IHostEnvironment Environment => innerBuilder.Environment;

public IAppHostEnvironment AppHostEnvironment => innerBuilder.AppHostEnvironment;

public IServiceCollection Services => innerBuilder.Services;

public DistributedApplicationExecutionContext ExecutionContext => innerBuilder.ExecutionContext;
Expand Down Expand Up @@ -386,6 +388,8 @@ static Assembly FindApplicationAssembly()

public IHostEnvironment Environment => _innerBuilder.Environment;

public IAppHostEnvironment AppHostEnvironment => _innerBuilder.AppHostEnvironment;

public IServiceCollection Services => _innerBuilder.Services;

public DistributedApplicationExecutionContext ExecutionContext => _innerBuilder.ExecutionContext;
Expand Down
63 changes: 63 additions & 0 deletions src/Aspire.Hosting/AppHostEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Aspire.Hosting;

/// <summary>
/// Provides information about the AppHost environment.
/// </summary>
internal sealed class AppHostEnvironment : IAppHostEnvironment
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _hostEnvironment;

/// <summary>
/// Initializes a new instance of the <see cref="AppHostEnvironment"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="hostEnvironment">The host environment.</param>
public AppHostEnvironment(IConfiguration configuration, IHostEnvironment hostEnvironment)
{
_configuration = configuration;
_hostEnvironment = hostEnvironment;
}

/// <inheritdoc />
public string ProjectName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;

/// <inheritdoc />
public string ProjectDirectory => _configuration["AppHost:Directory"]!;

/// <inheritdoc />
public string FullPath => _configuration["AppHost:Path"]!;

/// <inheritdoc />
public string DashboardApplicationName => _configuration["AppHost:DashboardApplicationName"] ?? _hostEnvironment.ApplicationName;

/// <inheritdoc />
public string DefaultHash => _configuration["AppHost:Sha256"]!;

/// <inheritdoc />
public string FullPathHash => _configuration["AppHost:PathSha256"]!;

/// <inheritdoc />
public string ProjectNameHash => _configuration["AppHost:ProjectNameSha256"]!;

/// <inheritdoc />
public string? ContainerHostname => _configuration["AppHost:ContainerHostname"];

/// <inheritdoc />
public string? DefaultLaunchProfileName => _configuration["AppHost:DefaultLaunchProfileName"];

/// <inheritdoc />
public string? OtlpApiKey => _configuration["AppHost:OtlpApiKey"];

/// <inheritdoc />
public string? ResourceServiceApiKey => _configuration["AppHost:ResourceService:ApiKey"];

/// <inheritdoc />
public string? ResourceServiceAuthMode => _configuration["AppHost:ResourceService:AuthMode"];
}
7 changes: 4 additions & 3 deletions src/Aspire.Hosting/Dashboard/DashboardEventHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

namespace Aspire.Hosting.Dashboard;

internal sealed class DashboardEventHandlers(IConfiguration configuration,
internal sealed class DashboardEventHandlers(IAppHostEnvironment appHostEnvironment,
IConfiguration configuration,
IOptions<DashboardOptions> dashboardOptions,
ILogger<DistributedApplication> distributedApplicationLogger,
IDashboardEndpointProvider dashboardEndpointProvider,
Expand Down Expand Up @@ -525,8 +526,8 @@ internal async Task ConfigureEnvironmentVariables(EnvironmentCallbackContext con
}

// Configure resource service API key
if (string.Equals(configuration["AppHost:ResourceService:AuthMode"], nameof(ResourceServiceAuthMode.ApiKey), StringComparison.OrdinalIgnoreCase)
&& configuration["AppHost:ResourceService:ApiKey"] is { Length: > 0 } resourceServiceApiKey)
if (string.Equals(appHostEnvironment.ResourceServiceAuthMode, nameof(ResourceServiceAuthMode.ApiKey), StringComparison.OrdinalIgnoreCase)
&& appHostEnvironment.ResourceServiceApiKey is { Length: > 0 } resourceServiceApiKey)
{
context.EnvironmentVariables[DashboardConfigNames.ResourceServiceClientAuthModeName.EnvVarName] = nameof(ResourceServiceAuthMode.ApiKey);
context.EnvironmentVariables[DashboardConfigNames.ResourceServiceClientApiKeyName.EnvVarName] = resourceServiceApiKey;
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Hosting/Dashboard/DashboardOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal class DashboardOptions
public bool? TelemetryOptOut { get; set; }
}

internal class ConfigureDefaultDashboardOptions(IConfiguration configuration, IOptions<DcpOptions> dcpOptions) : IConfigureOptions<DashboardOptions>
internal class ConfigureDefaultDashboardOptions(IAppHostEnvironment appHostEnvironment, IOptions<DcpOptions> dcpOptions, IConfiguration configuration) : IConfigureOptions<DashboardOptions>
{
public void Configure(DashboardOptions options)
{
Expand All @@ -29,7 +29,7 @@ public void Configure(DashboardOptions options)

options.OtlpGrpcEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpGrpcEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpGrpcEndpointUrl);
options.OtlpHttpEndpointUrl = configuration.GetString(KnownConfigNames.DashboardOtlpHttpEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpHttpEndpointUrl);
options.OtlpApiKey = configuration["AppHost:OtlpApiKey"];
options.OtlpApiKey = appHostEnvironment.OtlpApiKey;

options.AspNetCoreEnvironment = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Production";

Expand Down
7 changes: 3 additions & 4 deletions src/Aspire.Hosting/Dashboard/DashboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Aspire.DashboardService.Proto.V1;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using static Aspire.Hosting.Interaction;
Expand All @@ -20,7 +19,7 @@ namespace Aspire.Hosting.Dashboard;
/// required beyond a single request. Longer-scoped data is stored in <see cref="DashboardServiceData"/>.
/// </remarks>
[Authorize(Policy = ResourceServiceApiKeyAuthorization.PolicyName)]
internal sealed partial class DashboardService(DashboardServiceData serviceData, IHostEnvironment hostEnvironment, IHostApplicationLifetime hostApplicationLifetime, IConfiguration configuration, ILogger<DashboardService> logger)
internal sealed partial class DashboardService(DashboardServiceData serviceData, IAppHostEnvironment appHostEnvironment, IHostApplicationLifetime hostApplicationLifetime, ILogger<DashboardService> logger)
: Aspire.DashboardService.Proto.V1.DashboardService.DashboardServiceBase
{
// gRPC has a maximum receive size of 4MB. Force logs into batches to avoid exceeding receive size.
Expand All @@ -38,8 +37,8 @@ public override Task<ApplicationInformationResponse> GetApplicationInformation(
ApplicationInformationRequest request,
ServerCallContext context)
{
// Read the application name from configuration if available, otherwise fall back to the environment
var applicationName = configuration["AppHost:DashboardApplicationName"] ?? hostEnvironment.ApplicationName;
// Use the dashboard application name from the AppHostEnvironment
var applicationName = appHostEnvironment.DashboardApplicationName;

return Task.FromResult(new ApplicationInformationResponse
{
Expand Down
9 changes: 6 additions & 3 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

#pragma warning disable ASPIREEXTENSION001
#pragma warning disable IDE0005 // IConfiguration is used in this file
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Data;
Expand Down Expand Up @@ -93,11 +94,12 @@ internal sealed partial class DcpExecutor : IDcpExecutor, IConsoleLogsService, I
private readonly record struct LogInformationEntry(string ResourceName, bool? LogsAvailable, bool? HasSubscribers);
private readonly Channel<LogInformationEntry> _logInformationChannel = Channel.CreateUnbounded<LogInformationEntry>(
new UnboundedChannelOptions { SingleReader = true });
private readonly IAppHostEnvironment _appHostEnvironment;

public DcpExecutor(ILogger<DcpExecutor> logger,
ILogger<DistributedApplication> distributedApplicationLogger,
DistributedApplicationModel model,
IHostEnvironment hostEnvironment,
IAppHostEnvironment appHostEnvironment,
IKubernetesService kubernetesService,
IConfiguration configuration,
IDistributedApplicationEventing distributedApplicationEventing,
Expand Down Expand Up @@ -126,16 +128,17 @@ public DcpExecutor(ILogger<DcpExecutor> logger,
_executionContext = executionContext;
_resourceState = new(model.Resources.ToDictionary(r => r.Name), _appResources);
_snapshotBuilder = new(_resourceState);
_normalizedApplicationName = NormalizeApplicationName(hostEnvironment.ApplicationName);
_normalizedApplicationName = NormalizeApplicationName(appHostEnvironment.ProjectName);
_locations = locations;
_developerCertificateService = developerCertificateService;
_appHostEnvironment = appHostEnvironment;

DeleteResourceRetryPipeline = DcpPipelineBuilder.BuildDeleteRetryPipeline(logger);
CreateServiceRetryPipeline = DcpPipelineBuilder.BuildCreateServiceRetryPipeline(options.Value, logger);
WatchResourceRetryPipeline = DcpPipelineBuilder.BuildWatchResourcePipeline(logger);
}

private string DefaultContainerHostName => _configuration["AppHost:ContainerHostname"] ?? _dcpInfo?.Containers?.ContainerHostName ?? "host.docker.internal";
private string DefaultContainerHostName => _appHostEnvironment.ContainerHostname ?? _dcpInfo?.Containers?.ContainerHostName ?? "host.docker.internal";

public async Task RunApplicationAsync(CancellationToken cancellationToken = default)
{
Expand Down
9 changes: 4 additions & 5 deletions src/Aspire.Hosting/Dcp/DcpNameGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Immutable;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.Dcp;
Expand All @@ -16,12 +15,12 @@ internal sealed class DcpNameGenerator
// The length of 8 achieves that while keeping the names relatively short and readable.
// The second purpose of the suffix is to play a role of a unique OpenTelemetry service instance ID.
private const int RandomNameSuffixLength = 8;
private readonly IConfiguration _configuration;
private readonly IAppHostEnvironment _appHostEnvironment;
private readonly IOptions<DcpOptions> _options;

public DcpNameGenerator(IConfiguration configuration, IOptions<DcpOptions> options)
public DcpNameGenerator(IAppHostEnvironment appHostEnvironment, IOptions<DcpOptions> options)
{
_configuration = configuration;
_appHostEnvironment = appHostEnvironment;
_options = options;
}

Expand Down Expand Up @@ -115,7 +114,7 @@ public static string GetRandomNameSuffix()
public string GetProjectHashSuffix()
{
// Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar resource names
var suffix = _configuration["AppHost:Sha256"]!.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
var suffix = _appHostEnvironment.DefaultHash.Substring(0, RandomNameSuffixLength).ToLowerInvariant();
return suffix;
}

Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@ public class DistributedApplicationBuilder : IDistributedApplicationBuilder

private readonly DistributedApplicationOptions _options;
private readonly HostApplicationBuilder _innerBuilder;
private readonly AppHostEnvironment _appHostEnvironment;

/// <inheritdoc />
public IHostEnvironment Environment => _innerBuilder.Environment;

/// <inheritdoc />
public IAppHostEnvironment AppHostEnvironment => _appHostEnvironment;

/// <inheritdoc />
public ConfigurationManager Configuration => _innerBuilder.Configuration;

Expand Down Expand Up @@ -466,6 +470,11 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
}

_innerBuilder.Services.AddSingleton(ExecutionContext);

// Initialize the AppHostEnvironment for use within the builder and register in DI
_appHostEnvironment = new AppHostEnvironment(_innerBuilder.Configuration, _innerBuilder.Environment);
_innerBuilder.Services.AddSingleton<IAppHostEnvironment>(_appHostEnvironment);

LogBuilderConstructed(this);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Aspire.Hosting/DistributedApplicationLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Aspire.Hosting;

internal sealed class DistributedApplicationLifecycle(
ILogger<DistributedApplication> logger,
IConfiguration configuration,
IAppHostEnvironment appHostEnvironment,
DistributedApplicationExecutionContext executionContext,
LocaleOverrideContext localeOverrideContext) : IHostedLifecycleService
{
Expand Down Expand Up @@ -41,7 +40,7 @@ public Task StartingAsync(CancellationToken cancellationToken)
if (executionContext.IsRunMode)
{
logger.LogInformation("Distributed application starting.");
logger.LogInformation("Application host directory is: {AppHostDirectory}", configuration["AppHost:Directory"]);
logger.LogInformation("Application host directory is: {AppHostDirectory}", appHostEnvironment.ProjectDirectory);
}

if (localeOverrideContext.OverrideErrorMessage is { Length: > 0 } localOverrideError)
Expand Down
Loading
Loading