diff --git a/src/Aspire.Cli/Aspire.Cli.csproj b/src/Aspire.Cli/Aspire.Cli.csproj index dad1e557e4a..a7ce3f951e4 100644 --- a/src/Aspire.Cli/Aspire.Cli.csproj +++ b/src/Aspire.Cli/Aspire.Cli.csproj @@ -163,11 +163,6 @@ - - True - True - ExecCommandStrings.resx - True True @@ -273,10 +268,6 @@ - - ResXFileCodeGenerator - ExecCommandStrings.Designer.cs - Resx EmbeddedResource diff --git a/src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs b/src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs index e3c1238230f..bde7741825f 100644 --- a/src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs +++ b/src/Aspire.Cli/Backchannel/AppHostCliBackchannel.cs @@ -23,7 +23,6 @@ internal interface IAppHostCliBackchannel Task GetCapabilitiesAsync(CancellationToken cancellationToken); Task CompletePromptResponseAsync(string promptId, PublishingPromptInputAnswer[] answers, CancellationToken cancellationToken); Task UpdatePromptResponseAsync(string promptId, PublishingPromptInputAnswer[] answers, CancellationToken cancellationToken); - IAsyncEnumerable ExecAsync(CancellationToken cancellationToken); Task GetPipelineStepsAsync(string? step, CancellationToken cancellationToken); } @@ -477,24 +476,6 @@ await rpc.InvokeWithCancellationAsync( cancellationToken).ConfigureAwait(false); } - public async IAsyncEnumerable ExecAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - using var activity = telemetry.StartDiagnosticActivity(); - var rpc = await GetRpcTaskAsync().WaitAsync(cancellationToken).ConfigureAwait(false); - - logger.LogDebug("Requesting execution."); - var commandOutputs = await rpc.InvokeWithCancellationAsync>( - "ExecAsync", - Array.Empty(), - cancellationToken); - - logger.LogDebug("Requested execution."); - await foreach (var commandOutput in commandOutputs.WithCancellation(cancellationToken)) - { - yield return commandOutput; - } - } - public async Task GetPipelineStepsAsync(string? step, CancellationToken cancellationToken) { using var activity = telemetry.StartDiagnosticActivity(); diff --git a/src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs b/src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs index a0601fea16b..ddbff435ff1 100644 --- a/src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs +++ b/src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs @@ -33,8 +33,6 @@ namespace Aspire.Cli.Backchannel; [JsonSerializable(typeof(IEnumerable))] [JsonSerializable(typeof(PublishingPromptInputAnswer[]))] [JsonSerializable(typeof(ValidationResult))] -[JsonSerializable(typeof(IAsyncEnumerable))] -[JsonSerializable(typeof(MessageFormatterEnumerableTracker.EnumeratorResults))] [JsonSerializable(typeof(EnvVar))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(List))] diff --git a/src/Aspire.Cli/Commands/ExecCommand.cs b/src/Aspire.Cli/Commands/ExecCommand.cs deleted file mode 100644 index 7de4da0f226..00000000000 --- a/src/Aspire.Cli/Commands/ExecCommand.cs +++ /dev/null @@ -1,358 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CommandLine; -using System.Globalization; -using Aspire.Cli.Backchannel; -using Aspire.Cli.Certificates; -using Aspire.Cli.Configuration; -using Aspire.Cli.DotNet; -using Aspire.Cli.Interaction; -using Aspire.Cli.Projects; -using Aspire.Cli.Resources; -using Aspire.Cli.Telemetry; -using Aspire.Cli.Utils; -using Aspire.Hosting; - -namespace Aspire.Cli.Commands; - -internal class ExecCommand : BaseCommand -{ - private readonly IDotNetCliRunner _runner; - private readonly ICertificateService _certificateService; - private readonly IProjectLocator _projectLocator; - private readonly IDotNetSdkInstaller _sdkInstaller; - - private static readonly OptionWithLegacy s_appHostOption = new("--apphost", "--project", ExecCommandStrings.ProjectArgumentDescription); - private static readonly Option s_resourceOption = new("--resource", "-r") - { - Description = ExecCommandStrings.TargetResourceArgumentDescription - }; - private static readonly Option s_startResourceOption = new("--start-resource", "-s") - { - Description = ExecCommandStrings.StartTargetResourceArgumentDescription - }; - private static readonly Option s_workdirOption = new("--workdir", "-w") - { - Description = ExecCommandStrings.WorkdirArgumentDescription - }; - private static readonly Option s_commandOption = new("--") - { - Description = ExecCommandStrings.CommandArgumentDescription - }; - - public ExecCommand( - IDotNetCliRunner runner, - IInteractionService interactionService, - ICertificateService certificateService, - IProjectLocator projectLocator, - AspireCliTelemetry telemetry, - IDotNetSdkInstaller sdkInstaller, - IFeatures features, - ICliUpdateNotifier updateNotifier, - CliExecutionContext executionContext) - : base("exec", ExecCommandStrings.Description, features, updateNotifier, executionContext, interactionService, telemetry) - { - _runner = runner; - _certificateService = certificateService; - _projectLocator = projectLocator; - _sdkInstaller = sdkInstaller; - - Options.Add(s_appHostOption); - Options.Add(s_resourceOption); - Options.Add(s_startResourceOption); - Options.Add(s_workdirOption); - // only for --help output - Options.Add(s_commandOption); - - TreatUnmatchedTokensAsErrors = false; - } - - protected override async Task ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) - { - // Check if the .NET SDK is available - if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, Telemetry, cancellationToken: cancellationToken)) - { - return ExitCodeConstants.SdkNotInstalled; - } - - // validate required arguments firstly to fail fast if not found - var targetResourceMode = "--resource"; - var targetResource = parseResult.GetValue(s_resourceOption); - if (string.IsNullOrEmpty(targetResource)) - { - targetResourceMode = "--start-resource"; - targetResource = parseResult.GetValue(s_startResourceOption); - } - - if (targetResource is null) - { - InteractionService.DisplayError(ExecCommandStrings.TargetResourceNotSpecified); - return ExitCodeConstants.InvalidCommand; - } - - // unmatched tokens are those which will be tried to parse as command. - // if none - we should fail fast - if (parseResult.UnmatchedTokens.Count == 0) - { - InteractionService.DisplayError(ExecCommandStrings.NoCommandSpecified); - return ExitCodeConstants.InvalidCommand; - } - - var (arbitraryFlags, commandTokens) = ParseCmdArgs(parseResult); - - if (commandTokens is null || commandTokens.Count == 0) - { - InteractionService.DisplayError(ExecCommandStrings.FailedToParseCommand); - return ExitCodeConstants.InvalidCommand; - } - - var buildOutputCollector = new OutputCollector(); - var runOutputCollector = new OutputCollector(); - - IAppHostCliBackchannel? backchannel = null; - Task? pendingRun = null; - int? commandExitCode = null; - - (bool IsCompatibleAppHost, bool SupportsBackchannel, string? AspireHostingVersion)? appHostCompatibilityCheck = null; - try - { - using var activity = Telemetry.StartDiagnosticActivity(this.Name); - - var passedAppHostProjectFile = parseResult.GetValue(s_appHostOption); - var effectiveAppHostProjectFile = await _projectLocator.UseOrFindAppHostProjectFileAsync(passedAppHostProjectFile, createSettingsFile: true, cancellationToken); - - if (effectiveAppHostProjectFile is null) - { - return ExitCodeConstants.FailedToFindProject; - } - - if (string.Equals(effectiveAppHostProjectFile.Extension, ".cs", StringComparison.OrdinalIgnoreCase)) - { - InteractionService.DisplayError(ErrorStrings.CommandNotSupportedWithSingleFileAppHost); - return ExitCodeConstants.SingleFileAppHostNotSupported; - } - - var env = new Dictionary(); - - var waitForDebugger = parseResult.GetValue(RootCommand.WaitForDebuggerOption); - if (waitForDebugger) - { - env[KnownConfigNames.WaitForDebugger] = "true"; - } - - appHostCompatibilityCheck = await AppHostHelper.CheckAppHostCompatibilityAsync(_runner, InteractionService, effectiveAppHostProjectFile, Telemetry, ExecutionContext.WorkingDirectory, ExecutionContext.LogFilePath, cancellationToken); - if (!appHostCompatibilityCheck?.IsCompatibleAppHost ?? throw new InvalidOperationException(RunCommandStrings.IsCompatibleAppHostIsNull)) - { - return ExitCodeConstants.FailedToDotnetRunAppHost; - } - - var runOptions = new ProcessInvocationOptions - { - StandardOutputCallback = runOutputCollector.AppendOutput, - StandardErrorCallback = runOutputCollector.AppendError, - }; - - string[] args = [ - "--operation", "run", - targetResourceMode, targetResource!, - - // a bit hacky, but in order to pass a full command with possible quotes and etc properly without losing the signature - // we can wrap it in a string and pass it as a single argument - "--command", $"\"{string.Join(" ", commandTokens)}\"", - - ..arbitraryFlags, - ]; - - try - { - var backchannelCompletionSource = new TaskCompletionSource(); - pendingRun = _runner.RunAsync( - projectFile: effectiveAppHostProjectFile, - watch: false, - noBuild: false, - noRestore: false, - args: args, - env: env, - backchannelCompletionSource: backchannelCompletionSource, - options: runOptions, - cancellationToken: cancellationToken); - - // We wait for the back channel to be created to signal that - // the AppHost is ready to accept requests. - backchannel = await InteractionService.ShowStatusAsync( - RunCommandStrings.StartingAppHost, - async () => - { - // If we use the --wait-for-debugger option we print out the process ID - // of the apphost so that the user can attach to it. - if (waitForDebugger) - { - InteractionService.DisplayMessage(KnownEmojis.Bug, InteractionServiceStrings.WaitingForDebuggerToAttachToAppHost); - } - - // The wait for the debugger in the apphost is done inside the CreateBuilder(...) method - // before the backchannel is created, therefore waiting on the backchannel is a - // good signal that the debugger was attached (or timed out). - var backchannel = await backchannelCompletionSource.Task.WaitAsync(cancellationToken); - return backchannel; - }, emoji: KnownEmojis.LinkedPaperclips); - - commandExitCode = await InteractionService.ShowStatusAsync( - ExecCommandStrings.Running, - async () => - { - // execute tool and stream the output - int? exitCode = null; - var outputStream = backchannel.ExecAsync(cancellationToken); - await foreach (var output in outputStream) - { - InteractionService.WriteConsoleLog(output.Text, output.LineNumber, output.Type, output.IsErrorMessage); - if (output.ExitCode is not null) - { - exitCode = output.ExitCode; - } - } - - return exitCode; - }, emoji: KnownEmojis.RunningShoe); - } - finally - { - if (backchannel is not null) - { - _ = await InteractionService.ShowStatusAsync( - ExecCommandStrings.StoppingAppHost, - async () => - { - await backchannel.RequestStopAsync(cancellationToken); - return ExitCodeConstants.Success; - }, emoji: KnownEmojis.LinkedPaperclips); - } - } - - if (commandExitCode is not null) - { - // if there is a deterministic output of the command with exit code - we should display that - return commandExitCode.Value; - } - - if (pendingRun is not null) - { - var result = await pendingRun; - if (result != 0) - { - InteractionService.DisplayLines(runOutputCollector.GetLines()); - InteractionService.DisplayError(string.Format(CultureInfo.CurrentCulture, RunCommandStrings.ProjectCouldNotBeRun, ExecutionContext.LogFilePath)); - return result; - } - else - { - return ExitCodeConstants.Success; - } - } - else - { - InteractionService.DisplayLines(runOutputCollector.GetLines()); - InteractionService.DisplayError(string.Format(CultureInfo.CurrentCulture, RunCommandStrings.ProjectCouldNotBeRun, ExecutionContext.LogFilePath)); - return ExitCodeConstants.FailedToDotnetRunAppHost; - } - } - catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationToken) - { - InteractionService.DisplayCancellationMessage(); - return ExitCodeConstants.Success; - } - catch (ProjectLocatorException ex) - { - return HandleProjectLocatorException(ex, InteractionService, Telemetry); - } - catch (AppHostIncompatibleException ex) - { - Telemetry.RecordError(ex.Message, ex); - return InteractionService.DisplayIncompatibleVersionError( - ex, - appHostCompatibilityCheck?.AspireHostingVersion ?? throw new InvalidOperationException(ErrorStrings.AspireHostingVersionNull) - ); - } - catch (CertificateServiceException ex) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, TemplatingStrings.CertificateTrustError, ex.Message); - Telemetry.RecordError(errorMessage, ex); - InteractionService.DisplayError(errorMessage); - return ExitCodeConstants.FailedToTrustCertificates; - } - catch (FailedToConnectBackchannelConnection ex) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.ErrorConnectingToAppHost, ex.Message); - Telemetry.RecordError(errorMessage, ex); - InteractionService.DisplayError(errorMessage); - InteractionService.DisplayLines(runOutputCollector.GetLines()); - return ExitCodeConstants.FailedToDotnetRunAppHost; - } - catch (Exception ex) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.UnexpectedErrorOccurred, ex.Message); - Telemetry.RecordError(errorMessage, ex); - InteractionService.DisplayError(errorMessage); - InteractionService.DisplayLines(runOutputCollector.GetLines()); - return ExitCodeConstants.FailedToDotnetRunAppHost; - } - } - - private (ICollection arbitary, ICollection command) ParseCmdArgs(ParseResult parseResult) - { - var allTokens = parseResult.UnmatchedTokens.ToList(); - int delimiterIndex = allTokens.IndexOf("--"); - List arbitraryFlags = new(); - List commandTokens = new(); - - // Find the index of the first token that is not an option (doesn't start with '-') and is not the value for a known option - // We'll use the options defined in this command to skip known option values - var knownOptions = new HashSet(Options.SelectMany(o => o.Aliases)); - int i = 0; - while (i < allTokens.Count) - { - if (delimiterIndex >= 0 && i == delimiterIndex) - { - // Everything after -- is command - commandTokens.AddRange(allTokens.Skip(i + 1)); - break; - } - - var token = allTokens[i]; - if (knownOptions.Contains(token)) - { - // Skip the option and its value (if it has one) - var option = Options.FirstOrDefault(o => o.Aliases.Contains(token)); - if (option is not null) - { - // If the option is not a bool, it expects a value - var isFlag = option.Arity.MaximumNumberOfValues == 0; - if (!isFlag && i + 1 < allTokens.Count) - { - i += 2; - continue; - } - } - i++; - continue; - } - else if (token.StartsWith("-")) - { - // Arbitrary flag - arbitraryFlags.Add(token); - i++; - continue; - } - else - { - // First non-option, non-flag token is the start of the command (if not using --) - commandTokens.AddRange(allTokens.Skip(i)); - break; - } - } - - return (arbitraryFlags, commandTokens); - } -} diff --git a/src/Aspire.Cli/Commands/RootCommand.cs b/src/Aspire.Cli/Commands/RootCommand.cs index 766ba10616c..c3cf893e0d8 100644 --- a/src/Aspire.Cli/Commands/RootCommand.cs +++ b/src/Aspire.Cli/Commands/RootCommand.cs @@ -13,7 +13,6 @@ using Aspire.Cli.Bundles; using Aspire.Cli.Commands.Sdk; -using Aspire.Cli.Configuration; using Aspire.Cli.Interaction; using Aspire.Cli.Resources; using BaseRootCommand = System.CommandLine.RootCommand; @@ -128,7 +127,6 @@ public RootCommand( CacheCommand cacheCommand, CertificatesCommand certificatesCommand, DoctorCommand doctorCommand, - ExecCommand execCommand, UpdateCommand updateCommand, McpCommand mcpCommand, AgentCommand agentCommand, @@ -145,7 +143,6 @@ public RootCommand( #endif ExtensionInternalCommand extensionInternalCommand, IBundleService bundleService, - IFeatures featureFlags, IInteractionService interactionService, IAnsiConsole ansiConsole) : base(RootCommandStrings.Description) @@ -241,11 +238,6 @@ public RootCommand( Subcommands.Add(setupCommand); } - if (featureFlags.IsFeatureEnabled(KnownFeatures.ExecCommandEnabled, false)) - { - Subcommands.Add(execCommand); - } - Subcommands.Add(sdkCommand); Subcommands.Add(restoreCommand); diff --git a/src/Aspire.Cli/KnownFeatures.cs b/src/Aspire.Cli/KnownFeatures.cs index ea429c22878..b7f8769d79d 100644 --- a/src/Aspire.Cli/KnownFeatures.cs +++ b/src/Aspire.Cli/KnownFeatures.cs @@ -20,7 +20,6 @@ internal static class KnownFeatures { public static string FeaturePrefix => "features"; public static string UpdateNotificationsEnabled => "updateNotificationsEnabled"; - public static string ExecCommandEnabled => "execCommandEnabled"; public static string ShowDeprecatedPackages => "showDeprecatedPackages"; public static string StagingChannelEnabled => "stagingChannelEnabled"; public static string DefaultWatchEnabled => "defaultWatchEnabled"; @@ -37,12 +36,7 @@ internal static class KnownFeatures UpdateNotificationsEnabled, "Check if update notifications are disabled and set version check environment variable", DefaultValue: true), - - [ExecCommandEnabled] = new( - ExecCommandEnabled, - "Enable or disable the 'aspire exec' command for executing commands inside running resources", - DefaultValue: false), - + [ShowDeprecatedPackages] = new( ShowDeprecatedPackages, "Show or hide deprecated packages in 'aspire add' search results", diff --git a/src/Aspire.Cli/Program.cs b/src/Aspire.Cli/Program.cs index 9cd18229506..1851993eaea 100644 --- a/src/Aspire.Cli/Program.cs +++ b/src/Aspire.Cli/Program.cs @@ -501,7 +501,6 @@ internal static async Task BuildApplicationAsync(string[] args, CliStartu builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); diff --git a/src/Aspire.Cli/Resources/ExecCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/ExecCommandStrings.Designer.cs deleted file mode 100644 index d772e9e0a6f..00000000000 --- a/src/Aspire.Cli/Resources/ExecCommandStrings.Designer.cs +++ /dev/null @@ -1,168 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Aspire.Cli.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class ExecCommandStrings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ExecCommandStrings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Aspire.Cli.Resources.ExecCommandStrings", typeof(ExecCommandStrings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator.. - /// - internal static string CommandArgumentDescription { - get { - return ResourceManager.GetString("CommandArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Execute commands in the context of an Aspire application resource. Starts the apphost, waits for resources to initialize, then runs the specified command in the target resource's environment. (Preview) - /// - ///Examples: - /// aspire exec --resource api dotnet build - /// aspire exec --resource api -- dotnet test --logger console - /// aspire exec --start-resource worker pwsh -c "Get-Process" - /// . - /// - internal static string Description { - get { - return ResourceManager.GetString("Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to parse the command. Ensure the command is specified after all options or after the -- separator.. - /// - internal static string FailedToParseCommand { - get { - return ResourceManager.GetString("FailedToParseCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command is not specified.. - /// - internal static string NoCommandSpecified { - get { - return ResourceManager.GetString("NoCommandSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The path to the Aspire apphost project file. If not specified, searches for a project file in the current directory.. - /// - internal static string ProjectArgumentDescription { - get { - return ResourceManager.GetString("ProjectArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Running exec.... - /// - internal static string Running { - get { - return ResourceManager.GetString("Running", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running.. - /// - internal static string StartTargetResourceArgumentDescription { - get { - return ResourceManager.GetString("StartTargetResourceArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Stopping apphost.... - /// - internal static string StoppingAppHost { - get { - return ResourceManager.GetString("StoppingAppHost", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name of the target resource to execute the command against. The command will be executed as soon as the apphost starts, without waiting for the resource to be ready.. - /// - internal static string TargetResourceArgumentDescription { - get { - return ResourceManager.GetString("TargetResourceArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Target resource is not specified. Use --resource or --start-resource to specify the target.. - /// - internal static string TargetResourceNotSpecified { - get { - return ResourceManager.GetString("TargetResourceNotSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The working directory to execute command in.. - /// - internal static string WorkdirArgumentDescription { - get { - return ResourceManager.GetString("WorkdirArgumentDescription", resourceCulture); - } - } - } -} diff --git a/src/Aspire.Cli/Resources/ExecCommandStrings.resx b/src/Aspire.Cli/Resources/ExecCommandStrings.resx deleted file mode 100644 index 05771150028..00000000000 --- a/src/Aspire.Cli/Resources/ExecCommandStrings.resx +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - - - Command is not specified. - - - Running exec... - - - Stopping AppHost... - - - The working directory to execute the command in - - diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.cs.xlf deleted file mode 100644 index abddf73631b..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.cs.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Příkaz, který se má provést v kontextu cílového prostředku. Příkazy lze zadat přímo po parametrech nebo za oddělovačem --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Umožňuje spouštět příkazy v kontextu prostředku aplikace Aspire. Spustí AppHost, počká na inicializaci prostředků a pak spustí zadaný příkaz v prostředí cílového prostředku. (Preview) - -Příklady: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Nepovedlo se parsovat příkaz. Ujistěte se, že je příkaz zadán po všech parametrech nebo po oddělovači --. - - - - Command is not specified. - Příkaz není zadán. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Cesta k souboru projektu Aspire AppHost. Pokud není zadána, vyhledá se soubor projektu v aktuálním adresáři. - - - - Running exec... - Spouští se příkaz exec... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Název cílového prostředku, proti kterému má být příkaz proveden. Příkaz se spustí až po úspěšném spuštění cílového prostředku. - - - - Stopping AppHost... - Zastavuje se hostitel aplikací... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Název cílového prostředku, proti kterému má být příkaz proveden. Příkaz se spustí ihned po spuštění AppHost, aniž by se čekalo, až bude prostředek připravený. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - Cílový prostředek není zadaný. K určení cíle použijte --resource nebo --start-resource. - - - - The working directory to execute the command in - Pracovní adresář, ve kterém se má příkaz spustit - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.de.xlf deleted file mode 100644 index d258933705b..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.de.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Der Befehl, der im Kontext der Zielressource ausgeführt werden soll. Befehle können direkt nach Optionen oder nach einem -- Trennzeichen angegeben werden. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Führen Sie Befehle im Kontext einer Aspire-Anwendungsressource aus. Startet den AppHost, wartet auf die Initialisierung von Ressourcen und führt dann den angegebenen Befehl in der Umgebung der Zielressource aus. (Vorschau) - -Beispiele: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Fehler beim Analysieren des Befehls. Stellen Sie sicher, dass der Befehl nach allen Optionen oder nach dem -- Trennzeichen angegeben wird. - - - - Command is not specified. - Der Befehl wurde nicht angegeben. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Der Pfad zur Aspire AppHost-Projektdatei. Wenn keine Angabe erfolgt, wird im aktuellen Verzeichnis nach einer Projektdatei gesucht. - - - - Running exec... - Ausführung wird ausgeführt... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Der Name der Zielressource, für die der Befehl ausgeführt werden soll. Der Befehl wird erst ausgeführt, nachdem die Zielressource erfolgreich gestartet wurde und ausgeführt wird. - - - - Stopping AppHost... - App-Host wird beendet... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Der Name der Zielressource, für die der Befehl ausgeführt werden soll. Der Befehl wird ausgeführt, sobald der AppHost gestartet wird, ohne darauf zu warten, dass die Ressource bereit ist. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - Die Zielressource wurde nicht angegeben. Verwenden Sie „--resource“ oder „--start-resource“, um das Ziel anzugeben. - - - - The working directory to execute the command in - Das Arbeitsverzeichnis, in dem der Befehl ausgeführt wird. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.es.xlf deleted file mode 100644 index 38b9b823e75..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.es.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Comando que se va a ejecutar en el contexto del recurso de destino. Los comandos se pueden especificar directamente después de las opciones o después de un separador --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Ejecute comandos en el contexto de un recurso de aplicación Aspire. Inicie AppHost, espere a que se inicialicen los recursos y a continuación, ejecute el comando especificado en el entorno del recurso de destino. (Versión preliminar) - -Ejemplos: - exec de --resource api dotnet build - exec de --resource api -- dotnet test --logger console - exec de --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - No se pudo analizar el comando. Asegúrese de que el comando se especifica después de todas las opciones o después del separador --. - - - - Command is not specified. - El comando no se especificó. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - La ruta de acceso al archivo del proyecto host de la aplicación Aspire. Si no se especifica, se busca un archivo de proyecto en el directorio actual. - - - - Running exec... - Ejecutando el comando... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - El nombre del recurso de destino en el que se ejecutará el comando. El comando solo se ejecutará después de que el recurso de destino se haya iniciado correctamente y se esté ejecutando. - - - - Stopping AppHost... - Deteniendo el host de aplicaciones... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - El nombre del recurso de destino en el que se ejecutará el comando. El comando se ejecutará en cuanto se inicie AppHost, sin tener que esperar a que el recurso esté listo. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - No se especificó el recurso de destino. Use --resource o --start-resource para especificar el destino. - - - - The working directory to execute the command in - Directorio de trabajo en el que se va a ejecutar el comando. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.fr.xlf deleted file mode 100644 index 391038e8633..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.fr.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Commande à exécuter dans le contexte de la ressource cible. Vous pouvez spécifier les commandes directement après les options ou après un séparateur -- . - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Exécutez des commandes dans le contexte d’une ressource d’application Aspire. Démarre l’AppHost, attend l’initialisation des ressources, puis exécute la commande spécifiée dans l’environnement de la ressource cible. (Préversion) - -Exemples : - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Nous n’avons pas pu analyser la commande. Vérifiez que la commande est spécifiée après toutes les options ou après le séparateur --. - - - - Command is not specified. - La commande n’est pas spécifiée. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Chemin d’accès au fichier projet AppHost Aspire. Si rien n’est spécifié, recherche un fichier projet dans le répertoire actif. - - - - Running exec... - Exécution en cours... Merci de patienter. - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Nom de la ressource cible sur laquelle exécuter la commande. La commande ne sera exécutée qu’une fois que la ressource cible aura correctement démarré et sera en cours d’exécution. - - - - Stopping AppHost... - Arrêt en cours de l’application... Merci de patienter. - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Nom de la ressource cible sur laquelle exécuter la commande. La commande est exécutée dès le démarrage de l’AppHost, sans attendre que la ressource soit prête. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - La ressource cible n’est pas spécifiée. Utilisez --resource ou --start-resource pour spécifier la cible. - - - - The working directory to execute the command in - Répertoire de travail dans lequel exécuter la commande. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.it.xlf deleted file mode 100644 index 220b832ec4c..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.it.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Il comando da eseguire nel contesto della risorsa di destinazione. I comandi possono essere specificati direttamente dopo le opzioni o dopo un separatore --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Eseguire comandi nel contesto di una risorsa dell'applicazione Aspire. Avvia AppHost, attende che le risorse si inizializzino, quindi esegue il comando specificato nell'ambiente della risorsa di destinazione. (anteprima) - -Esempi: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Analisi del comando non riuscita. Assicurarsi che il comando sia specificato dopo tutte le opzioni o dopo il separatore --. - - - - Command is not specified. - Comando non specificato. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Percorso del file di un progetto AppHost di Aspire. Se non specificato, cerca un file di progetto nella directory corrente. - - - - Running exec... - Esecuzione in corso... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Nome della risorsa di destinazione rispetto al quale eseguire il comando. Il comando verrà eseguito solo dopo che la risorsa di destinazione è stata avviata correttamente ed è in esecuzione. - - - - Stopping AppHost... - Arresto dell'host delle app... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Nome della risorsa di destinazione rispetto al quale eseguire il comando. Il comando verrà eseguito non appena AppHost si avvia, senza attendere che la risorsa sia pronta. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - La risorsa di destinazione non è specificata. Utilizzare --resource o --start-resource per specificare la destinazione. - - - - The working directory to execute the command in - La directory di lavoro in cui eseguire il comando. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ja.xlf deleted file mode 100644 index 1e5a79d3152..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ja.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - ターゲット リソースのコンテキストで実行するコマンド。コマンドは、オプションの直後または -- 区切り記号の後に指定できます。 - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Aspire アプリケーション リソースのコンテキストでコマンドを実行します。AppHost を開始し、リソースが初期化されるのを待ってから、ターゲット リソースの環境で指定されたコマンドを実行します。(プレビュー) - -例: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - コマンドを解析できませんでした。すべてのオプションの後または -- 区切り記号の後にコマンドが指定されていることを確認します。 - - - - Command is not specified. - コマンドが指定されていません。 - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Aspire AppHost プロセス プロジェクト ファイルへのパス。指定しない場合は、現在のディレクトリ内のプロジェクトファイルを検索します。 - - - - Running exec... - exec を実行しています... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - コマンドを実行するターゲット リソースの名前。このコマンドは、ターゲットリソースが正常に起動し、実行中である後にのみ実行されます。 - - - - Stopping AppHost... - アプリ ホストを停止しています... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - コマンドを実行するターゲット リソースの名前。このコマンドは、リソースの準備が整うのを待たずに、AppHost が起動するとすぐに実行されます。 - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - ターゲット リソースが指定されていません。ターゲットを指定するには、--resource または --start-resource を使用します。 - - - - The working directory to execute the command in - コマンドを実行する作業ディレクトリ。 - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ko.xlf deleted file mode 100644 index 19591b3538d..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ko.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - 대상 리소스의 컨텍스트에서 실행할 명령입니다. 명령은 옵션 뒤에 바로 지정하거나 -- 구분 기호 뒤에 지정할 수 있습니다. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Aspire 애플리케이션 리소스의 컨텍스트에서 명령을 실행합니다. AppHost를 시작하고 리소스가 초기화될 때까지 기다린 후, 대상 리소스 환경에서 지정된 명령을 실행합니다. (미리 보기) - -예: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - 명령을 구문 분석하지 못했습니다. 명령이 모든 옵션 뒤에 또는 -- 구분 기호 뒤에 지정되어 있는지 확인하세요. - - - - Command is not specified. - 명령을 지정하지 않았습니다. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Aspire AppHost 프로젝트 파일의 경로입니다. 지정하지 않으면 현재 디렉터리에서 프로젝트 파일을 검색합니다. - - - - Running exec... - 실행 중... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - 명령을 실행할 대상 리소스의 이름입니다. 명령은 대상 리소스가 성공적으로 시작되고 실행된 후에만 실행됩니다. - - - - Stopping AppHost... - 앱 호스트를 중지하는 중... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - 명령을 실행할 대상 리소스의 이름입니다. 명령은 리소스가 준비될 때까지 기다리지 않고 AppHost가 시작되는 즉시 실행됩니다. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - 대상 리소스가 지정되지 않았습니다. --resource 또는 --start-resource를 사용하여 대상을 지정합니다. - - - - The working directory to execute the command in - 명령을 실행할 작업 디렉터리입니다. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pl.xlf deleted file mode 100644 index 6fb02f3cd16..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pl.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Polecenie do wykonania w kontekście zasobu docelowego. Polecenia można określać bezpośrednio po opcjach lub po separatorze --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Wykonywanie poleceń w kontekście zasobu aplikacji Aspire. Uruchamia usługę AppHost, czeka na zainicjowanie zasobów, a następnie uruchamia określone polecenie w środowisku zasobu docelowego. (Podgląd) - -Przykłady: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Nie udało się przeanalizować polecenia. Upewnij się, że polecenie jest określone po wszystkich opcjach lub po separatorze --. - - - - Command is not specified. - Nie określono polecenia. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Ścieżka do pliku projektu AppHost usługi Aspire. Jeżeli nie określono, wyszukuje plik projektu w bieżącym katalogu. - - - - Running exec... - Trwa uruchamianie polecenia exec... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Nazwa zasobu docelowego, względem którego ma zostać wykonane polecenie. Polecenie zostanie wykonane dopiero po pomyślnym uruchomieniu zasobu docelowego i jego działaniu. - - - - Stopping AppHost... - Trwa zatrzymywanie hosta aplikacji... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Nazwa zasobu docelowego, względem którego ma zostać wykonane polecenie. Polecenie zostanie wykonane zaraz po uruchomieniu usługi AppHost, bez oczekiwania na gotowość zasobu. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - Nie określono zasobu docelowego. Użyj polecenia --resource lub --start-resource, aby określić cel. - - - - The working directory to execute the command in - Katalog roboczy, w którym ma zostać wykonane polecenie. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pt-BR.xlf deleted file mode 100644 index 172f530cdd5..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.pt-BR.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - O comando a ser executado no contexto do recurso alvo. Os comandos podem ser especificados diretamente após as opções ou após um separador --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Execute comandos no contexto de um recurso de aplicativo Aspire. Inicia o AppHost, espera os recursos serem inicializados e então executa o comando especificado no ambiente do recurso alvo. (Prévia) - -Exemplos: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Falha ao analisar o comando. Certifique-se de que o comando seja especificado após todas as opções ou após o separador --. - - - - Command is not specified. - O comando não foi especificado. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - O caminho para o arquivo de projeto Aspire AppHost. Se não especificado, procura por um arquivo de projeto no diretório atual. - - - - Running exec... - Executando... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - O nome do recurso alvo para executar o comando contra. O comando só será executado após o recurso alvo ter sido iniciado com sucesso e estar em execução. - - - - Stopping AppHost... - Parando o host de aplicativo... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - O nome do recurso alvo para executar o comando contra. O comando será executado assim que o AppHost iniciar, sem esperar que o recurso esteja pronto. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - O recurso alvo não foi especificado. Use --resource ou --start-resource para especificar o alvo. - - - - The working directory to execute the command in - O diretório de trabalho no qual executar o comando. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ru.xlf deleted file mode 100644 index c0f8e29d8dd..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.ru.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Команда для выполнения в контексте целевого ресурса. Команды можно указывать непосредственно после параметров или после разделителя --. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Выполнение команд в контексте ресурса приложения Aspire. Запускает AppHost, ждет инициализации ресурсов, затем запускает указанную команду в среде целевого ресурса. (Предварительная версия) - -Примеры - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Не удалось анализировать команду. Убедитесь, что команда указана после всех параметров или после разделителя --. - - - - Command is not specified. - Команда не указана. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Путь к файлу проекта Aspire AppHost. Если не указано иное, ищет файл проекта в текущем каталоге. - - - - Running exec... - Запуск исполн... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Имя целевого ресурса для выполнения команды. Команда будет выполнена только после успешного запуска и работы целевого ресурса. - - - - Stopping AppHost... - Остановка хоста приложения... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Имя целевого ресурса для выполнения команды. Команда будет выполнена сразу после запуска AppHost, не дожидаясь готовности ресурса. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - Целевой ресурс не указан. Используйте --resource или --start-resource для указания цели. - - - - The working directory to execute the command in - Рабочий каталог, в котором будет выполнена команда. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.tr.xlf deleted file mode 100644 index 6a6616b9397..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.tr.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - Hedef kaynağın bağlamında yürütülecek komut. Komutlar, seçeneklerin hemen ardından veya -- ayırıcısından sonra belirtilebilir. - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - Aspire uygulama kaynağı bağlamında komutları yürütün. AppHost'u başlatır, kaynakların başlatılmasını bekler, ardından hedef kaynağın ortamında belirtilen komutu çalıştırır. (Önizleme) - -Örnekler: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - Komut ayrıştırılamadı. Komutun tüm seçeneklerden sonra veya -- ayırıcısından sonra belirtildiğinden emin olun. - - - - Command is not specified. - Komut belirtilmedi. - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Aspire AppHost proje dosyasının yolu. Belirtilmezse, geçerli dizinde bir proje dosyası aranır. - - - - Running exec... - Exec çalıştırılıyor... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - Komutun yürütüleceği hedef kaynağın adıdır. Komut, yalnızca hedef kaynak başarıyla başlatıldıktan ve çalışmaya başladıktan sonra yürütülür. - - - - Stopping AppHost... - Uygulama ana işlemi durduruluyor... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - Komutun yürütüleceği hedef kaynağın adıdır. Komut, kaynak hazır olana kadar beklemeden AppHost başlatılır başlatılmaz yürütülür. - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - Hedef kaynak belirtilmedi. Hedefi belirtmek için --resource veya --start-resource kullanın. - - - - The working directory to execute the command in - Komutun yürütüleceği çalışma dizini. - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hans.xlf deleted file mode 100644 index 7ec312b9613..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hans.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - 要在目标资源的上下文中执行的命令。可以在选项之后或 -- 分隔符之后直接指定命令。 - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - 在 Aspire 应用程序资源的上下文中执行命令。启动 AppHost,等待资源初始化,然后在目标资源的环境中运行指定的命令。(预览) - -示例: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - 无法分析命令。确保在所有选项之后或 -- 分隔符之后指定命令。 - - - - Command is not specified. - 未指定命令。 - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Aspire AppHost 项目文件的路径。如果未指定,则在当前目录中搜索项目文件。 - - - - Running exec... - 正在运行 exec... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - 要针对其执行命令的目标资源的名称。只有在目标资源成功启动并处于运行状态后,才会执行该命令。 - - - - Stopping AppHost... - 正在停止应用主机... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - 要针对其执行命令的目标资源的名称。该命令将在 AppHost 启动后立即执行,而无需等待资源就绪。 - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - 未指定目标资源。使用 --resource 或 --start-resource 指定目标。 - - - - The working directory to execute the command in - 要在其中执行命令的工作目录。 - - - - - \ No newline at end of file diff --git a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hant.xlf deleted file mode 100644 index 4602bb2622f..00000000000 --- a/src/Aspire.Cli/Resources/xlf/ExecCommandStrings.zh-Hant.xlf +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - The command to execute in the target resource's context. Commands can be specified directly after options or after a -- separator. - 要在目標資源的內容中執行的命令。命令可以直接在選項之後或在 -- 分隔符號之後指定。 - - - - Execute commands in the context of an Aspire application resource. Starts the AppHost, waits for resources to initialize, then runs the specified command in the target resource's environment (Preview) - -Examples: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - 在 Aspire 應用程式資源的內容中執行命令。啟動 AppHost,等待資源初始化,然後在目標資源的環境中執行指定的命令。(預覽) - -範例: - aspire exec --resource api dotnet build - aspire exec --resource api -- dotnet test --logger console - aspire exec --start-resource worker pwsh -c "Get-Process" - - - - - Failed to parse the command. Ensure the command is specified after all options or after the -- separator. - 無法剖析命令。請確保命令在所有選項之後或在 -- 分隔符號之後指定。 - - - - Command is not specified. - 未指定命令。 - - - - The path to the Aspire AppHost project file or a directory to search. If not specified, searches for a project file in the current directory. - Aspire AppHost 專案檔案的路徑。如果未指定,將在目前目錄中搜尋專案檔案。 - - - - Running exec... - 正在執行 exec... - - - - The name of the target resource to execute the command against. The command will only be executed after the target resource has successfully started and is running. - 要執行命令的目標資源名稱。命令僅在目標資源成功啟動並運行後執行。 - - - - Stopping AppHost... - 正在停止主控處理程序... - - - - The name of the target resource to execute the command against. The command will be executed as soon as the AppHost starts, without waiting for the resource to be ready. - 要執行命令的目標資源名稱。命令將在 AppHost 啟動後立即執行,而不會等待資源就緒。 - - - - Target resource is not specified. Use --resource or --start-resource to specify the target. - 未指定目標資源。請使用 --resource 或 --start-resource 來指定目標。 - - - - The working directory to execute the command in - 執行命令的工作目錄。 - - - - - \ No newline at end of file diff --git a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs index d694bfe92a7..ddcac2156d8 100644 --- a/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs +++ b/src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Exec; using Aspire.Hosting.Pipelines; using Aspire.Hosting.Utils; using Microsoft.Extensions.DependencyInjection; @@ -163,16 +162,6 @@ public async Task GetDashboardUrlsAsync(CancellationToken ca return await DashboardUrlsHelper.GetDashboardUrlsAsync(serviceProvider, logger, cancellationToken).ConfigureAwait(false); } - public async IAsyncEnumerable ExecAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var execResourceManager = serviceProvider.GetRequiredService(); - var logsStream = execResourceManager.StreamExecResourceLogs(cancellationToken); - await foreach (var commandOutput in logsStream.ConfigureAwait(false)) - { - yield return commandOutput; - } - } - #pragma warning disable CA1822 public Task GetCapabilitiesAsync(CancellationToken cancellationToken) { diff --git a/src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs b/src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs index e4e5b1da547..55fb740b058 100644 --- a/src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs +++ b/src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs @@ -739,20 +739,6 @@ internal class BackchannelLogEntry public required string CategoryName { get; set; } } -internal class CommandOutput -{ - public required string Text { get; init; } - public bool IsErrorMessage { get; init; } - public int? LineNumber { get; init; } - /// - /// Additional info about type of the message. - /// Should be used for controlling the display style. - /// - public string? Type { get; init; } - - public int? ExitCode { get; init; } -} - internal class PublishingPromptInputAnswer { public string? Name { get; set; } diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index 4f2d6f3f0b1..2315d4b5699 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -22,7 +22,6 @@ using Aspire.Hosting.Devcontainers.Codespaces; using Aspire.Hosting.Diagnostics; using Aspire.Hosting.Eventing; -using Aspire.Hosting.Exec; using Aspire.Hosting.Health; using Aspire.Hosting.Lifecycle; using Aspire.Hosting.Orchestrator; @@ -243,7 +242,6 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) var aspireDir = GetMetadataValue(assemblyMetadata, "AppHostProjectBaseIntermediateOutputPath"); ConfigurePipelineOptions(options); - var isExecMode = ConfigureExecOptions(options); // Compute the dashboard application name - use DashboardApplicationName if set for file-based apps, // otherwise fall back to the environment's ApplicationName @@ -318,13 +316,6 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) LoadDeploymentState(appHostPathSha); } - // exec - if (isExecMode) - { - _innerBuilder.Services.AddSingleton(); - Eventing.Subscribe(ExecEventingHandlers.InitializeExecResources); - } - // Core things // Create and register the directory service (first, so it can be used by other services) _directoryService = new FileSystemService(_innerBuilder.Configuration); @@ -404,7 +395,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) ConfigureHealthChecks(); - if (ExecutionContext.IsRunMode && !isExecMode) + if (ExecutionContext.IsRunMode) { // Dashboard if (!options.DisableDashboard) @@ -770,42 +761,6 @@ private void ConfigurePipelineOptions(DistributedApplicationOptions options) } } - private bool ConfigureExecOptions(DistributedApplicationOptions options) - { - var switchMappings = new Dictionary() - { - { "--operation", "AppHost:Operation" }, - { "--resource", "Exec:ResourceName" }, - { "--start-resource", "Exec:ResourceName" }, - { "--command", "Exec:Command" }, - { "--workdir", "Exec:WorkingDirectory" } - }; - _innerBuilder.Configuration.AddCommandLine(options.Args ?? [], switchMappings); - - var execOptionsSection = _innerBuilder.Configuration.GetSection(ExecOptions.SectionName); - _innerBuilder.Services - .Configure(execOptionsSection) - .PostConfigure(execOptions => - { - if (options.Args is null || !options.Args.Any()) - { - return; - } - - if (!string.IsNullOrEmpty(execOptions.Command)) - { - execOptions.Enabled = true; - } - - if (options.Args.Contains("--start-resource")) - { - execOptions.StartResource = true; - } - }); - - return options.Args?.Any(arg => arg == "--command") ?? false; - } - /// public DistributedApplication Build() { diff --git a/src/Aspire.Hosting/Exec/ExecEventingHandlers.cs b/src/Aspire.Hosting/Exec/ExecEventingHandlers.cs deleted file mode 100644 index bf6c58d72b0..00000000000 --- a/src/Aspire.Hosting/Exec/ExecEventingHandlers.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; -using Microsoft.Extensions.DependencyInjection; - -namespace Aspire.Hosting.Exec; - -internal static class ExecEventingHandlers -{ - public static Task InitializeExecResources(BeforeStartEvent beforeStartEvent, CancellationToken _) - { - var execResourceManager = beforeStartEvent.Services.GetRequiredService(); - var resource = execResourceManager.CreateExecResource(); - - if (resource is not null) - { - beforeStartEvent.Model.Resources.Add(resource); - } - - return Task.CompletedTask; - } -} diff --git a/src/Aspire.Hosting/Exec/ExecOptions.cs b/src/Aspire.Hosting/Exec/ExecOptions.cs deleted file mode 100644 index b9451bbefde..00000000000 --- a/src/Aspire.Hosting/Exec/ExecOptions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Aspire.Hosting.Exec; - -/// -/// Configuration options for running AppHost in exec mode. -/// -internal sealed class ExecOptions -{ - /// - /// The name of the exec configuration section in the appsettings.json file. - /// - public const string SectionName = "Exec"; - - /// - /// Represents whether the apphost is running in exec mode. - /// - public bool Enabled { get; set; } - - /// - /// Target resource to execute the command against. - /// - public required string ResourceName { get; set; } - - /// - /// Command to execute against the target resource by . - /// - public required string Command { get; set; } - - /// - /// Working directory for the command execution. - /// - public string? WorkingDirectory { get; set; } - - /// - /// Whether to start the resource before executing the command. - /// By default is false. - /// - public bool StartResource { get; set; } -} diff --git a/src/Aspire.Hosting/Exec/ExecResourceManager.cs b/src/Aspire.Hosting/Exec/ExecResourceManager.cs deleted file mode 100644 index eec551c2856..00000000000 --- a/src/Aspire.Hosting/Exec/ExecResourceManager.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Backchannel; -using Aspire.Hosting.Utils; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Aspire.Hosting.Exec; - -internal class ExecResourceManager -{ - private readonly ILogger _logger; - private readonly ExecOptions _execOptions; - private readonly DistributedApplicationModel _model; - - private readonly ResourceLoggerService _resourceLoggerService; - private readonly ResourceNotificationService _resourceNotificationService; - - private readonly TaskCompletionSource _execResourceInitialized = new(); - - public ExecResourceManager( - ILogger logger, - IOptions execOptions, - DistributedApplicationModel model, - ResourceLoggerService resourceLoggerService, - ResourceNotificationService resourceNotificationService) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _model = model ?? throw new ArgumentNullException(nameof(model)); - _execOptions = execOptions.Value; - - _resourceLoggerService = resourceLoggerService ?? throw new ArgumentNullException(nameof(resourceLoggerService)); - _resourceNotificationService = resourceNotificationService ?? throw new ArgumentNullException(nameof(resourceNotificationService)); - } - - public async IAsyncEnumerable StreamExecResourceLogs([EnumeratorCancellation] CancellationToken cancellationToken) - { - if (!_execOptions.Enabled) - { - yield break; - } - - string type = "waiting"; - - yield return new CommandOutput - { - Text = $"Waiting for resources to be initialized...", - Type = type - }; - - // wait until AppHost eventing fires ConfigureExecResource() - // and execResource is initialized - IResource? execResource = null; - Exception? execResourceInitializationException = null; - try - { - execResource = await _execResourceInitialized.Task.WaitAsync(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - _logger.LogInformation("Cancelled before exec resource was initialized."); - yield break; - } - catch (Exception ex) - { - _logger.LogError(execResourceInitializationException, "Exec resource initialization failed."); - execResourceInitializationException = ex; - } - - if (execResourceInitializationException is not null) - { - yield return new CommandOutput - { - Text = execResourceInitializationException.Message, - IsErrorMessage = true, - Type = type - }; - yield break; - } - - // dcp annotation is populated by other handler of BeforeStartEvent - var dcpExecResourceName = execResource!.GetResolvedResourceName(); - - yield return new CommandOutput - { - Text = $"Aspire exec starting...", - Type = type - }; - - // in the background wait for the exec resource to be running to change log type - _ = Task.Run(async () => - { - await _resourceNotificationService.WaitForResourceAsync(execResource!.Name, targetState: KnownResourceStates.Running, cancellationToken).ConfigureAwait(false); - type = "running"; - }, cancellationToken); - - // in the background wait for the exec resource to reach terminal state. Once done we can complete logging - _ = Task.Run(async () => - { - await _resourceNotificationService.WaitForResourceAsync(execResource!.Name, targetStates: KnownResourceStates.TerminalStates, cancellationToken).ConfigureAwait(false); - - // hack: https://github.com/microsoft/aspire/issues/10245 - // workarounds the race-condition between streaming all logs from the resource, and resource completion - await Task.Delay(1000, CancellationToken.None).ConfigureAwait(false); - - _resourceLoggerService.Complete(dcpExecResourceName); // complete stops the `WatchAsync` async-foreach below - }, cancellationToken); - - await foreach (var logs in _resourceLoggerService.WatchAsync(dcpExecResourceName).WithCancellation(cancellationToken).ConfigureAwait(false)) - { - foreach (var log in logs) - { - yield return new CommandOutput - { - Text = log.Content, - IsErrorMessage = log.IsErrorMessage, - LineNumber = log.LineNumber, - Type = type - }; - } - } - - if (!_resourceNotificationService.TryGetCurrentState(dcpExecResourceName, out var resourceEvent)) - { - yield break; - } - - int? exitCode; - if ((exitCode = resourceEvent?.Snapshot?.ExitCode) is not null) - { - yield return new CommandOutput - { - Text = "Aspire exec exit code: " + exitCode.Value, - IsErrorMessage = false, - Type = "exitCode", - ExitCode = exitCode.Value - }; - } - - if (resourceEvent?.Snapshot.State == KnownResourceStates.FailedToStart) - { - yield return new CommandOutput - { - Text = "Aspire exec failed to start", - IsErrorMessage = true, - Type = "failedToStart", - ExitCode = -1 // -1 indicates a failure - }; - } - } - - public IResource? CreateExecResource() - { - if (!_execOptions.Enabled) - { - return null; - } - - try - { - var targetResource = _model.Resources.FirstOrDefault(x => x.Name.Equals(_execOptions.ResourceName, StringComparisons.ResourceName)); - if (targetResource is null) - { - _logger.LogError("Target resource '{ResourceName}' not found in the model resources.", _execOptions.ResourceName); - throw new InvalidOperationException($"Target resource {_execOptions.ResourceName} not found in the model resources"); - } - - var execResource = BuildResource(targetResource); - - _logger.LogDebug("Resource '{ResourceName}' has been successfully built and added to the model resources.", execResource.Name); - _execResourceInitialized.SetResult(execResource); - return execResource; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create exec resource."); - _execResourceInitialized.SetException(ex); - return null; - } - } - - IResource BuildResource(IResource targetExecResource) - { - return targetExecResource switch - { - ProjectResource prj => BuildAgainstResource(prj), - ContainerResource container => BuildAgainstResource(container), - _ => throw new InvalidOperationException($"Target resource {targetExecResource.Name} does not support exec mode.") - }; - } - - private IResource BuildAgainstResource(ProjectResource project) - { - var projectMetadata = project.GetProjectMetadata(); - var projectDir = Path.GetDirectoryName(projectMetadata.ProjectPath) ?? throw new InvalidOperationException("Project path is invalid."); - var (exe, args) = ParseCommand(); - - string execResourceName = project.Name + "-exec"; - var executable = new ExecutableResource(execResourceName, exe, projectDir); - if (args is not null && args.Length > 0) - { - executable.Annotations.Add(new CommandLineArgsCallbackAnnotation((c) => - { - c.Args.AddRange(args); - return Task.CompletedTask; - })); - } - - // take all applicable annotations from target resource to replicate the environment - foreach (var annotation in project.Annotations.Where(annotation => - annotation is EnvironmentAnnotation or EnvironmentCallbackAnnotation - or ResourceRelationshipAnnotation or WaitAnnotation)) - { - executable.Annotations.Add(annotation); - } - - if (_execOptions.StartResource) - { - _logger.LogDebug("Exec resource '{ResourceName}' will wait until project '{Project}' starts up.", execResourceName, project.Name); - executable.Annotations.Add(new WaitAnnotation(project, waitType: WaitType.WaitUntilHealthy)); - } - - _logger.LogDebug("Exec resource '{ResourceName}' will run command '{Command}' with {ArgsCount} args '{Args}'.", execResourceName, exe, args?.Length ?? 0, string.Join(' ', args ?? [])); - - return executable; - - (string exe, string[] args) ParseCommand() - { - // cli wraps the command into the string with quotes - // to keep the command as a single argument - var command = _execOptions.Command; - var commandUnwrapped = command.AsSpan(1, command.Length - 2).ToString(); - Debug.Assert(command[0] == '"' && command[^1] == '"'); - - return CommandLineArgsParser.ParseCommand(commandUnwrapped); - } - } - - private IResource BuildAgainstResource(ContainerResource container) - { - var (exe, args) = ParseCommand(); - string execResourceName = container.Name + "-exec"; - var workingDirectory = _execOptions.WorkingDirectory; - - // we cant resolve dcp name of container resource here - too early in the startup pipeline - // it will be resolved later in the Dcp layer - var containerExecutable = new ContainerExecutableResource(execResourceName, container, exe, workingDirectory: workingDirectory) - { - Args = args - }; - - containerExecutable.Annotations.Add(new WaitAnnotation(container, waitType: WaitType.WaitUntilHealthy)); - - _logger.LogDebug("Exec container resource '{ResourceName}' will run command '{Command}' with {ArgsCount} args '{Args}'.", execResourceName, exe, args?.Length ?? 0, string.Join(' ', args ?? [])); - return containerExecutable; - - (string exe, string[] args) ParseCommand() - { - // cli wraps the command into the string with quotes - // to keep the command as a single argument - var command = _execOptions.Command; - var commandUnwrapped = command.AsSpan(1, command.Length - 2).ToString(); - Debug.Assert(command[0] == '"' && command[^1] == '"'); - - return CommandLineArgsParser.ParseCommand(commandUnwrapped); - } - } -} diff --git a/tests/Aspire.Cli.Tests/Commands/ExecCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/ExecCommandTests.cs deleted file mode 100644 index ff360831e3e..00000000000 --- a/tests/Aspire.Cli.Tests/Commands/ExecCommandTests.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CommandLine; -using Aspire.Cli.Projects; -using Aspire.Cli.Resources; -using Aspire.Cli.Tests.TestServices; -using Aspire.Cli.Tests.Utils; -using Microsoft.Extensions.DependencyInjection; -using RootCommand = Aspire.Cli.Commands.RootCommand; -using Microsoft.AspNetCore.InternalTesting; - -namespace Aspire.Cli.Tests.Commands; - -public class ExecCommandTests -{ - private readonly ITestOutputHelper _outputHelper; - public ExecCommandTests(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - } - - [Fact] - public async Task ExecCommandWithHelpArgumentReturnsZero() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var invokeConfiguration = new InvocationConfiguration(); - invokeConfiguration.Output = new TestOutputTextWriter(_outputHelper); - - var result = command.Parse("exec --help"); - - var exitCode = await result.InvokeAsync(invokeConfiguration).DefaultTimeout(); - Assert.Equal(ExitCodeConstants.Success, exitCode); - } - - [Fact] - public async Task ExecCommand_WhenNoProjectFileFound_ReturnsFailedToFindProject() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - options.ProjectLocatorFactory = _ => new NoProjectFileProjectLocator(); - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse("exec --resource api cmd"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(ExitCodeConstants.FailedToFindProject, exitCode); - } - - [Fact] - public async Task ExecCommand_WhenMultipleProjectFilesFound_ReturnsFailedToFindProject() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - options.ProjectLocatorFactory = _ => new MultipleProjectFilesProjectLocator(); - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse("exec --resource api cmd"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(ExitCodeConstants.FailedToFindProject, exitCode); - } - - [Fact] - public async Task ExecCommand_WhenProjectFileDoesNotExist_ReturnsFailedToFindProject() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - options.ProjectLocatorFactory = _ => new ProjectFileDoesNotExistLocator(); - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse("exec --resource api cmd"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(ExitCodeConstants.FailedToFindProject, exitCode); - } - - [Fact] - public async Task ExecCommand_WhenFeatureFlagEnabled_CommandAvailable() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var invokeConfiguration = new InvocationConfiguration(); - var testOutputWriter = new TestOutputTextWriter(_outputHelper); - invokeConfiguration.Output = testOutputWriter; - - var result = command.Parse("exec --help"); - - var exitCode = await result.InvokeAsync(invokeConfiguration).DefaultTimeout(); - - // Should succeed because exec command is registered when feature flag is enabled - Assert.Equal(ExitCodeConstants.Success, exitCode); - } - - [Fact] - public async Task ExecCommand_WhenTargetResourceNotSpecified_ReturnsInvalidCommand() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.ProjectLocatorFactory = _ => new TestProjectLocator(); - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var invokeConfiguration = new InvocationConfiguration(); - var testOutputWriter = new TestOutputTextWriter(_outputHelper); - invokeConfiguration.Output = testOutputWriter; - - var result = command.Parse("exec --apphost test.csproj echo hello"); - - var exitCode = await result.InvokeAsync(invokeConfiguration).DefaultTimeout(); - Assert.Equal(ExitCodeConstants.InvalidCommand, exitCode); - - // attempt to find app host should not happen - Assert.DoesNotContain(testOutputWriter.Logs, x => x.Contains(InteractionServiceStrings.FindingAppHosts)); - } - - [Fact] - public async Task ExecCommand_ExecutesSuccessfully() - { - using var workspace = TemporaryWorkspace.Create(_outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, _outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - options.ProjectLocatorFactory = _ => new TestProjectLocator(); - - options.DotNetCliRunnerFactory = _ => new TestDotNetCliRunner - { - RunAsyncCallback = (projectFile, watch, noBuild, noRestore, args, env, backchannelCompletionSource, runnerOptions, cancellationToken) => - { - var backchannel = new TestAppHostBackchannel(); - backchannelCompletionSource?.SetResult(backchannel); - return Task.FromResult(0); - } - }; - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse("exec --apphost test.csproj --resource myresource --command echo"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(ExitCodeConstants.Success, exitCode); - } - - private sealed class NoProjectFileProjectLocator : Aspire.Cli.Projects.IProjectLocator - { - public Task> FindAppHostProjectsAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("No project file found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.NoProjectFileFound); - } - - public Task> FindAppHostProjectFilesAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("No project file found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.NoProjectFileFound); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, MultipleAppHostProjectsFoundBehavior multipleAppHostProjectsFoundBehavior, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("No project file found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.NoProjectFileFound); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("No project file found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.NoProjectFileFound); - } - - public Task GetAppHostFromSettingsAsync(CancellationToken cancellationToken = default) => Task.FromResult(null); - } - - private sealed class MultipleProjectFilesProjectLocator : Aspire.Cli.Projects.IProjectLocator - { - public Task> FindAppHostProjectsAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Multiple project files found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.MultipleProjectFilesFound); - } - - public Task> FindAppHostProjectFilesAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Multiple project files found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.MultipleProjectFilesFound); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, MultipleAppHostProjectsFoundBehavior multipleAppHostProjectsFoundBehavior, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Multiple project files found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.MultipleProjectFilesFound); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Multiple project files found.", Aspire.Cli.Projects.ProjectLocatorFailureReason.MultipleProjectFilesFound); - } - - public Task GetAppHostFromSettingsAsync(CancellationToken cancellationToken = default) => Task.FromResult(null); - } - - private sealed class ProjectFileDoesNotExistLocator : Aspire.Cli.Projects.IProjectLocator - { - public Task> FindAppHostProjectsAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Project file does not exist.", Aspire.Cli.Projects.ProjectLocatorFailureReason.ProjectFileDoesntExist); - } - - public Task> FindAppHostProjectFilesAsync(DirectoryInfo searchDirectory, AppHostDiscoveryScope scope, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Project file does not exist.", Aspire.Cli.Projects.ProjectLocatorFailureReason.ProjectFileDoesntExist); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, MultipleAppHostProjectsFoundBehavior multipleAppHostProjectsFoundBehavior, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Project file does not exist.", Aspire.Cli.Projects.ProjectLocatorFailureReason.ProjectFileDoesntExist); - } - - public Task UseOrFindAppHostProjectFileAsync(FileInfo? projectFile, bool createSettingsFile, CancellationToken cancellationToken) - { - throw new Aspire.Cli.Projects.ProjectLocatorException("Project file does not exist.", Aspire.Cli.Projects.ProjectLocatorFailureReason.ProjectFileDoesntExist); - } - - public Task GetAppHostFromSettingsAsync(CancellationToken cancellationToken = default) => Task.FromResult(null); - } -} diff --git a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs index 02aaaff5474..d9d8be35984 100644 --- a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs @@ -832,12 +832,6 @@ public async IAsyncEnumerable GetResourceStatesAsync([Enumerat public Task ConnectAsync(string socketPath, bool autoReconnect, int retryCount, CancellationToken cancellationToken) => Task.CompletedTask; public Task GetCapabilitiesAsync(CancellationToken cancellationToken) => Task.FromResult(new[] { "baseline.v2" }); - public async IAsyncEnumerable ExecAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await Task.CompletedTask; // Suppress CS1998 - yield break; - } - public Task GetPipelineStepsAsync(string? step, CancellationToken cancellationToken) => Task.FromResult(new GetPipelineStepsResponse { Steps = [] }); } diff --git a/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs index c2abaf75e40..97fe578392d 100644 --- a/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs @@ -463,4 +463,16 @@ public void GroupedHelp_ContainsAllVisibleCommands() Assert.Contains(sub.Name, helpOutput); } } + + [Fact] + public void RootCommand_DoesNotExposeRemovedExecSubcommand() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper); + using var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + + Assert.DoesNotContain(command.Subcommands, subcommand => subcommand.Name == "exec"); + } } diff --git a/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs b/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs index 25d15fe7994..d90214cf6ff 100644 --- a/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs @@ -174,29 +174,6 @@ public async Task DeployCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() Assert.Equal(ExitCodeConstants.SdkNotInstalled, exitCode); } - [Fact] - public async Task ExecCommand_WhenSdkNotInstalled_ReturnsCorrectExitCode() - { - using var workspace = TemporaryWorkspace.Create(outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => - { - options.EnabledFeatures = [KnownFeatures.ExecCommandEnabled]; - options.DotNetSdkInstallerFactory = _ => new TestDotNetSdkInstaller - { - CheckAsyncCallback = _ => (false, null, "9.0.302") // SDK not installed - }; - - options.InteractionServiceFactory = _ => new TestInteractionService(); - }); - using var provider = services.BuildServiceProvider(); - - var command = provider.GetRequiredService(); - var result = command.Parse("exec"); - - var exitCode = await result.InvokeAsync().DefaultTimeout(); - Assert.Equal(ExitCodeConstants.SdkNotInstalled, exitCode); - } - [Fact] public async Task RunCommand_WhenSdkInstalled_ContinuesNormalExecution() { @@ -219,4 +196,4 @@ public async Task RunCommand_WhenSdkInstalled_ContinuesNormalExecution() // Should fail at project location, not SDK check Assert.Equal(ExitCodeConstants.FailedToFindProject, exitCode); } -} \ No newline at end of file +} diff --git a/tests/Aspire.Cli.Tests/TestServices/TestAppHostCliBackchannel.cs b/tests/Aspire.Cli.Tests/TestServices/TestAppHostCliBackchannel.cs index fdd7d422417..0b03da1f5d1 100644 --- a/tests/Aspire.Cli.Tests/TestServices/TestAppHostCliBackchannel.cs +++ b/tests/Aspire.Cli.Tests/TestServices/TestAppHostCliBackchannel.cs @@ -248,12 +248,6 @@ public Task UpdatePromptResponseAsync(string promptId, PublishingPromptInputAnsw return Task.CompletedTask; } - public async IAsyncEnumerable ExecAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken).ConfigureAwait(false); - yield return new CommandOutput { Text = "test", IsErrorMessage = false, LineNumber = 0 }; - } - public async Task GetPipelineStepsAsync(string? step, CancellationToken cancellationToken) { GetPipelineStepsAsyncCalled?.SetResult(); diff --git a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs index e3b32e00582..1b332b2a4ac 100644 --- a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs +++ b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs @@ -197,7 +197,6 @@ public static IServiceCollection CreateServiceCollection(TemporaryWorkspace work services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ContainerResourceExecTests.cs b/tests/Aspire.Hosting.Tests/Backchannel/Exec/ContainerResourceExecTests.cs deleted file mode 100644 index 8d6a93b5a8a..00000000000 --- a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ContainerResourceExecTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.Testing; -using Aspire.TestUtilities; - -namespace Aspire.Hosting.Tests.Backchannel.Exec; - -[Trait("Partition", "4")] -public class ContainerResourceExecTests : ExecTestsBase -{ - public ContainerResourceExecTests(ITestOutputHelper outputHelper) - : base(outputHelper) - { - } - - [Fact] - [RequiresFeature(TestFeature.Docker)] - public async Task Exec_NginxContainer_ListFiles_WithWorkdirSpecified_ProducesLogs_Success() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - "--command", "\"ls\"", - "--workdir", "/bin" - ]; - - using var builder = PrepareBuilder(args); - WithContainerResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, - "apt-get", "base32", "base64", // typical output of `ls /bin` in a container - "Aspire exec exit code: 0" // exit code is submitted separately from the command logs - ); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - [Fact] - [RequiresFeature(TestFeature.Docker)] - public async Task Exec_NginxContainer_ListFiles_ProducesLogs_Success() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - "--command", "\"ls\"", - ]; - - using var builder = PrepareBuilder(args); - WithContainerResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, - "bin", "boot", "dev", // typical output of `ls` in a container - "Aspire exec exit code: 0" // exit code is submitted separately from the command logs - ); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - private static void WithContainerResource(IDistributedApplicationTestingBuilder builder, string name = "test") - { - builder.AddResource(new TestContainerResource(name)) - .WithInitialState(new() - { - ResourceType = "TestProjectResource", - State = new("Running", null), - Properties = [new("A", "B"), new("c", "d")], - EnvironmentVariables = [new("e", "f", true), new("g", "h", false)] - }) - .WithImage("nginx") - .WithImageTag("1.25"); - } -} - -file sealed class TestContainerResource : ContainerResource -{ - public TestContainerResource(string name) : base(name) - { - } -} diff --git a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ExecTestsBase.cs b/tests/Aspire.Hosting.Tests/Backchannel/Exec/ExecTestsBase.cs deleted file mode 100644 index b8bfc11580e..00000000000 --- a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ExecTestsBase.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.Backchannel; -using Aspire.Hosting.Testing; -using Aspire.Hosting.Tests.Utils; -using Aspire.Hosting.Utils; -using Microsoft.Extensions.DependencyInjection; - -namespace Aspire.Hosting.Tests.Backchannel.Exec; - -public abstract class ExecTestsBase(ITestOutputHelper outputHelper) -{ - protected readonly ITestOutputHelper _outputHelper = outputHelper; - - /// - /// Performs an `exec` against the apphost, - /// collecting the logs of the `exec` resource apphost is being run against. - /// - /// Also awaits the app startup. It has to be built before running this method. - /// - internal async Task> ExecWithLogCollectionAsync( - DistributedApplication app, - int timeoutSec = 30) - { - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSec)); - - var appHostRpcTarget = app.Services.GetRequiredService(); - var outputStream = appHostRpcTarget.ExecAsync(cts.Token); - - var logs = new List(); - var startTask = app.StartAsync(cts.Token); - await foreach (var message in outputStream) - { - var logLevel = message.IsErrorMessage ? "error" : "info"; - var log = $"Received output: #{message.LineNumber} [level={logLevel}] [type={message.Type}] {message.Text}"; - - logs.Add(message); - _outputHelper.WriteLine(log); - } - - await startTask; - return logs; - } - - internal static void AssertLogsContain(List logs, params string[] expectedLogMessages) - { - if (expectedLogMessages.Length == 0) - { - Assert.Empty(logs); - return; - } - - foreach (var expectedMessage in expectedLogMessages) - { - var logFound = logs.Any(x => x.Text.Contains(expectedMessage)); - Assert.True(logFound, $"Expected log message '{expectedMessage}' not found in logs."); - } - } - - protected IDistributedApplicationTestingBuilder PrepareBuilder(string[] args) - { - var builder = TestDistributedApplicationBuilder.Create(_outputHelper, args).WithTestAndResourceLogging(_outputHelper); - builder.Configuration[KnownConfigNames.UnixSocketPath] = UnixSocketHelper.GetBackchannelSocketPath(); - return builder; - } -} diff --git a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ProjectResourceExecTests.cs b/tests/Aspire.Hosting.Tests/Backchannel/Exec/ProjectResourceExecTests.cs deleted file mode 100644 index 2c8d023a356..00000000000 --- a/tests/Aspire.Hosting.Tests/Backchannel/Exec/ProjectResourceExecTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.Testing; - -namespace Aspire.Hosting.Tests.Backchannel.Exec; - -[Trait("Partition", "4")] -public class ProjectResourceExecTests : ExecTestsBase -{ - public ProjectResourceExecTests(ITestOutputHelper outputHelper) - : base(outputHelper) - { - } - - [Fact] - public async Task Exec_NotFoundTargetResource_ShouldProduceLogs() - { - string[] args = [ - "--operation", "run", - "--resource", "randomnonexistingresource", - "--command", "\"dotnet --info\"", - ]; - - using var builder = PrepareBuilder(args); - WithTestProjectResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, "Target resource randomnonexistingresource not found in the model resources"); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - [Fact] - [ActiveIssue("https://github.com/microsoft/aspire/issues/11158")] - public async Task Exec_DotnetBuildFail_ProducesLogs_Fail() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - // not existing csproj, but we dont care if that succeeds or not - we are expecting - // whatever log output from the command - "--command", "\"dotnet build \"MyRandom.csproj\"\"", - ]; - - using var builder = PrepareBuilder(args); - WithTestProjectResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, "Project file does not exist", "Aspire exec exit code: 1"); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - [Fact] - public async Task Exec_NonExistingCommand_ProducesLogs_Fail() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - // not existing command. Executable should fail without start basically - "--command", "\"randombuildcommand doit\"", - ]; - - using var builder = PrepareBuilder(args); - WithTestProjectResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, "Aspire exec failed to start"); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - [Fact] - [ActiveIssue("https://github.com/microsoft/aspire/issues/11143", TestPlatforms.Windows)] - public async Task Exec_DotnetInfo_ProducesLogs_Success() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - "--command", "\"dotnet --info\"", - ]; - - using var builder = PrepareBuilder(args); - WithTestProjectResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, - ".NET SDKs installed", // command logs - "Aspire exec exit code: 0" // exit code is submitted separately from the command logs - ); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - [Fact] - public async Task Exec_DotnetHelp_ProducesLogs_Success() - { - string[] args = [ - "--operation", "run", - "--resource", "test", - "--command", "\"dotnet --help\"", - ]; - - using var builder = PrepareBuilder(args); - WithTestProjectResource(builder); - - using var app = builder.Build(); - - var logs = await ExecWithLogCollectionAsync(app); - AssertLogsContain(logs, - "Usage: dotnet [sdk-options] [command] [command-options] [arguments]", // command logs - "Aspire exec exit code: 0" // exit code is submitted separately from the command logs - ); - - await app.StopAsync().WaitAsync(TimeSpan.FromSeconds(60)); - } - - private static void WithTestProjectResource(IDistributedApplicationTestingBuilder builder, string name = "test") - { - builder.AddResource(new TestProjectResource(name)) - .WithInitialState(new() - { - ResourceType = "TestProjectResource", - State = new("Running", null), - Properties = [new("A", "B"), new("c", "d")], - EnvironmentVariables = [new("e", "f", true), new("g", "h", false)] - }) - .WithAnnotation(new ProjectMetadata(Directory.GetCurrentDirectory())); - } -} - -file sealed class TestProjectResource : ProjectResource -{ - public TestProjectResource(string name) : base(name) - { - } -}