Skip to content

Commit 85cef9b

Browse files
authored
Added WithEnvironment overload supporting Keyvault secret refs (#8821)
* Added WithEnvironment overload supporting Keyvault refs - Added tests - Moved KV tests to a new file * [test] Enhance WithEnvironment_AddsKeyVaultSecretReference to validate environment variables for both Run and Publish modes
1 parent efe7a39 commit 85cef9b

File tree

3 files changed

+186
-123
lines changed

3 files changed

+186
-123
lines changed

src/Aspire.Hosting.Azure/AzureBicepResourceExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@ public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> bu
102102
});
103103
}
104104

105+
/// <summary>
106+
/// Adds an environment variable to the resource with the value of the key vault secret.
107+
/// </summary>
108+
/// <typeparam name="T">The resource type.</typeparam>
109+
/// <param name="builder">The resource builder.</param>
110+
/// <param name="name">The name of the environment variable.</param>
111+
/// <param name="secretReference">The reference to the key vault secret.</param>
112+
/// <returns>An <see cref="IResourceBuilder{T}"/>.</returns>
113+
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, string name, IAzureKeyVaultSecretReference secretReference)
114+
where T : IResourceWithEnvironment
115+
{
116+
return builder.WithEnvironment(ctx =>
117+
{
118+
ctx.EnvironmentVariables[name] = secretReference;
119+
});
120+
}
121+
105122
/// <summary>
106123
/// Adds a parameter to the bicep template.
107124
/// </summary>

tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,129 +1275,6 @@ param keyVaultName string
12751275
Assert.Equal(expectedBicep, manifest.BicepText);
12761276
}
12771277

