Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,33 @@ 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'
}
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: {
Expand All @@ -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: [
Expand All @@ -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: {
Expand All @@ -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' = {
Expand All @@ -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
output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = infra.properties.defaultDomain
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -73,7 +73,7 @@ public static IManifestExpressionProvider GetSecretOutputKeyVault(AzureBicepReso
/// <summary>
/// Generates expressions for the volume storage account. That azd creates.
/// </summary>
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
{
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace Aspire.Hosting.Azure.AppContainers;
public class AzureContainerAppEnvironmentResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure) :
AzureProvisioningResource(name, configureInfrastructure), IAzureContainerAppEnvironment
{
internal bool UseAzdNamingConvention { get; set; }

/// <summary>
/// Gets the unique identifier of the Container App Environment.
/// </summary>
Expand Down Expand Up @@ -53,7 +55,7 @@ public class AzureContainerAppEnvironmentResource(string name, Action<AzureResou
/// </summary>
private BicepOutputReference ContainerAppEnvironmentName => new("AZURE_CONTAINER_APPS_ENVIRONMENT_NAME", this);

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

IManifestExpressionProvider IAzureContainerAppEnvironment.ContainerAppEnvironmentId => ContainerAppEnvironmentId;

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -64,6 +65,7 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon

var containerAppEnvResource = new AzureContainerAppEnvironmentResource(name, static infra =>
{
var appEnvResource = (AzureContainerAppEnvironmentResource)infra.AspireResource;
var userPrincipalId = new ProvisioningParameter("userPrincipalId", typeof(string));

infra.Add(userPrincipalId);
Expand All @@ -75,14 +77,24 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> 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
Expand All @@ -96,15 +108,15 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> 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
};

infra.Add(laWorkspace);

var containerAppEnvironment = new ContainerAppManagedEnvironment("cae")
var containerAppEnvironment = new ContainerAppManagedEnvironment(appEnvResource.GetBicepIdentifier())
{
WorkloadProfiles = [
new ContainerAppWorkloadProfile()
Expand Down Expand Up @@ -149,9 +161,10 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> 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 },
Expand Down Expand Up @@ -200,6 +213,26 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> 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);
}
}
}

Expand All @@ -208,10 +241,34 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> 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
Expand Down Expand Up @@ -272,4 +329,21 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon

return builder.AddResource(containerAppEnvResource);
}

/// <summary>
/// Configures the container app environment resources to use the same naming conventions as azd.
/// </summary>
/// <param name="builder">The AzureContainerAppEnvironmentResource to configure.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
/// <remarks>
/// 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 <see cref="AddAzureContainerAppEnvironment"/>
/// </remarks>
public static IResourceBuilder<AzureContainerAppEnvironmentResource> WithAzdResourceNaming(this IResourceBuilder<AzureContainerAppEnvironmentResource> builder)
{
builder.Resource.UseAzdNamingConvention = true;
return builder;
}
}
Loading
Loading