Skip to content
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
43 changes: 39 additions & 4 deletions src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
using Aspire.Cli.Resources;
using Aspire.Cli.Telemetry;
using Aspire.Cli.Utils;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Logging;
using Spectre.Console;

namespace Aspire.Cli.Backchannel;

/// <summary>
Expand Down Expand Up @@ -43,6 +43,7 @@ internal sealed class AppHostConnectionResolver(
IInteractionService interactionService,
IProjectLocator projectLocator,
CliExecutionContext executionContext,
ICliHostEnvironment hostEnvironment,
ILogger logger,
ProfilingTelemetry? profilingTelemetry = null)
{
Expand Down Expand Up @@ -134,9 +135,18 @@ public async Task<AppHostConnectionResult> ResolveConnectionAsync(
};
}

var targetPath = projectFile.FullName;
// Resolve symlinks before computing the backchannel socket key so the lookup
// matches the path the running AppHost used. A running AppHost keys its socket
// off Path.GetFullPath(appHostPath) evaluated against its own process working
// directory, which the OS already reports in physical (symlink-resolved) form
// (getcwd canonicalizes, e.g. /tmp -> /private/tmp on macOS). The producer side
// therefore hashes the canonical path, so the consumer must resolve symlinks too.
// ResolveToFilesystemPath only fixes Windows casing and is a no-op on Linux/macOS,
// so it cannot bridge the /tmp -> /private/tmp gap; ResolveSymlinks does.
// See https://github.com/microsoft/aspire/issues/17618.
var socketLookupPath = PathNormalizer.ResolveSymlinks(projectFile.FullName);
var matchingSockets = AppHostHelper.FindMatchingNonOrphanedSockets(
targetPath,
socketLookupPath,
executionContext.HomeDirectory.FullName,
Environment.ProcessId,
logger);
Expand All @@ -161,7 +171,9 @@ public async Task<AppHostConnectionResult> ResolveConnectionAsync(
}
}

var displayPath = Path.GetRelativePath(executionContext.WorkingDirectory.FullName, targetPath);
// Display the path the user supplied (not the symlink-resolved lookup path) so the
// error message stays relative to the working directory and matches what they typed.
var displayPath = Path.GetRelativePath(executionContext.WorkingDirectory.FullName, projectFile.FullName);

