Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for specifying an image pull policy for containers #7697

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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>
/// Image pull policies for container resources.
/// </summary>
public enum PullPolicy
{
/// <summary>
/// Always pull the image when creating the container.
/// </summary>
Always,
/// <summary>
/// Pull the image only if it does not already exist.
/// </summary>
Missing,
/// <summary>
/// Never pull the image.
/// </summary>
Never
}

/// <summary>
/// Annotation that controls the image pull policy for a container resource.
/// </summary>
[DebuggerDisplay("Type = {GetType().Name,nq}")]
public sealed class ContainerPullPolicyAnnotation : IResourceAnnotation
{
/// <summary>
/// Gets or sets the image pull policy for the container resource.
/// </summary>
public required PullPolicy PullPolicy { get; set; }
}
18 changes: 18 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,24 @@ internal static ContainerLifetime GetContainerLifetimeType(this IResource resour
return ContainerLifetime.Session;
}

/// <summary>
/// Determines whether the specified resource has a pull policy annotation and retrieves the value if it does.
/// </summary>
/// <param name="resource">The resource to check for a ContainerPullPolicy annotation</param>
/// <param name="pullPolicy">The <see cref="PullPolicy"/> for the annotation</param>
/// <returns>True if an annotation exists, false otherwise</returns>
internal static bool TryGetContainerPullPolicy(this IResource resource, [NotNullWhen(true)] out PullPolicy? pullPolicy)
{
if (resource.TryGetLastAnnotation<ContainerPullPolicyAnnotation>(out var pullPolicyAnnotation))
{
pullPolicy = pullPolicyAnnotation.PullPolicy;
return true;
}

pullPolicy = null;
return false;
}

/// <summary>
/// Determines whether a resource has proxy support enabled or not. Container resources may have a <see cref="ProxySupportAnnotation"/> setting that disables proxying for their
/// endpoints regardless of the endpoint proxy configuration.
Expand Down
14 changes: 14 additions & 0 deletions src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,20 @@ public static IResourceBuilder<T> WithLifetime<T>(this IResourceBuilder<T> build
return builder.WithAnnotation(new ContainerLifetimeAnnotation { Lifetime = lifetime }, ResourceAnnotationMutationBehavior.Replace);
}

/// <summary>
/// Sets the pull policy for the container resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">Builder for the container resource.</param>
/// <param name="pullPolicy">The pull policy behavior for the container resource.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithPullPolicy<T>(this IResourceBuilder<T> builder, PullPolicy pullPolicy) where T : ContainerResource
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithAnnotation(new ContainerPullPolicyAnnotation { PullPolicy = pullPolicy }, 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
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,17 @@ private void PrepareContainers()
ctr.Spec.Persistent = true;
}

if (container.TryGetContainerPullPolicy(out var pullPolicy))
{
ctr.Spec.PullPolicy = pullPolicy switch
{
PullPolicy.Always => ContainerPullPolicy.Always,
PullPolicy.Missing => ContainerPullPolicy.Missing,
PullPolicy.Never => ContainerPullPolicy.Never,
_ => throw new InvalidOperationException($"Unknown pull policy '{Enum.GetName(typeof(PullPolicy), pullPolicy)}' for container '{container.Name}'")
};
}

ctr.Annotate(CustomResource.ResourceNameAnnotation, container.Name);
ctr.Annotate(CustomResource.OtelServiceNameAnnotation, container.Name);
ctr.Annotate(CustomResource.OtelServiceInstanceIdAnnotation, containerObjectInstance.Suffix);
Expand Down
18 changes: 18 additions & 0 deletions src/Aspire.Hosting/Dcp/Model/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ internal sealed class ContainerSpec
/// </summary>
[JsonPropertyName("lifecycleKey")]
public string? LifecycleKey { get; set; }

/// <summary>
/// Optional pull policy for the container image.
/// </summary>
[JsonPropertyName("pullPolicy")]
public string? PullPolicy { get; set; }
}

internal sealed class BuildContext
Expand Down Expand Up @@ -199,6 +205,18 @@ internal static class ContainerRestartPolicy
public const string Always = "always";
}

internal static class ContainerPullPolicy
{
// Always attempt to pull a newer image from the registry
public const string Always = "always";

// Only pull the image if there isn't a version already available locally (this may mean the image is out of date)
public const string Missing = "missing";

// Never pull the image from the registry even if it is missing locally
public const string Never = "never";
}

internal static class PortProtocol
{
public const string TCP = "TCP";
Expand Down
Loading