Open afscrome opened 1 month ago
CRUD interface to manage resources at runtime from application code is really needed.
We are working on software which deployes services (from docker images) as our users request them. The ablity to implement this with Aspire for local development would be awesome.
Played with this a little based on what works today:
using Microsoft.Extensions.DependencyInjection;
var builder = DistributedApplication.CreateBuilder(args);
builder.AddContainer("redis", "redis").WithExplicitStart();
builder.Build().Run();
public static class ExplicitStartupExtensions
{
public static IResourceBuilder<T> WithExplicitStart<T>(this IResourceBuilder<T> builder)
where T : IResource
{
builder.ApplicationBuilder.Eventing.Subscribe<BeforeResourceStartedEvent>(builder.Resource, async (evt, ct) =>
{
var rns = evt.Services.GetRequiredService<ResourceNotificationService>();
// This is possibly the last safe place to update the resource's annotations
// we need to do it this late because the built in lifecycle annotations are added *very* late
var startCommand = evt.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault(c => c.Type == "resource-start");
if (startCommand is null)
{
return;
}
evt.Resource.Annotations.Remove(startCommand);
// This will block the resource from starting until the "resource-start" command is executed
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
// Create a new command that clones the start command
var newCommand = new ResourceCommandAnnotation(
startCommand.Type,
startCommand.DisplayName,
context =>
{
if (!tcs.Task.IsCompleted)
{
return ResourceCommandState.Enabled;
}
return startCommand.UpdateState(context);
},
context =>
{
if (!tcs.Task.IsCompleted)
{
tcs.SetResult();
return Task.FromResult(CommandResults.Success());
}
return startCommand.ExecuteCommand(context);
},
startCommand.DisplayDescription,
startCommand.Parameter,
startCommand.ConfirmationMessage,
startCommand.IconName,
startCommand.IconVariant,
startCommand.IsHighlighted);
evt.Resource.Annotations.Add(newCommand);
await rns.PublishUpdateAsync(evt.Resource, s => s with { State = new(KnownResourceStates.Waiting, KnownResourceStateStyles.Info) });
await tcs.Task.WaitAsync(ct);
});
return builder;
}
}
Thank you for the insight, but it's not quite what I have in mind.
I can envision an interface to manage Aspire resources, with its instance accessible from a running project in Aspire. Let’s write some pseudocode.
IAspireResourceManager
{
IAspireResource[] GetResources();
IAspireResource GetResource(name);
IAspireResource AddResource(name, ...)
}
IAspireResouce
{
Task<string> GetState();
Task WaitFor();
Task Remove();
....
string GetConnectionString()
}
// from running Aspire application project, let's say Blazor App
IAspireResourceManager resourceManager = .... get instance from DI
resourceManager.AddResource("worker-user-1", ...);
var worker = resourceManager.GetResource("worker-user-1");
await worker.WaitFor();
...
}
Sorry, I wasn't replying to your request for an api to dynamically add resources, I think that's a different, harder and longer term feature request. The other features in this issue are quite doable with existing APIs.
Is there an existing issue for this?
Is your feature request related to a problem? Please describe the problem.
With the addition of being able to start/stop resources in the dashboard, it would be useful to be able to support resources which are started on demand rather than automatically on startup. A few use cases I can think of:
CronJob
resources deployed to Kubernetes - We have a few resources we deploy as nightly cron jobs to kuberentes. In local dev, it would be helpful to have these availableDescribe the solution you'd like
Some kind of way to mark a resource so that Aspire doesn't automatically start the resource, instead waiting for the user to click the start button in the UI.
An alternative approach would be to provide a way for commands to dynamically add new resources, which would allow you to use a command to spawn up a new resource. This work well for options 2 & 3 above, but doesn't fit 1 so well.
Additional context
No response