return new AppHostConnectionResult
{
Expand Down Expand Up @@ -199,6 +211,17 @@ public async Task<AppHostConnectionResult> ResolveConnectionAsync(
}
else if (inScopeConnections.Count > 1)
{
if (!hostEnvironment.SupportsInteractiveInput)
{
// Can't prompt the user to pick an AppHost in non-interactive mode;
// fail with an actionable message instead of letting the prompt throw.
return new AppHostConnectionResult
{
ErrorMessage = SharedCommandStrings.MultipleAppHostsNonInteractive,
ExitCode = CliExitCodes.FailedToFindProject,
};
}

selectedConnection = await PromptForAppHostSelectionAsync(
inScopeConnections,
SharedCommandStrings.MultipleInScopeAppHosts,
Expand All @@ -208,6 +231,18 @@ public async Task<AppHostConnectionResult> ResolveConnectionAsync(
}
else if (outOfScopeConnections.Count > 0)
{
if (!hostEnvironment.SupportsInteractiveInput)
{
// No in-scope AppHosts, and selecting from out-of-scope AppHosts requires
// a prompt. In non-interactive mode treat this as "not found" so scripts
// get a clean error and exit code instead of an unexpected prompt failure.
return new AppHostConnectionResult
{
ErrorMessage = notFoundMessage,
ExitCode = CliExitCodes.FailedToFindProject,
};
}

selectedConnection = await PromptForAppHostSelectionAsync(
outOfScopeConnections,
SharedCommandStrings.NoInScopeAppHostsShowingAll,
Expand Down
4 changes: 3 additions & 1 deletion src/Aspire.Cli/Commands/CommonCommandServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ internal sealed class CommonCommandServices(
IInteractionService interactionService,
AspireCliTelemetry telemetry,
ConsoleCancellationManager cancellationManager,
ILoggerFactory loggerFactory)
ILoggerFactory loggerFactory,
ICliHostEnvironment hostEnvironment)
{
public IFeatures Features { get; } = features;
public ICliUpdateNotifier UpdateNotifier { get; } = updateNotifier;
Expand All @@ -25,4 +26,5 @@ internal sealed class CommonCommandServices(
public AspireCliTelemetry Telemetry { get; } = telemetry;
public ConsoleCancellationManager CancellationManager { get; } = cancellationManager;
public ILoggerFactory LoggerFactory { get; } = loggerFactory;
public ICliHostEnvironment HostEnvironment { get; } = hostEnvironment;
}
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/DescribeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public DescribeCommand(
{
Aliases.Add("resources");
_resourceColorMap = resourceColorMap;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/ExportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public ExportCommand(
_httpClientFactory = httpClientFactory;
_timeProvider = timeProvider;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/LogsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public LogsCommand(
_resourceColorMap = resourceColorMap;
_hostEnvironment = hostEnvironment;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger, profilingTelemetry);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger, profilingTelemetry);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/McpCallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public McpCallCommand(
CommonCommandServices services)
: base("call", McpCommandStrings.CallCommand_Description, services)
{
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Arguments.Add(s_toolArgument);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/McpToolsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public McpToolsCommand(
CommonCommandServices services)
: base("tools", McpCommandStrings.ToolsCommand_Description, services)
{
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Options.Add(s_appHostOption);
Options.Add(s_formatOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/ResourceCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public ResourceCommand(
{
_backchannelMonitor = backchannelMonitor;
_projectLocator = projectLocator;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);
_logger = logger;

Arguments.Add(s_resourceArgument);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/StopCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public StopCommand(
CommonCommandServices services)
: base("stop", StopCommandStrings.Description, services)
{
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, logger, profilingTelemetry);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger, profilingTelemetry);
_hostEnvironment = hostEnvironment;
_processShutdownService = processShutdownService;
_logger = logger;
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/TelemetryLogsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public TelemetryLogsCommand(
_resourceColorMap = resourceColorMap;
_timeProvider = timeProvider;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/TelemetrySpansCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public TelemetrySpansCommand(
_resourceColorMap = resourceColorMap;
_timeProvider = timeProvider;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/TelemetryTracesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public TelemetryTracesCommand(
_resourceColorMap = resourceColorMap;
_timeProvider = timeProvider;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/TerminalAttachCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public TerminalAttachCommand(
{
_interactionService = services.InteractionService;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Arguments.Add(s_resourceArgument);
Options.Add(s_appHostOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/TerminalPsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public TerminalPsCommand(
{
_interactionService = services.InteractionService;
_logger = logger;
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger);

Options.Add(s_appHostOption);
Options.Add(s_formatOption);
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Cli/Commands/WaitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public WaitCommand(
TimeProvider? timeProvider = null)
: base("wait", WaitCommandStrings.Description, services)
{
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, ExecutionContext, logger);
_connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, ExecutionContext, services.HostEnvironment, logger);
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;

Expand Down
14 changes: 14 additions & 0 deletions src/Aspire.Cli/Projects/AppHostServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,18 @@ public async Task<AppHostServerSessionResult> CreateAsync(
BuildOutput: prepareResult.Output,
ChannelName: prepareResult.ChannelName);
}

/// <inheritdoc />
public IAppHostServerSession Start(
IAppHostServerProject appHostServerProject,
Dictionary<string, string>? environmentVariables,
bool debug)
{
return AppHostServerSession.Start(
appHostServerProject,
environmentVariables,
debug,
_logger,
_profilingTelemetry);
}
}
Loading
Loading