1278-
[Fact]
1279-
public async Task AddKeyVaultViaRunMode()
1280-
{
1281-
using var builder = TestDistributedApplicationBuilder.Create();
1282-
1283-
var mykv = builder.AddAzureKeyVault("mykv");
1284-
1285-
var manifest = await AzureManifestUtils.GetManifestWithBicep(mykv.Resource);
1286-
1287-
var expectedManifest = """
1288-
{
1289-
"type": "azure.bicep.v0",
1290-
"connectionString": "{mykv.outputs.vaultUri}",
1291-
"path": "mykv.module.bicep"
1292-
}
1293-
""";
1294-
Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
1295-
1296-
var expectedBicep = """
1297-
@description('The location for the resource(s) to be deployed.')
1298-
param location string = resourceGroup().location
1299-
1300-
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' = {
1301-
name: take('mykv-${uniqueString(resourceGroup().id)}', 24)
1302-
location: location
1303-
properties: {
1304-
tenantId: tenant().tenantId
1305-
sku: {
1306-
family: 'A'
1307-
name: 'standard'
1308-
}
1309-
enableRbacAuthorization: true
1310-
}
1311-
tags: {
1312-
'aspire-resource-name': 'mykv'
1313-
}
1314-
}
1315-
1316-
output vaultUri string = mykv.properties.vaultUri
1317-
1318-
output name string = mykv.name
1319-
""";
1320-
output.WriteLine(manifest.BicepText);
1321-
Assert.Equal(expectedBicep, manifest.BicepText);
1322-
}
1323-
1324-
[Fact]
1325-
public async Task AddKeyVaultViaPublishMode()
1326-
{
1327-
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
1328-
1329-
var mykv = builder.AddAzureKeyVault("mykv");
1330-
1331-
using var app = builder.Build();
1332-
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
1333-
var manifest = await GetManifestWithBicep(model, mykv.Resource);
1334-
1335-
var expectedManifest = """
1336-
{
1337-
"type": "azure.bicep.v0",
1338-
"connectionString": "{mykv.outputs.vaultUri}",
1339-
"path": "mykv.module.bicep"
1340-
}
1341-
""";
1342-
Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
1343-
1344-
var expectedBicep = """
1345-
@description('The location for the resource(s) to be deployed.')
1346-
param location string = resourceGroup().location
1347-
1348-
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' = {
1349-
name: take('mykv-${uniqueString(resourceGroup().id)}', 24)
1350-
location: location
1351-
properties: {
1352-
tenantId: tenant().tenantId
1353-
sku: {
1354-
family: 'A'
1355-
name: 'standard'
1356-
}
1357-
enableRbacAuthorization: true
1358-
}
1359-
tags: {
1360-
'aspire-resource-name': 'mykv'
1361-
}
1362-
}
1363-
1364-
output vaultUri string = mykv.properties.vaultUri
1365-
1366-
output name string = mykv.name
1367-
""";
1368-
output.WriteLine(manifest.BicepText);
1369-
Assert.Equal(expectedBicep, manifest.BicepText);
1370-
1371-
var kvRoles = Assert.Single(model.Resources.OfType<AzureProvisioningResource>().Where(r => r.Name == $"mykv-roles"));
1372-
var kvRolesManifest = await GetManifestWithBicep(kvRoles, skipPreparer: true);
1373-
expectedBicep = """
1374-
@description('The location for the resource(s) to be deployed.')
1375-
param location string = resourceGroup().location
1376-
1377-
param mykv_outputs_name string
1378-
1379-
param principalType string
1380-
1381-
param principalId string
1382-
1383-
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
1384-
name: mykv_outputs_name
1385-
}
1386-
1387-
resource mykv_KeyVaultSecretsUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
1388-
name: guid(mykv.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6'))
1389-
properties: {
1390-
principalId: principalId
1391-
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
1392-
principalType: principalType
1393-
}
1394-
scope: mykv
1395-
}
1396-
""";
1397-
output.WriteLine(kvRolesManifest.BicepText);
1398-
Assert.Equal(expectedBicep, kvRolesManifest.BicepText);
1399-
}
1400-
14011278
[Fact]
14021279
public async Task AsAzureSqlDatabaseViaRunMode()
14031280
{
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.ApplicationModel;
5+
using Aspire.Hosting.Utils;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Xunit;
8+
9+
namespace Aspire.Hosting.Azure.Tests;
10+
11+
public class AzureKeyVaultTests(ITestOutputHelper output)
12+
{
13+
[Fact]
14+
public async Task AddKeyVaultViaRunMode()
15+
{
16+
using var builder = TestDistributedApplicationBuilder.Create();
17+
18+
var mykv = builder.AddAzureKeyVault("mykv");
19+
20+
var manifest = await AzureManifestUtils.GetManifestWithBicep(mykv.Resource);
21+
22+
var expectedManifest = """
23+
{
24+
"type": "azure.bicep.v0",
25+
"connectionString": "{mykv.outputs.vaultUri}",
26+
"path": "mykv.module.bicep"
27+
}
28+
""";
29+
Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
30+
31+
var expectedBicep = """
32+
@description('The location for the resource(s) to be deployed.')
33+
param location string = resourceGroup().location
34+
35+
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' = {
36+
name: take('mykv-${uniqueString(resourceGroup().id)}', 24)
37+
location: location
38+
properties: {
39+
tenantId: tenant().tenantId
40+
sku: {
41+
family: 'A'
42+
name: 'standard'
43+
}
44+
enableRbacAuthorization: true
45+
}
46+
tags: {
47+
'aspire-resource-name': 'mykv'
48+
}
49+
}
50+
51+
output vaultUri string = mykv.properties.vaultUri
52+
53+
output name string = mykv.name
54+
""";
55+
output.WriteLine(manifest.BicepText);
56+
Assert.Equal(expectedBicep, manifest.BicepText);
57+
}
58+
59+
[Fact]
60+
public async Task AddKeyVaultViaPublishMode()
61+
{
62+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
63+
64+
var mykv = builder.AddAzureKeyVault("mykv");
65+
66+
using var app = builder.Build();
67+
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
68+
var manifest = await AzureManifestUtils.GetManifestWithBicep(model, mykv.Resource);
69+
70+
var expectedManifest = """
71+
{
72+
"type": "azure.bicep.v0",
73+
"connectionString": "{mykv.outputs.vaultUri}",
74+
"path": "mykv.module.bicep"
75+
}
76+
""";
77+
Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
78+
79+
var expectedBicep = """
80+
@description('The location for the resource(s) to be deployed.')
81+
param location string = resourceGroup().location
82+
83+
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' = {
84+
name: take('mykv-${uniqueString(resourceGroup().id)}', 24)
85+
location: location
86+
properties: {
87+
tenantId: tenant().tenantId
88+
sku: {
89+
family: 'A'
90+
name: 'standard'
91+
}
92+
enableRbacAuthorization: true
93+
}
94+
tags: {
95+
'aspire-resource-name': 'mykv'
96+
}
97+
}
98+
99+
output vaultUri string = mykv.properties.vaultUri
100+
101+
output name string = mykv.name
102+
""";
103+
output.WriteLine(manifest.BicepText);
104+
Assert.Equal(expectedBicep, manifest.BicepText);
105+
106+
var kvRoles = Assert.Single(model.Resources.OfType<AzureProvisioningResource>().Where(r => r.Name == $"mykv-roles"));
107+
var kvRolesManifest = await AzureManifestUtils.GetManifestWithBicep(kvRoles, skipPreparer: true);
108+
expectedBicep = """
109+
@description('The location for the resource(s) to be deployed.')
110+
param location string = resourceGroup().location
111+
112+
param mykv_outputs_name string
113+
114+
param principalType string
115+
116+
param principalId string
117+
118+
resource mykv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
119+
name: mykv_outputs_name
120+
}
121+
122+
resource mykv_KeyVaultSecretsUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
123+
name: guid(mykv.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6'))
124+
properties: {
125+
principalId: principalId
126+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
127+
principalType: principalType
128+
}
129+
scope: mykv
130+
}
131+
""";
132+
output.WriteLine(kvRolesManifest.BicepText);
133+
Assert.Equal(expectedBicep, kvRolesManifest.BicepText);
134+
}
135+
136+
[Fact]
137+
public async Task WithEnvironment_AddsKeyVaultSecretReference()
138+
{
139+
// Arrange: Create a test application builder.
140+
using var builder = TestDistributedApplicationBuilder.Create();
141+
142+
// Add a key vault resource.
143+
var kv = builder.AddAzureKeyVault("myKeyVault");
144+
145+
kv.Resource.SecretResolver = (s, ct) =>
146+
{
147+
return Task.FromResult<string?>("my secret value");
148+
};
149+
150+
// Get a secret reference from the key vault resource.
151+
var secretReference = kv.Resource.GetSecret("mySecret");
152+
153+
// Add a container resource that supports environment variables.
154+
var containerBuilder = builder.AddContainer("myContainer", "nginx")
155+
.WithEnvironment("MY_SECRET", secretReference);
156+
157+
var runEnv = await containerBuilder.Resource.GetEnvironmentVariableValuesAsync(DistributedApplicationOperation.Run);
158+
var publishEnv = await containerBuilder.Resource.GetEnvironmentVariableValuesAsync(DistributedApplicationOperation.Publish);
159+
160+
var runKvp = Assert.Single(runEnv);
161+
var pubishKvp = Assert.Single(publishEnv);
162+
163+
Assert.Equal("MY_SECRET", runKvp.Key);
164+
Assert.Same("my secret value", runKvp.Value);
165+
166+
Assert.Equal("MY_SECRET", pubishKvp.Key);
167+
Assert.Equal("{myKeyVault.secrets.mySecret}", pubishKvp.Value);
168+
}
169+
}

0 commit comments

Comments
 (0)