Skip to content
Closed
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
4 changes: 0 additions & 4 deletions src/Aspire.Hosting.Garnet/Aspire.Hosting.Garnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
<MinCodeCoverage>92</MinCodeCoverage>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedDir)VolumeNameGenerator.cs" Link="Utils\VolumeNameGenerator.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>
Expand Down
78 changes: 30 additions & 48 deletions src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Garnet;
using Aspire.Hosting.Utils;
using Aspire.Hosting.Utils.Cache;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -48,85 +47,73 @@ public static class GarnetBuilderExtensions
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="port">The host port to bind the underlying container to.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GarnetResource> AddGarnet(this IDistributedApplicationBuilder builder, string name,
public static IResourceBuilder<GarnetResource> AddGarnet(this IDistributedApplicationBuilder builder,
string name,
int? port = null)
{
var garnet = new GarnetResource(name);
return builder.AddResource(garnet)
.WithEndpoint(port: port, targetPort: 6379, name: GarnetResource.PrimaryEndpointName)
.WithImage(GarnetContainerImageTags.Image, GarnetContainerImageTags.Tag)
.WithImageRegistry(GarnetContainerImageTags.Registry);
var garnetResource = new GarnetResource(name);
return builder.AddCache(garnetResource,
GarnetContainerImageTags.Registry,
GarnetContainerImageTags.Image,
GarnetContainerImageTags.Tag,
6379,
port);
}

/// <summary>
/// Adds a named volume for the data folder to a Garnet container resource and enables Garnet persistence.
/// Adds a named volume for the data folder to a Cache container resource and enables Cache persistence.
/// </summary>
/// <example>
/// Use <see cref="WithPersistence(IResourceBuilder{GarnetResource}, TimeSpan?, long)"/> to adjust Garnet persistence configuration, e.g.:
/// Use <see cref="WithPersistence(IResourceBuilder{GarnetResource}, TimeSpan?, long)"/> to adjust Cache persistence configuration, e.g.:
/// <code lang="csharp">
/// var cache = builder.AddGarnet("cache")
/// var cache = builder.AddCache("cache")
/// .WithDataVolume()
/// .WithPersistence(TimeSpan.FromSeconds(10), 5);
/// </code>
/// </example>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the application and resource names.</param>
/// <param name="isReadOnly">
/// A flag that indicates if this is a read-only volume. Setting this to <c>true</c> will disable Garnet persistence.<br/>
/// A flag that indicates if this is a read-only volume. Setting this to <c>true</c> will disable Cache persistence.<br/>
/// Defaults to <c>false</c>.
/// </param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GarnetResource> WithDataVolume(this IResourceBuilder<GarnetResource> builder,
string? name = null, bool isReadOnly = false)
{
builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), GarnetContainerDataDirectory,
isReadOnly);
if (!isReadOnly)
{
builder.WithPersistence();
}

return builder;
}
string? name = null,
bool isReadOnly = false)
=> builder.WithDataVolume(GarnetContainerDataDirectory, name, isReadOnly);

/// <summary>
/// Adds a bind mount for the data folder to a Garnet container resource and enables Garnet persistence.
/// Adds a bind mount for the data folder to a Cache container resource and enables Cache persistence.
/// </summary>
/// <example>
/// Use <see cref="WithPersistence(IResourceBuilder{GarnetResource}, TimeSpan?, long)"/> to adjust Garnet persistence configuration, e.g.:
/// Use <see cref="WithPersistence(IResourceBuilder{GarnetResource}, TimeSpan?, long)"/> to adjust Cache persistence configuration, e.g.:
/// <code lang="csharp">
/// var garnet = builder.AddGarnet("garnet")
/// var cache = builder.AddCache("cache")
/// .WithDataBindMount()
/// .WithPersistence(TimeSpan.FromSeconds(10), 5);
/// </code>
/// </example>
/// <param name="builder">The resource builder.</param>
/// <param name="source">The source directory on the host to mount into the container.</param>
/// <param name="isReadOnly">
/// A flag that indicates if this is a read-only mount. Setting this to <c>true</c> will disable Garnet persistence.<br/>
/// A flag that indicates if this is a read-only mount. Setting this to <c>true</c> will disable Cache persistence.<br/>
/// Defaults to <c>false</c>.
/// </param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GarnetResource> WithDataBindMount(this IResourceBuilder<GarnetResource> builder,
string source, bool isReadOnly = false)
{
builder.WithBindMount(source, GarnetContainerDataDirectory, isReadOnly);
if (!isReadOnly)
{
builder.WithPersistence();
}

return builder;
}
string source,
bool isReadOnly = false)
=> builder.WithDataBindMount(source, GarnetContainerDataDirectory, isReadOnly);

