WIP: Use annotations to determine project & executable resources#18052
WIP: Use annotations to determine project & executable resources#18052afscrome wants to merge 4 commits into
Conversation
…resources, rather than the resource class hierarchy. This brings the executable and project behaviour in line with containers which are already annotation based. - Updated ResourceSnapshotBuilder to utilize project metadata directly from app model resources. - Introduced new methods in ExecutableResourceExtensions for retrieving executable annotations and filtering executable resources. - Modified ApplicationOrchestrator to check for project and executable annotations when determining resource lifetimes. - Adjusted ManifestPublishingContext and ResourceContainerImageManager to use project annotations for metadata retrieval. - Enhanced LaunchProfileExtensions to support generic resource handling and improved launch profile selection logic. - Added unit tests for annotated executable and project resources to ensure correct behaviour when annotations are modified. - Created a new playground project for resource substitution with appropriate launch settings and configurations.
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18052Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 18052" |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR shifts Aspire hosting logic from concrete resource types (e.g., ProjectResource, ExecutableResource) toward annotation-driven detection (IProjectMetadata, ExecutableAnnotation), enabling resource “transmutation” scenarios (e.g., turning a container into an executable) and adds coverage + a playground sample.
Changes:
- Introduces
TryGetProjectAnnotation/TryGetExecutableAnnotationand model enumerators for “project-like” and “executable-like” resources. - Updates DCP/orchestrator/publishing/launch-profile logic to rely on annotations rather than resource CLR types.
- Adds new tests for annotation-driven executable/project creation and a new “ResourceSubstitution” playground sample.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs | Adds test coverage for executable/project creation driven by annotations and mutation scenarios. |
| tests/Aspire.Hosting.Python.Tests/AddPythonAppTests.cs | Adjusts test resource discovery to avoid relying on executable resource filtering. |
| src/Shared/LaunchProfiles/LaunchProfileExtensions.cs | Generalizes launch profile selection to IResource + project metadata annotations. |
| src/Aspire.Hosting/Publishing/ResourceContainerImageManager.cs | Switches project detection for image builds to project metadata annotations. |
| src/Aspire.Hosting/Publishing/ManifestPublishingContext.cs | Uses project metadata annotation helper for manifest project publishing. |
| src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs | Updates “own lifetime” detection to use project/executable annotations. |
| src/Aspire.Hosting/ExecutableResourceExtensions.cs | Adds executable-annotation helpers and enumerator for annotated executables. |
| src/Aspire.Hosting/Dcp/ResourceSnapshotBuilder.cs | Derives project details from project metadata annotations when building snapshots. |
| src/Aspire.Hosting/Dcp/ExecutableCreator.cs | Prepares executables/projects using annotation-based resource discovery. |
| src/Aspire.Hosting/Dcp/DcpNameGenerator.cs | Populates DCP instance names based on project/executable annotations. |
| src/Aspire.Hosting/Dcp/DcpExecutor.cs | Uses project annotation presence to classify executable resources as “Project” vs “Executable”. |
| src/Aspire.Hosting/BuiltInDistributedApplicationEventSubscriptionHandlers.cs | Initializes DCP annotations using annotation-based resource enumeration. |
| src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs | Updates resource type classification and project endpoint tracking to rely on annotations. |
| src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs | Adds TryGetProjectAnnotation and enumerator for project-annotated resources. |
| src/Aspire.Hosting/ApplicationModel/ProjectResource.cs | Uses project annotation helper for debugger string. |
| src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs | Gates rebuild/restart messaging and rebuild command on project annotations. |
| playground/ResourceSubstitution/aspire.config.json | Adds config for new ResourceSubstitution playground. |
| playground/ResourceSubstitution/ResourceSubstitution.AppHost/ResourceSubstitution.AppHost.csproj | Adds new app host project for resource substitution demo. |
| playground/ResourceSubstitution/ResourceSubstitution.AppHost/Properties/launchSettings.json | Adds launch profiles for the ResourceSubstitution app host. |
| playground/ResourceSubstitution/ResourceSubstitution.AppHost/Extensions.cs | Adds sample “RunAsContainer/RunAsProject/RunAsTool” transmutation helpers. |
| playground/ResourceSubstitution/ResourceSubstitution.AppHost/AppHost.cs | Demonstrates running the same app as project/container/tool variations. |
| playground/ResourceSubstitution/ResourceSubstitution.Api/ResourceSubstitution.Api.csproj | Adds API project used by the ResourceSubstitution playground. |
| playground/ResourceSubstitution/ResourceSubstitution.Api/Properties/launchSettings.json | Launch settings for the sample API. |
| playground/ResourceSubstitution/ResourceSubstitution.Api/Program.cs | Minimal API for displaying runtime identity info. |
| playground/ResourceSubstitution/.vscode/launch.json | Adds VS Code launch config for the playground app host. |
| Aspire.slnx | Includes the new ResourceSubstitution playground projects in the solution. |
| internal static string GetResourceType(this IResource resource) | ||
| { | ||
| if (resource.TryGetProjectAnnotation(out _)) | ||
| { | ||
| return KnownResourceTypes.Project; | ||
| } | ||
|
|
||
| if (resource.TryGetLastAnnotation<ContainerImageAnnotation>(out _)) | ||
| { | ||
| return KnownResourceTypes.Container; | ||
| } | ||
|
|
||
| if (resource.TryGetAnnotationsOfType<DotnetToolAnnotation>(out _)) | ||
| { | ||
| return KnownResourceTypes.Tool; | ||
| } | ||
|
|
||
| if (resource.TryGetExecutableAnnotation(out _)) | ||
| { | ||
| return resource is ContainerExecutableResource | ||
| ? KnownResourceTypes.ContainerExec | ||
| : KnownResourceTypes.Executable; | ||
| } |
| internal static LaunchProfile? GetLaunchProfile(this IResource resource, string launchProfileName, bool throwIfNotFound = false) | ||
| { | ||
| var profiles = projectResource.GetLaunchSettings()?.Profiles; | ||
| if (!resource.TryGetLastAnnotation<IProjectMetadata>(out var projectMetadata)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var profiles = projectMetadata.GetLaunchSettings(resource.Name)?.Profiles; |
There was a problem hiding this comment.
No can do - TryGetProjectAnnotation is internal to Aspire.Hosting, but this file is source shared with some other projects.
…notation to the ProjectResource.
|
@afscrome can you make this a draft? |
|
@davidfowl I want to get CI running on this to validate - if I mark as draft, you have to manually kick off CI runs (which I dont' have permissions to do). I'll mark as draft once I've got what I need from CI if that's OK with you.. |
|
Thats OK I dont think we would merge this anyways. This is a big enough change that it should just be in the drafts. |
|
Turns out I lied, looks like CI is run for draft pull requests, so I can leave it as draft. |
Description
This brings the executable and project behaviour in line with containers which are already annotation based.
This lays some groundwork for #8984, but is far from a complete solution - it simply adds internal support, there are no ergonomic helpers to make it work - it is your responsibility to manipulate the resource annotations as needed.
Whilst Aspire now supports, it does mean y. Many will work (e.g. if they're extension methods on
IResourceWithEnvironment, then it will probably work for your needs), but if they're tightly coupled to a specific resource type, you'll##API Awkwardness
This does now introduce some awkwardness with the
DistributedApplicationModel.GetProjectResourcesandGetExecutableResourcesapis. They continue to return "All resources of typeExecutableResourceandProjectResource", but that may not quite match up which resources DCP creates as executables / containers.The confusion with project resources is less worrying than executables
The other awkwardness is that if you do try and switch one resource to another type, you may not have the right extension methods on your mutated resource.
Again, this isn't a completely new issue if working with custom resources or eventing, as those scenarios don't always have access to the
IResourceBuilder<T>extensions, so you have to directly manipulate annotations in those cases anyway.That said, these problems only start to happen once you start messing with annotations like this, in which case you can probably deal with the extra complexity it brings.
Test Breaks
These changes may cause some tests to break - specifically tests which manually construct
new ContainerResource(),new ProjectResource()ornew ExecutableResource()without the annotations, and then try to make an assertion on some behaviour.This won't affect real app hosts, as if you don't have the annotations, DCP would below up anyway. But it may cause some consumer tests to break. In the aspire repo, there were only two classes of tests affected by this, which I resolved by changing the tests. I considered putting backwards compatibility paths to try and maintain the existing behaviour, but it would add cruft to DCP, for resources that ultimately wouldn't execute anyway, so felt like it wasn't worth it.
Substitution Project
The substitution project shows how with the small change in DCP to make it annotation based, it vastly simplifies the effort to bait and switch a resource. It includes some example helper methods showing how you can swap a project resource for a container and vice versa. It also shows an example for contains.
aspire/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs
Line 807 in 9f4d6a7
The bait and switch once Aspire relies on Annotations and works pretty well. The main gotcha is if baiting and switching between a container and a host process, endpoints need a few tweaks
localhost, but containers may need to listen on all ipsThe current implementation in the ResourceSubstitution project also has a few gotchas with
EndpointReferencesas theAsProjectimplementation relies on creating a temporary project, stealing it's annotations and then throwing it away. The EndpointReferences in that copy end up pointing to the thrown away resource instance , and then end up being stuck waiting for the discarded resurce to start (which never will), so it needs a further hack to work around that.That said, the gotchas here pale in comparison to what you have to do right now for a bait and switch. As well as being rather brittle - I think I've had 3 aspire upgrades to this point which have broken the implementation and required work arounds, with 13.4 being the latest. (Although it works again now with the reversions in 13.4.3)
AsProject before this change
Checklist