| File: VolumeResourceBuilderExtensions.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; namespace Aspire.Hosting; /// <summary> /// Provides extension methods for adding volume-backed storage to compute resources. /// </summary> public static class VolumeResourceBuilderExtensions { /// <summary> /// Adds a volume to a compute resource and exposes its effective path through an environment variable. /// </summary> /// <typeparam name="T">The resource type.</typeparam> /// <param name="builder">The resource builder.</param> /// <param name="name">The name of the volume.</param> /// <param name="target">The target path where the volume is mounted after publishing.</param> /// <param name="env">The environment variable that receives the effective volume path.</param> /// <param name="isReadOnly">A flag that indicates if the published volume should be mounted as read-only.</param> /// <returns>The <see cref="IResourceBuilder{T}"/>.</returns> /// <remarks> /// Containers receive <paramref name="target"/> in run and publish modes. Projects and /// executables receive a workload-scoped <see cref="IAspireStore"/> directory in run mode /// and <paramref name="target"/> in publish mode. /// Named storage is independent of the resource lifetime. Session resources stop with the /// AppHost and reuse their named storage on the next run; persistent resources can keep the /// compute instance alive and continue using the same storage. Cleaning the AppHost store can /// remove local project and executable data. /// </remarks> /// <example> /// <code language="csharp"> /// builder.AddProject<Projects.Api>("api") /// .WithVolume("data", "/usr/data", env: "DATA_PATH"); /// </code> /// </example> [AspireExportIgnore(Reason = "Polyglot export is via CoreExports.WithVolume which reorders parameters.")] public static IResourceBuilder<T> WithVolume<T>( this IResourceBuilder<T> builder, string name, string target, string env, bool isReadOnly = false) where T : IComputeResource, IResourceWithEnvironment { ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrEmpty(name); ArgumentException.ThrowIfNullOrEmpty(target); ArgumentException.ThrowIfNullOrEmpty(env); return WithVolumeCore(builder, name, target, isReadOnly, env); } internal static IResourceBuilder<T> WithVolumeCore<T>( IResourceBuilder<T> builder, string? name, string target, bool isReadOnly, string? env) where T : IComputeResource { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(target); if (env is null) { builder.WithAnnotation(new ContainerMountAnnotation(name, target, ContainerMountType.Volume, isReadOnly)); return builder; } ArgumentException.ThrowIfNullOrEmpty(env); // Binding an environment variable requires a named volume. Run mode scopes a local directory by // the volume name, and the binding annotation below is keyed on it, so an anonymous volume has // nothing to resolve against and nothing a compute environment could inspect. ArgumentException.ThrowIfNullOrEmpty(name); if (builder.Resource is not IResourceWithEnvironment) { throw new InvalidOperationException( $"Resource '{builder.Resource.Name}' does not support environment variables and cannot use the '{env}' volume path variable."); } builder.WithAnnotation(new ContainerMountAnnotation(name, target, ContainerMountType.Volume, isReadOnly)); // Restate the binding declaratively. The env callback below captures env in a closure, so a // compute environment inspecting the model afterwards cannot otherwise tell that this mount // resolves a local path in run mode. var binding = new VolumeMountBindingAnnotation(name) { EnvironmentVariableName = env, MountPath = target }; builder.WithAnnotation(binding); builder.WithAnnotation(new EnvironmentCallbackAnnotation( context => context.EnvironmentVariables[env] = binding.ResolvePath(context))); return builder; } }