Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/ContainerLifetimeAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Lifetime modes for container resources
/// </summary>
public enum ContainerLifetimeType
{
/// <summary>
/// The default lifetime behavior should apply. This will create the resource when the AppHost starts and dispose of it when the AppHost shuts down.
/// </summary>
Default,
/// <summary>
/// The resource is persistent and will not be disposed of when the AppHost shuts down.
/// </summary>
Persistent,
}

/// <summary>
/// Annotation that controls the lifetime of a container resource (default behavior that matches the lifetime of the AppHost or a persistent lifetime across AppHost restarts)
/// </summary>
[DebuggerDisplay("Type = {GetType().Name,nq}")]
public sealed class ContainerLifetimeAnnotation : IResourceAnnotation
{
/// <summary>
/// Gets or sets the lifetime type for the container resource.
/// </summary>
public required ContainerLifetimeType LifetimeType { get; set; }
}
22 changes: 19 additions & 3 deletions src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ public static bool TryGetEnvironmentVariables(this IResource resource, [NotNullW
/// var container = builder.AddContainer("elasticsearch", "library/elasticsearch", "8.14.0")
/// .WithEnvironment("discovery.type", "single-node")
/// .WithEnvironment("xpack.security.enabled", "true");
///
///
/// var env = await container.Resource.GetEnvironmentVariableValuesAsync();
///
///
/// Assert.Collection(env,
/// env =>
/// {
Expand Down Expand Up @@ -179,7 +179,7 @@ public static IEnumerable<EndpointReference> GetEndpoints(this IResourceWithEndp
/// </summary>
/// <param name="resource">The <see cref="IResourceWithEndpoints"/> which contains <see cref="EndpointAnnotation"/> annotations.</param>
/// <param name="endpointName">The name of the endpoint.</param>
/// <returns>An <see cref="EndpointReference"/> object representing the endpoint reference
/// <returns>An <see cref="EndpointReference"/> object representing the endpoint reference
/// for the specified endpoint.</returns>
public static EndpointReference GetEndpoint(this IResourceWithEndpoints resource, string endpointName)
{
Expand Down Expand Up @@ -233,4 +233,20 @@ public static int GetReplicaCount(this IResource resource)
return 1;
}
}

/// <summary>
/// Gets the lifetime type of the container for the specified resoruce. Defaults to <see cref="ContainerLifetimeType.Default"/> if
/// no <see cref="ContainerLifetimeAnnotation"/> is found.
/// </summary>
/// <param name="resource">The resource to the get the ContainerLifetimeType for.</param>
/// <returns>The <see cref="ContainerLifetimeType"/> from the <see cref="ContainerLifetimeAnnotation"/> for the resource (if the annotation exists). Defaults to <see cref="ContainerLifetimeType.Default"/> if the annotation is not set.</returns>
internal static ContainerLifetimeType GetContainerLifetimeType(this IResource resource)
{
if (resource.TryGetLastAnnotation<ContainerLifetimeAnnotation>(out var lifetimeAnnotation))
{
return lifetimeAnnotation.LifetimeType;
}

return ContainerLifetimeType.Default;
}
}
22 changes: 22 additions & 0 deletions src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;

Expand Down Expand Up @@ -221,6 +222,27 @@ public static IResourceBuilder<T> WithContainerRuntimeArgs<T>(this IResourceBuil
return builder.WithAnnotation(annotation);
}

/// <summary>
/// Sets the lifetime behavior of the container resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">Builder for the container resource.</param>
/// <param name="lifetimeType">The lifetime behavior of the container resource (defaults behavior is <see cref="ContainerLifetimeType.Default"/>)</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <example>
/// Marking a container resource to have a <see cref="ContainerLifetimeType.Persistent"/> lifetime.
/// <code language="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
/// builder.AddContainer("mycontainer", "myimage")
/// .WithContainerLifetime(ContainerLifetimeType.Persistent);
/// </code>
/// </example>
[Experimental("ASPIRECONTAINERLIFETIME001")]
public static IResourceBuilder<T> WithContainerLifetime<T>(this IResourceBuilder<T> builder, ContainerLifetimeType lifetimeType) where T : ContainerResource
{
return builder.WithAnnotation(new ContainerLifetimeAnnotation { LifetimeType = lifetimeType }, ResourceAnnotationMutationBehavior.Replace);
}

private static IResourceBuilder<T> ThrowResourceIsNotContainer<T>(IResourceBuilder<T> builder) where T : ContainerResource
{
throw new InvalidOperationException($"The resource '{builder.Resource.Name}' does not have a container image specified. Use WithImage to specify the container image and tag.");
Expand Down
14 changes: 13 additions & 1 deletion src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,11 +1327,23 @@ private void PrepareContainers()
throw new InvalidOperationException();
}

var nameSuffix = GetRandomNameSuffix();
var nameSuffix = string.Empty;

if (container.GetContainerLifetimeType() == ContainerLifetimeType.Default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ReubenBond we need to make sure this doesn't regress testing scenarios right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been hesitant to add tests to this PR for persistent resources as I don't want to introduce any test instability by leaving resources behind after a test run.

{
nameSuffix = GetRandomNameSuffix();
}

var containerObjectName = GetObjectNameForResource(container, nameSuffix);
var ctr = Container.Create(containerObjectName, containerImageName);

ctr.Spec.ContainerName = containerObjectName; // Use the same name for container orchestrator (Docker, Podman) resource and DCP object name.

if (container.GetContainerLifetimeType() == ContainerLifetimeType.Persistent)
{
ctr.Spec.Persistent = true;
}

ctr.Annotate(CustomResource.ResourceNameAnnotation, container.Name);
ctr.Annotate(CustomResource.OtelServiceNameAnnotation, container.Name);
ctr.Annotate(CustomResource.OtelServiceInstanceIdAnnotation, nameSuffix);
Expand Down
8 changes: 8 additions & 0 deletions src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ Aspire.Hosting.ApplicationModel.BeforeStartEvent
Aspire.Hosting.ApplicationModel.BeforeStartEvent.BeforeStartEvent(System.IServiceProvider! services, Aspire.Hosting.ApplicationModel.DistributedApplicationModel! model) -> void
Aspire.Hosting.ApplicationModel.BeforeStartEvent.Model.get -> Aspire.Hosting.ApplicationModel.DistributedApplicationModel!
Aspire.Hosting.ApplicationModel.BeforeStartEvent.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.ResourceNotificationService.ResourceNotificationService(Microsoft.Extensions.Logging.ILogger<Aspire.Hosting.ApplicationModel.ResourceNotificationService!>! logger, Microsoft.Extensions.Hosting.IHostApplicationLifetime! hostApplicationLifetime) -> void
Aspire.Hosting.DistributedApplicationBuilder.Eventing.get -> Aspire.Hosting.Eventing.IDistributedApplicationEventing!
Aspire.Hosting.Eventing.DistributedApplicationEventing
Expand All @@ -37,6 +44,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<System.Collections.Generic.Dictionary<string!, string!>!>
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<string!>! targetStates, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<string!>!
static Aspire.Hosting.ContainerResourceBuilderExtensions.WithContainerLifetime<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, Aspire.Hosting.ApplicationModel.ContainerLifetimeType lifetimeType) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
static Aspire.Hosting.ProjectResourceBuilderExtensions.WithEndpointsInEnvironment(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ProjectResource!>! builder, System.Func<Aspire.Hosting.ApplicationModel.EndpointAnnotation!, bool>! filter) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ProjectResource!>!
Aspire.Hosting.DistributedApplicationExecutionContext.DistributedApplicationExecutionContext(Aspire.Hosting.DistributedApplicationExecutionContextOptions! options) -> void
Aspire.Hosting.DistributedApplicationExecutionContext.ServiceProvider.get -> System.IServiceProvider!
Expand Down