From b0259de102c45445ca6f37774125715043ad7297 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:46:00 +0000 Subject: [PATCH 1/4] Initial plan From 17270b5746bf65bc44aaae67fdcc6e4d55b0701e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:01:06 +0000 Subject: [PATCH 2/4] Fix ParameterProcessor to use ExecutionContextOptions and skip excluded resources - Use DistributedApplicationExecutionContextOptions constructor in CollectDependentParameterResourcesAsync - Copy ServiceProvider from existing execution context when available - Skip resources excluded from publish using IsExcludedFromPublish() - Add tests to verify ServiceProvider access and excluded resource filtering Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../Orchestrator/ParameterProcessor.cs | 22 +++++- .../Orchestrator/ParameterProcessorTests.cs | 75 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs index e8730049eab..b9873087dec 100644 --- a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs +++ b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs @@ -96,10 +96,30 @@ public async Task InitializeParametersAsync(DistributedApplicationModel model, b private async Task CollectDependentParameterResourcesAsync(DistributedApplicationModel model, Dictionary referencedParameters, HashSet currentDependencySet, CancellationToken cancellationToken) { - var publishExecutionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish); + var publishExecutionContextOptions = new DistributedApplicationExecutionContextOptions(DistributedApplicationOperation.Publish); + + // Try to get the ServiceProvider from the existing execution context if available + if (executionContext.Operation == DistributedApplicationOperation.Run) + { + try + { + publishExecutionContextOptions.ServiceProvider = executionContext.ServiceProvider; + } + catch (InvalidOperationException) + { + // ServiceProvider might not be available yet, which is fine for parameter collection + } + } + + var publishExecutionContext = new DistributedApplicationExecutionContext(publishExecutionContextOptions); foreach (var resource in model.Resources) { + if (resource.IsExcludedFromPublish()) + { + continue; + } + await ProcessResourceDependenciesAsync(resource, publishExecutionContext, referencedParameters, currentDependencySet, cancellationToken).ConfigureAwait(false); } diff --git a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs index a2d8f330652..cf1aa6bd248 100644 --- a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs +++ b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs @@ -679,6 +679,81 @@ public async Task InitializeParametersAsync_WithDistributedApplicationModel_Hand } } + [Fact] + public async Task InitializeParametersAsync_UsesExecutionContextOptions_DoesNotThrow() + { + // Arrange + using var builder = TestDistributedApplicationBuilder.Create(); + var param = builder.AddParameter("testParam", () => "testValue"); + + var serviceProviderAccessed = false; + builder.AddContainer("testContainer", "nginx") + .WithEnvironment(context => + { + // This should not throw InvalidOperationException + // when using the proper execution context constructor + var sp = context.ExecutionContext.ServiceProvider; + serviceProviderAccessed = sp is not null; + context.EnvironmentVariables["TEST_ENV"] = param; + }); + + using var app = builder.Build(); + var model = app.Services.GetRequiredService(); + + // Get the ParameterProcessor from the built app's service provider + // This ensures it has the proper execution context with ServiceProvider + var parameterProcessor = app.Services.GetRequiredService(); + + // Act - Should not throw InvalidOperationException about IServiceProvider not being available + await parameterProcessor.InitializeParametersAsync(model); + + // Assert + Assert.True(serviceProviderAccessed); + var parameterResource = model.Resources.OfType().Single(); + Assert.NotNull(parameterResource.WaitForValueTcs); + Assert.True(parameterResource.WaitForValueTcs.Task.IsCompletedSuccessfully); + } + + [Fact] + public async Task InitializeParametersAsync_SkipsResourcesExcludedFromPublish() + { + // Arrange + using var builder = TestDistributedApplicationBuilder.Create(); + var param = builder.AddParameter("excludedParam", () => "excludedValue"); + + var excludedContainer = builder.AddContainer("excludedContainer", "nginx") + .WithEnvironment(context => + { + context.EnvironmentVariables["EXCLUDED_ENV"] = param; + }); + + // Mark the container as excluded from publish + excludedContainer.WithAnnotation(ApplicationModel.ManifestPublishingCallbackAnnotation.Ignore); + + using var app = builder.Build(); + var model = app.Services.GetRequiredService(); + + var parameterProcessor = CreateParameterProcessor(); + + // Act - The excluded container should be skipped during parameter collection + await parameterProcessor.InitializeParametersAsync(model); + + // Assert + // The environment callback should have been invoked during parameter collection + // because we now create a publish execution context to collect dependent parameters + // However, since we filter out excluded resources, the parameter should not be initialized + // unless it's explicitly in the model + var parameters = model.Resources.OfType().ToList(); + Assert.Single(parameters); + + var parameterResource = parameters[0]; + Assert.Equal("excludedParam", parameterResource.Name); + + // The parameter should be initialized since it's explicitly in the model + Assert.NotNull(parameterResource.WaitForValueTcs); + Assert.True(parameterResource.WaitForValueTcs.Task.IsCompletedSuccessfully); + } + private static ParameterProcessor CreateParameterProcessor( ResourceNotificationService? notificationService = null, ResourceLoggerService? loggerService = null, From d2eaea256cdeb38026ee6e43f5f71e52cb2140be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:22:10 +0000 Subject: [PATCH 3/4] Simplify: use existing executionContext instead of creating new one Use the executionContext from constructor instead of creating a new publishExecutionContext Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../Orchestrator/ParameterProcessor.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs index b9873087dec..549868e494c 100644 --- a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs +++ b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs @@ -96,23 +96,6 @@ public async Task InitializeParametersAsync(DistributedApplicationModel model, b private async Task CollectDependentParameterResourcesAsync(DistributedApplicationModel model, Dictionary referencedParameters, HashSet currentDependencySet, CancellationToken cancellationToken) { - var publishExecutionContextOptions = new DistributedApplicationExecutionContextOptions(DistributedApplicationOperation.Publish); - - // Try to get the ServiceProvider from the existing execution context if available - if (executionContext.Operation == DistributedApplicationOperation.Run) - { - try - { - publishExecutionContextOptions.ServiceProvider = executionContext.ServiceProvider; - } - catch (InvalidOperationException) - { - // ServiceProvider might not be available yet, which is fine for parameter collection - } - } - - var publishExecutionContext = new DistributedApplicationExecutionContext(publishExecutionContextOptions); - foreach (var resource in model.Resources) { if (resource.IsExcludedFromPublish()) @@ -120,7 +103,7 @@ private async Task CollectDependentParameterResourcesAsync(DistributedApplicatio continue; } - await ProcessResourceDependenciesAsync(resource, publishExecutionContext, referencedParameters, currentDependencySet, cancellationToken).ConfigureAwait(false); + await ProcessResourceDependenciesAsync(resource, executionContext, referencedParameters, currentDependencySet, cancellationToken).ConfigureAwait(false); } } From dec1613eb2186964f24f1593eefac1fd965110ca Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 30 Sep 2025 00:32:42 -0700 Subject: [PATCH 4/4] Update tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs --- .../Orchestrator/ParameterProcessorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs index cf1aa6bd248..79ccb9dabb0 100644 --- a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs +++ b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs @@ -728,7 +728,7 @@ public async Task InitializeParametersAsync_SkipsResourcesExcludedFromPublish() }); // Mark the container as excluded from publish - excludedContainer.WithAnnotation(ApplicationModel.ManifestPublishingCallbackAnnotation.Ignore); + excludedContainer.ExcludeFromManifest(); using var app = builder.Build(); var model = app.Services.GetRequiredService();