-
Notifications
You must be signed in to change notification settings - Fork 776
Add WithRedisInsight #5227
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
Merged
Merged
Add WithRedisInsight #5227
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
a8266cb
Add WithRedisInsight
Alirexaa 0d0f65c
Add unit tests
Alirexaa df3d287
Add functionl test
Alirexaa c2c5564
revert test container registery
Alirexaa 7b4c2f2
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa 4c6fb2a
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa 6c7ecbb
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa ac2350a
Use Eventing API
Alirexaa 80a2e38
Adress PR feedback
Alirexaa 67bb195
fix build
Alirexaa 9e4a013
disable test
Alirexaa 9144004
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa 23fde0e
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa 3f97382
Revert formatting
Alirexaa 859afb6
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa 6b35c52
Merge branch 'main' into WithRedisInsight
Alirexaa 7f8f7fd
Merge branch 'main' into WithRedisInsight
Alirexaa a70276a
Address PR feedback
Alirexaa 5cf971b
Apply suggestions from code review
Alirexaa ef6e8dd
Update tests/Aspire.Hosting.Redis.Tests/AddRedisTests.cs
Alirexaa 0a0fb8b
Update src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
davidfowl 8549e2c
Fix build and test
Alirexaa 2ae5cd3
Update tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs
Alirexaa f3fa22e
Address PR feedback
Alirexaa 3bfdac9
nit
Alirexaa 5ac764f
Add accept eula
Alirexaa 90b21b9
Add manual eula acceptance
Alirexaa eac485b
Merge branch 'dotnet:main' into WithRedisInsight
Alirexaa ccbbdbb
Update Redis playground app
Alirexaa 3d506fc
Adress PR feedback
Alirexaa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,7 @@ | ||
| #nullable enable | ||
|
|
||
| Aspire.Hosting.Redis.RedisInsightResource | ||
| Aspire.Hosting.Redis.RedisInsightResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference! | ||
| Aspire.Hosting.Redis.RedisInsightResource.RedisInsightResource(string! name) -> void | ||
| static Aspire.Hosting.RedisBuilderExtensions.WithAcceptEula(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Redis.RedisInsightResource!>! builder, bool accept = true) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Redis.RedisInsightResource!>! | ||
| static Aspire.Hosting.RedisBuilderExtensions.WithHostPort(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Redis.RedisInsightResource!>! builder, int? port) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Redis.RedisInsightResource!>! | ||
| static Aspire.Hosting.RedisBuilderExtensions.WithRedisInsight(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.RedisResource!>! builder, System.Action<Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Redis.RedisInsightResource!>!>? configureContainer = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.RedisResource!>! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,13 @@ | |
|
|
||
| using System.Globalization; | ||
| using System.Text; | ||
| using System.Text.Json; | ||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Redis; | ||
| using Aspire.Hosting.Utils; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging; | ||
| using Polly; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
|
|
@@ -123,12 +126,189 @@ public static IResourceBuilder<RedisResource> WithRedisCommander(this IResourceB | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures a container resource for Redis Insight which is pre-configured to connect to the <see cref="RedisResource"/> that this method is used on. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IResourceBuilder{T}"/> for the <see cref="RedisResource"/>.</param> | ||
| /// <param name="configureContainer">Configuration callback for Redis Insight container resource.</param> | ||
| /// <param name="containerName">Override the container name used for Redis Insight.</param> | ||
| /// <returns></returns> | ||
| public static IResourceBuilder<RedisResource> WithRedisInsight(this IResourceBuilder<RedisResource> builder, Action<IResourceBuilder<RedisInsightResource>>? configureContainer = null, string? containerName = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
||
| if (builder.ApplicationBuilder.Resources.OfType<RedisInsightResource>().SingleOrDefault() is { } existingRedisCommanderResource) | ||
| { | ||
| var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingRedisCommanderResource); | ||
| configureContainer?.Invoke(builderForExistingResource); | ||
| return builder; | ||
| } | ||
| else | ||
| { | ||
| builder.ApplicationBuilder.Services.AddHttpClient(); | ||
| containerName ??= $"{builder.Resource.Name}-insight"; | ||
|
|
||
| var resource = new RedisInsightResource(containerName); | ||
| var resourceBuilder = builder.ApplicationBuilder.AddResource(resource) | ||
| .WithImage(RedisContainerImageTags.RedisInsightImage, RedisContainerImageTags.RedisInsightTag) | ||
| .WithImageRegistry(RedisContainerImageTags.RedisInsightRegistry) | ||
| .WithHttpEndpoint(targetPort: 5540, name: "http") | ||
| .ExcludeFromManifest(); | ||
|
|
||
| builder.ApplicationBuilder.Eventing.Subscribe<AfterResourcesCreatedEvent>(async (e, ct) => | ||
| { | ||
| var redisInstances = builder.ApplicationBuilder.Resources.OfType<RedisResource>(); | ||
|
|
||
| if (!redisInstances.Any()) | ||
| { | ||
| // No-op if there are no Redis resources present. | ||
| return; | ||
| } | ||
|
|
||
| var httpClientFactory = e.Services.GetRequiredService<IHttpClientFactory>(); | ||
|
|
||
| var redisInsightResource = builder.ApplicationBuilder.Resources.OfType<RedisInsightResource>().Single(); | ||
| var insightEndpoint = redisInsightResource.PrimaryEndpoint; | ||
|
|
||
| var client = httpClientFactory.CreateClient(); | ||
| client.BaseAddress = new Uri($"{insightEndpoint.Scheme}://{insightEndpoint.Host}:{insightEndpoint.Port}"); | ||
|
|
||
| var rls = e.Services.GetRequiredService<ResourceLoggerService>(); | ||
| var resourceLogger = rls.GetLogger(resource); | ||
|
|
||
| if (resource.AcceptedEula) | ||
| { | ||
| await AcceptRedisInsightEula(resourceLogger, client, ct).ConfigureAwait(false); | ||
| } | ||
|
|
||
| await ImportRedisDatabases(resourceLogger, redisInstances, client, ct).ConfigureAwait(false); | ||
| }); | ||
|
|
||
| configureContainer?.Invoke(resourceBuilder); | ||
|
|
||
| return builder; | ||
| } | ||
|
|
||
| static async Task ImportRedisDatabases(ILogger resourceLogger, IEnumerable<RedisResource> redisInstances, HttpClient client, CancellationToken ct) | ||
| { | ||
| using (var stream = new MemoryStream()) | ||
| { | ||
| using var writer = new Utf8JsonWriter(stream); | ||
|
|
||
| writer.WriteStartArray(); | ||
|
|
||
| foreach (var redisResource in redisInstances) | ||
| { | ||
| if (redisResource.PrimaryEndpoint.IsAllocated) | ||
| { | ||
| var endpoint = redisResource.PrimaryEndpoint; | ||
| writer.WriteStartObject(); | ||
| writer.WriteString("host", redisResource.Name); | ||
| writer.WriteNumber("port", endpoint.TargetPort!.Value); | ||
| writer.WriteString("name", redisResource.Name); | ||
| writer.WriteNumber("db", 0); | ||
| //todo: provide username and password when https://github.com/dotnet/aspire/pull/4642 merged. | ||
| writer.WriteNull("username"); | ||
| writer.WriteNull("password"); | ||
| writer.WriteString("connectionType", "STANDALONE"); | ||
| writer.WriteEndObject(); | ||
| } | ||
| } | ||
| writer.WriteEndArray(); | ||
| await writer.FlushAsync(ct).ConfigureAwait(false); | ||
| stream.Seek(0, SeekOrigin.Begin); | ||
|
|
||
| var content = new MultipartFormDataContent(); | ||
|
|
||
| var fileContent = new StreamContent(stream); | ||
|
|
||
| content.Add(fileContent, "file", "RedisInsight_connections.json"); | ||
|
|
||
| var apiUrl = $"/api/databases/import"; | ||
|
|
||
| var pipeline = new ResiliencePipelineBuilder().AddRetry(new Polly.Retry.RetryStrategyOptions | ||
| { | ||
| Delay = TimeSpan.FromSeconds(2), | ||
| MaxRetryAttempts = 5, | ||
| }).Build(); | ||
|
|
||
| try | ||
| { | ||
| await pipeline.ExecuteAsync(async (ctx) => | ||
| { | ||
| var response = await client.PostAsync(apiUrl, content, ctx) | ||
| .ConfigureAwait(false); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
| }, ct).ConfigureAwait(false); | ||
|
|
||
| } | ||
| catch (Exception ex) | ||
| { | ||
| resourceLogger.LogError("Could not import Redis databases into RedisInsight. Reason: {reason}", ex.Message); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| static async Task AcceptRedisInsightEula(ILogger resourceLogger, HttpClient client, CancellationToken ct) | ||
| { | ||
| using (var stream = new MemoryStream()) | ||
| { | ||
| using var writer = new Utf8JsonWriter(stream); | ||
|
|
||
| writer.WriteStartObject(); | ||
|
|
||
| writer.WritePropertyName("agreements"); | ||
| writer.WriteStartObject(); | ||
| writer.WriteBoolean("eula", true); | ||
| writer.WriteBoolean("analytics", false); | ||
| writer.WriteBoolean("notifications", false); | ||
| writer.WriteBoolean("encryption", false); | ||
| writer.WriteEndObject(); | ||
|
|
||
| writer.WriteEndObject(); | ||
|
|
||
| await writer.FlushAsync(ct).ConfigureAwait(false); | ||
| stream.Seek(0, SeekOrigin.Begin); | ||
| string json = Encoding.UTF8.GetString(stream.ToArray()); | ||
| var content = new StringContent(json, Encoding.UTF8, "application/json"); | ||
|
Comment on lines
+255
to
+274
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to write a follow up @Alirexaa, this can be simplified using JsonContent and JsonObject. |
||
|
|
||
| var apiUrl = $"/api/settings"; | ||
|
|
||
| var pipeline = new ResiliencePipelineBuilder().AddRetry(new Polly.Retry.RetryStrategyOptions | ||
| { | ||
| Delay = TimeSpan.FromSeconds(2), | ||
| MaxRetryAttempts = 5, | ||
| }).Build(); | ||
|
|
||
| try | ||
| { | ||
| await pipeline.ExecuteAsync(async (ctx) => | ||
| { | ||
| var response = await client.PatchAsync(apiUrl, content, ctx) | ||
| .ConfigureAwait(false); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| var d = await response.Content.ReadAsStringAsync(ctx).ConfigureAwait(false); | ||
|
|
||
| }, ct).ConfigureAwait(false); | ||
|
|
||
| } | ||
| catch (Exception ex) | ||
| { | ||
| resourceLogger.LogError("Could accept RedisInsight eula. Reason: {reason}", ex.Message); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| } | ||
| /// <summary> | ||
| /// Configures the host port that the Redis Commander resource is exposed on instead of using randomly assigned port. | ||
| /// </summary> | ||
| /// <param name="builder">The resource builder for Redis Commander.</param> | ||
| /// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param> | ||
| /// <returns>The resource builder for PGAdmin.</returns> | ||
| /// <returns>The resource builder for RedisCommander.</returns> | ||
| public static IResourceBuilder<RedisCommanderResource> WithHostPort(this IResourceBuilder<RedisCommanderResource> builder, int? port) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
@@ -139,6 +319,36 @@ public static IResourceBuilder<RedisCommanderResource> WithHostPort(this IResour | |
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures the host port that the Redis Insight resource is exposed on instead of using randomly assigned port. | ||
| /// </summary> | ||
| /// <param name="builder">The resource builder for Redis Insight.</param> | ||
| /// <param name="port">The port to bind on the host. If <see langword="null"/> is used random port will be assigned.</param> | ||
| /// <returns>The resource builder for RedisInsight.</returns> | ||
| public static IResourceBuilder<RedisInsightResource> WithHostPort(this IResourceBuilder<RedisInsightResource> builder, int? port) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
||
| return builder.WithEndpoint("http", endpoint => | ||
| { | ||
| endpoint.Port = port; | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures the acceptance of the End User License Agreement (EULA) for Redis Insight. | ||
| /// </summary> | ||
| /// <param name="builder">The resource builder for Redis Insight.</param> | ||
| /// <param name="accept">A boolean value indicating whether to accept the EULA. If <see langword="true"/>, the EULA is accepted.</param> | ||
| /// <returns>The resource builder for Redis Insight.</returns> | ||
| public static IResourceBuilder<RedisInsightResource> WithAcceptEula(this IResourceBuilder<RedisInsightResource> builder, bool accept = true) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
||
| builder.Resource.AcceptedEula = accept; | ||
| return builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a named volume for the data folder to a Redis container resource and enables Redis persistence. | ||
| /// </summary> | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // 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.Redis; | ||
|
|
||
| /// <summary> | ||
| /// A resource that represents a Redis Insight container. | ||
| /// </summary> | ||
| /// <param name="name">The name of the resource.</param> | ||
| public class RedisInsightResource(string name) : ContainerResource(name) | ||
| { | ||
| internal const string PrimaryEndpointName = "http"; | ||
|
|
||
| private EndpointReference? _primaryEndpoint; | ||
|
|
||
| /// <summary> | ||
| /// Gets the primary endpoint for the Redis Insight. | ||
| /// </summary> | ||
| public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName); | ||
|
|
||
| /// <summary> | ||
| /// A boolean value indicating whether to accept the EULA. If <see langword="true"/>, the EULA is accepted. | ||
| /// </summary> | ||
| internal bool AcceptedEula { get; set; } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.