From d9e4a8c78671b536b51322202f6c0b1128faaec8 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 May 2026 09:34:06 -0700 Subject: [PATCH 1/6] Add polyglot publish lifecycle events Expose BeforePublish and AfterPublish lifecycle events through ATS-friendly builder and eventing subscriber APIs so TypeScript and other polyglot AppHosts can subscribe during publish flows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Hosting/Ats/BuilderExports.cs | 51 ++++ .../Ats/ServiceCollectionExports.cs | 31 +++ .../Publishing/AfterPublishEvent.cs | 3 +- .../Publishing/BeforePublishEvent.cs | 3 +- .../api/Aspire.Hosting.Capabilities.txt | 8 + src/Aspire.Hosting/api/Aspire.Hosting.ats.txt | 10 + ...TwoPassScanningGeneratedAspire.verified.go | 232 +++++++++++++++++- ...oPassScanningGeneratedAspire.verified.java | 126 +++++++++- ...TwoPassScanningGeneratedAspire.verified.py | 112 ++++++++- .../AtsTypeScriptCodeGeneratorTests.cs | 4 + ...TwoPassScanningGeneratedAspire.verified.ts | 192 ++++++++++++++- .../Aspire.Hosting/Go/apphost.go | 22 ++ .../Aspire.Hosting/Java/AppHost.java | 16 ++ .../Aspire.Hosting/Python/apphost.py | 18 ++ .../Aspire.Hosting/TypeScript/apphost.ts | 36 +++ 15 files changed, 858 insertions(+), 6 deletions(-) diff --git a/src/Aspire.Hosting/Ats/BuilderExports.cs b/src/Aspire.Hosting/Ats/BuilderExports.cs index 8bfca79182b..95954787770 100644 --- a/src/Aspire.Hosting/Ats/BuilderExports.cs +++ b/src/Aspire.Hosting/Ats/BuilderExports.cs @@ -3,6 +3,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Eventing; +using Aspire.Hosting.Publishing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -30,6 +31,8 @@ namespace Aspire.Hosting.Ats; /// /// subscribeBeforeStart - Called before the application starts /// subscribeAfterResourcesCreated - Called after resources are created +/// subscribeBeforePublish - Called before the application is published +/// subscribeAfterPublish - Called after the application is published /// /// /// @@ -209,6 +212,54 @@ public static DistributedApplicationEventSubscription SubscribeBeforeStart( }); } + /// + /// Subscribes to the BeforePublish event, which fires before the application is published. + /// + /// + /// This event provides access to the service provider and distributed application model, + /// allowing you to perform final configuration or validation before publish pipeline steps run. + /// + /// The builder handle. + /// A callback that receives the exported event when the event fires. + /// A subscription handle that can be used to unsubscribe. + [AspireExport(Description = "Subscribes to the BeforePublish event")] + public static DistributedApplicationEventSubscription SubscribeBeforePublish( + this IDistributedApplicationBuilder builder, + Func callback) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(callback); + + return builder.Eventing.Subscribe(async (@event, ct) => + { + await callback(@event).ConfigureAwait(false); + }); + } + + /// + /// Subscribes to the AfterPublish event, which fires after the application is published. + /// + /// + /// This event provides access to the service provider and distributed application model, + /// allowing you to inspect the model after publish pipeline steps complete. + /// + /// The builder handle. + /// A callback that receives the exported event when the event fires. + /// A subscription handle that can be used to unsubscribe. + [AspireExport(Description = "Subscribes to the AfterPublish event")] + public static DistributedApplicationEventSubscription SubscribeAfterPublish( + this IDistributedApplicationBuilder builder, + Func callback) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(callback); + + return builder.Eventing.Subscribe(async (@event, ct) => + { + await callback(@event).ConfigureAwait(false); + }); + } + /// /// Subscribes to the AfterResourcesCreated event, which fires after all resources are created. /// diff --git a/src/Aspire.Hosting/Ats/ServiceCollectionExports.cs b/src/Aspire.Hosting/Ats/ServiceCollectionExports.cs index 084c22bcba7..a7198497743 100644 --- a/src/Aspire.Hosting/Ats/ServiceCollectionExports.cs +++ b/src/Aspire.Hosting/Ats/ServiceCollectionExports.cs @@ -4,6 +4,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Eventing; using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Publishing; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting.Ats; @@ -76,6 +77,36 @@ public static DistributedApplicationEventSubscription OnBeforeStart(this Eventin return context.Eventing.Subscribe((@event, _) => callback(@event)); } + /// + /// Subscribes to the BeforePublish event from an eventing subscriber registration context. + /// + /// The eventing subscriber registration context. + /// The callback to invoke when the event fires. + /// The event subscription. + [AspireExport("eventingSubscriberOnBeforePublish", MethodName = "onBeforePublish", Description = "Subscribes an eventing subscriber to the BeforePublish event")] + public static DistributedApplicationEventSubscription OnBeforePublish(this EventingSubscriberRegistrationContext context, Func callback) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(callback); + + return context.Eventing.Subscribe((@event, _) => callback(@event)); + } + + /// + /// Subscribes to the AfterPublish event from an eventing subscriber registration context. + /// + /// The eventing subscriber registration context. + /// The callback to invoke when the event fires. + /// The event subscription. + [AspireExport("eventingSubscriberOnAfterPublish", MethodName = "onAfterPublish", Description = "Subscribes an eventing subscriber to the AfterPublish event")] + public static DistributedApplicationEventSubscription OnAfterPublish(this EventingSubscriberRegistrationContext context, Func callback) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(callback); + + return context.Eventing.Subscribe((@event, _) => callback(@event)); + } + /// /// Subscribes to the AfterResourcesCreated event from an eventing subscriber registration context. /// diff --git a/src/Aspire.Hosting/Publishing/AfterPublishEvent.cs b/src/Aspire.Hosting/Publishing/AfterPublishEvent.cs index 1ab45f4e543..69adce6db69 100644 --- a/src/Aspire.Hosting/Publishing/AfterPublishEvent.cs +++ b/src/Aspire.Hosting/Publishing/AfterPublishEvent.cs @@ -11,6 +11,7 @@ namespace Aspire.Hosting.Publishing; /// /// The for the app host. /// The . +[AspireExport(ExposeProperties = true)] public sealed class AfterPublishEvent(IServiceProvider services, DistributedApplicationModel model) : IDistributedApplicationEvent { /// @@ -22,4 +23,4 @@ public sealed class AfterPublishEvent(IServiceProvider services, DistributedAppl /// The instance. /// public DistributedApplicationModel Model { get; } = model; -} \ No newline at end of file +} diff --git a/src/Aspire.Hosting/Publishing/BeforePublishEvent.cs b/src/Aspire.Hosting/Publishing/BeforePublishEvent.cs index dc6e26947da..b8d7b0100a6 100644 --- a/src/Aspire.Hosting/Publishing/BeforePublishEvent.cs +++ b/src/Aspire.Hosting/Publishing/BeforePublishEvent.cs @@ -11,6 +11,7 @@ namespace Aspire.Hosting.Publishing; /// /// The for the app host. /// The . +[AspireExport(ExposeProperties = true)] public sealed class BeforePublishEvent(IServiceProvider services, DistributedApplicationModel model) : IDistributedApplicationEvent { /// @@ -22,4 +23,4 @@ public sealed class BeforePublishEvent(IServiceProvider services, DistributedApp /// The instance. /// public DistributedApplicationModel Model { get; } = model; -} \ No newline at end of file +} diff --git a/src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt b/src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt index eb49d9ba830..77a9db017e7 100644 --- a/src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt +++ b/src/Aspire.Hosting/api/Aspire.Hosting.Capabilities.txt @@ -59,6 +59,8 @@ Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepFactoryContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineSummary [ExposeMethods] Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent [ExposeProperties] Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfiguration [interface] Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfigurationSection [interface, ExposeProperties] Microsoft.Extensions.Hosting.Abstractions/Microsoft.Extensions.Hosting.IHostEnvironment [interface, ExposeProperties] @@ -232,6 +234,10 @@ Aspire.Hosting.Pipelines/PipelineStepFactoryContext.setPipelineContext(context: Aspire.Hosting.Pipelines/PipelineStepFactoryContext.setResource(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepFactoryContext, value: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource) -> Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepFactoryContext Aspire.Hosting.Pipelines/PipelineSummary.add(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineSummary, key: string, value: string) -> void Aspire.Hosting.Pipelines/requiredBy(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep, stepName: string) -> void +Aspire.Hosting.Publishing/AfterPublishEvent.model(context: Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.DistributedApplicationModel +Aspire.Hosting.Publishing/AfterPublishEvent.services(context: Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent) -> System.ComponentModel/System.IServiceProvider +Aspire.Hosting.Publishing/BeforePublishEvent.model(context: Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.DistributedApplicationModel +Aspire.Hosting.Publishing/BeforePublishEvent.services(context: Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent) -> System.ComponentModel/System.IServiceProvider Aspire.Hosting/addConnectionString(name: string, environmentVariableName?: string) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString Aspire.Hosting/addConnectionStringBuilder(name: string, connectionStringBuilder: callback) -> Aspire.Hosting/Aspire.Hosting.ConnectionStringResource Aspire.Hosting/addConnectionStringExpression(name: string, connectionStringExpression: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression) -> Aspire.Hosting/Aspire.Hosting.ConnectionStringResource @@ -350,7 +356,9 @@ Aspire.Hosting/publishResourceUpdate(resource: Aspire.Hosting/Aspire.Hosting.App Aspire.Hosting/publishWithContainerFiles(source: Aspire.Hosting/Aspire.Hosting.IResourceWithContainerFiles, destinationPath: string) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IContainerFilesDestinationResource Aspire.Hosting/run(context: Aspire.Hosting/Aspire.Hosting.DistributedApplication, cancellationToken?: cancellationToken) -> void Aspire.Hosting/saveStateJson(json: string, cancellationToken?: cancellationToken) -> void +Aspire.Hosting/subscribeAfterPublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/subscribeAfterResourcesCreated(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription +Aspire.Hosting/subscribeBeforePublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/subscribeBeforeStart(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/tryGetResourceState(resourceName: string) -> Aspire.Hosting/Aspire.Hosting.Ats.ResourceEventDto Aspire.Hosting/updateTask(statusText: string, cancellationToken?: cancellationToken) -> void diff --git a/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt b/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt index c34f82e7916..8c93261e782 100644 --- a/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt +++ b/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt @@ -79,6 +79,8 @@ Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStepFactoryContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineSummary [ExposeMethods] Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent [ExposeProperties] Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfiguration [interface] Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfigurationSection [interface, ExposeProperties] Microsoft.Extensions.Hosting.Abstractions/Microsoft.Extensions.Hosting.IHostEnvironment [interface, ExposeProperties] @@ -400,6 +402,10 @@ Aspire.Hosting.Pipelines/PipelineSummary.add(context: Aspire.Hosting/Aspire.Host Aspire.Hosting.Pipelines/requiredBy(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep, stepName: string) -> void Aspire.Hosting.Pipelines/steps(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineEditor) -> Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep[] Aspire.Hosting.Pipelines/stepsByTag(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineEditor, tag: string) -> Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep[] +Aspire.Hosting.Publishing/AfterPublishEvent.model(context: Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.DistributedApplicationModel +Aspire.Hosting.Publishing/AfterPublishEvent.services(context: Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent) -> System.ComponentModel/System.IServiceProvider +Aspire.Hosting.Publishing/BeforePublishEvent.model(context: Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.DistributedApplicationModel +Aspire.Hosting.Publishing/BeforePublishEvent.services(context: Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent) -> System.ComponentModel/System.IServiceProvider Aspire.Hosting/addConnectionString(name: string, environmentVariableNameOrExpression?: string|Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString Aspire.Hosting/addContainer(name: string, image: string|Aspire.Hosting/Aspire.Hosting.Ats.AddContainerOptions) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource Aspire.Hosting/addContainerRegistry(name: string, endpoint: string|Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource, repository?: string|Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerRegistryResource @@ -462,7 +468,9 @@ Aspire.Hosting/dockerfileStageRun(command: string) -> Aspire.Hosting/Aspire.Host Aspire.Hosting/emptyLine() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileStage Aspire.Hosting/entrypoint(command: string[]) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileStage Aspire.Hosting/env(name: string, value: string) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileStage +Aspire.Hosting/eventingSubscriberOnAfterPublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/eventingSubscriberOnAfterResourcesCreated(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription +Aspire.Hosting/eventingSubscriberOnBeforePublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/eventingSubscriberOnBeforeStart(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/excludeFromManifest() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource Aspire.Hosting/excludeFromMcp() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource @@ -544,7 +552,9 @@ Aspire.Hosting/publishWithContainerFilesFromResource(source: Aspire.Hosting/Aspi Aspire.Hosting/run(context: Aspire.Hosting/Aspire.Hosting.DistributedApplication, cancellationToken?: cancellationToken) -> void Aspire.Hosting/runWithMounts(command: string, mounts: string[]) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileStage Aspire.Hosting/saveStateJson(json: string, cancellationToken?: cancellationToken) -> void +Aspire.Hosting/subscribeAfterPublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/subscribeAfterResourcesCreated(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription +Aspire.Hosting/subscribeBeforePublish(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/subscribeBeforeStart(callback: callback) -> Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription Aspire.Hosting/tryAddEventingSubscriber(subscribe: callback) -> void Aspire.Hosting/tryGetResourceState(resourceName: string) -> Aspire.Hosting/Aspire.Hosting.Ats.ResourceEventDto diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go index 31b8c31b4e8..6fa0d090064 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go @@ -1,4 +1,4 @@ -// aspire.go - Capability-based Aspire SDK +// aspire.go - Capability-based Aspire SDK // This SDK uses the ATS (Aspire Type System) capability API. // Capabilities are endpoints like 'Aspire.Hosting/createBuilder'. // @@ -835,6 +835,62 @@ type TestVaultResource interface { // Handle wrappers // ============================================================================ +// AfterPublishEvent is the public interface for handle type AfterPublishEvent. +type AfterPublishEvent interface { + handleReference + Model() DistributedApplicationModel + Services() ServiceProvider + Err() error +} + +// afterPublishEvent is the unexported impl of AfterPublishEvent. +type afterPublishEvent struct { + *resourceBuilderBase +} + +// newAfterPublishEventFromHandle wraps an existing handle as AfterPublishEvent. +func newAfterPublishEventFromHandle(h *handle, c *client) AfterPublishEvent { + return &afterPublishEvent{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// Model gets the Model property +func (s *afterPublishEvent) Model() DistributedApplicationModel { + if s.err != nil { return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Publishing/AfterPublishEvent.model", reqArgs) + if err != nil { + return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Publishing/AfterPublishEvent.model returned unexpected type %T", result) + return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationModel{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// Services gets the Services property +func (s *afterPublishEvent) Services() ServiceProvider { + if s.err != nil { return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Publishing/AfterPublishEvent.services", reqArgs) + if err != nil { + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Publishing/AfterPublishEvent.services returned unexpected type %T", result) + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &serviceProvider{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // AfterResourcesCreatedEvent is the public interface for handle type AfterResourcesCreatedEvent. type AfterResourcesCreatedEvent interface { handleReference @@ -2712,6 +2768,62 @@ func (s *aspire_Hosting_CodeGeneration_Go_TestsTestVaultResource) WithoutHttpsCe return s } +// BeforePublishEvent is the public interface for handle type BeforePublishEvent. +type BeforePublishEvent interface { + handleReference + Model() DistributedApplicationModel + Services() ServiceProvider + Err() error +} + +// beforePublishEvent is the unexported impl of BeforePublishEvent. +type beforePublishEvent struct { + *resourceBuilderBase +} + +// newBeforePublishEventFromHandle wraps an existing handle as BeforePublishEvent. +func newBeforePublishEventFromHandle(h *handle, c *client) BeforePublishEvent { + return &beforePublishEvent{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// Model gets the Model property +func (s *beforePublishEvent) Model() DistributedApplicationModel { + if s.err != nil { return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Publishing/BeforePublishEvent.model", reqArgs) + if err != nil { + return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Publishing/BeforePublishEvent.model returned unexpected type %T", result) + return &distributedApplicationModel{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationModel{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// Services gets the Services property +func (s *beforePublishEvent) Services() ServiceProvider { + if s.err != nil { return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Publishing/BeforePublishEvent.services", reqArgs) + if err != nil { + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Publishing/BeforePublishEvent.services returned unexpected type %T", result) + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &serviceProvider{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // BeforeResourceStartedEvent is the public interface for handle type BeforeResourceStartedEvent. type BeforeResourceStartedEvent interface { handleReference @@ -7504,7 +7616,9 @@ type DistributedApplicationBuilder interface { ExecutionContext() DistributedApplicationExecutionContext GetConfiguration() Configuration Pipeline() DistributedApplicationPipeline + SubscribeAfterPublish(callback func(arg AfterPublishEvent)) DistributedApplicationEventSubscription SubscribeAfterResourcesCreated(callback func(arg AfterResourcesCreatedEvent)) DistributedApplicationEventSubscription + SubscribeBeforePublish(callback func(arg BeforePublishEvent)) DistributedApplicationEventSubscription SubscribeBeforeStart(callback func(arg BeforeStartEvent)) DistributedApplicationEventSubscription TryAddEventingSubscriber(subscribe func(arg EventingSubscriberRegistrationContext)) error UserSecretsManager() UserSecretsManager @@ -8061,6 +8175,33 @@ func (s *distributedApplicationBuilder) Pipeline() DistributedApplicationPipelin return &distributedApplicationPipeline{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } +// SubscribeAfterPublish subscribes to the AfterPublish event +func (s *distributedApplicationBuilder) SubscribeAfterPublish(callback func(arg AfterPublishEvent)) DistributedApplicationEventSubscription { + if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "builder": s.handle.ToJSON(), + } + if callback != nil { + cb := callback + shim := func(args ...any) any { + cb(callbackArg[AfterPublishEvent](args, 0)) + return nil + } + reqArgs["callback"] = s.client.registerCallback(shim) + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/subscribeAfterPublish", reqArgs) + if err != nil { + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/subscribeAfterPublish returned unexpected type %T", result) + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // SubscribeAfterResourcesCreated subscribes to the AfterResourcesCreated event func (s *distributedApplicationBuilder) SubscribeAfterResourcesCreated(callback func(arg AfterResourcesCreatedEvent)) DistributedApplicationEventSubscription { if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } @@ -8088,6 +8229,33 @@ func (s *distributedApplicationBuilder) SubscribeAfterResourcesCreated(callback return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } +// SubscribeBeforePublish subscribes to the BeforePublish event +func (s *distributedApplicationBuilder) SubscribeBeforePublish(callback func(arg BeforePublishEvent)) DistributedApplicationEventSubscription { + if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "builder": s.handle.ToJSON(), + } + if callback != nil { + cb := callback + shim := func(args ...any) any { + cb(callbackArg[BeforePublishEvent](args, 0)) + return nil + } + reqArgs["callback"] = s.client.registerCallback(shim) + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/subscribeBeforePublish", reqArgs) + if err != nil { + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/subscribeBeforePublish returned unexpected type %T", result) + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // SubscribeBeforeStart subscribes to the BeforeStart event func (s *distributedApplicationBuilder) SubscribeBeforeStart(callback func(arg BeforeStartEvent)) DistributedApplicationEventSubscription { if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } @@ -11311,7 +11479,9 @@ type EventingSubscriberRegistrationContext interface { handleReference CancellationToken() (*CancellationToken, error) ExecutionContext() DistributedApplicationExecutionContext + OnAfterPublish(callback func(arg AfterPublishEvent)) DistributedApplicationEventSubscription OnAfterResourcesCreated(callback func(arg AfterResourcesCreatedEvent)) DistributedApplicationEventSubscription + OnBeforePublish(callback func(arg BeforePublishEvent)) DistributedApplicationEventSubscription OnBeforeStart(callback func(arg BeforeStartEvent)) DistributedApplicationEventSubscription Err() error } @@ -11360,6 +11530,33 @@ func (s *eventingSubscriberRegistrationContext) ExecutionContext() DistributedAp return &distributedApplicationExecutionContext{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } +// OnAfterPublish subscribes an eventing subscriber to the AfterPublish event +func (s *eventingSubscriberRegistrationContext) OnAfterPublish(callback func(arg AfterPublishEvent)) DistributedApplicationEventSubscription { + if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + if callback != nil { + cb := callback + shim := func(args ...any) any { + cb(callbackArg[AfterPublishEvent](args, 0)) + return nil + } + reqArgs["callback"] = s.client.registerCallback(shim) + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/eventingSubscriberOnAfterPublish", reqArgs) + if err != nil { + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/eventingSubscriberOnAfterPublish returned unexpected type %T", result) + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // OnAfterResourcesCreated subscribes an eventing subscriber to the AfterResourcesCreated event func (s *eventingSubscriberRegistrationContext) OnAfterResourcesCreated(callback func(arg AfterResourcesCreatedEvent)) DistributedApplicationEventSubscription { if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } @@ -11387,6 +11584,33 @@ func (s *eventingSubscriberRegistrationContext) OnAfterResourcesCreated(callback return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } +// OnBeforePublish subscribes an eventing subscriber to the BeforePublish event +func (s *eventingSubscriberRegistrationContext) OnBeforePublish(callback func(arg BeforePublishEvent)) DistributedApplicationEventSubscription { + if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + if callback != nil { + cb := callback + shim := func(args ...any) any { + cb(callbackArg[BeforePublishEvent](args, 0)) + return nil + } + reqArgs["callback"] = s.client.registerCallback(shim) + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/eventingSubscriberOnBeforePublish", reqArgs) + if err != nil { + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/eventingSubscriberOnBeforePublish returned unexpected type %T", result) + return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &distributedApplicationEventSubscription{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // OnBeforeStart subscribes an eventing subscriber to the BeforeStart event func (s *eventingSubscriberRegistrationContext) OnBeforeStart(callback func(arg BeforeStartEvent)) DistributedApplicationEventSubscription { if s.err != nil { return &distributedApplicationEventSubscription{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } @@ -24234,6 +24458,9 @@ func registerWrappers(c *client) { c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression", func(h *handle, c *client) any { return newHandleBackedReferenceExpression(h, c) }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent", func(h *handle, c *client) any { + return newAfterPublishEventFromHandle(h, c) + }) c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent", func(h *handle, c *client) any { return newAfterResourcesCreatedEventFromHandle(h, c) }) @@ -24243,6 +24470,9 @@ func registerWrappers(c *client) { c.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestVaultResource", func(h *handle, c *client) any { return newAspire_Hosting_CodeGeneration_Go_TestsTestVaultResourceFromHandle(h, c) }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent", func(h *handle, c *client) any { + return newBeforePublishEventFromHandle(h, c) + }) c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent", func(h *handle, c *client) any { return newBeforeResourceStartedEventFromHandle(h, c) }) diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java index c56156fe1d2..fed0bc36264 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java @@ -1,4 +1,4 @@ -// ===== AddContainerOptions.java ===== +// ===== AddContainerOptions.java ===== // AddContainerOptions.java - GENERATED CODE - DO NOT EDIT package aspire; @@ -139,6 +139,36 @@ public AddStepOptions requiredBy(String[] value) { } +// ===== AfterPublishEvent.java ===== +// AfterPublishEvent.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent. */ +public class AfterPublishEvent extends HandleWrapperBase { + AfterPublishEvent(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Services property */ + public IServiceProvider services() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (IServiceProvider) getClient().invokeCapability("Aspire.Hosting.Publishing/AfterPublishEvent.services", reqArgs); + } + + /** Gets the Model property */ + public DistributedApplicationModel model() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationModel) getClient().invokeCapability("Aspire.Hosting.Publishing/AfterPublishEvent.model", reqArgs); + } + +} + // ===== AfterResourcesCreatedEvent.java ===== // AfterResourcesCreatedEvent.java - GENERATED CODE - DO NOT EDIT @@ -1269,6 +1299,8 @@ public class AspireRegistrations { AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext", (h, c) -> new InputsDialogValidationContext(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions", (h, c) -> new ProjectResourceOptions(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IUserSecretsManager", (h, c) -> new IUserSecretsManager(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent", (h, c) -> new AfterPublishEvent(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent", (h, c) -> new BeforePublishEvent(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineConfigurationContext", (h, c) -> new PipelineConfigurationContext(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineContext", (h, c) -> new PipelineContext(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineEditor", (h, c) -> new PipelineEditor(h, c)); @@ -1407,6 +1439,36 @@ static void ensureRegistered() { } } +// ===== BeforePublishEvent.java ===== +// BeforePublishEvent.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent. */ +public class BeforePublishEvent extends HandleWrapperBase { + BeforePublishEvent(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Services property */ + public IServiceProvider services() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (IServiceProvider) getClient().invokeCapability("Aspire.Hosting.Publishing/BeforePublishEvent.services", reqArgs); + } + + /** Gets the Model property */ + public DistributedApplicationModel model() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationModel) getClient().invokeCapability("Aspire.Hosting.Publishing/BeforePublishEvent.model", reqArgs); + } + +} + // ===== BeforeResourceStartedEvent.java ===== // BeforeResourceStartedEvent.java - GENERATED CODE - DO NOT EDIT @@ -8659,6 +8721,36 @@ public DistributedApplicationEventSubscription onBeforeStart(AspireAction1 callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var callbackId = getClient().registerCallback(args -> { + var arg = (BeforePublishEvent) args[0]; + callback.invoke(arg); + return null; + }); + if (callbackId != null) { + reqArgs.put("callback", callbackId); + } + return (DistributedApplicationEventSubscription) getClient().invokeCapability("Aspire.Hosting/eventingSubscriberOnBeforePublish", reqArgs); + } + + /** Subscribes an eventing subscriber to the AfterPublish event */ + public DistributedApplicationEventSubscription onAfterPublish(AspireAction1 callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var callbackId = getClient().registerCallback(args -> { + var arg = (AfterPublishEvent) args[0]; + callback.invoke(arg); + return null; + }); + if (callbackId != null) { + reqArgs.put("callback", callbackId); + } + return (DistributedApplicationEventSubscription) getClient().invokeCapability("Aspire.Hosting/eventingSubscriberOnAfterPublish", reqArgs); + } + /** Subscribes an eventing subscriber to the AfterResourcesCreated event */ public DistributedApplicationEventSubscription onAfterResourcesCreated(AspireAction1 callback) { Map reqArgs = new HashMap<>(); @@ -11776,6 +11868,36 @@ public DistributedApplicationEventSubscription subscribeBeforeStart(AspireAction return (DistributedApplicationEventSubscription) getClient().invokeCapability("Aspire.Hosting/subscribeBeforeStart", reqArgs); } + /** Subscribes to the BeforePublish event */ + public DistributedApplicationEventSubscription subscribeBeforePublish(AspireAction1 callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + var callbackId = getClient().registerCallback(args -> { + var arg = (BeforePublishEvent) args[0]; + callback.invoke(arg); + return null; + }); + if (callbackId != null) { + reqArgs.put("callback", callbackId); + } + return (DistributedApplicationEventSubscription) getClient().invokeCapability("Aspire.Hosting/subscribeBeforePublish", reqArgs); + } + + /** Subscribes to the AfterPublish event */ + public DistributedApplicationEventSubscription subscribeAfterPublish(AspireAction1 callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + var callbackId = getClient().registerCallback(args -> { + var arg = (AfterPublishEvent) args[0]; + callback.invoke(arg); + return null; + }); + if (callbackId != null) { + reqArgs.put("callback", callbackId); + } + return (DistributedApplicationEventSubscription) getClient().invokeCapability("Aspire.Hosting/subscribeAfterPublish", reqArgs); + } + /** Subscribes to the AfterResourcesCreated event */ public DistributedApplicationEventSubscription subscribeAfterResourcesCreated(AspireAction1 callback) { Map reqArgs = new HashMap<>(); @@ -23254,6 +23376,7 @@ public WithVolumeOptions isReadOnly(Boolean value) { .modules/AddParameterOptions.java .modules/AddParameterWithGeneratedValueOptions.java .modules/AddStepOptions.java +.modules/AfterPublishEvent.java .modules/AfterResourcesCreatedEvent.java .modules/Aspire.java .modules/AspireAction0.java @@ -23272,6 +23395,7 @@ public WithVolumeOptions isReadOnly(Boolean value) { .modules/AspireRegistrations.java .modules/AspireUnion.java .modules/BaseRegistrations.java +.modules/BeforePublishEvent.java .modules/BeforeResourceStartedEvent.java .modules/BeforeStartEvent.java .modules/BuildOptions.java diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py index ff6649ed3c9..05031727da4 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py @@ -1,4 +1,4 @@ -# ------------------------------------------------------------- +# ------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See LICENSE in project root for information. # @@ -2329,6 +2329,26 @@ def subscribe_before_start(self, callback: typing.Callable[[BeforeStartEvent], N ) return typing.cast(DistributedApplicationEventSubscription, result) + def subscribe_before_publish(self, callback: typing.Callable[[BeforePublishEvent], None]) -> DistributedApplicationEventSubscription: + """Subscribes to the BeforePublish event""" + rpc_args: dict[str, typing.Any] = {'builder': self._handle} + rpc_args['callback'] = self._client.register_callback(callback) + result = self._client.invoke_capability( + 'Aspire.Hosting/subscribeBeforePublish', + rpc_args, + ) + return typing.cast(DistributedApplicationEventSubscription, result) + + def subscribe_after_publish(self, callback: typing.Callable[[AfterPublishEvent], None]) -> DistributedApplicationEventSubscription: + """Subscribes to the AfterPublish event""" + rpc_args: dict[str, typing.Any] = {'builder': self._handle} + rpc_args['callback'] = self._client.register_callback(callback) + result = self._client.invoke_capability( + 'Aspire.Hosting/subscribeAfterPublish', + rpc_args, + ) + return typing.cast(DistributedApplicationEventSubscription, result) + def subscribe_after_resources_created(self, callback: typing.Callable[[AfterResourcesCreatedEvent], None]) -> DistributedApplicationEventSubscription: """Subscribes to the AfterResourcesCreated event""" rpc_args: dict[str, typing.Any] = {'builder': self._handle} @@ -3035,6 +3055,40 @@ def get_or_set_secret(self, resource_builder: AbstractResource, name: str, value ) +class AfterPublishEvent: + """Type class for AfterPublishEvent.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"AfterPublishEvent(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + @_cached_property + def services(self) -> AbstractServiceProvider: + """Gets the Services property""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Publishing/AfterPublishEvent.services', + {'context': self._handle} + ) + return typing.cast(AbstractServiceProvider, result) + + @_cached_property + def model(self) -> DistributedApplicationModel: + """Gets the Model property""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Publishing/AfterPublishEvent.model', + {'context': self._handle} + ) + return typing.cast(DistributedApplicationModel, result) + + class AfterResourcesCreatedEvent: """Type class for AfterResourcesCreatedEvent.""" @@ -3069,6 +3123,40 @@ def model(self) -> DistributedApplicationModel: return typing.cast(DistributedApplicationModel, result) +class BeforePublishEvent: + """Type class for BeforePublishEvent.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"BeforePublishEvent(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + @_cached_property + def services(self) -> AbstractServiceProvider: + """Gets the Services property""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Publishing/BeforePublishEvent.services', + {'context': self._handle} + ) + return typing.cast(AbstractServiceProvider, result) + + @_cached_property + def model(self) -> DistributedApplicationModel: + """Gets the Model property""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Publishing/BeforePublishEvent.model', + {'context': self._handle} + ) + return typing.cast(DistributedApplicationModel, result) + + class BeforeResourceStartedEvent: """Type class for BeforeResourceStartedEvent.""" @@ -4299,6 +4387,26 @@ def on_before_start(self, callback: typing.Callable[[BeforeStartEvent], None]) - ) return typing.cast(DistributedApplicationEventSubscription, result) + def on_before_publish(self, callback: typing.Callable[[BeforePublishEvent], None]) -> DistributedApplicationEventSubscription: + """Subscribes an eventing subscriber to the BeforePublish event""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['callback'] = self._client.register_callback(callback) + result = self._client.invoke_capability( + 'Aspire.Hosting/eventingSubscriberOnBeforePublish', + rpc_args, + ) + return typing.cast(DistributedApplicationEventSubscription, result) + + def on_after_publish(self, callback: typing.Callable[[AfterPublishEvent], None]) -> DistributedApplicationEventSubscription: + """Subscribes an eventing subscriber to the AfterPublish event""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['callback'] = self._client.register_callback(callback) + result = self._client.invoke_capability( + 'Aspire.Hosting/eventingSubscriberOnAfterPublish', + rpc_args, + ) + return typing.cast(DistributedApplicationEventSubscription, result) + def on_after_resources_created(self, callback: typing.Callable[[AfterResourcesCreatedEvent], None]) -> DistributedApplicationEventSubscription: """Subscribes an eventing subscriber to the AfterResourcesCreated event""" rpc_args: dict[str, typing.Any] = {'context': self._handle} @@ -10691,7 +10799,9 @@ def create_builder( _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingTask", AbstractReportingTask) _register_handle_wrapper("System.ComponentModel/System.IServiceProvider", AbstractServiceProvider) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.IUserSecretsManager", AbstractUserSecretsManager) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent", AfterPublishEvent) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent", AfterResourcesCreatedEvent) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent", BeforePublishEvent) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent", BeforeResourceStartedEvent) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeStartEvent", BeforeStartEvent) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext", CommandLineArgsCallbackContext) diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs index fcd00b2f656..e7bf0545e08 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs @@ -1038,6 +1038,10 @@ public void Generate_HostingAssembly_IncludesCoreFrameworkPolyglotHelpers() Assert.Contains("getDistributedApplicationModel", aspireTs); Assert.Contains("subscribeBeforeStart", aspireTs); Assert.Contains("subscribeAfterResourcesCreated", aspireTs); + Assert.Contains("subscribeBeforePublish", aspireTs); + Assert.Contains("subscribeAfterPublish", aspireTs); + Assert.Contains("onBeforePublish", aspireTs); + Assert.Contains("onAfterPublish", aspireTs); Assert.Contains("onBeforeResourceStarted", aspireTs); Assert.Contains("onResourceStopped", aspireTs); Assert.Contains("onConnectionStringAvailable", aspireTs); diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts index 6a28275306b..637a4a028f2 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts @@ -1,4 +1,4 @@ -// aspire.ts - Capability-based Aspire SDK +// aspire.ts - Capability-based Aspire SDK // This SDK uses the ATS (Aspire Type System) capability API. // Capabilities are endpoints like 'Aspire.Hosting/createBuilder'. // @@ -289,6 +289,12 @@ type PipelineSummaryHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Pipelines.Pip /** Handle to ProjectResourceOptions */ type ProjectResourceOptionsHandle = Handle<'Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions'>; +/** Handle to AfterPublishEvent */ +type AfterPublishEventHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent'>; + +/** Handle to BeforePublishEvent */ +type BeforePublishEventHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent'>; + /** Handle to IConfiguration */ type IConfigurationHandle = Handle<'Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfiguration'>; @@ -1087,6 +1093,55 @@ export interface WithVolumeOptions { isReadOnly?: boolean; } +// ============================================================================ +// AfterPublishEvent +// ============================================================================ + +export interface AfterPublishEvent { + toJSON(): MarshalledHandle; + /** Gets the Services property */ + services(): ServiceProviderPromise; + /** Gets the Model property */ + model(): DistributedApplicationModelPromise; +} + +// ============================================================================ +// AfterPublishEventImpl +// ============================================================================ + +/** + * Type class for AfterPublishEvent. + */ +class AfterPublishEventImpl implements AfterPublishEvent { + constructor(private _handle: AfterPublishEventHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + services(): ServiceProviderPromise { + const promise = (async () => { + const handle = await this._client.invokeCapability( + 'Aspire.Hosting.Publishing/AfterPublishEvent.services', + { context: this._handle } + ); + return new ServiceProviderImpl(handle, this._client); + })(); + return new ServiceProviderPromiseImpl(promise, this._client, false); + } + + model(): DistributedApplicationModelPromise { + const promise = (async () => { + const handle = await this._client.invokeCapability( + 'Aspire.Hosting.Publishing/AfterPublishEvent.model', + { context: this._handle } + ); + return new DistributedApplicationModelImpl(handle, this._client); + })(); + return new DistributedApplicationModelPromiseImpl(promise, this._client, false); + } + +} + // ============================================================================ // AfterResourcesCreatedEvent // ============================================================================ @@ -1136,6 +1191,55 @@ class AfterResourcesCreatedEventImpl implements AfterResourcesCreatedEvent { } +// ============================================================================ +// BeforePublishEvent +// ============================================================================ + +export interface BeforePublishEvent { + toJSON(): MarshalledHandle; + /** Gets the Services property */ + services(): ServiceProviderPromise; + /** Gets the Model property */ + model(): DistributedApplicationModelPromise; +} + +// ============================================================================ +// BeforePublishEventImpl +// ============================================================================ + +/** + * Type class for BeforePublishEvent. + */ +class BeforePublishEventImpl implements BeforePublishEvent { + constructor(private _handle: BeforePublishEventHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + services(): ServiceProviderPromise { + const promise = (async () => { + const handle = await this._client.invokeCapability( + 'Aspire.Hosting.Publishing/BeforePublishEvent.services', + { context: this._handle } + ); + return new ServiceProviderImpl(handle, this._client); + })(); + return new ServiceProviderPromiseImpl(promise, this._client, false); + } + + model(): DistributedApplicationModelPromise { + const promise = (async () => { + const handle = await this._client.invokeCapability( + 'Aspire.Hosting.Publishing/BeforePublishEvent.model', + { context: this._handle } + ); + return new DistributedApplicationModelImpl(handle, this._client); + })(); + return new DistributedApplicationModelPromiseImpl(promise, this._client, false); + } + +} + // ============================================================================ // BeforeResourceStartedEvent // ============================================================================ @@ -3079,6 +3183,10 @@ export interface EventingSubscriberRegistrationContext { cancellationToken(): Promise; /** Subscribes an eventing subscriber to the BeforeStart event */ onBeforeStart(callback: (arg: BeforeStartEvent) => Promise): Promise; + /** Subscribes an eventing subscriber to the BeforePublish event */ + onBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise; + /** Subscribes an eventing subscriber to the AfterPublish event */ + onAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise; /** Subscribes an eventing subscriber to the AfterResourcesCreated event */ onAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise; } @@ -3090,6 +3198,10 @@ export interface EventingSubscriberRegistrationContextPromise extends PromiseLik cancellationToken(): Promise; /** Subscribes an eventing subscriber to the BeforeStart event */ onBeforeStart(callback: (arg: BeforeStartEvent) => Promise): Promise; + /** Subscribes an eventing subscriber to the BeforePublish event */ + onBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise; + /** Subscribes an eventing subscriber to the AfterPublish event */ + onAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise; /** Subscribes an eventing subscriber to the AfterResourcesCreated event */ onAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise; } @@ -3136,6 +3248,32 @@ class EventingSubscriberRegistrationContextImpl implements EventingSubscriberReg ); } + async onBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise { + const callbackId = registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as BeforePublishEventHandle; + const arg = new BeforePublishEventImpl(argHandle, this._client); + await callback(arg); + }); + const rpcArgs: Record = { context: this._handle, callback: callbackId }; + return await this._client.invokeCapability( + 'Aspire.Hosting/eventingSubscriberOnBeforePublish', + rpcArgs + ); + } + + async onAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise { + const callbackId = registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as AfterPublishEventHandle; + const arg = new AfterPublishEventImpl(argHandle, this._client); + await callback(arg); + }); + const rpcArgs: Record = { context: this._handle, callback: callbackId }; + return await this._client.invokeCapability( + 'Aspire.Hosting/eventingSubscriberOnAfterPublish', + rpcArgs + ); + } + async onAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise { const callbackId = registerCallback(async (argData: unknown) => { const argHandle = wrapIfHandle(argData) as AfterResourcesCreatedEventHandle; @@ -3178,6 +3316,14 @@ class EventingSubscriberRegistrationContextPromiseImpl implements EventingSubscr return this._promise.then(obj => obj.onBeforeStart(callback)); } + onBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise { + return this._promise.then(obj => obj.onBeforePublish(callback)); + } + + onAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise { + return this._promise.then(obj => obj.onAfterPublish(callback)); + } + onAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise { return this._promise.then(obj => obj.onAfterResourcesCreated(callback)); } @@ -5795,6 +5941,10 @@ export interface DistributedApplicationBuilder { getConfiguration(): ConfigurationPromise; /** Subscribes to the BeforeStart event */ subscribeBeforeStart(callback: (arg: BeforeStartEvent) => Promise): Promise; + /** Subscribes to the BeforePublish event */ + subscribeBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise; + /** Subscribes to the AfterPublish event */ + subscribeAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise; /** Subscribes to the AfterResourcesCreated event */ subscribeAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise; /** Adds an eventing subscriber */ @@ -5852,6 +6002,10 @@ export interface DistributedApplicationBuilderPromise extends PromiseLike Promise): Promise; + /** Subscribes to the BeforePublish event */ + subscribeBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise; + /** Subscribes to the AfterPublish event */ + subscribeAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise; /** Subscribes to the AfterResourcesCreated event */ subscribeAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise; /** Adds an eventing subscriber */ @@ -6196,6 +6350,32 @@ class DistributedApplicationBuilderImpl implements DistributedApplicationBuilder ); } + async subscribeBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise { + const callbackId = registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as BeforePublishEventHandle; + const arg = new BeforePublishEventImpl(argHandle, this._client); + await callback(arg); + }); + const rpcArgs: Record = { builder: this._handle, callback: callbackId }; + return await this._client.invokeCapability( + 'Aspire.Hosting/subscribeBeforePublish', + rpcArgs + ); + } + + async subscribeAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise { + const callbackId = registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as AfterPublishEventHandle; + const arg = new AfterPublishEventImpl(argHandle, this._client); + await callback(arg); + }); + const rpcArgs: Record = { builder: this._handle, callback: callbackId }; + return await this._client.invokeCapability( + 'Aspire.Hosting/subscribeAfterPublish', + rpcArgs + ); + } + async subscribeAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise { const callbackId = registerCallback(async (argData: unknown) => { const argHandle = wrapIfHandle(argData) as AfterResourcesCreatedEventHandle; @@ -6382,6 +6562,14 @@ class DistributedApplicationBuilderPromiseImpl implements DistributedApplication return this._promise.then(obj => obj.subscribeBeforeStart(callback)); } + subscribeBeforePublish(callback: (arg: BeforePublishEvent) => Promise): Promise { + return this._promise.then(obj => obj.subscribeBeforePublish(callback)); + } + + subscribeAfterPublish(callback: (arg: AfterPublishEvent) => Promise): Promise { + return this._promise.then(obj => obj.subscribeAfterPublish(callback)); + } + subscribeAfterResourcesCreated(callback: (arg: AfterResourcesCreatedEvent) => Promise): Promise { return this._promise.then(obj => obj.subscribeAfterResourcesCreated(callback)); } @@ -32893,7 +33081,9 @@ process.on('uncaughtException', (error: Error) => { // ============================================================================ // Register wrapper factories for typed handle wrapping in callbacks +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent', (handle, client) => new AfterPublishEventImpl(handle as AfterPublishEventHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent', (handle, client) => new AfterResourcesCreatedEventImpl(handle as AfterResourcesCreatedEventHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent', (handle, client) => new BeforePublishEventImpl(handle as BeforePublishEventHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent', (handle, client) => new BeforeResourceStartedEventImpl(handle as BeforeResourceStartedEventHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeStartEvent', (handle, client) => new BeforeStartEventImpl(handle as BeforeStartEventHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext', (handle, client) => new CommandLineArgsCallbackContextImpl(handle as CommandLineArgsCallbackContextHandle, client)); diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go b/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go index 2856ddfb70a..f2ab6901f28 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go @@ -377,6 +377,26 @@ func main() { _ = beforeStartServices.GetDistributedApplicationModel() }) + beforePublishSub := builder.SubscribeBeforePublish(func(e aspire.BeforePublishEvent) { + beforePublishModel := e.Model() + _, _ = beforePublishModel.GetResources() + _ = beforePublishModel.FindResourceByName("mycontainer") + beforePublishServices := e.Services() + beforePublishLoggerFactory := beforePublishServices.GetLoggerFactory() + beforePublishLogger := beforePublishLoggerFactory.CreateLogger("ValidationAppHost.BeforePublish") + _ = beforePublishLogger.LogInformation("BeforePublish") + }) + + afterPublishSub := builder.SubscribeAfterPublish(func(e aspire.AfterPublishEvent) { + afterPublishModel := e.Model() + _, _ = afterPublishModel.GetResources() + _ = afterPublishModel.FindResourceByName("mycontainer") + afterPublishServices := e.Services() + afterPublishLoggerFactory := afterPublishServices.GetLoggerFactory() + afterPublishLogger := afterPublishLoggerFactory.CreateLogger("ValidationAppHost.AfterPublish") + _ = afterPublishLogger.LogInformation("AfterPublish") + }) + afterResourcesSub := builder.SubscribeAfterResourcesCreated(func(e aspire.AfterResourcesCreatedEvent) { afterResourcesModel := e.Model() _, _ = afterResourcesModel.GetResources() @@ -390,6 +410,8 @@ func main() { builderEventing := builder.Eventing() _ = builderEventing.Unsubscribe(beforeStartSub) _ = builderEventing.Unsubscribe(afterResourcesSub) + _ = builderEventing.Unsubscribe(beforePublishSub) + _ = builderEventing.Unsubscribe(afterPublishSub) // Resource events — typed callbacks _ = container.OnBeforeResourceStarted(func(e aspire.BeforeResourceStartedEvent) { diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java b/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java index b43dee03246..7f2c639fc8d 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java @@ -129,6 +129,18 @@ void main() throws Exception { var aspireStore = subscriberServices.getAspireStore(); var _contentBackedFilename = aspireStore.getFileNameWithContent("validation-apphost.java", "AppHost.java"); }); + registrationContext.onBeforePublish((beforePublishEvent) -> { + var beforePublishServices = beforePublishEvent.services(); + var beforePublishModel = beforePublishEvent.model(); + var _beforePublishEventing = beforePublishServices.getEventing(); + var _beforePublishResources = beforePublishModel.getResources(); + }); + registrationContext.onAfterPublish((afterPublishEvent) -> { + var afterPublishServices = afterPublishEvent.services(); + var afterPublishModel = afterPublishEvent.model(); + var _afterPublishEventing = afterPublishServices.getEventing(); + var _afterPublishResources = afterPublishModel.getResources(); + }); registrationContext.onAfterResourcesCreated((afterResourcesCreatedEvent) -> { var afterResourcesCreatedServices = afterResourcesCreatedEvent.services(); var afterResourcesCreatedModel = afterResourcesCreatedEvent.model(); @@ -164,9 +176,13 @@ void main() throws Exception { var _httpsCertificateData = executionConfiguration.getHttpsCertificateData(); var beforeStartSubscription = builder.subscribeBeforeStart((beforeStartEvent) -> { var beforeStartServices = beforeStartEvent.services(); var beforeStartModel = beforeStartEvent.model(); var _beforeStartResources = beforeStartModel.getResources(); var _beforeStartContainer = beforeStartModel.findResourceByName("mycontainer"); var _beforeStartEventing = beforeStartServices.getEventing(); var beforeStartLoggerFactory = beforeStartServices.getLoggerFactory(); var beforeStartLogger = beforeStartLoggerFactory.createLogger("ValidationAppHost.BeforeStart"); beforeStartLogger.logInformation("BeforeStart information"); beforeStartLogger.logWarning("BeforeStart warning"); beforeStartLogger.logError("BeforeStart error"); beforeStartLogger.logDebug("BeforeStart debug"); beforeStartLogger.log("critical", "BeforeStart critical"); var beforeStartResourceLoggerService = beforeStartServices.getResourceLoggerService(); beforeStartResourceLoggerService.completeLog(container); beforeStartResourceLoggerService.completeLogByName("mycontainer"); var beforeStartNotificationService = beforeStartServices.getResourceNotificationService(); beforeStartNotificationService.waitForResourceState("mycontainer", "Running"); var _matchedResourceState = beforeStartNotificationService.waitForResourceStates("mycontainer", new String[] { "Running", "FailedToStart" }); var _healthyResourceEvent = beforeStartNotificationService.waitForResourceHealthy("mycontainer"); beforeStartNotificationService.waitForDependencies(container); var _currentResourceState = beforeStartNotificationService.tryGetResourceState("mycontainer"); beforeStartNotificationService.publishResourceUpdate(container, new PublishResourceUpdateOptions().state("Validated").stateStyle("info")); var userSecretsManager = beforeStartServices.getUserSecretsManager(); var _userSecretsAvailable = userSecretsManager.isAvailable(); var _userSecretsFilePath = userSecretsManager.filePath(); var _secretSet = userSecretsManager.trySetSecret("Validation:Key", "value"); userSecretsManager.getOrSetSecret(container, "Validation:GeneratedKey", "generated-value"); var _generatedSecretValue = builderConfiguration.getConfigValue("Validation:GeneratedKey"); userSecretsManager.saveStateJson("{\"Validation\":\"Value\"}"); var _modelFromServices = beforeStartServices.getDistributedApplicationModel(); }); var afterResourcesCreatedSubscription = builder.subscribeAfterResourcesCreated((afterResourcesCreatedEvent) -> { var afterResourcesCreatedServices = afterResourcesCreatedEvent.services(); var afterResourcesCreatedModel = afterResourcesCreatedEvent.model(); var _afterResources = afterResourcesCreatedModel.getResources(); var _afterResourcesContainer = afterResourcesCreatedModel.findResourceByName("mycontainer"); var afterResourcesCreatedLoggerFactory = afterResourcesCreatedServices.getLoggerFactory(); var afterResourcesCreatedLogger = afterResourcesCreatedLoggerFactory.createLogger("ValidationAppHost.AfterResourcesCreated"); afterResourcesCreatedLogger.logInformation("AfterResourcesCreated"); }); + var beforePublishSubscription = builder.subscribeBeforePublish((beforePublishEvent) -> { var beforePublishServices = beforePublishEvent.services(); var beforePublishModel = beforePublishEvent.model(); var _beforePublishResources = beforePublishModel.getResources(); var _beforePublishContainer = beforePublishModel.findResourceByName("mycontainer"); var beforePublishLoggerFactory = beforePublishServices.getLoggerFactory(); var beforePublishLogger = beforePublishLoggerFactory.createLogger("ValidationAppHost.BeforePublish"); beforePublishLogger.logInformation("BeforePublish"); }); + var afterPublishSubscription = builder.subscribeAfterPublish((afterPublishEvent) -> { var afterPublishServices = afterPublishEvent.services(); var afterPublishModel = afterPublishEvent.model(); var _afterPublishResources = afterPublishModel.getResources(); var _afterPublishContainer = afterPublishModel.findResourceByName("mycontainer"); var afterPublishLoggerFactory = afterPublishServices.getLoggerFactory(); var afterPublishLogger = afterPublishLoggerFactory.createLogger("ValidationAppHost.AfterPublish"); afterPublishLogger.logInformation("AfterPublish"); }); var builderEventing = builder.eventing(); builderEventing.unsubscribe(beforeStartSubscription); builderEventing.unsubscribe(afterResourcesCreatedSubscription); + builderEventing.unsubscribe(beforePublishSubscription); + builderEventing.unsubscribe(afterPublishSubscription); container.onBeforeResourceStarted((beforeResourceStartedEvent) -> { var _resource = beforeResourceStartedEvent.resource(); var services = beforeResourceStartedEvent.services(); var loggerFactory = services.getLoggerFactory(); var logger = loggerFactory.createLogger("ValidationAppHost.BeforeResourceStarted"); logger.logInformation("BeforeResourceStarted"); }); container.onResourceStopped((resourceStoppedEvent) -> { var _resource = resourceStoppedEvent.resource(); var services = resourceStoppedEvent.services(); var loggerFactory = services.getLoggerFactory(); var logger = loggerFactory.createLogger("ValidationAppHost.ResourceStopped"); logger.logWarning("ResourceStopped"); }); cache.onConnectionStringAvailable((connectionStringAvailableEvent) -> { var _resource = connectionStringAvailableEvent.resource(); var services = connectionStringAvailableEvent.services(); var notifications = services.getResourceNotificationService(); var _connectionState = notifications.tryGetResourceState("cache"); }); diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py b/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py index 099cdec51d8..d6ba7e13e72 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py @@ -276,6 +276,18 @@ def on_eventing_before_start(before_start_event): aspire_store = subscriber_services.get_aspire_store() _content_backed_filename = aspire_store.get_file_name_with_content("validation-apphost.py", "apphost.py") + def on_eventing_before_publish(before_publish_event): + before_publish_services = before_publish_event.services + before_publish_model = before_publish_event.model + _before_publish_eventing = before_publish_services.get_eventing() + _before_publish_resources = before_publish_model.get_resources() + + def on_eventing_after_publish(after_publish_event): + after_publish_services = after_publish_event.services + after_publish_model = after_publish_event.model + _after_publish_eventing = after_publish_services.get_eventing() + _after_publish_resources = after_publish_model.get_resources() + def on_eventing_after_resources_created(after_resources_created_event): after_resources_created_services = after_resources_created_event.services after_resources_created_model = after_resources_created_event.model @@ -283,6 +295,8 @@ def on_eventing_after_resources_created(after_resources_created_event): _after_resources_created_resources = after_resources_created_model.get_resources() registration_context.on_before_start(on_eventing_before_start) + registration_context.on_before_publish(on_eventing_before_publish) + registration_context.on_after_publish(on_eventing_after_publish) registration_context.on_after_resources_created(on_eventing_after_resources_created) builder.add_eventing_subscriber(configure_eventing_subscriber) @@ -318,9 +332,13 @@ def configure_https_certificate(certificate_info): _https_certificate_data = execution_config.get_https_certificate_data() before_start_subscription = builder.subscribe_before_start(lambda *_args, **_kwargs: None) after_resources_created_subscription = builder.subscribe_after_resources_created(lambda *_args, **_kwargs: None) + before_publish_subscription = builder.subscribe_before_publish(lambda *_args, **_kwargs: None) + after_publish_subscription = builder.subscribe_after_publish(lambda *_args, **_kwargs: None) builder_eventing = builder.eventing builder_eventing.unsubscribe(None) builder_eventing.unsubscribe(None) + builder_eventing.unsubscribe(None) + builder_eventing.unsubscribe(None) container.on_before_resource_started(lambda *_args, **_kwargs: None) container.on_resource_stopped(lambda *_args, **_kwargs: None) built_connection_string.on_connection_string_available(lambda *_args, **_kwargs: None) diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.ts b/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.ts index c8b6e255fd1..2d372764a46 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.ts +++ b/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.ts @@ -423,6 +423,20 @@ await builder.addEventingSubscriber(async (registrationContext) => { const _contentBackedFilename = await aspireStore.getFileNameWithContent("validation-apphost.ts", "apphost.ts"); }); + await registrationContext.onBeforePublish(async (beforePublishEvent) => { + const beforePublishServices = await beforePublishEvent.services(); + const beforePublishModel = await beforePublishEvent.model(); + const _beforePublishEventing = await beforePublishServices.getEventing(); + const _beforePublishResources = await beforePublishModel.getResources(); + }); + + await registrationContext.onAfterPublish(async (afterPublishEvent) => { + const afterPublishServices = await afterPublishEvent.services(); + const afterPublishModel = await afterPublishEvent.model(); + const _afterPublishEventing = await afterPublishServices.getEventing(); + const _afterPublishResources = await afterPublishModel.getResources(); + }); + await registrationContext.onAfterResourcesCreated(async (afterResourcesCreatedEvent) => { const afterResourcesCreatedServices = await afterResourcesCreatedEvent.services(); const afterResourcesCreatedModel = await afterResourcesCreatedEvent.model(); @@ -512,9 +526,31 @@ const afterResourcesCreatedSubscription = await builder.subscribeAfterResourcesC await afterResourcesCreatedLogger.logInformation("AfterResourcesCreated"); }); +const beforePublishSubscription = await builder.subscribeBeforePublish(async (beforePublishEvent) => { + const beforePublishServices = await beforePublishEvent.services(); + const beforePublishModel = await beforePublishEvent.model(); + const _beforePublishResources = await beforePublishModel.getResources(); + const _beforePublishContainer = await beforePublishModel.findResourceByName("mycontainer"); + const beforePublishLoggerFactory = await beforePublishServices.getLoggerFactory(); + const beforePublishLogger = await beforePublishLoggerFactory.createLogger("ValidationAppHost.BeforePublish"); + await beforePublishLogger.logInformation("BeforePublish"); +}); + +const afterPublishSubscription = await builder.subscribeAfterPublish(async (afterPublishEvent) => { + const afterPublishServices = await afterPublishEvent.services(); + const afterPublishModel = await afterPublishEvent.model(); + const _afterPublishResources = await afterPublishModel.getResources(); + const _afterPublishContainer = await afterPublishModel.findResourceByName("mycontainer"); + const afterPublishLoggerFactory = await afterPublishServices.getLoggerFactory(); + const afterPublishLogger = await afterPublishLoggerFactory.createLogger("ValidationAppHost.AfterPublish"); + await afterPublishLogger.logInformation("AfterPublish"); +}); + const builderEventing = await builder.eventing(); await builderEventing.unsubscribe(beforeStartSubscription); await builderEventing.unsubscribe(afterResourcesCreatedSubscription); +await builderEventing.unsubscribe(beforePublishSubscription); +await builderEventing.unsubscribe(afterPublishSubscription); await container.onBeforeResourceStarted(async (beforeResourceStartedEvent) => { const _resource = await beforeResourceStartedEvent.resource(); From 2d7c38c8754bc8b61db36b6b092f8f44f9caa5e6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 May 2026 11:13:47 -0700 Subject: [PATCH 2/6] Update Rust codegen snapshot Regenerate the Rust ATS codegen snapshot for the new publish lifecycle event exports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...TwoPassScanningGeneratedAspire.verified.rs | 134 +++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs index 16c540685f0..1d84f5d5009 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs @@ -1,4 +1,4 @@ -//! aspire.rs - Capability-based Aspire SDK +//! aspire.rs - Capability-based Aspire SDK //! GENERATED CODE - DO NOT EDIT use std::collections::HashMap; @@ -1305,6 +1305,50 @@ pub mod well_known_pipeline_tags { // Handle Wrappers // ============================================================================ +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Publishing.AfterPublishEvent +pub struct AfterPublishEvent { + handle: Handle, + client: Arc, +} + +impl HasHandle for AfterPublishEvent { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl AfterPublishEvent { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Services property + pub fn services(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Publishing/AfterPublishEvent.services", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IServiceProvider::new(handle, self.client.clone())) + } + + /// Gets the Model property + pub fn model(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Publishing/AfterPublishEvent.model", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationModel::new(handle, self.client.clone())) + } +} + /// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent pub struct AfterResourcesCreatedEvent { handle: Handle, @@ -1349,6 +1393,50 @@ impl AfterResourcesCreatedEvent { } } +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Publishing.BeforePublishEvent +pub struct BeforePublishEvent { + handle: Handle, + client: Arc, +} + +impl HasHandle for BeforePublishEvent { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl BeforePublishEvent { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Services property + pub fn services(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Publishing/BeforePublishEvent.services", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IServiceProvider::new(handle, self.client.clone())) + } + + /// Gets the Model property + pub fn model(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Publishing/BeforePublishEvent.model", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationModel::new(handle, self.client.clone())) + } +} + /// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent pub struct BeforeResourceStartedEvent { handle: Handle, @@ -6939,6 +7027,28 @@ impl EventingSubscriberRegistrationContext { Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) } + /// Subscribes an eventing subscriber to the BeforePublish event + pub fn on_before_publish(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/eventingSubscriberOnBeforePublish", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) + } + + /// Subscribes an eventing subscriber to the AfterPublish event + pub fn on_after_publish(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/eventingSubscriberOnAfterPublish", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) + } + /// Subscribes an eventing subscriber to the AfterResourcesCreated event pub fn on_after_resources_created(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { let mut args: HashMap = HashMap::new(); @@ -9174,6 +9284,28 @@ impl IDistributedApplicationBuilder { Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) } + /// Subscribes to the BeforePublish event + pub fn subscribe_before_publish(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/subscribeBeforePublish", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) + } + + /// Subscribes to the AfterPublish event + pub fn subscribe_after_publish(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/subscribeAfterPublish", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationEventSubscription::new(handle, self.client.clone())) + } + /// Subscribes to the AfterResourcesCreated event pub fn subscribe_after_resources_created(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { let mut args: HashMap = HashMap::new(); From b9c1dc287a42f63290830384273aeb84765972ef Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 May 2026 19:05:28 -0700 Subject: [PATCH 3/6] Relax aspire new prompt timeout in E2E helper Allow the shared AspireNewAsync helper to wait through template version resolution before the URLs prompt appears under CI load. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Shared/Hex1bAutomatorTestHelpers.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Shared/Hex1bAutomatorTestHelpers.cs b/tests/Shared/Hex1bAutomatorTestHelpers.cs index 4c7c69b77ef..0a94ffeeaf4 100644 --- a/tests/Shared/Hex1bAutomatorTestHelpers.cs +++ b/tests/Shared/Hex1bAutomatorTestHelpers.cs @@ -618,10 +618,12 @@ await auto.WaitUntilAsync( description: "output path prompt"); await auto.EnterAsync(); - // Step 5: URLs prompt (all templates have this) + // Step 5: URLs prompt (all templates have this). The CLI may spend time + // resolving template versions after the output path is entered, so reuse + // the template-selection timeout for this first post-resolution prompt. await auto.WaitUntilAsync( s => new CellPatternSearcher().Find("Use *.dev.localhost URLs").Search(s).Count > 0, - timeout: TimeSpan.FromSeconds(10), + timeout: templateTimeout, description: "URLs prompt"); await auto.EnterAsync(); // Accept default "No" From a56d0826c700411b1bb5a12dd41953511cbb2dd9 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 May 2026 19:18:23 -0700 Subject: [PATCH 4/6] Use published Go devcontainer image Switch Go polyglot validation from the unpublished 1.26-trixie image tag to the available 1.25-trixie tag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/polyglot-validation/Dockerfile.golang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/polyglot-validation/Dockerfile.golang b/.github/workflows/polyglot-validation/Dockerfile.golang index 154c87af2a2..f6be33a6145 100644 --- a/.github/workflows/polyglot-validation/Dockerfile.golang +++ b/.github/workflows/polyglot-validation/Dockerfile.golang @@ -10,7 +10,7 @@ # # Note: Expects self-extracting binary and NuGet artifacts to be pre-downloaded to /workspace/artifacts/ # -FROM mcr.microsoft.com/devcontainers/go:1.26-trixie +FROM mcr.microsoft.com/devcontainers/go:1.25-trixie # Ensure Yarn APT repository signing key is available (base image includes Yarn repo) RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /etc/apt/keyrings/yarn-archive-keyring.gpg > /dev/null From 18ffcfa9f6ac5bc8216e7bf0abaf8f08efc0329d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 18 May 2026 08:26:01 -0700 Subject: [PATCH 5/6] Use Go 1.26 validation image Use the official Go 1.26 Trixie image for Go polyglot validation so the validation AppHosts satisfy their go.mod toolchain requirement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/polyglot-validation/Dockerfile.golang | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/polyglot-validation/Dockerfile.golang b/.github/workflows/polyglot-validation/Dockerfile.golang index f6be33a6145..31924ff34c0 100644 --- a/.github/workflows/polyglot-validation/Dockerfile.golang +++ b/.github/workflows/polyglot-validation/Dockerfile.golang @@ -10,10 +10,7 @@ # # Note: Expects self-extracting binary and NuGet artifacts to be pre-downloaded to /workspace/artifacts/ # -FROM mcr.microsoft.com/devcontainers/go:1.25-trixie - -# Ensure Yarn APT repository signing key is available (base image includes Yarn repo) -RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /etc/apt/keyrings/yarn-archive-keyring.gpg > /dev/null +FROM golang:1.26-trixie # Install system dependencies (wget, docker CLI, jq for JSON manipulation) RUN apt-get update && apt-get install -y \ From 18232238422d094054db105a5a72dd05b703e99f Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 18 May 2026 08:37:31 -0700 Subject: [PATCH 6/6] Install ICU in Go validation image Install the ICU runtime dependency in the official Go validation image so the native Aspire CLI can run inside the container. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/polyglot-validation/Dockerfile.golang | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/polyglot-validation/Dockerfile.golang b/.github/workflows/polyglot-validation/Dockerfile.golang index 31924ff34c0..42075d6de69 100644 --- a/.github/workflows/polyglot-validation/Dockerfile.golang +++ b/.github/workflows/polyglot-validation/Dockerfile.golang @@ -12,11 +12,12 @@ # FROM golang:1.26-trixie -# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +# Install system dependencies (wget, Docker CLI, jq for JSON manipulation, ICU for the Aspire CLI) RUN apt-get update && apt-get install -y \ wget \ docker.io \ jq \ + libicu76 \ && rm -rf /var/lib/apt/lists/* # Pre-configure Aspire CLI path