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 an azure redis resource #435

Merged
merged 1 commit into from
Oct 21, 2023
Merged
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<PackageVersion Include="Azure.ResourceManager.ServiceBus" Version="1.1.0-beta.3" />
<PackageVersion Include="Azure.ResourceManager.Storage" Version="1.1.1" />
<PackageVersion Include="Azure.ResourceManager.Authorization" Version="1.1.0-beta.1" />
<PackageVersion Include="Azure.ResourceManager.Redis" Version="1.2.0" />
<!-- ASP.NET Core dependencies -->
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="$(AspNetCoreVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(AspNetCoreVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="Azure.ResourceManager.KeyVault" />
<PackageReference Include="Azure.ResourceManager.Redis" />
<PackageReference Include="Azure.ResourceManager.ServiceBus" />
<PackageReference Include="Azure.ResourceManager.Storage" />
<PackageReference Include="Azure.ResourceManager.Authorization" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Hosting.Lifecycle;
using Azure.ResourceManager;
using Azure.ResourceManager.KeyVault;
using Azure.ResourceManager.Redis;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.ServiceBus;
using Azure.ResourceManager.Storage;
Expand All @@ -32,6 +33,9 @@ public static IDistributedApplicationBuilder AddAzureProvisioning(this IDistribu

builder.AddAzureProvisioner<AzureServiceBusResource, ServiceBusProvisioner>();
builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetServiceBusNamespaces(), resource => resource.Data.Tags);

builder.AddAzureProvisioner<AzureRedisResource, AzureRedisProvisioner>();
builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetAllRedis(), resource => resource.Data.Tags);
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ await PopulateExistingAspireResources(

var tasks = new List<Task>();

// Try to find the user secrets path
// we're going to cache access tokens in the user secrets file
// to speed up credential acquisition.
// Try to find the user secrets path so that provisioners can persist connection information.
static string? GetUserSecretsPath()
{
return Assembly.GetEntryAssembly()?.GetCustomAttribute<UserSecretsIdAttribute>()?.UserSecretsId switch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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;
using System.Text.Json.Nodes;
using Azure;
using Azure.Core;
using Azure.ResourceManager;
using Azure.ResourceManager.Redis;
using Azure.ResourceManager.Redis.Models;
using Azure.ResourceManager.Resources;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Aspire.Hosting.Azure.Provisioning;

internal sealed class AzureRedisProvisioner(ILogger<AzureRedisProvisioner> logger) : AzureResourceProvisioner<AzureRedisResource>
{
public override bool ConfigureResource(IConfiguration configuration, AzureRedisResource resource)
{
if (configuration.GetConnectionString(resource.Name) is string connectionString)
{
resource.ConnectionString = connectionString;
return true;
}

return false;
}

public override async Task GetOrCreateResourceAsync(
ArmClient armClient,
SubscriptionResource subscription,
ResourceGroupResource resourceGroup,
Dictionary<string, ArmResource> resourceMap,
AzureLocation location,
AzureRedisResource resource,
Guid principalId,
JsonObject userSecrets,
CancellationToken cancellationToken)
{

resourceMap.TryGetValue(resource.Name, out var azureResource);

if (azureResource is not null && azureResource is not RedisResource)
{
logger.LogWarning("Resource {resourceName} is not a redis resource. Deleting it.", resource.Name);

await armClient.GetGenericResource(azureResource.Id).DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false);
}

var redisResource = azureResource as RedisResource;

if (redisResource is null)
{
var redisName = Guid.NewGuid().ToString().Replace("-", string.Empty)[0..20];

logger.LogInformation("Creating redis {redisName} in {location}...", redisName, location);

var redisCreateOrUpdateContent = new RedisCreateOrUpdateContent(location, new RedisSku(RedisSkuName.Basic, RedisSkuFamily.BasicOrStandard, 0));
redisCreateOrUpdateContent.Tags.Add(AzureProvisioner.AspireResourceNameTag, resource.Name);

var sw = Stopwatch.StartNew();
var operation = await resourceGroup.GetAllRedis().CreateOrUpdateAsync(WaitUntil.Completed, redisName, redisCreateOrUpdateContent, cancellationToken).ConfigureAwait(false);
redisResource = operation.Value;
sw.Stop();

logger.LogInformation("Redis {redisName} created in {elapsed}", redisResource.Data.Name, sw.Elapsed);
}

// This must be an explicit call to get the keys
var keysOperation = await redisResource.GetKeysAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var keys = keysOperation.Value;

// REVIEW: Do we need to use the port?
resource.ConnectionString = $"{redisResource.Data.HostName},ssl=true,password={keys.PrimaryKey}";

var connectionStrings = userSecrets.Prop("ConnectionStrings");
connectionStrings[resource.Name] = resource.ConnectionString;
}
}
13 changes: 13 additions & 0 deletions src/Aspire.Hosting.Azure/AzureRedisResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.Azure;

public class AzureRedisResource(string name) : DistributedApplicationResource(name), IAzureResource, IDistributedApplicationResourceWithConnectionString
{
public string? ConnectionString { get; set; }

public string? GetConnectionString() => ConnectionString;
}
26 changes: 22 additions & 4 deletions src/Aspire.Hosting.Azure/AzureResourceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ private static void WriteTableStorageToManifest(Utf8JsonWriter json, AzureTableS
/// <summary>
/// Adds an Azure queue storage resource to the application model. This resource requires an <see cref="AzureStorageResource"/> to be added to the application model.
/// </summary>
/// <param name="storageBuilder">The Azure storage resource builder.</param>
/// <param name="builder">The Azure storage resource builder.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IDistributedApplicationResourceBuilder{AzureQueueStorageResource}"/>.</returns>
public static IDistributedApplicationResourceBuilder<AzureQueueStorageResource> AddQueues(this IDistributedApplicationResourceBuilder<AzureStorageResource> storageBuilder, string name)
public static IDistributedApplicationResourceBuilder<AzureQueueStorageResource> AddQueues(this IDistributedApplicationResourceBuilder<AzureStorageResource> builder, string name)
{
var resource = new AzureQueueStorageResource(name, storageBuilder.Resource);
return storageBuilder.ApplicationBuilder.AddResource(resource)
var resource = new AzureQueueStorageResource(name, builder.Resource);
return builder.ApplicationBuilder.AddResource(resource)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(json => WriteQueueStorageToManifest(json, resource)));
}

Expand All @@ -146,4 +146,22 @@ private static void WriteQueueStorageToManifest(Utf8JsonWriter json, AzureQueueS
json.WriteString("type", "azure.storage.queue.v1");
json.WriteString("parent", resource.Parent.Name);
}

/// <summary>
/// Adds an Azure Redis resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IDistributedApplicationResourceBuilder{AzureRedisResource}"/>.</returns>
public static IDistributedApplicationResourceBuilder<AzureRedisResource> AddAzureRedis(this IDistributedApplicationBuilder builder, string name)
{
var resource = new AzureRedisResource(name);
return builder.AddResource(resource)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteAzureRedisToManifest));
}

private static void WriteAzureRedisToManifest(Utf8JsonWriter writer)
{
writer.WriteString("type", "azure.redis.v1");
}
}