diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index 5588d7a3a8a..f2cf3deb3ec 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -128,14 +128,16 @@ private DistributedApplicationExecutionContextOptions BuildExecutionContextOptio { "publish" => DistributedApplicationOperation.Publish, "run" => DistributedApplicationOperation.Run, - _ => throw new DistributedApplicationException("Invalid operation specified. Valid operations are 'publish' or 'run'.") + "inspect" => DistributedApplicationOperation.Inspect, + _ => throw new DistributedApplicationException("Invalid operation specified. Valid operations are 'publish', 'run', or 'inspect'.") }; return operation switch { DistributedApplicationOperation.Run => new DistributedApplicationExecutionContextOptions(operation), DistributedApplicationOperation.Publish => new DistributedApplicationExecutionContextOptions(operation, _innerBuilder.Configuration["Publishing:Publisher"] ?? "manifest"), - _ => throw new DistributedApplicationException("Invalid operation specified. Valid operations are 'publish' or 'run'.") + DistributedApplicationOperation.Inspect => new DistributedApplicationExecutionContextOptions(operation), + _ => throw new DistributedApplicationException("Invalid operation specified. Valid operations are 'publish', 'run', or 'inspect'.") }; } @@ -477,9 +479,9 @@ private void ConfigureHealthChecks() { return new ConfigureOptions(options => { - if (ExecutionContext.IsPublishMode) + if (ExecutionContext.IsPublishMode || ExecutionContext.IsInspectMode) { - // In publish mode we don't run any checks. + // In publish mode and inspect mode we don't run any checks. options.Predicate = (check) => false; } }); diff --git a/src/Aspire.Hosting/DistributedApplicationExecutionContext.cs b/src/Aspire.Hosting/DistributedApplicationExecutionContext.cs index 7af4adc9ad8..d7d957f2239 100644 --- a/src/Aspire.Hosting/DistributedApplicationExecutionContext.cs +++ b/src/Aspire.Hosting/DistributedApplicationExecutionContext.cs @@ -85,4 +85,9 @@ public IServiceProvider ServiceProvider /// Returns true if the current operation is running. /// public bool IsRunMode => Operation == DistributedApplicationOperation.Run; + + /// + /// Returns true if the current operation is inspect mode. + /// + public bool IsInspectMode => Operation == DistributedApplicationOperation.Inspect; } diff --git a/src/Aspire.Hosting/DistributedApplicationOperation.cs b/src/Aspire.Hosting/DistributedApplicationOperation.cs index a3e78a6e884..d04afcb847e 100644 --- a/src/Aspire.Hosting/DistributedApplicationOperation.cs +++ b/src/Aspire.Hosting/DistributedApplicationOperation.cs @@ -16,5 +16,10 @@ public enum DistributedApplicationOperation /// /// AppHost is being run for the purpose of publishing a manifest for deployment. /// - Publish + Publish, + + /// + /// AppHost is being run in inspect mode for introspection without activating DCP or Publishers. + /// + Inspect } diff --git a/src/Aspire.Hosting/Orchestrator/OrchestratorHostService.cs b/src/Aspire.Hosting/Orchestrator/OrchestratorHostService.cs index 49cd43d066e..c4023a25272 100644 --- a/src/Aspire.Hosting/Orchestrator/OrchestratorHostService.cs +++ b/src/Aspire.Hosting/Orchestrator/OrchestratorHostService.cs @@ -27,7 +27,7 @@ public OrchestratorHostService( _dcpHost = dcpHost; } - private bool IsSupported => !_executionContext.IsPublishMode; + private bool IsSupported => _executionContext.IsRunMode; public async Task StartAsync(CancellationToken cancellationToken = default) { diff --git a/tests/Aspire.Hosting.Tests/OperationModesTests.cs b/tests/Aspire.Hosting.Tests/OperationModesTests.cs index 02de295229d..8148431a3b8 100644 --- a/tests/Aspire.Hosting.Tests/OperationModesTests.cs +++ b/tests/Aspire.Hosting.Tests/OperationModesTests.cs @@ -3,6 +3,7 @@ #pragma warning disable ASPIREPUBLISHERS001 +using Aspire.Hosting.Publishing; using Aspire.Hosting.Utils; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; @@ -138,4 +139,92 @@ public void VerifyExplicitPublishModeInvocation() .WithTestAndResourceLogging(outputHelper); Assert.Equal(DistributedApplicationOperation.Publish, builder.ExecutionContext.Operation); } + + [Fact] + public void VerifyExplicitInspectModeParsingOnly() + { + // The purpose of this test is to verify that the apphost executable will enter + // inspect mode if executed with the "--operation inspect" argument. + + using var builder = TestDistributedApplicationBuilder + .Create(["--operation", "inspect"]) + .WithTestAndResourceLogging(outputHelper); + + Assert.Equal(DistributedApplicationOperation.Inspect, builder.ExecutionContext.Operation); + Assert.True(builder.ExecutionContext.IsInspectMode); + Assert.False(builder.ExecutionContext.IsRunMode); + Assert.False(builder.ExecutionContext.IsPublishMode); + } + + [Fact] + public async Task VerifyExplicitInspectModeInvocation() + { + // The purpose of this test is to verify that the apphost executable will enter + // inspect mode if executed with the "--operation inspect" argument. + + using var builder = TestDistributedApplicationBuilder + .Create(["--operation", "inspect"]) + .WithTestAndResourceLogging(outputHelper); + + var tcs = new TaskCompletionSource(); + builder.Eventing.Subscribe((e, ct) => { + var context = e.Services.GetRequiredService(); + tcs.SetResult(context); + return Task.CompletedTask; + }); + + using var app = builder.Build(); + + await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); + + var context = await tcs.Task.WaitAsync(TestConstants.DefaultTimeoutTimeSpan); + + await app.StopAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); + + Assert.Equal(DistributedApplicationOperation.Inspect, context.Operation); + Assert.True(context.IsInspectMode); + Assert.False(context.IsRunMode); + Assert.False(context.IsPublishMode); + } + + [Fact] + public void VerifyInspectModeDoesNotRegisterDcp() + { + // Verify that DCP services are not registered in inspect mode + using var builder = TestDistributedApplicationBuilder + .Create(["--operation", "inspect"]) + .WithTestAndResourceLogging(outputHelper); + + Assert.Equal(DistributedApplicationOperation.Inspect, builder.ExecutionContext.Operation); + + using var app = builder.Build(); + + // Verify DCP-related services are not registered + Assert.Null(app.Services.GetService()); + Assert.Null(app.Services.GetService()); + Assert.Null(app.Services.GetService()); + } + + [Fact] + public async Task VerifyInspectModeDoesNotExecutePublisher() + { + // Verify that publishers are not executed in inspect mode + using var builder = TestDistributedApplicationBuilder + .Create(["--operation", "inspect"]) + .WithTestAndResourceLogging(outputHelper); + + var publisherExecuted = false; + builder.Eventing.Subscribe((e, ct) => { + publisherExecuted = true; + return Task.CompletedTask; + }); + + using var app = builder.Build(); + + await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); + await app.StopAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); + + // Publisher should not have executed + Assert.False(publisherExecuted); + } } diff --git a/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs b/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs index 0d0bff96007..807b256f80d 100644 --- a/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs +++ b/tests/Aspire.Hosting.Tests/Utils/TestDistributedApplicationBuilder.cs @@ -23,6 +23,7 @@ public static IDistributedApplicationTestingBuilder Create(DistributedApplicatio { DistributedApplicationOperation.Run => (string[])[], DistributedApplicationOperation.Publish => [$"Publishing:Publisher={publisher}", $"Publishing:OutputPath={outputPath}", $"Publishing:Deploy={isDeploy}"], + DistributedApplicationOperation.Inspect => ["--operation", "inspect"], _ => throw new ArgumentOutOfRangeException(nameof(operation)) };