Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
29 changes: 29 additions & 0 deletions src/Aspire.Cli/Backchannel/AppHostBackchannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ public async Task ConnectAsync(Process process, string socketPath, CancellationT
var stream = new NetworkStream(socket, true);
var rpc = JsonRpc.Attach(stream, target);

var capabilities = await rpc.InvokeWithCancellationAsync<string[]>(
"GetCapabilitiesAsync",
Array.Empty<object>(),
cancellationToken);

if (!capabilities.Any(s => s == "baseline.v0"))
{
throw new AppHostIncompatibleException(
$"AppHost is incompatible with the CLI. The AppHost must be updated to a version that supports the baseline.v0 capability.",
Comment thread
mitchdenny marked this conversation as resolved.
"baseline.v0"
);
}

_rpcTaskCompletionSource.SetResult(rpc);
}

Expand Down Expand Up @@ -145,4 +158,20 @@ public async Task<string[]> GetPublishersAsync(CancellationToken cancellationTok
yield return state;
}
}

public async Task<string[]> GetCapabilitiesAsync(CancellationToken cancellationToken)
{
using var activity = _activitySource.StartActivity();

var rpc = await _rpcTaskCompletionSource.Task.ConfigureAwait(false);

logger.LogDebug("Requesting capabilities");

var capabilities = await rpc.InvokeWithCancellationAsync<string[]>(
"GetCapabilitiesAsync",
Array.Empty<object>(),
cancellationToken).ConfigureAwait(false);

return capabilities;
}
}
9 changes: 9 additions & 0 deletions src/Aspire.Cli/Backchannel/AppHostIncompatibleException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Cli.Backchannel;

internal sealed class AppHostIncompatibleException(string message, string requiredCapability) : Exception(message)
{
public string RequiredCapability { get; } = requiredCapability;
}
8 changes: 4 additions & 4 deletions src/Aspire.Cli/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public NewCommand(DotNetCliRunner runner, INuGetPackageCache nuGetPackageCache)
}
else
{
return await PromptUtils.PromptForSelectionAsync(
return await InteractionUtils.PromptForSelectionAsync(
"Select a project template:",
validTemplates,
t => $"{t.TemplateName} ({t.TemplateDescription})",
Expand All @@ -84,7 +84,7 @@ private static async Task<string> GetProjectNameAsync(ParseResult parseResult, C
if (parseResult.GetValue<string>("--name") is not { } name)
{
var defaultName = new DirectoryInfo(Environment.CurrentDirectory).Name;
name = await PromptUtils.PromptForStringAsync("Enter the project name:",
name = await InteractionUtils.PromptForStringAsync("Enter the project name:",
defaultValue: defaultName,
cancellationToken: cancellationToken);
}
Expand All @@ -96,7 +96,7 @@ private static async Task<string> GetOutputPathAsync(ParseResult parseResult, st
{
if (parseResult.GetValue<string>("--output") is not { } outputPath)
{
outputPath = await PromptUtils.PromptForStringAsync(
outputPath = await InteractionUtils.PromptForStringAsync(
"Enter the output path:",
defaultValue: pathAppendage ?? ".",
cancellationToken: cancellationToken
Expand All @@ -114,7 +114,7 @@ private static async Task<string> GetProjectTemplatesVersionAsync(ParseResult pa
}
else
{
version = await PromptUtils.PromptForStringAsync(
version = await InteractionUtils.PromptForStringAsync(
"Project templates version:",
defaultValue: VersionHelper.GetDefaultTemplateVersion(),
validator: (string value) => {
Expand Down
351 changes: 179 additions & 172 deletions src/Aspire.Cli/Commands/PublishCommand.cs

Large diffs are not rendered by default.

279 changes: 143 additions & 136 deletions src/Aspire.Cli/Commands/RunCommand.cs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/Aspire.Cli/DotNetCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,22 @@ private async Task StartBackchannelAsync(Process process, string socketPath, Tas
// We don't want to spam the logs with our early connection attempts.
}
}
catch (AppHostIncompatibleException ex)
{
logger.LogError(
ex,
"AppHost is incompatible with the CLI. The AppHost must be updated to a version that supports the {RequiredCapability} capability.",
Comment thread
mitchdenny marked this conversation as resolved.
Outdated
ex.RequiredCapability
);

// If the app host is incompatable then there is no point
// trying to reconnect, we should propogate the exception
// up to the code that needs to back channel so it can display
// and error message to the user.
backchannelCompletionSource.SetException(ex);

throw;
}

} while (await timer.WaitForNextTickAsync(cancellationToken));
}
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Cli/ExitCodeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ internal static class ExitCodeConstants
public const int FailedToBuildArtifacts = 6;
public const int FailedToFindProject = 7;
public const int FailedToTrustCertificates = 8;
public const int AppHostIncompatible = 9;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Cli.Backchannel;
using Spectre.Console;

namespace Aspire.Cli.Utils;

internal static class PromptUtils
internal static class InteractionUtils
{
public static async Task<string> PromptForStringAsync(string promptText, string? defaultValue = null, Func<string, ValidationResult>? validator = null, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -42,4 +43,10 @@ public static async Task<T> PromptForSelectionAsync<T>(string promptText, IEnume

return await AnsiConsole.PromptAsync(prompt, cancellationToken);
}

public static int DisplayIncompatibleVersionError(AppHostIncompatibleException ex)
{
AnsiConsole.MarkupLine($"[red bold]:thumbs_down: The app host is not compatible. {ex.Message}[/]");
Comment thread
mitchdenny marked this conversation as resolved.
Outdated
return ExitCodeConstants.AppHostIncompatible;
}
}
27 changes: 27 additions & 0 deletions src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,31 @@ public async Task<string[]> GetPublishersAsync(CancellationToken cancellationTok
var publishers = e.Advertisements.Select(x => x.Name);
return [..publishers];
}

#pragma warning disable CA1822
public Task<string[]> GetCapabilitiesAsync(CancellationToken cancellationToken)
{
// The purpose of this API is to allow the CLI to determine what API surfaces
// the AppHost supports. In 9.2 we'll be saying that you need a 9.2 apphost,
// but the 9.3 CLI might actually support working with 9.2 apphosts. The idea
// is that when the backchannel is established the CLI will call this API
// and store the results. The "baseline.v0" capability is the bare minimum
// that we need as of CLI version 9.2-preview*.
//
// Some capabilties will be opt in. For example in 9.3 we might refine the
// publishing activities API to return more information, or add log streaming
// features. So that would add a new capability that the apphsot can report
// on initial backchannel negotiation and the CLI can adapt its behavior around
// that. There may be scenarios where we need to break compataiblity at which
Comment thread
mitchdenny marked this conversation as resolved.
// point we might increase the baseline version that the apphost reports.
//
// The ability to support a back channel at all is determined by the CLI by
// making sure that the apphost version is at least > 9.2.

_ = cancellationToken;
return Task.FromResult(new string[] {
"baseline.v0"
});
}
#pragma warning restore CA1822
}
Loading