/// <summary>
/// Configures a Garnet container resource for persistence.
/// Configures a Cache container resource for persistence.
/// </summary>
/// <example>
/// Use with <see cref="WithDataBindMount(IResourceBuilder{GarnetResource}, string, bool)"/>
/// or <see cref="WithDataVolume(IResourceBuilder{GarnetResource}, string?, bool)"/> to persist Garnet data across sessions with custom persistence configuration, e.g.:
/// or <see cref="WithDataVolume(IResourceBuilder{GarnetResource}, string?, bool)"/> to persist Cache data across sessions with custom persistence configuration, e.g.:
/// <code lang="csharp">
/// var cache = builder.AddGarnet("cache")
/// var cache = builder.AddCache("cache")
/// .WithDataVolume()
/// .WithPersistence(TimeSpan.FromSeconds(10), 5);
/// </code>
Expand All @@ -136,12 +123,7 @@ public static IResourceBuilder<GarnetResource> WithDataBindMount(this IResourceB
/// <param name="keysChangedThreshold">The number of key change operations required to trigger a snapshot at the interval. Defaults to 1.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GarnetResource> WithPersistence(this IResourceBuilder<GarnetResource> builder,
TimeSpan? interval = null, long keysChangedThreshold = 1)
=> builder.WithAnnotation(new CommandLineArgsCallbackAnnotation(context =>
{
context.Args.Add("--save");
context.Args.Add((interval ?? TimeSpan.FromSeconds(60)).TotalSeconds.ToString(CultureInfo.InvariantCulture));
context.Args.Add(keysChangedThreshold.ToString(CultureInfo.InvariantCulture));
return Task.CompletedTask;
}), ResourceAnnotationMutationBehavior.Replace);
TimeSpan? interval = null,
long keysChangedThreshold = 1)
=> CacheBuilderExtensions.WithPersistence(builder, interval, keysChangedThreshold);
}
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Garnet/GarnetContainerImageTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Aspire.Hosting.Garnet;

