From 1e3d0802ae78f345c56fa92c07297404b1118ee4 Mon Sep 17 00:00:00 2001 From: David Negstad Date: Mon, 9 Sep 2024 14:34:06 -0700 Subject: [PATCH 1/2] Update container lifetime API and add schema for lifecycleKey property --- .../ApplicationModel/ContainerLifetimeAnnotation.cs | 6 +++--- .../ApplicationModel/ResourceExtensions.cs | 10 +++++----- .../ContainerResourceBuilderExtensions.cs | 8 ++++---- src/Aspire.Hosting/Dcp/ApplicationExecutor.cs | 4 ++-- src/Aspire.Hosting/Dcp/Model/Container.cs | 13 +++++++++++++ src/Aspire.Hosting/PublicAPI.Unshipped.txt | 12 ++++++------ 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs index 5cc5bfc000..6f89957570 100644 --- a/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs @@ -8,7 +8,7 @@ namespace Aspire.Hosting.ApplicationModel; /// /// Lifetime modes for container resources /// -public enum ContainerLifetimeType +public enum ContainerLifetime { /// /// The default lifetime behavior should apply. This will create the resource when the AppHost starts and dispose of it when the AppHost shuts down. @@ -17,7 +17,7 @@ public enum ContainerLifetimeType /// /// The resource is persistent and will not be disposed of when the AppHost shuts down. /// - Persistent, + CreateIfNotExistsPersistOnExit, } /// @@ -29,5 +29,5 @@ public sealed class ContainerLifetimeAnnotation : IResourceAnnotation /// /// Gets or sets the lifetime type for the container resource. /// - public required ContainerLifetimeType LifetimeType { get; set; } + public required ContainerLifetime Lifetime { get; set; } } \ No newline at end of file diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs index 9801d6b1b5..893f65671c 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs @@ -235,18 +235,18 @@ public static int GetReplicaCount(this IResource resource) } /// - /// Gets the lifetime type of the container for the specified resoruce. Defaults to if + /// Gets the lifetime type of the container for the specified resoruce. Defaults to if /// no is found. /// /// The resource to the get the ContainerLifetimeType for. - /// The from the for the resource (if the annotation exists). Defaults to if the annotation is not set. - internal static ContainerLifetimeType GetContainerLifetimeType(this IResource resource) + /// The from the for the resource (if the annotation exists). Defaults to if the annotation is not set. + internal static ContainerLifetime GetContainerLifetimeType(this IResource resource) { if (resource.TryGetLastAnnotation(out var lifetimeAnnotation)) { - return lifetimeAnnotation.LifetimeType; + return lifetimeAnnotation.Lifetime; } - return ContainerLifetimeType.Default; + return ContainerLifetime.Default; } } diff --git a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs index 5f6dc2412c..1da9c0bb50 100644 --- a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs @@ -227,10 +227,10 @@ public static IResourceBuilder WithContainerRuntimeArgs(this IResourceBuil /// /// The resource type. /// Builder for the container resource. - /// The lifetime behavior of the container resource (defaults behavior is ) + /// The lifetime behavior of the container resource (defaults behavior is ) /// The . /// - /// Marking a container resource to have a lifetime. + /// Marking a container resource to have a lifetime. /// /// var builder = DistributedApplication.CreateBuilder(args); /// builder.AddContainer("mycontainer", "myimage") @@ -238,9 +238,9 @@ public static IResourceBuilder WithContainerRuntimeArgs(this IResourceBuil /// /// [Experimental("ASPIRECONTAINERLIFETIME001")] - public static IResourceBuilder WithContainerLifetime(this IResourceBuilder builder, ContainerLifetimeType lifetimeType) where T : ContainerResource + public static IResourceBuilder WithLifetime(this IResourceBuilder builder, ContainerLifetime lifetime) where T : ContainerResource { - return builder.WithAnnotation(new ContainerLifetimeAnnotation { LifetimeType = lifetimeType }, ResourceAnnotationMutationBehavior.Replace); + return builder.WithAnnotation(new ContainerLifetimeAnnotation { Lifetime = lifetime }, ResourceAnnotationMutationBehavior.Replace); } private static IResourceBuilder ThrowResourceIsNotContainer(IResourceBuilder builder) where T : ContainerResource diff --git a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs index 7b4a32c9ac..64a1c32de5 100644 --- a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs +++ b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs @@ -1356,7 +1356,7 @@ private void PrepareContainers() var nameSuffix = string.Empty; - if (container.GetContainerLifetimeType() == ContainerLifetimeType.Default) + if (container.GetContainerLifetimeType() == ContainerLifetime.Default) { nameSuffix = GetRandomNameSuffix(); } @@ -1366,7 +1366,7 @@ private void PrepareContainers() ctr.Spec.ContainerName = containerObjectName; // Use the same name for container orchestrator (Docker, Podman) resource and DCP object name. - if (container.GetContainerLifetimeType() == ContainerLifetimeType.Persistent) + if (container.GetContainerLifetimeType() == ContainerLifetime.CreateIfNotExistsPersistOnExit) { ctr.Spec.Persistent = true; } diff --git a/src/Aspire.Hosting/Dcp/Model/Container.cs b/src/Aspire.Hosting/Dcp/Model/Container.cs index 6e2c9a09d0..f54a08e93e 100644 --- a/src/Aspire.Hosting/Dcp/Model/Container.cs +++ b/src/Aspire.Hosting/Dcp/Model/Container.cs @@ -63,6 +63,13 @@ internal sealed class ContainerSpec [JsonPropertyName("networks")] public List? Networks { get; set; } + + /// + /// Optional lifecycle key for the resource (used to identify changes to persistent resources requiring a restart). + /// If unset, DCP will calculate a default lifecycle key based on a hash of various resource spec properties. + /// + [JsonPropertyName("lifecycleKey")] + public string? LifecycleKey { get; set; } } internal sealed class BuildContext @@ -305,6 +312,12 @@ internal sealed class ContainerStatus : V1Status [JsonPropertyName("healthProbeResults")] public List? HealthProbeResults { get; set;} + /// + /// The lifecycle key for the resource (used to identify changes to persistent resources requiring a restart). + /// + [JsonPropertyName("lifecycleKey")] + public string? LifecycleKey { get; set; } + // Note: the ContainerStatus has "Message" property that represents a human-readable information about Container state. // It is provided by V1Status base class. } diff --git a/src/Aspire.Hosting/PublicAPI.Unshipped.txt b/src/Aspire.Hosting/PublicAPI.Unshipped.txt index 17ce2b8d4a..b4c2ecab62 100644 --- a/src/Aspire.Hosting/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting/PublicAPI.Unshipped.txt @@ -21,11 +21,11 @@ Aspire.Hosting.ApplicationModel.ConnectionStringAvailableEvent.Resource.get -> A Aspire.Hosting.ApplicationModel.ConnectionStringAvailableEvent.Services.get -> System.IServiceProvider! Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.ContainerLifetimeAnnotation() -> void -Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.LifetimeType.get -> Aspire.Hosting.ApplicationModel.ContainerLifetimeType -Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.LifetimeType.set -> void -Aspire.Hosting.ApplicationModel.ContainerLifetimeType -Aspire.Hosting.ApplicationModel.ContainerLifetimeType.Default = 0 -> Aspire.Hosting.ApplicationModel.ContainerLifetimeType -Aspire.Hosting.ApplicationModel.ContainerLifetimeType.Persistent = 1 -> Aspire.Hosting.ApplicationModel.ContainerLifetimeType +Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.Lifetime.get -> Aspire.Hosting.ApplicationModel.ContainerLifetime +Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.Lifetime.set -> void +Aspire.Hosting.ApplicationModel.ContainerLifetime +Aspire.Hosting.ApplicationModel.ContainerLifetime.Default = 0 -> Aspire.Hosting.ApplicationModel.ContainerLifetime +Aspire.Hosting.ApplicationModel.ContainerLifetime.CreateIfNotExistsPersistOnExit = 1 -> Aspire.Hosting.ApplicationModel.ContainerLifetime Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.get -> string? Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.set -> void Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? @@ -74,7 +74,7 @@ Aspire.Hosting.IDistributedApplicationBuilder.Eventing.get -> Aspire.Hosting.Eve static Aspire.Hosting.ApplicationModel.ResourceExtensions.GetEnvironmentVariableValuesAsync(this Aspire.Hosting.ApplicationModel.IResourceWithEnvironment! resource, Aspire.Hosting.DistributedApplicationOperation applicationOperation = Aspire.Hosting.DistributedApplicationOperation.Run) -> System.Threading.Tasks.ValueTask!> Aspire.Hosting.ApplicationModel.ResourceNotificationService.WaitForResourceAsync(string! resourceName, string? targetState = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Aspire.Hosting.ApplicationModel.ResourceNotificationService.WaitForResourceAsync(string! resourceName, System.Collections.Generic.IEnumerable! targetStates, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! -static Aspire.Hosting.ContainerResourceBuilderExtensions.WithContainerLifetime(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, Aspire.Hosting.ApplicationModel.ContainerLifetimeType lifetimeType) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.ContainerResourceBuilderExtensions.WithLifetime(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, Aspire.Hosting.ApplicationModel.ContainerLifetime lifetime) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ParameterResourceBuilderExtensions.AddParameter(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.ParameterDefault! value, bool secret = false, bool persist = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ParameterResourceBuilderExtensions.AddParameter(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string! value, bool secret = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.ProjectResourceBuilderExtensions.WithEndpointsInEnvironment(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Func! filter) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! From e01cffcceaba726c61920940f1d69a668d6e0fa7 Mon Sep 17 00:00:00 2001 From: David Negstad Date: Thu, 12 Sep 2024 13:06:20 -0700 Subject: [PATCH 2/2] Change back to Persistent, update doc comment --- .../ContainerLifetimeAnnotation.cs | 18 ++++++++++++++++-- .../ContainerResourceBuilderExtensions.cs | 2 +- src/Aspire.Hosting/Dcp/ApplicationExecutor.cs | 2 +- src/Aspire.Hosting/PublicAPI.Unshipped.txt | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs index 6f89957570..f915295662 100644 --- a/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs @@ -15,9 +15,23 @@ public enum ContainerLifetime /// Default, /// - /// The resource is persistent and will not be disposed of when the AppHost shuts down. + /// Attempt to re-use a previously created resource (based on the container name) if one exists. Do not destroy the container on AppHost shutdown. /// - CreateIfNotExistsPersistOnExit, + /// + /// In the event that a container with the given name does not exist, a new container will always be created based on the + /// current configuration. + /// When an existing container IS found, Aspire MAY re-use it based on the following criteria: + /// + /// If the container WAS NOT originally created by Aspire, the existing container will be re-used. + /// If the container WAS originally created by Aspire: + /// + /// And the configuration DOES match the existing container, the existing container will be re-used. + /// And the configuration DOES NOT match the existing container, the existing container will be stopped + /// and a new container created in order to apply the updated configuration. + /// + /// + /// + Persistent, } /// diff --git a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs index 1da9c0bb50..9e37acaeee 100644 --- a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs @@ -230,7 +230,7 @@ public static IResourceBuilder WithContainerRuntimeArgs(this IResourceBuil /// The lifetime behavior of the container resource (defaults behavior is ) /// The . /// - /// Marking a container resource to have a lifetime. + /// Marking a container resource to have a lifetime. /// /// var builder = DistributedApplication.CreateBuilder(args); /// builder.AddContainer("mycontainer", "myimage") diff --git a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs index 64a1c32de5..7103805aa4 100644 --- a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs +++ b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs @@ -1366,7 +1366,7 @@ private void PrepareContainers() ctr.Spec.ContainerName = containerObjectName; // Use the same name for container orchestrator (Docker, Podman) resource and DCP object name. - if (container.GetContainerLifetimeType() == ContainerLifetime.CreateIfNotExistsPersistOnExit) + if (container.GetContainerLifetimeType() == ContainerLifetime.Persistent) { ctr.Spec.Persistent = true; } diff --git a/src/Aspire.Hosting/PublicAPI.Unshipped.txt b/src/Aspire.Hosting/PublicAPI.Unshipped.txt index b4c2ecab62..f0927d2b6d 100644 --- a/src/Aspire.Hosting/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting/PublicAPI.Unshipped.txt @@ -25,7 +25,7 @@ Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.Lifetime.get -> Aspi Aspire.Hosting.ApplicationModel.ContainerLifetimeAnnotation.Lifetime.set -> void Aspire.Hosting.ApplicationModel.ContainerLifetime Aspire.Hosting.ApplicationModel.ContainerLifetime.Default = 0 -> Aspire.Hosting.ApplicationModel.ContainerLifetime -Aspire.Hosting.ApplicationModel.ContainerLifetime.CreateIfNotExistsPersistOnExit = 1 -> Aspire.Hosting.ApplicationModel.ContainerLifetime +Aspire.Hosting.ApplicationModel.ContainerLifetime.Persistent = 1 -> Aspire.Hosting.ApplicationModel.ContainerLifetime Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.get -> string? Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.set -> void Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?