Skip to content

feat: run apphost in tool mode #9912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft

Conversation

DeagleGross
Copy link
Member

@DeagleGross DeagleGross commented Jun 17, 2025

Description

Current PR supports running AppHost in tool mode. That mode is an extension of run mode, and should be invoked as:

aspire run --tool myTool

The idea is to run apphost, and then find a resource with specified name (i.e. myTool), run it, and then shutdown the apphost. It is the first part of supporting EntityFramework e2e scenarios, and later this foundation can be reused for EF specific commands.

Implementation

If CLI detects the tool run, it invokes the IAsyncEnumerable<CommandOutput> GetToolExecutionOutputStreamAsync(CancellationToken cancellationToken) which streams the output of the tool to the CLI.

For now apphost only supports "toolexecution" as ExecutableResource. It runs the process by supplying explicitly defined args in the apphost (i.e. WithArgs(...)) and also supplies the args coming to appHost directly (those which are passed via aspire run --tool myTool --....).

Testing

ToolTests does the testing - it builds the DistributedApplication, by registering a couple of dependencies and an executable resource. After that it resolves the ToolExecutionService which runs the ExecutableResource, and streams the output.

cli-tool-run.mp4

Relates to #9859

@DeagleGross DeagleGross requested a review from mitchdenny June 17, 2025 08:37
@DeagleGross DeagleGross self-assigned this Jun 17, 2025
@DeagleGross
Copy link
Member Author

Problems that I see now:

  1. not all args passed into DistributedApplication should be passed into the underlying command. In EF example I have -add-postgres flag which AppHost utilizes to decide whether to add a dependency (pgsql). However, if I pass that argument to dotnet ef migrations add Initial -add-postgres, it will throw because it does not know about this argument. We have to come up with a syntax to explicitly let people know what arguments will end up in the command as well
  2. supports only ExecutableResource for now (will be done in follow-up PRs)


namespace Aspire.Hosting.Tools;

internal class ToolExecutionService
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should just run the tool inside of dcp and use the ResourceLoggingService to extract logs and ResourceNotificationService to track the lifetime of the resource.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what Mitch has proposed, I thought that we want to run apphost, and once it has started, we would want to wait until caller explicitly invokes the tool execution, that is why I did not put the service in dcp. For example if later we would add --keep-alive option for apphost, then all we would need to do is change CLI side to not invoke shutdown, and no dcp changes will be required. What do you think?

I will try to reuse ResourceLoggerService for logging extraction and streaming to cli

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would use:

  • ResourceLoggerService (capture logs)
  • ResourceNoticationService (wait for lifetime)
  • ResourceCommandService (execute commands)

The last one is new and allows you to execute the "resource-start" command for a resource.

I was discussing this today with @DamianEdwards and in a slight pivot I think we said we want to explore running the tool or command with another resource's configuration. In essence, when you invoke a tool, it runs in the context of another resource (maybe as a replacement?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'm imagining we introduce something like IResourceSupportsExec which means it can be targeted by aspire exec. It would likely have a Prepare method that's invoked during app host start, and an Execute method that's invoked to perform the actual command execution. The implementation for ContainerResource would utilize DCP's support for container exec (this can be done later), and the implementation for ExecutableResource and ProjectResource would execute the exec command using the configuration (i.e. working directory, environment variables, wait-for, etc.) of the resource. All the plumbing for capturing the logs and sending them back to the invocation terminal are still required. Eventually we'd want to support interactive commands too but that's not required initially.

The advantage of this approach is that nothing needs to be added to the AppHost to enable executing existing command line tools. One can simply use the aspire CLI to execute them assuming they're already installed. The concept of supporting tools in the AppHost is still an interesting one though and something that we could revisit later.

Open questions:

  • Does the ExecutableResource/ProjectResource still run if it's the target of an aspire exec, or is its config just used? In the case of dotnet ef you don't need the project started, you just want the config. Maybe it's an option?
  • We might need to support options to IResourceSupportsExec (see last point above) that can be passed in from the call site on the command line, to allow for different behaviors, e.g. start resource, replace resource, etc., e.g. aspire exec --resource MyApp --start-resource false dotnet ef migrations add Initial
  • What happens once the command is completed? Does the AppHost just do a graceful shut down?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably do both approaches. I think we should probably define the tool in the app model (which will support a broader range of scenarios). But aspire exec would effectively dynamically create an AddExecutable(...) call to the app model on startup.

Then its just about figuring out how you tell the orchestrator that you want resource X to effectively run with the same environment as resource Y. The way I would tackle that is that I would add an annotation which the orchestrator can spot and then make a call out to apply the target resources environment to the dynamically injected tool resource.

I kind of see these things as distinct but related things.

@@ -51,6 +51,10 @@ public RunCommand(IDotNetCliRunner runner, IInteractionService interactionServic
watchOption.Description = RunCommandStrings.WatchArgumentDescription;
Options.Add(watchOption);

var toolParseOption = new Option<string>("--tool", "-t");
toolParseOption.Description = "Runs a resource as a tool.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to localize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants