Skip to content

Commit 0ec69cb

Browse files
authored
AddAzureContainerAppEnvironment should use the environment name as a prefix (#8606)
* AddAzureContainerAppEnvironment should use the environment name as a prefix We should be prefixing the resources created via AddAzureContainerAppEnvironment with the environment name. This will allow for multiple environments to be in a single distributed application in the future. With this change, it means deploying to existing environments will duplicate resources. To solve that, add a new method WithAzdResourceNaming, which will revert the resource names back to the previous naming scheme. * Revert local testing changes * Ensure resourceToken is declared before it is used in bicep * Support volume names
1 parent 494f86c commit 0ec69cb

File tree

7 files changed

+434
-197
lines changed

7 files changed

+434
-197
lines changed

playground/AzureContainerApps/AzureContainerApps.AppHost/infra.module.bicep

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,33 @@ param userPrincipalId string
55

66
param tags object = { }
77

8-
resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
9-
name: take('mi-${uniqueString(resourceGroup().id)}', 128)
8+
resource infra_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
9+
name: take('infra_mi-${uniqueString(resourceGroup().id)}', 128)
1010
location: location
1111
tags: tags
1212
}
1313

14-
resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
15-
name: take('acr${uniqueString(resourceGroup().id)}', 50)
14+
resource infra_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
15+
name: take('infraacr${uniqueString(resourceGroup().id)}', 50)
1616
location: location
1717
sku: {
1818
name: 'Basic'
1919
}
2020
tags: tags
2121
}
2222

23-
resource acr_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
24-
name: guid(acr.id, mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
23+
resource infra_acr_infra_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
24+
name: guid(infra_acr.id, infra_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
2525
properties: {
26-
principalId: mi.properties.principalId
26+
principalId: infra_mi.properties.principalId
2727
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
2828
principalType: 'ServicePrincipal'
2929
}
30-
scope: acr
30+
scope: infra_acr
3131
}
3232

33-
resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
34-
name: take('law-${uniqueString(resourceGroup().id)}', 63)
33+
resource infra_law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
34+
name: take('infralaw-${uniqueString(resourceGroup().id)}', 63)
3535
location: location
3636
properties: {
3737
sku: {
@@ -41,15 +41,15 @@ resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
4141
tags: tags
4242
}
4343

44-
resource cae 'Microsoft.App/managedEnvironments@2024-03-01' = {
45-
name: take('cae${uniqueString(resourceGroup().id)}', 24)
44+
resource infra 'Microsoft.App/managedEnvironments@2024-03-01' = {
45+
name: take('infra${uniqueString(resourceGroup().id)}', 24)
4646
location: location
4747
properties: {
4848
appLogsConfiguration: {
4949
destination: 'log-analytics'
5050
logAnalyticsConfiguration: {
51-
customerId: law.properties.customerId
52-
sharedKey: law.listKeys().primarySharedKey
51+
customerId: infra_law.properties.customerId
52+
sharedKey: infra_law.listKeys().primarySharedKey
5353
}
5454
}
5555
workloadProfiles: [
@@ -67,20 +67,20 @@ resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@202
6767
properties: {
6868
componentType: 'AspireDashboard'
6969
}
70-
parent: cae
70+
parent: infra
7171
}
7272

73-
resource cae_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
74-
name: guid(cae.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))
73+
resource infra_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
74+
name: guid(infra.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))
7575
properties: {
7676
principalId: userPrincipalId
7777
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
7878
}
79-
scope: cae
79+
scope: infra
8080
}
8181

82-
resource storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = {
83-
name: take('storagevolume${uniqueString(resourceGroup().id)}', 24)
82+
resource infra_storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = {
83+
name: take('infrastoragevolume${uniqueString(resourceGroup().id)}', 24)
8484
kind: 'StorageV2'
8585
location: location
8686
sku: {
@@ -94,7 +94,7 @@ resource storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = {
9494

9595
resource storageVolumeFileService 'Microsoft.Storage/storageAccounts/fileServices@2024-01-01' = {
9696
name: 'default'
97-
parent: storageVolume
97+
parent: infra_storageVolume
9898
}
9999

100100
resource shares_volumes_cache_0 'Microsoft.Storage/storageAccounts/fileServices/shares@2024-01-01' = {
@@ -110,33 +110,33 @@ resource managedStorage_volumes_cache_0 'Microsoft.App/managedEnvironments/stora
110110
name: take('managedstoragevolumescache${uniqueString(resourceGroup().id)}', 24)
111111
properties: {
112112
azureFile: {
113-
accountName: storageVolume.name
114-
accountKey: storageVolume.listKeys().keys[0].value
113+
accountName: infra_storageVolume.name
114+
accountKey: infra_storageVolume.listKeys().keys[0].value
115115
accessMode: 'ReadWrite'
116116
shareName: shares_volumes_cache_0.name
117117
}
118118
}
119-
parent: cae
119+
parent: infra
120120
}
121121

122122
output volumes_cache_0 string = managedStorage_volumes_cache_0.name
123123

124-
output MANAGED_IDENTITY_NAME string = mi.name
124+
output MANAGED_IDENTITY_NAME string = infra_mi.name
125125

126-
output MANAGED_IDENTITY_PRINCIPAL_ID string = mi.properties.principalId
126+
output MANAGED_IDENTITY_PRINCIPAL_ID string = infra_mi.properties.principalId
127127

128-
output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = law.name
128+
output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = infra_law.name
129129

130-
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = law.id
130+
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = infra_law.id
131131

132-
output AZURE_CONTAINER_REGISTRY_NAME string = acr.name
132+
output AZURE_CONTAINER_REGISTRY_NAME string = infra_acr.name
133133

134-
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = acr.properties.loginServer
134+
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = infra_acr.properties.loginServer
135135

136-
output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = mi.id
136+
output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = infra_mi.id
137137

138-
output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = cae.name
138+
output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = infra.name
139139

140-
output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = cae.id
140+
output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = infra.id
141141

142-
output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = cae.properties.defaultDomain
142+
output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = infra.properties.defaultDomain

src/Aspire.Hosting.Azure.AppContainers/AzdAzureContainerAppEnvironment.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepResource re
3333
return SecretOutputExpression.GetSecretOutputKeyVault(resource);
3434
}
3535

36-
public IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, string volumeIndex)
36+
public IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountAnnotation volume, int volumeIndex)
3737
{
38-
return VolumeStorageExpression.GetVolumeStorage(resource, type, volumeIndex);
38+
return VolumeStorageExpression.GetVolumeStorage(resource, volume.Type, volumeIndex);
3939
}
4040

4141
/// <summary>
@@ -73,7 +73,7 @@ public static IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepReso
7373
/// <summary>
7474
/// Generates expressions for the volume storage account. That azd creates.
7575
/// </summary>
76-
private sealed class VolumeStorageExpression(IResource resource, ContainerMountType type, string index) : IManifestExpressionProvider
76+
private sealed class VolumeStorageExpression(IResource resource, ContainerMountType type, int index) : IManifestExpressionProvider
7777
{
7878
public string ValueExpression => type switch
7979
{
@@ -82,7 +82,7 @@ private sealed class VolumeStorageExpression(IResource resource, ContainerMountT
8282
_ => throw new NotSupportedException()
8383
};
8484

85-
public static IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, string index) =>
85+
public static IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, int index) =>
8686
new VolumeStorageExpression(resource, type, index);
8787
}
8888
}

src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ namespace Aspire.Hosting.Azure.AppContainers;
1313
public class AzureContainerAppEnvironmentResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure) :
1414
AzureProvisioningResource(name, configureInfrastructure), IAzureContainerAppEnvironment
1515
{
16+
internal bool UseAzdNamingConvention { get; set; }
17+
1618
/// <summary>
1719
/// Gets the unique identifier of the Container App Environment.
1820
/// </summary>
@@ -53,7 +55,7 @@ public class AzureContainerAppEnvironmentResource(string name, Action<AzureResou
5355
/// </summary>
5456
private BicepOutputReference ContainerAppEnvironmentName => new("AZURE_CONTAINER_APPS_ENVIRONMENT_NAME", this);
5557

56-
internal Dictionary<string, BicepOutputReference> VolumeNames { get; } = [];
58+
internal Dictionary<string, (IResource resource, ContainerMountAnnotation volume, int index, BicepOutputReference outputReference)> VolumeNames { get; } = [];
5759

5860
IManifestExpressionProvider IAzureContainerAppEnvironment.ContainerAppEnvironmentId => ContainerAppEnvironmentId;
5961

@@ -76,18 +78,18 @@ IManifestExpressionProvider IAzureContainerAppEnvironment.GetSecretOutputKeyVaul
7678
throw new NotSupportedException("Automatic Key vault generation is not supported in this environment. Please create a key vault resource directly.");
7779
}
7880

79-
IManifestExpressionProvider IAzureContainerAppEnvironment.GetVolumeStorage(IResource resource, ContainerMountType type, string volumeIndex)
81+
IManifestExpressionProvider IAzureContainerAppEnvironment.GetVolumeStorage(IResource resource, ContainerMountAnnotation volume, int volumeIndex)
8082
{
8183
// REVIEW: Should we use the same naming algorithm as azd?
8284
var outputName = $"volumes_{resource.Name}_{volumeIndex}";
8385

84-
if (!VolumeNames.TryGetValue(outputName, out var outputReference))
86+
if (!VolumeNames.TryGetValue(outputName, out var volumeName))
8587
{
86-
outputReference = new BicepOutputReference(outputName, this);
88+
volumeName = (resource, volume, volumeIndex, new BicepOutputReference(outputName, this));
8789

88-
VolumeNames[outputName] = outputReference;
90+
VolumeNames[outputName] = volumeName;
8991
}
9092

91-
return outputReference;
93+
return volumeName.outputReference;
9294
}
9395
}

src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using Aspire.Hosting.ApplicationModel;
56
using Aspire.Hosting.Azure;
67
using Aspire.Hosting.Azure.AppContainers;
@@ -64,6 +65,7 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
6465

6566
var containerAppEnvResource = new AzureContainerAppEnvironmentResource(name, static infra =>
6667
{
68+
var appEnvResource = (AzureContainerAppEnvironmentResource)infra.AspireResource;
6769
var userPrincipalId = new ProvisioningParameter("userPrincipalId", typeof(string));
6870

6971
infra.Add(userPrincipalId);
@@ -75,14 +77,24 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
7577

7678
infra.Add(tags);
7779

78-
var identity = new UserAssignedIdentity("mi")
80+
ProvisioningVariable? resourceToken = null;
81+
if (appEnvResource.UseAzdNamingConvention)
82+
{
83+
resourceToken = new ProvisioningVariable("resourceToken", typeof(string))
84+
{
85+
Value = BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id)
86+
};
87+
infra.Add(resourceToken);
88+
}
89+
90+
var identity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_mi"))
7991
{
8092
Tags = tags
8193
};
8294

8395
infra.Add(identity);
8496

85-
var containerRegistry = new ContainerRegistryService("acr")
97+
var containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_acr"))
8698
{
8799
Sku = new() { Name = ContainerRegistrySkuName.Basic },
88100
Tags = tags
@@ -96,15 +108,15 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
96108
pullRa.Name = BicepFunction.CreateGuid(containerRegistry.Id, identity.Id, pullRa.RoleDefinitionId);
97109
infra.Add(pullRa);
98110

99-
var laWorkspace = new OperationalInsightsWorkspace("law")
111+
var laWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_law"))
100112
{
101113
Sku = new() { Name = OperationalInsightsWorkspaceSkuName.PerGB2018 },
102114
Tags = tags
103115
};
104116

105117
infra.Add(laWorkspace);
106118

107-
var containerAppEnvironment = new ContainerAppManagedEnvironment("cae")
119+
var containerAppEnvironment = new ContainerAppManagedEnvironment(appEnvResource.GetBicepIdentifier())
108120
{
109121
WorkloadProfiles = [
110122
new ContainerAppWorkloadProfile()
@@ -149,9 +161,10 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
149161

150162
var resource = (AzureContainerAppEnvironmentResource)infra.AspireResource;
151163

164+
StorageAccount? storageVolume = null;
152165
if (resource.VolumeNames.Count > 0)
153166
{
154-
var storageVolume = new StorageAccount("storageVolume")
167+
storageVolume = new StorageAccount(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_storageVolume"))
155168
{
156169
Tags = tags,
157170
Sku = new StorageSku() { Name = StorageSkuName.StandardLrs },
@@ -200,6 +213,26 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
200213
infra.Add(containerAppStorage);
201214

202215
managedStorages[outputName] = containerAppStorage;
216+
217+
if (appEnvResource.UseAzdNamingConvention)
218+
{
219+
var volumeName = output.volume.Type switch
220+
{
221+
ContainerMountType.BindMount => $"bm{output.index}",
222+
ContainerMountType.Volume => output.volume.Source ?? $"v{output.index}",
223+
_ => throw new NotSupportedException()
224+
};
225+
226+
share.Name = BicepFunction.Take(
227+
BicepFunction.Interpolate(
228+
$"{BicepFunction.ToLower(output.resource.Name)}-{BicepFunction.ToLower(volumeName)}"),
229+
60);
230+
231+
containerAppStorage.Name = BicepFunction.Take(
232+
BicepFunction.Interpolate(
233+
$"{BicepFunction.ToLower(output.resource.Name)}-{BicepFunction.ToLower(volumeName)}"),
234+
32);
235+
}
203236
}
204237
}
205238

@@ -208,10 +241,34 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
208241
{
209242
infra.Add(new ProvisioningOutput(key, typeof(string))
210243
{
211-
Value = value.Name
244+
// use an expression here in case the resource's Name was set to a function expression above
245+
Value = new MemberExpression(new IdentifierExpression(value.BicepIdentifier), "name")
212246
});
213247
}
214248

249+
if (appEnvResource.UseAzdNamingConvention)
250+
{
251+
Debug.Assert(resourceToken is not null);
252+
253+
identity.Name = BicepFunction.Interpolate($"mi-{resourceToken}");
254+
containerRegistry.Name = new FunctionCallExpression(
255+
new IdentifierExpression("replace"),
256+
new InterpolatedStringExpression(
257+
[
258+
new StringLiteralExpression("acr-"),
259+
new IdentifierExpression(resourceToken.BicepIdentifier)
260+
]),
261+
new StringLiteralExpression("-"),
262+
new StringLiteralExpression(""));
263+
laWorkspace.Name = BicepFunction.Interpolate($"law-{resourceToken}");
264+
containerAppEnvironment.Name = BicepFunction.Interpolate($"cae-{resourceToken}");
265+
266+
if (storageVolume is not null)
267+
{
268+
storageVolume.Name = BicepFunction.Interpolate($"vol{resourceToken}");
269+
}
270+
}
271+
215272
infra.Add(new ProvisioningOutput("MANAGED_IDENTITY_NAME", typeof(string))
216273
{
217274
Value = identity.Name
@@ -272,4 +329,21 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
272329

273330
return builder.AddResource(containerAppEnvResource);
274331
}
332+
333+
/// <summary>
334+
/// Configures the container app environment resources to use the same naming conventions as azd.
335+
/// </summary>
336+
/// <param name="builder">The AzureContainerAppEnvironmentResource to configure.</param>
337+
/// <returns><see cref="IResourceBuilder{T}"/></returns>
338+
/// <remarks>
339+
/// By default, the container app environment resources use a different naming convention than azd.
340+
///
341+
/// This method allows for reusing the previously deployed resources if the application was deployed using
342+
/// azd without calling <see cref="AddAzureContainerAppEnvironment"/>
343+
/// </remarks>
344+
public static IResourceBuilder<AzureContainerAppEnvironmentResource> WithAzdResourceNaming(this IResourceBuilder<AzureContainerAppEnvironmentResource> builder)
345+
{
346+
builder.Resource.UseAzdNamingConvention = true;
347+
return builder;
348+
}
275349
}

0 commit comments

Comments
 (0)