From 111b82fbb3626cd5ca3a1a581e98850bcaf1e50c Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 25 Mar 2025 23:37:42 -0700 Subject: [PATCH 1/5] Follow up keyvault changes - Pass the IKeyVaultSecretReference to the SecretResolver - Don't add the default keyvault when using the emulator. --- .../AzureCosmosDBExtensions.cs | 6 +++++ .../AzureKeyVaultResource.cs | 4 ++-- .../AzureKeyVaultSecretReference.cs | 2 +- .../AzurePostgresExtensions.cs | 6 +++++ .../AzureRedisExtensions.cs | 6 +++++ src/Aspire.Hosting.Azure/IKeyVaultResource.cs | 2 +- .../Provisioners/BicepProvisioner.cs | 4 ++-- .../AzureBicepResourceTests.cs | 22 +++++++++++++++---- .../AzurePostgresExtensionsTests.cs | 10 +++++++++ .../AzureRedisExtensionsTests.cs | 10 +++++++++ 10 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 7c66464a5d4..76aa30434cb 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -339,6 +339,12 @@ public static IResourceBuilder WithAccessKeyAuthenticatio { ArgumentNullException.ThrowIfNull(builder); + if (builder.Resource.IsEmulator) + { + // Don't add the automatic key vault when using the emulator. + return builder; + } + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); diff --git a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResource.cs b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResource.cs index a0577f19630..920530ae724 100644 --- a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResource.cs +++ b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResource.cs @@ -40,9 +40,9 @@ public class AzureKeyVaultResource(string name, Action Id; // In run mode, this is set to the secret client used to access the Azure Key Vault. - internal Func>? SecretResolver { get; set; } + internal Func>? SecretResolver { get; set; } - Func>? IKeyVaultResource.SecretResolver + Func>? IKeyVaultResource.SecretResolver { get => SecretResolver; set => SecretResolver = value; diff --git a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultSecretReference.cs b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultSecretReference.cs index 3f078529b32..b5579afd20e 100644 --- a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultSecretReference.cs +++ b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultSecretReference.cs @@ -28,7 +28,7 @@ internal sealed class AzureKeyVaultSecretReference(string secretName, AzureKeyVa { if (azureKeyVaultResource.SecretResolver is { } secretResolver) { - return await secretResolver(secretName, cancellationToken).ConfigureAwait(false); + return await secretResolver(this, cancellationToken).ConfigureAwait(false); } throw new InvalidOperationException($"Secret '{secretName}' not found in Key Vault '{azureKeyVaultResource.Name}'."); diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index 292b8accc05..a8b7dad3895 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -286,6 +286,12 @@ public static IResourceBuilder WithPassword { ArgumentNullException.ThrowIfNull(builder); + if (builder.Resource.IsContainer()) + { + // If the resource is running in a container, we need to set the username and password parameters on the inner resource. + return builder; + } + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 061b1bd819e..a5fda5d3acf 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -192,6 +192,12 @@ public static IResourceBuilder WithAccessKeyAuthenticat { ArgumentNullException.ThrowIfNull(builder); + if (builder.Resource.IsContainer()) + { + // If the resource is a container, don't add the key vault reference. + return builder; + } + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); diff --git a/src/Aspire.Hosting.Azure/IKeyVaultResource.cs b/src/Aspire.Hosting.Azure/IKeyVaultResource.cs index be53c8cdf31..098defe2acd 100644 --- a/src/Aspire.Hosting.Azure/IKeyVaultResource.cs +++ b/src/Aspire.Hosting.Azure/IKeyVaultResource.cs @@ -28,7 +28,7 @@ public interface IKeyVaultResource : IResource, IAzureResource /// /// Gets or sets the secret resolver function used to resolve secrets at runtime. /// - Func>? SecretResolver { get; set; } + Func>? SecretResolver { get; set; } /// /// Gets a secret reference for the specified secret name. diff --git a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs index 4f40e859dab..64c86eec023 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs @@ -279,9 +279,9 @@ await notificationService.PublishUpdateAsync(resource, state => // Set the client for resolving secrets at runtime var client = new SecretClient(new(vaultUri), context.Credential); - kvr.SecretResolver = async (secretName, ct) => + kvr.SecretResolver = async (secretRef, ct) => { - var secret = await client.GetSecretAsync(secretName, cancellationToken: ct).ConfigureAwait(false); + var secret = await client.GetSecretAsync(secretRef.SecretName, cancellationToken: ct).ConfigureAwait(false); return secret.Value.Value; }; } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs index 9069880406c..15bff810eba 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs @@ -237,6 +237,20 @@ public async Task AddAzureCosmosDBEmulator() Assert.Equal(cs, await ((IResourceWithConnectionString)cosmos.Resource).GetConnectionStringAsync()); } + [Fact] + public void AddAzureCosmosDB_WithAccessKeyAuthentication_NoKeyVaultWithEmulator() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + builder.AddAzureCosmosDB("cosmos").WithAccessKeyAuthentication().RunAsEmulator(); + +#pragma warning disable ASPIRECOSMOSDB001 + builder.AddAzureCosmosDB("cosmos2").WithAccessKeyAuthentication().RunAsPreviewEmulator(); +#pragma warning restore ASPIRECOSMOSDB001 + + Assert.Empty(builder.Resources.OfType()); + } + [Theory] [InlineData(null)] [InlineData("mykeyvault")] @@ -271,9 +285,9 @@ public async Task AddAzureCosmosDBViaRunMode_WithAccessKeyAuthentication(string? ["connectionstrings--cosmos"] = "mycosmosconnectionstring" }; - kv.Resource.SecretResolver = (name, _) => + kv.Resource.SecretResolver = (secretRef, _) => { - if (!secrets.TryGetValue(name, out var value)) + if (!secrets.TryGetValue(secretRef.SecretName, out var value)) { return Task.FromResult(null); } @@ -537,9 +551,9 @@ public async Task AddAzureCosmosDBViaPublishMode_WithAccessKeyAuthentication(str ["connectionstrings--cosmos"] = "mycosmosconnectionstring" }; - kv.Resource.SecretResolver = (name, _) => + kv.Resource.SecretResolver = (secretRef, _) => { - if (!secrets.TryGetValue(name, out var value)) + if (!secrets.TryGetValue(secretRef.SecretName, out var value)) { return Task.FromResult(null); } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs index af034a4fde7..a5265e40ce9 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs @@ -144,6 +144,16 @@ param principalName string Assert.Equal(expectedBicep, manifest.BicepText); } + [Fact] + public void AddAzurePostgresFlexibleServer_WithPasswordAuthentication_NoKeyVaultWithContainer() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + builder.AddAzurePostgresFlexibleServer("pg").WithPasswordAuthentication().RunAsContainer(); + + Assert.Empty(builder.Resources.OfType()); + } + [Theory] [InlineData(true, true, null)] [InlineData(true, true, "mykeyvault")] diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs index d235b542712..47498e8d664 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs @@ -89,6 +89,16 @@ param principalName string Assert.Equal(expectedBicep, manifest.BicepText); } + [Fact] + public void AddAzureRedis_WithAccessKeyAuthentication_NoKeyVaultWithContainer() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + builder.AddAzureRedis("redis").WithAccessKeyAuthentication().RunAsContainer(); + + Assert.Empty(builder.Resources.OfType()); + } + [Theory] [InlineData(null)] [InlineData("mykeyvault")] From bb09426440de10017e1903c58db366ad106133b1 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 29 Mar 2025 09:53:33 -0700 Subject: [PATCH 2/5] Enhance Azure Key Vault integration and add access key authentication support for CosmosDB and other resources --- .../CosmosEndToEnd.AppHost/Program.cs | 3 ++- .../AzureCosmosDBExtensions.cs | 8 +------ .../AzureKeyVaultResourceExtensions.cs | 24 ++++++++++++++++++- .../AzurePostgresExtensions.cs | 2 +- .../AzureRedisExtensions.cs | 8 +------ .../AzureBicepResourceTests.cs | 16 +++++++++++-- 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs index 97db04b0684..1a0b4bb5a6e 100644 --- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs +++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs @@ -6,7 +6,8 @@ var builder = DistributedApplication.CreateBuilder(args); var cosmos = builder.AddAzureCosmosDB("cosmos") - .RunAsPreviewEmulator(e => e.WithDataExplorer()); + .RunAsPreviewEmulator(e => e.WithDataExplorer()) + .WithAccessKeyAuthentication(); var db = cosmos.AddCosmosDatabase("db"); var container = db.AddContainer("entries", "/id"); diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 76aa30434cb..0ac2affd890 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -339,13 +339,7 @@ public static IResourceBuilder WithAccessKeyAuthenticatio { ArgumentNullException.ThrowIfNull(builder); - if (builder.Resource.IsEmulator) - { - // Don't add the automatic key vault when using the emulator. - return builder; - } - - var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); return builder.WithAccessKeyAuthentication(kv); diff --git a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs index 75e88d89530..0fd67852a55 100644 --- a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs @@ -28,6 +28,28 @@ public static class AzureKeyVaultResourceExtensions /// These can be replaced by calling . /// public static IResourceBuilder AddAzureKeyVault(this IDistributedApplicationBuilder builder, [ResourceName] string name) + { + var rb = builder.CreateAzureKeyVaultResourceBuilder(name); + + builder.AddResource(rb.Resource); + + return rb; + } + + /// + /// Creates an Azure Key Vault resource but does not add it to the application model. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + /// + /// By default references to the Azure Key Vault resource will be assigned the following roles: + /// + /// - + /// + /// These can be replaced by calling . + /// + public static IResourceBuilder CreateAzureKeyVaultResourceBuilder(this IDistributedApplicationBuilder builder, [ResourceName] string name) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); @@ -82,7 +104,7 @@ public static IResourceBuilder AddAzureKeyVault(this IDis }; var resource = new AzureKeyVaultResource(name, configureInfrastructure); - return builder.AddResource(resource) + return builder.CreateResourceBuilder(resource) .WithDefaultRoleAssignments(KeyVaultBuiltInRole.GetBuiltInRoleName, KeyVaultBuiltInRole.KeyVaultAdministrator); } diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index a8b7dad3895..761d9d717be 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -292,7 +292,7 @@ public static IResourceBuilder WithPassword return builder; } - var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); return builder.WithPasswordAuthentication(kv, userName, password); diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index a5fda5d3acf..6870372407c 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -192,13 +192,7 @@ public static IResourceBuilder WithAccessKeyAuthenticat { ArgumentNullException.ThrowIfNull(builder); - if (builder.Resource.IsContainer()) - { - // If the resource is a container, don't add the key vault reference. - return builder; - } - - var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); return builder.WithAccessKeyAuthentication(kv); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs index 15bff810eba..9069811efce 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Text.Json.Nodes; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; @@ -278,14 +279,22 @@ public async Task AddAzureCosmosDBViaRunMode_WithAccessKeyAuthentication(string? var db = cosmos.AddCosmosDatabase("db", databaseName: "mydatabase"); db.AddContainer("container", "mypartitionkeypath", containerName: "mycontainer"); - var kv = builder.CreateResourceBuilder(kvName); + var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, CancellationToken.None); + + var model = app.Services.GetRequiredService(); + + var kv = model.Resources.OfType().Single(); + + Assert.Equal(kvName, kv.Name); var secrets = new Dictionary { ["connectionstrings--cosmos"] = "mycosmosconnectionstring" }; - kv.Resource.SecretResolver = (secretRef, _) => + kv.SecretResolver = (secretRef, _) => { if (!secrets.TryGetValue(secretRef.SecretName, out var value)) { @@ -3177,6 +3186,9 @@ public async Task InfrastructureCanBeMutatedAfterCreation() Assert.Equal(expectedBicep, bicep); } + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")] + private static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken); + private sealed class ProjectA : IProjectMetadata { public string ProjectPath => "projectA"; From d5ea3590ce159d0629ce6367dbf66bb5296fd19f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 1 Apr 2025 19:14:27 -0500 Subject: [PATCH 3/5] Add the KeyVault resource, but remove it from the model in BeforeStart if the Azure resource is emulated or container in run mode. --- .../CosmosEndToEnd.AppHost/Program.cs | 3 +-- .../AzureCosmosDBExtensions.cs | 14 ++++++++++- .../AzureCosmosDBResource.cs | 2 +- .../AzureKeyVaultResourceExtensions.cs | 24 +------------------ .../AzurePostgresExtensions.cs | 21 ++++++++++------ .../AzureRedisExtensions.cs | 14 ++++++++++- .../AzureBicepResourceTests.cs | 8 +++++-- .../AzureCosmosDBExtensionsTests.cs | 14 +++++++++-- .../AzurePostgresExtensionsTests.cs | 8 +++++-- .../AzureRedisExtensionsTests.cs | 8 +++++-- 10 files changed, 73 insertions(+), 43 deletions(-) diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs index 69327ea4027..5a81fa6e9bc 100644 --- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs +++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs @@ -6,8 +6,7 @@ var builder = DistributedApplication.CreateBuilder(args); var cosmos = builder.AddAzureCosmosDB("cosmos") - .RunAsPreviewEmulator(e => e.WithDataExplorer()) - .WithAccessKeyAuthentication(); + .RunAsPreviewEmulator(e => e.WithDataExplorer()); var db = cosmos.AddCosmosDatabase("db"); var entries = db.AddContainer("entries", "/id", "staging-entries"); diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 15aa4df2acc..87fc49b3298 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -339,9 +339,21 @@ public static IResourceBuilder WithAccessKeyAuthenticatio { ArgumentNullException.ThrowIfNull(builder); - var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); + // remove the KeyVault from the model if the emulator is used. + // need to do this later in case builder becomes an emulator after this method is called. + builder.ApplicationBuilder.Eventing.Subscribe((data, _) => + { + var executionContext = data.Services.GetRequiredService(); + if (executionContext.IsRunMode && builder.Resource.IsEmulator) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + return builder.WithAccessKeyAuthentication(kv); } diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs index eb9cb02fc62..b48179003e3 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs @@ -134,7 +134,7 @@ internal ReferenceExpression GetChildConnectionString(string childResourceName, var builder = new ReferenceExpressionBuilder(); - if (UseAccessKeyAuthentication) + if (UseAccessKeyAuthentication && !IsEmulator) { builder.AppendFormatted(ConnectionStringSecretOutput.Resource.GetSecretReference(GetKeyValueSecretName(childResourceName))); } diff --git a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs index 9fc2f94e719..1c074afb48f 100644 --- a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs @@ -28,28 +28,6 @@ public static class AzureKeyVaultResourceExtensions /// These can be replaced by calling . /// public static IResourceBuilder AddAzureKeyVault(this IDistributedApplicationBuilder builder, [ResourceName] string name) - { - var rb = builder.CreateAzureKeyVaultResourceBuilder(name); - - builder.AddResource(rb.Resource); - - return rb; - } - - /// - /// Creates an Azure Key Vault resource but does not add it to the application model. - /// - /// The . - /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. - /// A reference to the . - /// - /// By default references to the Azure Key Vault resource will be assigned the following roles: - /// - /// - - /// - /// These can be replaced by calling . - /// - public static IResourceBuilder CreateAzureKeyVaultResourceBuilder(this IDistributedApplicationBuilder builder, [ResourceName] string name) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); @@ -90,7 +68,7 @@ public static IResourceBuilder CreateAzureKeyVaultResourc }; var resource = new AzureKeyVaultResource(name, configureInfrastructure); - return builder.CreateResourceBuilder(resource) + return builder.AddResource(resource) .WithDefaultRoleAssignments(KeyVaultBuiltInRole.GetBuiltInRoleName, KeyVaultBuiltInRole.KeyVaultSecretsUser); } diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index 711066c0edc..57d731258f3 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -8,6 +8,7 @@ using Azure.Provisioning.Expressions; using Azure.Provisioning.KeyVault; using Azure.Provisioning.PostgreSql; +using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting; @@ -286,15 +287,21 @@ public static IResourceBuilder WithPassword { ArgumentNullException.ThrowIfNull(builder); - if (builder.Resource.IsContainer()) - { - // If the resource is running in a container, we need to set the username and password parameters on the inner resource. - return builder; - } - - var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); + // remove the KeyVault from the model if the emulator is used. + // need to do this later in case builder becomes an emulator after this method is called. + builder.ApplicationBuilder.Eventing.Subscribe((data, token) => + { + var executionContext = data.Services.GetRequiredService(); + if (executionContext.IsRunMode && builder.Resource.IsContainer()) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + return builder.WithPasswordAuthentication(kv, userName, password); } diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 01f5e4cc181..555e337ae76 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -192,9 +192,21 @@ public static IResourceBuilder WithAccessKeyAuthenticat { ArgumentNullException.ThrowIfNull(builder); - var kv = builder.ApplicationBuilder.CreateAzureKeyVaultResourceBuilder($"{builder.Resource.Name}-kv") + var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); + // remove the KeyVault from the model if the emulator is used. + // need to do this later in case builder becomes an emulator after this method is called. + builder.ApplicationBuilder.Eventing.Subscribe((data, token) => + { + var executionContext = data.Services.GetRequiredService(); + if (executionContext.IsRunMode && builder.Resource.IsContainer()) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + return builder.WithAccessKeyAuthentication(kv); } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs index d280aa51639..5cd01f6b256 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs @@ -239,7 +239,7 @@ public async Task AddAzureCosmosDBEmulator() } [Fact] - public void AddAzureCosmosDB_WithAccessKeyAuthentication_NoKeyVaultWithEmulator() + public async Task AddAzureCosmosDB_WithAccessKeyAuthentication_NoKeyVaultWithEmulator() { using var builder = TestDistributedApplicationBuilder.Create(); @@ -249,7 +249,11 @@ public void AddAzureCosmosDB_WithAccessKeyAuthentication_NoKeyVaultWithEmulator( builder.AddAzureCosmosDB("cosmos2").WithAccessKeyAuthentication().RunAsPreviewEmulator(); #pragma warning restore ASPIRECOSMOSDB001 - Assert.Empty(builder.Resources.OfType()); + var app = builder.Build(); + var model = app.Services.GetRequiredService(); + await ExecuteBeforeStartHooksAsync(app, CancellationToken.None); + + Assert.Empty(model.Resources.OfType()); } [Theory] diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBExtensionsTests.cs index 7f7e2d2e18a..4c5475efbec 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBExtensionsTests.cs @@ -106,16 +106,26 @@ public void AzureCosmosDBHasCorrectConnectionStrings_ForAccountEndpoint() Assert.Equal("AccountEndpoint={cosmos.outputs.connectionString};Database=db1;Container=container1", container1.Resource.ConnectionStringExpression.ValueExpression); } - [Fact] - public void AzureCosmosDBHasCorrectConnectionStrings() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AzureCosmosDBHasCorrectConnectionStrings(bool useAccessKeyAuth) { using var builder = TestDistributedApplicationBuilder.Create(); var cosmos = builder.AddAzureCosmosDB("cosmos").RunAsEmulator(); + if (useAccessKeyAuth) + { + cosmos.WithAccessKeyAuthentication(); + } var db1 = cosmos.AddCosmosDatabase("db1"); var container1 = db1.AddContainer("container1", "id"); var cosmos1 = builder.AddAzureCosmosDB("cosmos1").RunAsEmulator(); + if (useAccessKeyAuth) + { + cosmos1.WithAccessKeyAuthentication(); + } var db2 = cosmos1.AddCosmosDatabase("db2", "db"); var container2 = db2.AddContainer("container2", "id", "container"); diff --git a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs index 222610e83f0..cc011572937 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzurePostgresExtensionsTests.cs @@ -146,13 +146,17 @@ param principalName string } [Fact] - public void AddAzurePostgresFlexibleServer_WithPasswordAuthentication_NoKeyVaultWithContainer() + public async Task AddAzurePostgresFlexibleServer_WithPasswordAuthentication_NoKeyVaultWithContainer() { using var builder = TestDistributedApplicationBuilder.Create(); builder.AddAzurePostgresFlexibleServer("pg").WithPasswordAuthentication().RunAsContainer(); - Assert.Empty(builder.Resources.OfType()); + var app = builder.Build(); + var model = app.Services.GetRequiredService(); + await ExecuteBeforeStartHooksAsync(app, CancellationToken.None); + + Assert.Empty(model.Resources.OfType()); } [Theory] diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs index 0e3dfab0fff..bc3d6481fda 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs @@ -100,13 +100,17 @@ param principalName string } [Fact] - public void AddAzureRedis_WithAccessKeyAuthentication_NoKeyVaultWithContainer() + public async Task AddAzureRedis_WithAccessKeyAuthentication_NoKeyVaultWithContainer() { using var builder = TestDistributedApplicationBuilder.Create(); builder.AddAzureRedis("redis").WithAccessKeyAuthentication().RunAsContainer(); - Assert.Empty(builder.Resources.OfType()); + var app = builder.Build(); + var model = app.Services.GetRequiredService(); + await ExecuteBeforeStartHooksAsync(app, CancellationToken.None); + + Assert.Empty(model.Resources.OfType()); } [Theory] From 67bca475d98d2645f35b37a10a551ec31bfb1dbf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 1 Apr 2025 23:41:23 -0700 Subject: [PATCH 4/5] Add Microsoft.Extensions.DependencyInjection using statement --- src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 555e337ae76..7dce50d81c9 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -7,6 +7,7 @@ using Azure.Provisioning.Expressions; using Azure.Provisioning.KeyVault; using Azure.Provisioning.Redis; +using Microsoft.Extensions.DependencyInjection; using CdkRedisResource = Azure.Provisioning.Redis.RedisResource; using RedisResource = Aspire.Hosting.ApplicationModel.RedisResource; From af954677ed05205092cb8dcf1da571071475d46f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 2 Apr 2025 09:59:37 -0500 Subject: [PATCH 5/5] Respond to PR feedback --- .../AzureCosmosDBExtensions.cs | 18 ++++++++++-------- .../AzurePostgresExtensions.cs | 19 ++++++++++--------- .../AzureRedisExtensions.cs | 19 ++++++++++--------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 87fc49b3298..67c092d0413 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -342,17 +342,19 @@ public static IResourceBuilder WithAccessKeyAuthenticatio var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); - // remove the KeyVault from the model if the emulator is used. + // remove the KeyVault from the model if the emulator is used during run mode. // need to do this later in case builder becomes an emulator after this method is called. - builder.ApplicationBuilder.Eventing.Subscribe((data, _) => + if (builder.ApplicationBuilder.ExecutionContext.IsRunMode) { - var executionContext = data.Services.GetRequiredService(); - if (executionContext.IsRunMode && builder.Resource.IsEmulator) + builder.ApplicationBuilder.Eventing.Subscribe((data, _) => { - data.Model.Resources.Remove(kv.Resource); - } - return Task.CompletedTask; - }); + if (builder.Resource.IsEmulator) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + } return builder.WithAccessKeyAuthentication(kv); } diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index 57d731258f3..7f147620a2b 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -8,7 +8,6 @@ using Azure.Provisioning.Expressions; using Azure.Provisioning.KeyVault; using Azure.Provisioning.PostgreSql; -using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting; @@ -290,17 +289,19 @@ public static IResourceBuilder WithPassword var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); - // remove the KeyVault from the model if the emulator is used. + // remove the KeyVault from the model if the emulator is used during run mode. // need to do this later in case builder becomes an emulator after this method is called. - builder.ApplicationBuilder.Eventing.Subscribe((data, token) => + if (builder.ApplicationBuilder.ExecutionContext.IsRunMode) { - var executionContext = data.Services.GetRequiredService(); - if (executionContext.IsRunMode && builder.Resource.IsContainer()) + builder.ApplicationBuilder.Eventing.Subscribe((data, token) => { - data.Model.Resources.Remove(kv.Resource); - } - return Task.CompletedTask; - }); + if (builder.Resource.IsContainer()) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + } return builder.WithPasswordAuthentication(kv, userName, password); } diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 7dce50d81c9..219fb258009 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -7,7 +7,6 @@ using Azure.Provisioning.Expressions; using Azure.Provisioning.KeyVault; using Azure.Provisioning.Redis; -using Microsoft.Extensions.DependencyInjection; using CdkRedisResource = Azure.Provisioning.Redis.RedisResource; using RedisResource = Aspire.Hosting.ApplicationModel.RedisResource; @@ -196,17 +195,19 @@ public static IResourceBuilder WithAccessKeyAuthenticat var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv") .WithParentRelationship(builder.Resource); - // remove the KeyVault from the model if the emulator is used. + // remove the KeyVault from the model if the emulator is used during run mode. // need to do this later in case builder becomes an emulator after this method is called. - builder.ApplicationBuilder.Eventing.Subscribe((data, token) => + if (builder.ApplicationBuilder.ExecutionContext.IsRunMode) { - var executionContext = data.Services.GetRequiredService(); - if (executionContext.IsRunMode && builder.Resource.IsContainer()) + builder.ApplicationBuilder.Eventing.Subscribe((data, token) => { - data.Model.Resources.Remove(kv.Resource); - } - return Task.CompletedTask; - }); + if (builder.Resource.IsContainer()) + { + data.Model.Resources.Remove(kv.Resource); + } + return Task.CompletedTask; + }); + } return builder.WithAccessKeyAuthentication(kv); }