diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/infra.module.bicep b/playground/AzureContainerApps/AzureContainerApps.AppHost/infra.module.bicep index cceee108f0b..f3755339344 100644 --- a/playground/AzureContainerApps/AzureContainerApps.AppHost/infra.module.bicep +++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/infra.module.bicep @@ -5,14 +5,14 @@ param userPrincipalId string param tags object = { } -resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: take('mi-${uniqueString(resourceGroup().id)}', 128) +resource infra_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: take('infra_mi-${uniqueString(resourceGroup().id)}', 128) location: location tags: tags } -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { - name: take('acr${uniqueString(resourceGroup().id)}', 50) +resource infra_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: take('infraacr${uniqueString(resourceGroup().id)}', 50) location: location sku: { name: 'Basic' @@ -20,18 +20,18 @@ resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { tags: tags } -resource acr_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(acr.id, mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) +resource infra_acr_infra_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(infra_acr.id, infra_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) properties: { - principalId: mi.properties.principalId + principalId: infra_mi.properties.principalId roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') principalType: 'ServicePrincipal' } - scope: acr + scope: infra_acr } -resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { - name: take('law-${uniqueString(resourceGroup().id)}', 63) +resource infra_law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { + name: take('infralaw-${uniqueString(resourceGroup().id)}', 63) location: location properties: { sku: { @@ -41,15 +41,15 @@ resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { tags: tags } -resource cae 'Microsoft.App/managedEnvironments@2024-03-01' = { - name: take('cae${uniqueString(resourceGroup().id)}', 24) +resource infra 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: take('infra${uniqueString(resourceGroup().id)}', 24) location: location properties: { appLogsConfiguration: { destination: 'log-analytics' logAnalyticsConfiguration: { - customerId: law.properties.customerId - sharedKey: law.listKeys().primarySharedKey + customerId: infra_law.properties.customerId + sharedKey: infra_law.listKeys().primarySharedKey } } workloadProfiles: [ @@ -67,20 +67,20 @@ resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@202 properties: { componentType: 'AspireDashboard' } - parent: cae + parent: infra } -resource cae_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(cae.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')) +resource infra_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(infra.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')) properties: { principalId: userPrincipalId roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') } - scope: cae + scope: infra } -resource storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { - name: take('storagevolume${uniqueString(resourceGroup().id)}', 24) +resource infra_storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { + name: take('infrastoragevolume${uniqueString(resourceGroup().id)}', 24) kind: 'StorageV2' location: location sku: { @@ -94,7 +94,7 @@ resource storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { resource storageVolumeFileService 'Microsoft.Storage/storageAccounts/fileServices@2024-01-01' = { name: 'default' - parent: storageVolume + parent: infra_storageVolume } 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 name: take('managedstoragevolumescache${uniqueString(resourceGroup().id)}', 24) properties: { azureFile: { - accountName: storageVolume.name - accountKey: storageVolume.listKeys().keys[0].value + accountName: infra_storageVolume.name + accountKey: infra_storageVolume.listKeys().keys[0].value accessMode: 'ReadWrite' shareName: shares_volumes_cache_0.name } } - parent: cae + parent: infra } output volumes_cache_0 string = managedStorage_volumes_cache_0.name -output MANAGED_IDENTITY_NAME string = mi.name +output MANAGED_IDENTITY_NAME string = infra_mi.name -output MANAGED_IDENTITY_PRINCIPAL_ID string = mi.properties.principalId +output MANAGED_IDENTITY_PRINCIPAL_ID string = infra_mi.properties.principalId -output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = law.name +output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = infra_law.name -output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = law.id +output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = infra_law.id -output AZURE_CONTAINER_REGISTRY_NAME string = acr.name +output AZURE_CONTAINER_REGISTRY_NAME string = infra_acr.name -output AZURE_CONTAINER_REGISTRY_ENDPOINT string = acr.properties.loginServer +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = infra_acr.properties.loginServer -output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = mi.id +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = infra_mi.id -output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = cae.name +output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = infra.name -output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = cae.id +output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = infra.id -output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = cae.properties.defaultDomain \ No newline at end of file +output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = infra.properties.defaultDomain \ No newline at end of file diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzdAzureContainerAppEnvironment.cs b/src/Aspire.Hosting.Azure.AppContainers/AzdAzureContainerAppEnvironment.cs index 86b5001fdb6..23a69108406 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/AzdAzureContainerAppEnvironment.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/AzdAzureContainerAppEnvironment.cs @@ -33,9 +33,9 @@ public IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepResource re return SecretOutputExpression.GetSecretOutputKeyVault(resource); } - public IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, string volumeIndex) + public IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountAnnotation volume, int volumeIndex) { - return VolumeStorageExpression.GetVolumeStorage(resource, type, volumeIndex); + return VolumeStorageExpression.GetVolumeStorage(resource, volume.Type, volumeIndex); } /// @@ -73,7 +73,7 @@ public static IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepReso /// /// Generates expressions for the volume storage account. That azd creates. /// - private sealed class VolumeStorageExpression(IResource resource, ContainerMountType type, string index) : IManifestExpressionProvider + private sealed class VolumeStorageExpression(IResource resource, ContainerMountType type, int index) : IManifestExpressionProvider { public string ValueExpression => type switch { @@ -82,7 +82,7 @@ private sealed class VolumeStorageExpression(IResource resource, ContainerMountT _ => throw new NotSupportedException() }; - public static IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, string index) => + public static IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, int index) => new VolumeStorageExpression(resource, type, index); } } diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs index 276cce3e9f6..06336ef87ab 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs @@ -13,6 +13,8 @@ namespace Aspire.Hosting.Azure.AppContainers; public class AzureContainerAppEnvironmentResource(string name, Action configureInfrastructure) : AzureProvisioningResource(name, configureInfrastructure), IAzureContainerAppEnvironment { + internal bool UseAzdNamingConvention { get; set; } + /// /// Gets the unique identifier of the Container App Environment. /// @@ -53,7 +55,7 @@ public class AzureContainerAppEnvironmentResource(string name, Action private BicepOutputReference ContainerAppEnvironmentName => new("AZURE_CONTAINER_APPS_ENVIRONMENT_NAME", this); - internal Dictionary VolumeNames { get; } = []; + internal Dictionary VolumeNames { get; } = []; IManifestExpressionProvider IAzureContainerAppEnvironment.ContainerAppEnvironmentId => ContainerAppEnvironmentId; @@ -76,18 +78,18 @@ IManifestExpressionProvider IAzureContainerAppEnvironment.GetSecretOutputKeyVaul throw new NotSupportedException("Automatic Key vault generation is not supported in this environment. Please create a key vault resource directly."); } - IManifestExpressionProvider IAzureContainerAppEnvironment.GetVolumeStorage(IResource resource, ContainerMountType type, string volumeIndex) + IManifestExpressionProvider IAzureContainerAppEnvironment.GetVolumeStorage(IResource resource, ContainerMountAnnotation volume, int volumeIndex) { // REVIEW: Should we use the same naming algorithm as azd? var outputName = $"volumes_{resource.Name}_{volumeIndex}"; - if (!VolumeNames.TryGetValue(outputName, out var outputReference)) + if (!VolumeNames.TryGetValue(outputName, out var volumeName)) { - outputReference = new BicepOutputReference(outputName, this); + volumeName = (resource, volume, volumeIndex, new BicepOutputReference(outputName, this)); - VolumeNames[outputName] = outputReference; + VolumeNames[outputName] = volumeName; } - return outputReference; + return volumeName.outputReference; } } diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs index fdee3e1a583..3c942f9aad1 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs @@ -1,6 +1,7 @@ // 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 Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; using Aspire.Hosting.Azure.AppContainers; @@ -64,6 +65,7 @@ public static IResourceBuilder AddAzureCon var containerAppEnvResource = new AzureContainerAppEnvironmentResource(name, static infra => { + var appEnvResource = (AzureContainerAppEnvironmentResource)infra.AspireResource; var userPrincipalId = new ProvisioningParameter("userPrincipalId", typeof(string)); infra.Add(userPrincipalId); @@ -75,14 +77,24 @@ public static IResourceBuilder AddAzureCon infra.Add(tags); - var identity = new UserAssignedIdentity("mi") + ProvisioningVariable? resourceToken = null; + if (appEnvResource.UseAzdNamingConvention) + { + resourceToken = new ProvisioningVariable("resourceToken", typeof(string)) + { + Value = BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id) + }; + infra.Add(resourceToken); + } + + var identity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_mi")) { Tags = tags }; infra.Add(identity); - var containerRegistry = new ContainerRegistryService("acr") + var containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_acr")) { Sku = new() { Name = ContainerRegistrySkuName.Basic }, Tags = tags @@ -96,7 +108,7 @@ public static IResourceBuilder AddAzureCon pullRa.Name = BicepFunction.CreateGuid(containerRegistry.Id, identity.Id, pullRa.RoleDefinitionId); infra.Add(pullRa); - var laWorkspace = new OperationalInsightsWorkspace("law") + var laWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_law")) { Sku = new() { Name = OperationalInsightsWorkspaceSkuName.PerGB2018 }, Tags = tags @@ -104,7 +116,7 @@ public static IResourceBuilder AddAzureCon infra.Add(laWorkspace); - var containerAppEnvironment = new ContainerAppManagedEnvironment("cae") + var containerAppEnvironment = new ContainerAppManagedEnvironment(appEnvResource.GetBicepIdentifier()) { WorkloadProfiles = [ new ContainerAppWorkloadProfile() @@ -149,9 +161,10 @@ public static IResourceBuilder AddAzureCon var resource = (AzureContainerAppEnvironmentResource)infra.AspireResource; + StorageAccount? storageVolume = null; if (resource.VolumeNames.Count > 0) { - var storageVolume = new StorageAccount("storageVolume") + storageVolume = new StorageAccount(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_storageVolume")) { Tags = tags, Sku = new StorageSku() { Name = StorageSkuName.StandardLrs }, @@ -200,6 +213,26 @@ public static IResourceBuilder AddAzureCon infra.Add(containerAppStorage); managedStorages[outputName] = containerAppStorage; + + if (appEnvResource.UseAzdNamingConvention) + { + var volumeName = output.volume.Type switch + { + ContainerMountType.BindMount => $"bm{output.index}", + ContainerMountType.Volume => output.volume.Source ?? $"v{output.index}", + _ => throw new NotSupportedException() + }; + + share.Name = BicepFunction.Take( + BicepFunction.Interpolate( + $"{BicepFunction.ToLower(output.resource.Name)}-{BicepFunction.ToLower(volumeName)}"), + 60); + + containerAppStorage.Name = BicepFunction.Take( + BicepFunction.Interpolate( + $"{BicepFunction.ToLower(output.resource.Name)}-{BicepFunction.ToLower(volumeName)}"), + 32); + } } } @@ -208,10 +241,34 @@ public static IResourceBuilder AddAzureCon { infra.Add(new ProvisioningOutput(key, typeof(string)) { - Value = value.Name + // use an expression here in case the resource's Name was set to a function expression above + Value = new MemberExpression(new IdentifierExpression(value.BicepIdentifier), "name") }); } + if (appEnvResource.UseAzdNamingConvention) + { + Debug.Assert(resourceToken is not null); + + identity.Name = BicepFunction.Interpolate($"mi-{resourceToken}"); + containerRegistry.Name = new FunctionCallExpression( + new IdentifierExpression("replace"), + new InterpolatedStringExpression( + [ + new StringLiteralExpression("acr-"), + new IdentifierExpression(resourceToken.BicepIdentifier) + ]), + new StringLiteralExpression("-"), + new StringLiteralExpression("")); + laWorkspace.Name = BicepFunction.Interpolate($"law-{resourceToken}"); + containerAppEnvironment.Name = BicepFunction.Interpolate($"cae-{resourceToken}"); + + if (storageVolume is not null) + { + storageVolume.Name = BicepFunction.Interpolate($"vol{resourceToken}"); + } + } + infra.Add(new ProvisioningOutput("MANAGED_IDENTITY_NAME", typeof(string)) { Value = identity.Name @@ -272,4 +329,21 @@ public static IResourceBuilder AddAzureCon return builder.AddResource(containerAppEnvResource); } + + /// + /// Configures the container app environment resources to use the same naming conventions as azd. + /// + /// The AzureContainerAppEnvironmentResource to configure. + /// + /// + /// By default, the container app environment resources use a different naming convention than azd. + /// + /// This method allows for reusing the previously deployed resources if the application was deployed using + /// azd without calling + /// + public static IResourceBuilder WithAzdResourceNaming(this IResourceBuilder builder) + { + builder.Resource.UseAzdNamingConvention = true; + return builder; + } } diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs index cfce5b42e64..6e50d12c716 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs @@ -600,8 +600,8 @@ private void ProcessVolumes() { var (index, volumeName) = volume.Type switch { - ContainerMountType.BindMount => ($"{bindMountIndex}", $"bm{bindMountIndex}"), - ContainerMountType.Volume => ($"{volumeIndex}", $"v{volumeIndex}"), + ContainerMountType.BindMount => (bindMountIndex, $"bm{bindMountIndex}"), + ContainerMountType.Volume => (volumeIndex, $"v{volumeIndex}"), _ => throw new NotSupportedException() }; @@ -614,7 +614,7 @@ private void ProcessVolumes() volumeIndex++; } - var volumeStorageParameter = AllocateVolumeStorageAccount(volume.Type, index); + var volumeStorageParameter = AllocateVolumeStorageAccount(volume, index); var containerAppVolume = new ContainerAppVolume { @@ -768,8 +768,8 @@ BicepValue GetHostValue(string? prefix = null, string? suffix = null) throw new NotSupportedException("Unsupported value type " + value.GetType()); } - private ProvisioningParameter AllocateVolumeStorageAccount(ContainerMountType type, string volumeIndex) => - AllocateParameter(_containerAppEnvironmentContext.Environment.GetVolumeStorage(resource, type, volumeIndex)); + private ProvisioningParameter AllocateVolumeStorageAccount(ContainerMountAnnotation volume, int volumeIndex) => + AllocateParameter(_containerAppEnvironmentContext.Environment.GetVolumeStorage(resource, volume, volumeIndex)); private BicepValue AllocateKeyVaultSecretUriReference(BicepSecretOutputReference secretOutputReference) { diff --git a/src/Aspire.Hosting.Azure.AppContainers/IAzureContainerAppEnvironment.cs b/src/Aspire.Hosting.Azure.AppContainers/IAzureContainerAppEnvironment.cs index 9a70e8faea9..b2d6d63778c 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/IAzureContainerAppEnvironment.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/IAzureContainerAppEnvironment.cs @@ -17,5 +17,5 @@ internal interface IAzureContainerAppEnvironment IManifestExpressionProvider ContainerAppEnvironmentName { get; } IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepResource resource); - IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountType type, string volumeIndex); + IManifestExpressionProvider GetVolumeStorage(IResource resource, ContainerMountAnnotation volume, int volumeIndex); } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs index 8515984717c..4fb643c8fd1 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs @@ -3212,12 +3212,19 @@ public async Task KnownParametersAreNotSetWhenUsingAzdResources() } } - [Fact] - public async Task AddContainerAppEnvironmentAddsEnvironmentResource() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task AddContainerAppEnvironmentAddsEnvironmentResource(bool useAzdNaming) { var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); - builder.AddAzureContainerAppEnvironment("env"); + var env = builder.AddAzureContainerAppEnvironment("env"); + + if (useAzdNaming) + { + env.WithAzdResourceNaming(); + } var pg = builder.AddAzurePostgresFlexibleServer("pg") .WithPasswordAuthentication() @@ -3252,151 +3259,305 @@ public async Task AddContainerAppEnvironmentAddsEnvironmentResource() Assert.Equal(expectedManifest, m); - var expectedBicep = - """ - @description('The location for the resource(s) to be deployed.') - param location string = resourceGroup().location - - param userPrincipalId string - - param tags object = { } - - resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: take('mi-${uniqueString(resourceGroup().id)}', 128) - location: location - tags: tags - } - - resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { - name: take('acr${uniqueString(resourceGroup().id)}', 50) - location: location - sku: { - name: 'Basic' - } - tags: tags - } - - resource acr_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(acr.id, mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) - properties: { - principalId: mi.properties.principalId - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') - principalType: 'ServicePrincipal' - } - scope: acr - } - - resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { - name: take('law-${uniqueString(resourceGroup().id)}', 63) - location: location - properties: { - sku: { - name: 'PerGB2018' + string expectedBicep; + if (useAzdNaming) + { + expectedBicep = + """ + @description('The location for the resource(s) to be deployed.') + param location string = resourceGroup().location + + param userPrincipalId string + + param tags object = { } + + var resourceToken = uniqueString(resourceGroup().id) + + resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: 'mi-${resourceToken}' + location: location + tags: tags } - } - tags: tags - } - - resource cae 'Microsoft.App/managedEnvironments@2024-03-01' = { - name: take('cae${uniqueString(resourceGroup().id)}', 24) - location: location - properties: { - appLogsConfiguration: { - destination: 'log-analytics' - logAnalyticsConfiguration: { - customerId: law.properties.customerId - sharedKey: law.listKeys().primarySharedKey + + resource env_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: replace('acr-${resourceToken}', '-', '') + location: location + sku: { + name: 'Basic' } + tags: tags } - workloadProfiles: [ - { - name: 'consumption' - workloadProfileType: 'Consumption' + + resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env_acr.id, env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) + properties: { + principalId: env_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + principalType: 'ServicePrincipal' } - ] - } - tags: tags - } - - resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@2024-10-02-preview' = { - name: 'aspire-dashboard' - properties: { - componentType: 'AspireDashboard' - } - parent: cae - } - - resource cae_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(cae.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')) - properties: { - principalId: userPrincipalId - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') - } - scope: cae - } - - resource storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { - name: take('storagevolume${uniqueString(resourceGroup().id)}', 24) - kind: 'StorageV2' - location: location - sku: { - name: 'Standard_LRS' - } - properties: { - largeFileSharesState: 'Enabled' - } - tags: tags - } - - resource storageVolumeFileService 'Microsoft.Storage/storageAccounts/fileServices@2024-01-01' = { - name: 'default' - parent: storageVolume - } - - resource shares_volumes_cache_0 'Microsoft.Storage/storageAccounts/fileServices/shares@2024-01-01' = { - name: take('sharesvolumescache0-${uniqueString(resourceGroup().id)}', 63) - properties: { - enabledProtocols: 'SMB' - shareQuota: 1024 - } - parent: storageVolumeFileService + scope: env_acr + } + + resource env_law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { + name: 'law-${resourceToken}' + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } + tags: tags + } + + resource env 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: 'cae-${resourceToken}' + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: env_law.properties.customerId + sharedKey: env_law.listKeys().primarySharedKey + } + } + workloadProfiles: [ + { + name: 'consumption' + workloadProfileType: 'Consumption' + } + ] + } + tags: tags + } + + resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@2024-10-02-preview' = { + name: 'aspire-dashboard' + properties: { + componentType: 'AspireDashboard' + } + parent: env + } + + resource env_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')) + properties: { + principalId: userPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + } + scope: env + } + + resource env_storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { + name: 'vol${resourceToken}' + kind: 'StorageV2' + location: location + sku: { + name: 'Standard_LRS' + } + properties: { + largeFileSharesState: 'Enabled' + } + tags: tags + } + + resource storageVolumeFileService 'Microsoft.Storage/storageAccounts/fileServices@2024-01-01' = { + name: 'default' + parent: env_storageVolume + } + + resource shares_volumes_cache_0 'Microsoft.Storage/storageAccounts/fileServices/shares@2024-01-01' = { + name: take('${toLower('cache')}-${toLower('data')}', 60) + properties: { + enabledProtocols: 'SMB' + shareQuota: 1024 + } + parent: storageVolumeFileService + } + + resource managedStorage_volumes_cache_0 'Microsoft.App/managedEnvironments/storages@2024-03-01' = { + name: take('${toLower('cache')}-${toLower('data')}', 32) + properties: { + azureFile: { + accountName: env_storageVolume.name + accountKey: env_storageVolume.listKeys().keys[0].value + accessMode: 'ReadWrite' + shareName: shares_volumes_cache_0.name + } + } + parent: env + } + + output volumes_cache_0 string = managedStorage_volumes_cache_0.name + + output MANAGED_IDENTITY_NAME string = 'mi-${resourceToken}' + + output MANAGED_IDENTITY_PRINCIPAL_ID string = env_mi.properties.principalId + + output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = 'law-${resourceToken}' + + output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id + + output AZURE_CONTAINER_REGISTRY_NAME string = replace('acr-${resourceToken}', '-', '') + + output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer + + output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id + + output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = 'cae-${resourceToken}' + + output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = env.id + + output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = env.properties.defaultDomain + """; } - - resource managedStorage_volumes_cache_0 'Microsoft.App/managedEnvironments/storages@2024-03-01' = { - name: take('managedstoragevolumescache${uniqueString(resourceGroup().id)}', 24) - properties: { - azureFile: { - accountName: storageVolume.name - accountKey: storageVolume.listKeys().keys[0].value - accessMode: 'ReadWrite' - shareName: shares_volumes_cache_0.name + else + { + expectedBicep = + """ + @description('The location for the resource(s) to be deployed.') + param location string = resourceGroup().location + + param userPrincipalId string + + param tags object = { } + + resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: take('env_mi-${uniqueString(resourceGroup().id)}', 128) + location: location + tags: tags } - } - parent: cae + + resource env_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: take('envacr${uniqueString(resourceGroup().id)}', 50) + location: location + sku: { + name: 'Basic' + } + tags: tags + } + + resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env_acr.id, env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) + properties: { + principalId: env_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + principalType: 'ServicePrincipal' + } + scope: env_acr + } + + resource env_law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { + name: take('envlaw-${uniqueString(resourceGroup().id)}', 63) + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } + tags: tags + } + + resource env 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: take('env${uniqueString(resourceGroup().id)}', 24) + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: env_law.properties.customerId + sharedKey: env_law.listKeys().primarySharedKey + } + } + workloadProfiles: [ + { + name: 'consumption' + workloadProfileType: 'Consumption' + } + ] + } + tags: tags + } + + resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@2024-10-02-preview' = { + name: 'aspire-dashboard' + properties: { + componentType: 'AspireDashboard' + } + parent: env + } + + resource env_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')) + properties: { + principalId: userPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + } + scope: env + } + + resource env_storageVolume 'Microsoft.Storage/storageAccounts@2024-01-01' = { + name: take('envstoragevolume${uniqueString(resourceGroup().id)}', 24) + kind: 'StorageV2' + location: location + sku: { + name: 'Standard_LRS' + } + properties: { + largeFileSharesState: 'Enabled' + } + tags: tags + } + + resource storageVolumeFileService 'Microsoft.Storage/storageAccounts/fileServices@2024-01-01' = { + name: 'default' + parent: env_storageVolume + } + + resource shares_volumes_cache_0 'Microsoft.Storage/storageAccounts/fileServices/shares@2024-01-01' = { + name: take('sharesvolumescache0-${uniqueString(resourceGroup().id)}', 63) + properties: { + enabledProtocols: 'SMB' + shareQuota: 1024 + } + parent: storageVolumeFileService + } + + resource managedStorage_volumes_cache_0 'Microsoft.App/managedEnvironments/storages@2024-03-01' = { + name: take('managedstoragevolumescache${uniqueString(resourceGroup().id)}', 24) + properties: { + azureFile: { + accountName: env_storageVolume.name + accountKey: env_storageVolume.listKeys().keys[0].value + accessMode: 'ReadWrite' + shareName: shares_volumes_cache_0.name + } + } + parent: env + } + + output volumes_cache_0 string = managedStorage_volumes_cache_0.name + + output MANAGED_IDENTITY_NAME string = env_mi.name + + output MANAGED_IDENTITY_PRINCIPAL_ID string = env_mi.properties.principalId + + output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = env_law.name + + output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id + + output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name + + output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer + + output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id + + output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = env.name + + output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = env.id + + output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = env.properties.defaultDomain + """; } - - output volumes_cache_0 string = managedStorage_volumes_cache_0.name - - output MANAGED_IDENTITY_NAME string = mi.name - - output MANAGED_IDENTITY_PRINCIPAL_ID string = mi.properties.principalId - - output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = law.name - - output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = law.id - - output AZURE_CONTAINER_REGISTRY_NAME string = acr.name - - output AZURE_CONTAINER_REGISTRY_ENDPOINT string = acr.properties.loginServer - - output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = mi.id - - output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = cae.name - - output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = cae.id - - output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = cae.properties.defaultDomain - """; output.WriteLine(bicep); Assert.Equal(expectedBicep, bicep); }