File: KubernetesPersistentVolumeResource.cs
Web Access
Project: src\src\Aspire.Hosting.Kubernetes\Aspire.Hosting.Kubernetes.csproj (Aspire.Hosting.Kubernetes)
// 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.Kubernetes.Extensions;
using Aspire.Hosting.Kubernetes.Resources;
 
namespace Aspire.Hosting.Kubernetes;
 
/// <summary>
/// Represents a Kubernetes PersistentVolumeClaim as a first-class resource in the
/// Aspire application model. A persistent volume resource carries the storage class,
/// capacity, access modes, and metadata annotations needed to render a
/// <c>v1.PersistentVolumeClaim</c> at publish time. Workloads bind to it with
/// <see cref="KubernetesPersistentVolumeExtensions.WithPersistentVolume{T}(IResourceBuilder{T}, IResourceBuilder{KubernetesPersistentVolumeResource})"/>.
/// </summary>
/// <param name="name">The name of the persistent volume resource. Used as the
/// generated <c>PersistentVolumeClaim.metadata.name</c> after lower-casing.</param>
/// <param name="environment">The parent Kubernetes environment resource.</param>
/// <remarks>
/// <para>
/// Bind a workload to the volume by either:
/// </para>
/// <list type="bullet">
/// <item><description>
/// Adding a matching <c>WithVolume("name", "/mount/path")</c> on a container resource
/// and then calling <c>WithPersistentVolume(volume)</c>. The publisher matches by
/// volume name and routes the pod's <c>volumes[]</c> entry through this resource's
/// generated PVC.
/// </description></item>
/// <item><description>
/// Calling the
/// <see cref="KubernetesPersistentVolumeExtensions.WithPersistentVolume{T}(IResourceBuilder{T}, IResourceBuilder{KubernetesPersistentVolumeResource}, string, bool)"/>
/// overload that takes a mount path. Works for both <c>ContainerResource</c> and
/// <c>ProjectResource</c>.
/// </description></item>
/// </list>
/// <para>
/// Any workload bound to a persistent volume is automatically rendered as a
/// <c>StatefulSet</c> rather than a <c>Deployment</c> — Kubernetes requires
/// stable identity and ordered rollout for pods that share named PVCs.
/// </para>
/// <para>
/// This resource is publish-only. It has no run-mode behavior or dashboard surface.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// var k8s = builder.AddKubernetesEnvironment("k8s");
///
/// var data = k8s.AddPersistentVolume("data")
///     .WithStorageClass("managed-csi")
///     .WithCapacity("20Gi")
///     .WithAccessMode(PersistentVolumeAccessMode.ReadWriteOnce)
///     .WithVolumeAnnotation("volume.beta.kubernetes.io/storage-provisioner", "disk.csi.azure.com");
///
/// builder.AddContainer("postgres", "postgres:16")
///        .WithVolume("data", "/var/lib/postgresql/data")
///        .WithPersistentVolume(data);
/// </code>
/// </example>
[Experimental("ASPIRECOMPUTE002", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
[AspireExport]
public sealed class KubernetesPersistentVolumeResource(
    string name,
    KubernetesEnvironmentResource environment) : Resource(name), IResourceWithParent<KubernetesEnvironmentResource>
{
    /// <summary>
    /// Gets the parent Kubernetes environment resource.
    /// </summary>
    public KubernetesEnvironmentResource Parent { get; } = environment ?? throw new ArgumentNullException(nameof(environment));
 
    /// <summary>
    /// Gets or sets the storage class name for the generated PVC. When unset, the
    /// cluster's default storage class is used.
    /// </summary>
    internal ReferenceExpression? StorageClassName { get; set; }
 
    /// <summary>
    /// Gets or sets the requested storage capacity for the generated PVC (e.g.
    /// <c>"10Gi"</c>). When unset, falls back to
    /// <see cref="KubernetesEnvironmentResource.DefaultStorageSize"/>.
    /// </summary>
    internal ReferenceExpression? Capacity { get; set; }
 
    /// <summary>
    /// Gets the access modes configured for the volume. When empty, falls back to
    /// <see cref="KubernetesEnvironmentResource.DefaultStorageReadWritePolicy"/>.
    /// </summary>
    internal List<PersistentVolumeAccessMode> AccessModes { get; } = [];
 
    /// <summary>
    /// Gets the Kubernetes metadata annotations to add to the generated PVC. These
    /// are key-value pairs placed in the <c>metadata.annotations</c> field of the
    /// rendered Kubernetes resource — not Aspire <see cref="IResourceAnnotation"/>
    /// instances. CSI drivers, dynamic provisioners, and external operators
    /// (external-secrets, cert-manager, etc.) can pick them up at deploy time.
    /// </summary>
    internal Dictionary<string, ReferenceExpression> VolumeAnnotations { get; } = [];
 
    /// <summary>
    /// Gets the generated <see cref="PersistentVolumeClaim"/> for this resource,
    /// populated during publish processing. <see langword="null"/> until publish runs.
    /// </summary>
    internal PersistentVolumeClaim? GeneratedClaim { get; set; }
 
    /// <summary>
    /// The canonical Kubernetes name of the PVC that backs this volume resource.
    /// Both the PVC emission path (<c>BuildPersistentVolumeClaim</c>) and the pod
    /// volume binding path (<c>WithPodSpecVolumes</c>) resolve the name via this
    /// helper so the pod's <c>claimName</c> can never drift from the emitted PVC's
    /// <c>metadata.name</c>, even though the two paths run in different phases of
    /// publish. Do not derive the PVC name from the resource name directly.
    /// </summary>
    internal string GetClaimName() => Name.ToKubernetesResourceName();
}
 
/// <summary>
/// Specifies how a persistent volume may be mounted by pods. Maps directly to the
/// Kubernetes <c>PersistentVolumeAccessMode</c> values.
/// </summary>
[Experimental("ASPIRECOMPUTE002", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
public enum PersistentVolumeAccessMode
{
    /// <summary>
    /// The volume can be mounted as read-write by a single node. Most common for
    /// block storage backed databases.
    /// </summary>
    ReadWriteOnce,
 
    /// <summary>
    /// The volume can be mounted as read-only by many nodes simultaneously.
    /// </summary>
    ReadOnlyMany,
 
    /// <summary>
    /// The volume can be mounted as read-write by many nodes simultaneously.
    /// Typically used for shared file stores (e.g. Azure Files, NFS).
    /// </summary>
    ReadWriteMany,
 
    /// <summary>
    /// The volume can be mounted as read-write by a single pod. Requires Kubernetes
    /// 1.27 or later (<c>ReadWriteOncePod</c> access mode).
    /// </summary>
    ReadWriteOncePod,
}