internal static class GarnetContainerImageTags
internal sealed class GarnetContainerImageTags
{
public const string Registry = "ghcr.io";
public const string Image = "microsoft/garnet";
Expand Down
21 changes: 3 additions & 18 deletions src/Aspire.Hosting.Garnet/GarnetResource.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
// 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.Utils.Cache;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a Garnet resource independent of the hosting model.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class GarnetResource(string name) : ContainerResource(name), IResourceWithConnectionString
{
internal const string PrimaryEndpointName = "tcp";

private EndpointReference? _primaryEndpoint;

/// <summary>
/// Gets the primary endpoint for the Garnet server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);

/// <summary>
/// Gets the connection string expression for the Garnet server.
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");
}
public class GarnetResource(string name) : CacheResource(name);
2 changes: 0 additions & 2 deletions src/Aspire.Hosting.Garnet/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#nullable enable
Aspire.Hosting.ApplicationModel.GarnetResource
Aspire.Hosting.ApplicationModel.GarnetResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
Aspire.Hosting.ApplicationModel.GarnetResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference!
Aspire.Hosting.ApplicationModel.GarnetResource.GarnetResource(string! name) -> void
Aspire.Hosting.GarnetBuilderExtensions
static Aspire.Hosting.GarnetBuilderExtensions.AddGarnet(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.GarnetResource!>!
Expand Down
4 changes: 0 additions & 4 deletions src/Aspire.Hosting.Redis/Aspire.Hosting.Redis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
<MinCodeCoverage>90</MinCodeCoverage>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedDir)VolumeNameGenerator.cs" Link="Utils\VolumeNameGenerator.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/Aspire.Hosting.Redis/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#nullable enable
Aspire.Hosting.ApplicationModel.RedisResource
Aspire.Hosting.ApplicationModel.RedisResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
Aspire.Hosting.ApplicationModel.RedisResource.GetConnectionStringAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<string?>
Aspire.Hosting.ApplicationModel.RedisResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference!
Aspire.Hosting.ApplicationModel.RedisResource.RedisResource(string! name) -> void
Aspire.Hosting.Redis.RedisCommanderResource
Aspire.Hosting.Redis.RedisCommanderResource.RedisCommanderResource(string! name) -> void
Expand Down
60 changes: 26 additions & 34 deletions src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Redis;
using Aspire.Hosting.Utils;
using Aspire.Hosting.Utils.Cache;

namespace Aspire.Hosting;

Expand All @@ -14,6 +13,8 @@ namespace Aspire.Hosting;
/// </summary>
public static class RedisBuilderExtensions
{
private const string RedisContainerDataDirectory = "/data";

/// <summary>
/// Adds a Redis container to the application model.
/// </summary>
Expand All @@ -24,13 +25,17 @@ public static class RedisBuilderExtensions
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="port">The host port to bind the underlying container to.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> AddRedis(this IDistributedApplicationBuilder builder, string name, int? port = null)
public static IResourceBuilder<RedisResource> AddRedis(this IDistributedApplicationBuilder builder,
string name,
int? port = null)
{
var redis = new RedisResource(name);
return builder.AddResource(redis)
.WithEndpoint(port: port, targetPort: 6379, name: RedisResource.PrimaryEndpointName)
.WithImage(RedisContainerImageTags.Image, RedisContainerImageTags.Tag)
.WithImageRegistry(RedisContainerImageTags.Registry);
var redisResource = new RedisResource(name);
return builder.AddCache(redisResource,
RedisContainerImageTags.Registry,
RedisContainerImageTags.Image,
RedisContainerImageTags.Tag,
6379,
port);
}

/// <summary>
Expand Down Expand Up @@ -98,15 +103,10 @@ public static IResourceBuilder<RedisCommanderResource> WithHostPort(this IResour
/// Defaults to <c>false</c>.
/// </param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> WithDataVolume(this IResourceBuilder<RedisResource> builder, string? name = null, bool isReadOnly = false)
{
builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/data", isReadOnly);
if (!isReadOnly)
{
builder.WithPersistence();
}
return builder;
}
public static IResourceBuilder<RedisResource> WithDataVolume(this IResourceBuilder<RedisResource> builder,
string? name = null,
bool isReadOnly = false)
=> builder.WithDataVolume(RedisContainerDataDirectory, name, isReadOnly);

/// <summary>
/// Adds a bind mount for the data folder to a Redis container resource and enables Redis persistence.
Expand All @@ -126,15 +126,10 @@ public static IResourceBuilder<RedisResource> WithDataVolume(this IResourceBuild
/// Defaults to <c>false</c>.
/// </param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> WithDataBindMount(this IResourceBuilder<RedisResource> builder, string source, bool isReadOnly = false)
{
builder.WithBindMount(source, "/data", isReadOnly);
if (!isReadOnly)
{
builder.WithPersistence();
}
return builder;
}
public static IResourceBuilder<RedisResource> WithDataBindMount(this IResourceBuilder<RedisResource> builder,
string source,
bool isReadOnly = false)
=> builder.WithDataBindMount(source, RedisContainerDataDirectory, isReadOnly);

/// <summary>
/// Configures a Redis container resource for persistence.
Expand All @@ -152,12 +147,9 @@ public static IResourceBuilder<RedisResource> WithDataBindMount(this IResourceBu
/// <param name="interval">The interval between snapshot exports. Defaults to 60 seconds.</param>
/// <param name="keysChangedThreshold">The number of key change operations required to trigger a snapshot at the interval. Defaults to 1.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<RedisResource> WithPersistence(this IResourceBuilder<RedisResource> builder, TimeSpan? interval = null, long keysChangedThreshold = 1)
=> builder.WithAnnotation(new CommandLineArgsCallbackAnnotation(context =>
{
context.Args.Add("--save");
context.Args.Add((interval ?? TimeSpan.FromSeconds(60)).TotalSeconds.ToString(CultureInfo.InvariantCulture));
context.Args.Add(keysChangedThreshold.ToString(CultureInfo.InvariantCulture));
return Task.CompletedTask;
}), ResourceAnnotationMutationBehavior.Replace);
public static IResourceBuilder<RedisResource> WithPersistence(this IResourceBuilder<RedisResource> builder,
TimeSpan? interval = null,
long keysChangedThreshold = 1)
=> CacheBuilderExtensions.WithPersistence(builder, interval, keysChangedThreshold);

}
4 changes: 1 addition & 3 deletions src/Aspire.Hosting.Redis/RedisCommanderResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ namespace Aspire.Hosting.Redis;
/// A resource that represents a Redis Commander container.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class RedisCommanderResource(string name) : ContainerResource(name)
{
}
public class RedisCommanderResource(string name) : ContainerResource(name);
38 changes: 7 additions & 31 deletions src/Aspire.Hosting.Redis/RedisResource.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,16 @@
// 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.Utils.Cache;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a Redis resource independent of the hosting model.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class RedisResource(string name) : ContainerResource(name), IResourceWithConnectionString
public class RedisResource(string name) : CacheResource(name)
{
internal const string PrimaryEndpointName = "tcp";

private EndpointReference? _primaryEndpoint;

/// <summary>
/// Gets the primary endpoint for the Redis server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);

private ReferenceExpression ConnectionString =>
ReferenceExpression.Create(
$"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");

/// <summary>
/// Gets the connection string expression for the Redis server.
/// </summary>
public ReferenceExpression ConnectionStringExpression
{
get
{
if (this.TryGetLastAnnotation<ConnectionStringRedirectAnnotation>(out var connectionStringAnnotation))
{
return connectionStringAnnotation.Resource.ConnectionStringExpression;
}

return ConnectionString;
}
}

/// <summary>
/// Gets the connection string for the Redis server.
/// </summary>
Expand All @@ -50,6 +23,9 @@ public ReferenceExpression ConnectionStringExpression
return connectionStringAnnotation.Resource.GetConnectionStringAsync(cancellationToken);
}

return ConnectionString.GetValueAsync(cancellationToken);
var referenceExpression = ReferenceExpression.Create(
$"{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");

return referenceExpression.GetValueAsync(cancellationToken);
}
}
4 changes: 0 additions & 4 deletions src/Aspire.Hosting.Valkey/Aspire.Hosting.Valkey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@
<MinCodeCoverage>92</MinCodeCoverage>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedDir)VolumeNameGenerator.cs" Link="Utils\VolumeNameGenerator.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
</ItemGroup>
Expand Down
Loading