diff --git a/Directory.Packages.props b/Directory.Packages.props index ddc8a6da1d1..9eadcda4ef2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ true true 3.10.0 - 1.0.0-beta.1 + 1.0.0 diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs index 0e0aff76d49..84a07732dfe 100644 --- a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs +++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs @@ -37,7 +37,7 @@ app.ConfigureCustomDomain(customDomain, certificateName); // Scale to 0 - app.Template.Value!.Scale.Value!.MinReplicas = 0; + app.Template.Scale.MinReplicas = 0; }); #if !SKIP_DASHBOARD_REFERENCE diff --git a/playground/bicep/BicepSample.AppHost/redis.module.bicep b/playground/bicep/BicepSample.AppHost/redis.module.bicep index 969e501807e..e4b434c67ac 100644 --- a/playground/bicep/BicepSample.AppHost/redis.module.bicep +++ b/playground/bicep/BicepSample.AppHost/redis.module.bicep @@ -15,11 +15,11 @@ resource redis 'Microsoft.Cache/redis@2024-03-01' = { capacity: 1 } enableNonSslPort: false + disableAccessKeyAuthentication: true minimumTlsVersion: '1.2' redisConfiguration: { 'aad-enabled': 'true' } - disableAccessKeyAuthentication: 'true' } tags: { 'aspire-resource-name': 'redis' diff --git a/playground/cdk/CdkSample.AppHost/Program.cs b/playground/cdk/CdkSample.AppHost/Program.cs index c63e19b819d..db6bc499930 100644 --- a/playground/cdk/CdkSample.AppHost/Program.cs +++ b/playground/cdk/CdkSample.AppHost/Program.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Azure.Provisioning.ApplicationInsights; -using Azure.Provisioning.Expressions; using Azure.Provisioning.KeyVault; using Azure.Provisioning.OperationalInsights; using Azure.Provisioning.ServiceBus; @@ -17,7 +16,7 @@ var storage = builder.AddAzureStorage("storage") .ConfigureInfrastructure(infrastructure => { - var account = infrastructure.GetResources().OfType().Single(); + var account = infrastructure.GetProvisionableResources().OfType().Single(); account.Sku = new StorageSku() { Name = sku.AsProvisioningParameter(infrastructure) }; account.Location = locationOverride.AsProvisioningParameter(infrastructure); }); @@ -30,7 +29,7 @@ var keyvault = builder.AddAzureKeyVault("mykv") .ConfigureInfrastructure(infrastructure => { - var keyVault = infrastructure.GetResources().OfType().Single(); + var keyVault = infrastructure.GetProvisionableResources().OfType().Single(); var secret = new KeyVaultSecret("mysecret") { Parent = keyVault, @@ -55,26 +54,22 @@ .AddQueue("queue1") .ConfigureInfrastructure(infrastructure => { - var queue = infrastructure.GetResources().OfType().Single(q => q.IdentifierName == "queue1"); + var queue = infrastructure.GetProvisionableResources().OfType().Single(q => q.BicepIdentifier == "queue1"); queue.MaxDeliveryCount = 5; - queue.LockDuration = new StringLiteral("PT5M"); - // TODO: this should be - // queue.LockDuration = TimeSpan.FromMinutes(5); + queue.LockDuration = TimeSpan.FromMinutes(5); }) .AddTopic("topic1") .ConfigureInfrastructure(infrastructure => { - var topic = infrastructure.GetResources().OfType().Single(q => q.IdentifierName == "topic1"); + var topic = infrastructure.GetProvisionableResources().OfType().Single(q => q.BicepIdentifier == "topic1"); topic.EnablePartitioning = true; }) .AddTopic("topic2") .AddSubscription("topic1", "subscription1") .ConfigureInfrastructure(infrastructure => { - var subscription = infrastructure.GetResources().OfType().Single(q => q.IdentifierName == "subscription1"); - subscription.LockDuration = new StringLiteral("PT5M"); - // TODO: this should be - //subscription.LockDuration = TimeSpan.FromMinutes(5); + var subscription = infrastructure.GetProvisionableResources().OfType().Single(q => q.BicepIdentifier == "subscription1"); + subscription.LockDuration = TimeSpan.FromMinutes(5); subscription.RequiresSession = true; }) .AddSubscription("topic1", "subscription2") @@ -89,7 +84,7 @@ var logAnalyticsWorkspace = builder.AddAzureLogAnalyticsWorkspace("logAnalyticsWorkspace") .ConfigureInfrastructure(infrastructure => { - var logAnalyticsWorkspace = infrastructure.GetResources().OfType().Single(); + var logAnalyticsWorkspace = infrastructure.GetProvisionableResources().OfType().Single(); logAnalyticsWorkspace.Sku = new OperationalInsightsWorkspaceSku() { Name = OperationalInsightsWorkspaceSkuName.PerNode @@ -99,7 +94,7 @@ var appInsights = builder.AddAzureApplicationInsights("appInsights", logAnalyticsWorkspace) .ConfigureInfrastructure(infrastructure => { - var appInsights = infrastructure.GetResources().OfType().Single(); + var appInsights = infrastructure.GetProvisionableResources().OfType().Single(); appInsights.IngestionMode = ComponentIngestionMode.LogAnalytics; }); diff --git a/playground/cdk/CdkSample.AppHost/cache.module.bicep b/playground/cdk/CdkSample.AppHost/cache.module.bicep index 83b2ee2c22d..ab540b7b748 100644 --- a/playground/cdk/CdkSample.AppHost/cache.module.bicep +++ b/playground/cdk/CdkSample.AppHost/cache.module.bicep @@ -15,11 +15,11 @@ resource cache 'Microsoft.Cache/redis@2024-03-01' = { capacity: 1 } enableNonSslPort: false + disableAccessKeyAuthentication: true minimumTlsVersion: '1.2' redisConfiguration: { 'aad-enabled': 'true' } - disableAccessKeyAuthentication: 'true' } tags: { 'aspire-resource-name': 'cache' diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs index 0b69c9fc65b..fa294154ea6 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; -using System.Text; using System.Text.RegularExpressions; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; @@ -12,6 +11,7 @@ using Azure.Provisioning.KeyVault; using Azure.Provisioning.Resources; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Aspire.Hosting.Azure; @@ -19,7 +19,10 @@ namespace Aspire.Hosting.Azure; /// Represents the infrastructure for Azure Container Apps within the Aspire Hosting environment. /// Implements the interface to provide lifecycle hooks for distributed applications. /// -internal sealed class AzureContainerAppsInfrastructure(ILogger logger, DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook +internal sealed class AzureContainerAppsInfrastructure( + ILogger logger, + IOptions provisioningOptions, + DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook { public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) { @@ -49,7 +52,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell continue; } - var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, executionContext, cancellationToken).ConfigureAwait(false); + var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, provisioningOptions.Value, executionContext, cancellationToken).ConfigureAwait(false); r.Annotations.Add(new DeploymentTargetAnnotation(containerApp)); } @@ -75,11 +78,12 @@ IManifestExpressionProvider clientId private readonly Dictionary _containerApps = []; - public async Task CreateContainerAppAsync(IResource resource, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken) + public async Task CreateContainerAppAsync(IResource resource, AzureProvisioningOptions provisioningOptions, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken) { var context = await ProcessResourceAsync(resource, executionContext, cancellationToken).ConfigureAwait(false); var provisioningResource = new AzureProvisioningResource(resource.Name, context.BuildContainerApp); + provisioningResource.ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions; provisioningResource.Annotations.Add(new ManifestPublishingCallbackAnnotation(provisioningResource.WriteToManifest)); @@ -143,7 +147,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c) containerImageParam = AllocateContainerImageParameter(); } - var containerAppResource = new ContainerApp(Infrastructure.NormalizeIdentifierName(resource.Name)) + var containerAppResource = new ContainerApp(Infrastructure.NormalizeBicepIdentifier(resource.Name)) { Name = resource.Name.ToLowerInvariant() }; @@ -180,7 +184,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c) var containerAppContainer = new ContainerAppContainer(); template.Containers = [containerAppContainer]; - containerAppContainer.Image = containerImageParam is null ? containerImageName : containerImageParam; + containerAppContainer.Image = containerImageParam is null ? containerImageName! : containerImageParam; containerAppContainer.Name = resource.Name; AddEnvironmentVariablesAndCommandLineArgs(containerAppContainer); @@ -484,7 +488,8 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex { var managedIdentityParameter = AllocateManagedIdentityIdParameter(); secret.Identity = managedIdentityParameter; - secret.KeyVaultUri = new BicepValue(argValue.Expression!); + // TODO: this should be able to use ToUri(), but it hit an issue + secret.KeyVaultUri = new BicepValue(((BicepExpression?)argValue)!); } else { @@ -518,7 +523,6 @@ private static BicepValue ResolveValue(object val) { BicepValue s => s, string s => s, - BicepValueFormattableString fs => Interpolate(fs), ProvisioningParameter p => p, _ => throw new NotSupportedException("Unsupported value type " + val.GetType()) }; @@ -685,7 +689,7 @@ BicepValue GetHostValue(string? prefix = null, string? suffix = null) args[index++] = val; } - return (new BicepValueFormattableString(expr.Format, args), finalSecretType); + return (Interpolate(expr.Format, args), finalSecretType); } @@ -701,7 +705,7 @@ private BicepValue AllocateKeyVaultSecretUriReference(BicepSecretOutputR { // We resolve the keyvault that represents the storage for secret outputs var parameter = AllocateParameter(SecretOutputExpression.GetSecretOutputKeyVault(secretOutputReference.Resource)); - kv = KeyVaultService.FromExisting($"{parameter.IdentifierName}_kv"); + kv = KeyVaultService.FromExisting($"{parameter.BicepIdentifier}_kv"); kv.Name = parameter; KeyVaultRefs[secretOutputReference.Resource.Name] = kv; @@ -710,19 +714,15 @@ private BicepValue AllocateKeyVaultSecretUriReference(BicepSecretOutputR if (!KeyVaultSecretRefs.TryGetValue(secretOutputReference.ValueExpression, out var secret)) { // Now we resolve the secret - var secretIdentifierName = Infrastructure.NormalizeIdentifierName($"{kv.IdentifierName}_{secretOutputReference.Name}"); - secret = KeyVaultSecret.FromExisting(secretIdentifierName); + var secretBicepIdentifier = Infrastructure.NormalizeBicepIdentifier($"{kv.BicepIdentifier}_{secretOutputReference.Name}"); + secret = KeyVaultSecret.FromExisting(secretBicepIdentifier); secret.Name = secretOutputReference.Name; secret.Parent = kv; KeyVaultSecretRefs[secretOutputReference.ValueExpression] = secret; } - // TODO: There should be a better way to do this? - return new MemberExpression( - new MemberExpression( - new IdentifierExpression(secret.IdentifierName), "properties"), - "secretUri"); + return secret.Properties.SecretUri; } private ProvisioningParameter AllocateContainerImageParameter() @@ -882,81 +882,45 @@ private void AddContainerRegistryParameters(ContainerAppConfiguration app) } } - // REVIEW: BicepFunction.Interpolate is buggy and doesn't handle nested formattable strings correctly - // This is a workaround to handle nested formattable strings until the bug is fixed. - private static BicepValue Interpolate(BicepValueFormattableString text) + private static BicepValue Interpolate(string format, object[] args) { - var formatStringBuilder = new StringBuilder(); - var arguments = new List>(); + var bicepStringBuilder = new BicepStringBuilder(); - void ProcessFormattableString(BicepValueFormattableString formattableString, int argumentIndex) - { - var span = formattableString.Format.AsSpan(); - var skip = 0; - - foreach (var match in Regex.EnumerateMatches(span, @"{\d+}")) - { - formatStringBuilder.Append(span[..(match.Index - skip)]); + var span = format.AsSpan(); + var skip = 0; + var argumentIndex = 0; - var argument = formattableString.GetArgument(argumentIndex); + foreach (var match in Regex.EnumerateMatches(span, @"{\d+}")) + { + bicepStringBuilder.Append(span[..(match.Index - skip)].ToString()); - if (argument is BicepValueFormattableString nested) - { - // Inline the nested formattable string - ProcessFormattableString(nested, 0); - } - else - { - formatStringBuilder.Append(CultureInfo.InvariantCulture, $"{{{arguments.Count}}}"); - if (argument is BicepValue bicepValue) - { - arguments.Add(bicepValue); - } - else if (argument is string s) - { - arguments.Add(s); - } - else if (argument is ProvisioningParameter provisioningParameter) - { - arguments.Add(provisioningParameter); - } - else - { - throw new NotSupportedException($"{argument} is not supported"); - } - } + var argument = args[argumentIndex]; - argumentIndex++; - span = span[(match.Index + match.Length - skip)..]; - skip = match.Index + match.Length; + if (argument is BicepValue bicepValue) + { + bicepStringBuilder.Append($"{bicepValue}"); + } + else if (argument is string s) + { + bicepStringBuilder.Append(s); + } + else if (argument is ProvisioningParameter provisioningParameter) + { + bicepStringBuilder.Append($"{provisioningParameter}"); + } + else + { + throw new NotSupportedException($"{argument} is not supported"); } - formatStringBuilder.Append(span); - } - - ProcessFormattableString(text, 0); - - var formatString = formatStringBuilder.ToString(); - - if (formatString == "{0}") - { - return arguments[0]; + argumentIndex++; + span = span[(match.Index + match.Length - skip)..]; + skip = match.Index + match.Length; } - return BicepFunction.Interpolate(new BicepValueFormattableString(formatString, [.. arguments])); - } + bicepStringBuilder.Append(span.ToString()); - /// - /// A custom FormattableString implementation that allows us to inline nested formattable strings. - /// - private sealed class BicepValueFormattableString(string formatString, object[] values) : FormattableString - { - public override int ArgumentCount => values.Length; - public override string Format => formatString; - public override object? GetArgument(int index) => values[index]; - public override object?[] GetArguments() => values; - public override string ToString(IFormatProvider? formatProvider) => Format; - public override string ToString() => formatString; + return bicepStringBuilder.Build(); } /// diff --git a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs index f1f004d121b..bce9c2ba7c9 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs @@ -1,12 +1,12 @@ // 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.CodeAnalysis; using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Azure; +using Azure.Provisioning; using Azure.Provisioning.AppContainers; using Azure.Provisioning.Expressions; -using Azure.Provisioning; -using System.Diagnostics.CodeAnalysis; -using Aspire.Hosting.Azure; namespace Aspire.Hosting; @@ -62,40 +62,40 @@ public static void ConfigureCustomDomain(this ContainerApp app, IResourceBuilder throw new ArgumentException("Cannot configure custom domain when resource is not parented by ResourceModuleConstruct.", nameof(app)); } - var containerAppManagedEnvironmentIdParameter = module.GetResources().OfType().Single( - p => p.IdentifierName == "outputs_azure_container_apps_environment_id"); - var certificatNameParameter = certificateName.AsProvisioningParameter(module); + var containerAppManagedEnvironmentIdParameter = module.GetProvisionableResources().OfType().Single( + p => p.BicepIdentifier == "outputs_azure_container_apps_environment_id"); + var certificateNameParameter = certificateName.AsProvisioningParameter(module); var customDomainParameter = customDomain.AsProvisioningParameter(module); var bindingTypeConditional = new ConditionalExpression( new BinaryExpression( - new IdentifierExpression(certificatNameParameter.IdentifierName), - BinaryOperator.NotEqual, - new StringLiteral(string.Empty)), - new StringLiteral("SniEnabled"), - new StringLiteral("Disabled") + new IdentifierExpression(certificateNameParameter.BicepIdentifier), + BinaryBicepOperator.NotEqual, + new StringLiteralExpression(string.Empty)), + new StringLiteralExpression("SniEnabled"), + new StringLiteralExpression("Disabled") ); var certificateOrEmpty = new ConditionalExpression( new BinaryExpression( - new IdentifierExpression(certificatNameParameter.IdentifierName), - BinaryOperator.NotEqual, - new StringLiteral(string.Empty)), - new InterpolatedString( - "{0}/managedCertificates/{1}", + new IdentifierExpression(certificateNameParameter.BicepIdentifier), + BinaryBicepOperator.NotEqual, + new StringLiteralExpression(string.Empty)), + new InterpolatedStringExpression( [ - new IdentifierExpression(containerAppManagedEnvironmentIdParameter.IdentifierName), - new IdentifierExpression(certificatNameParameter.IdentifierName) + new IdentifierExpression(containerAppManagedEnvironmentIdParameter.BicepIdentifier), + new StringLiteralExpression("/managedCertificates/"), + new IdentifierExpression(certificateNameParameter.BicepIdentifier) ]), - new NullLiteral() + new NullLiteralExpression() ); - app.Configuration.Value!.Ingress!.Value!.CustomDomains = new BicepList() + app.Configuration.Ingress.CustomDomains = new BicepList() { new ContainerAppCustomDomain() { BindingType = bindingTypeConditional, - Name = new IdentifierExpression(customDomainParameter.IdentifierName), + Name = customDomainParameter, CertificateId = certificateOrEmpty } }; diff --git a/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs b/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs index 04c06574efc..06d7d191be8 100644 --- a/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs +++ b/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs @@ -5,7 +5,6 @@ using Aspire.Hosting.Azure; using Azure.Provisioning; using Azure.Provisioning.ApplicationInsights; -using Azure.Provisioning.Expressions; using Azure.Provisioning.OperationalInsights; namespace Aspire.Hosting; @@ -41,13 +40,13 @@ public static IResourceBuilder AddAzureApplica { var appTypeParameter = new ProvisioningParameter("applicationType", typeof(string)) { - Value = new StringLiteral("web") + Value = "web" }; infrastructure.Add(appTypeParameter); var kindParameter = new ProvisioningParameter("kind", typeof(string)) { - Value = new StringLiteral("web") + Value = "web" }; infrastructure.Add(kindParameter); @@ -67,7 +66,7 @@ public static IResourceBuilder AddAzureApplica else if (builder.ExecutionContext.IsRunMode) { // ... otherwise if we are in run mode, the provisioner expects us to create one ourselves. - var autoInjectedLogAnalyticsWorkspaceName = $"law_{appInsights.IdentifierName}"; + var autoInjectedLogAnalyticsWorkspaceName = $"law_{appInsights.BicepIdentifier}"; var autoInjectedLogAnalyticsWorkspace = new OperationalInsightsWorkspace(autoInjectedLogAnalyticsWorkspaceName) { Sku = new OperationalInsightsWorkspaceSku() @@ -89,7 +88,7 @@ public static IResourceBuilder AddAzureApplica infrastructure.AspireResource.Parameters.TryAdd(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId, null); var logAnalyticsWorkspaceParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId, typeof(string)) { - Value = new StringLiteral("web") + Value = "web" }; infrastructure.Add(kindParameter); appInsights.WorkspaceResourceId = logAnalyticsWorkspaceParameter; diff --git a/src/Aspire.Hosting.Azure.CognitiveServices/AzureOpenAIExtensions.cs b/src/Aspire.Hosting.Azure.CognitiveServices/AzureOpenAIExtensions.cs index e9e3b85b696..27b98ffda8e 100644 --- a/src/Aspire.Hosting.Azure.CognitiveServices/AzureOpenAIExtensions.cs +++ b/src/Aspire.Hosting.Azure.CognitiveServices/AzureOpenAIExtensions.cs @@ -5,7 +5,6 @@ using Aspire.Hosting.Azure; using Azure.Provisioning; using Azure.Provisioning.CognitiveServices; -using Azure.Provisioning.Expressions; using static Azure.Provisioning.Expressions.BicepFunction; namespace Aspire.Hosting; @@ -47,17 +46,7 @@ public static IResourceBuilder AddAzureOpenAI(this IDistrib infrastructure.Add(new ProvisioningOutput("connectionString", typeof(string)) { - Value = new InterpolatedString( - "Endpoint={0}", - [ - new MemberExpression( - new MemberExpression( - new IdentifierExpression(cogServicesAccount.IdentifierName), - "properties"), - "endpoint") - ]) - // TODO This should be - // Value = BicepFunction.Interpolate($"Endpoint={cogServicesAccount.Endpoint}") + Value = Interpolate($"Endpoint={cogServicesAccount.Properties.Endpoint}") }); var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string)); @@ -71,7 +60,7 @@ public static IResourceBuilder AddAzureOpenAI(this IDistrib var cdkDeployments = new List(); foreach (var deployment in resource.Deployments) { - var cdkDeployment = new CognitiveServicesAccountDeployment(Infrastructure.NormalizeIdentifierName(deployment.Name)) + var cdkDeployment = new CognitiveServicesAccountDeployment(Infrastructure.NormalizeBicepIdentifier(deployment.Name)) { Name = deployment.Name, Parent = cogServicesAccount, diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 75c06dbb327..9599f164dca 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -65,7 +65,7 @@ public static IResourceBuilder AddAzureCosmosDB(this IDis List cosmosSqlDatabases = new List(); foreach (var databaseName in azureResource.Databases) { - var cosmosSqlDatabase = new CosmosDBSqlDatabase(Infrastructure.NormalizeIdentifierName(databaseName)) + var cosmosSqlDatabase = new CosmosDBSqlDatabase(Infrastructure.NormalizeBicepIdentifier(databaseName)) { Parent = cosmosAccount, Name = databaseName, diff --git a/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs b/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs index 7cf2dd6dd3c..faf57b7c9df 100644 --- a/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs +++ b/src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs @@ -9,7 +9,6 @@ using Azure.Messaging.EventHubs.Producer; using Azure.Provisioning; using Azure.Provisioning.EventHubs; -using Azure.Provisioning.Expressions; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting; @@ -34,7 +33,7 @@ public static IResourceBuilder AddAzureEventHubs( { var skuParameter = new ProvisioningParameter("sku", typeof(string)) { - Value = new StringLiteral("Standard") + Value = "Standard" }; infrastructure.Add(skuParameter); @@ -58,7 +57,7 @@ public static IResourceBuilder AddAzureEventHubs( foreach (var hub in azureResource.Hubs) { - var hubResource = new EventHub(Infrastructure.NormalizeIdentifierName(hub)) + var hubResource = new EventHub(Infrastructure.NormalizeBicepIdentifier(hub)) { Parent = eventHubsNamespace, Name = hub diff --git a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs index 7030369bc93..7d07dd878af 100644 --- a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs @@ -54,7 +54,7 @@ public static class AzureFunctionsProjectResourceExtensions var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string)); var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string)); - var storageAccount = infrastructure.GetResources().OfType().FirstOrDefault(r => r.IdentifierName == storageResourceName) + var storageAccount = infrastructure.GetProvisionableResources().OfType().FirstOrDefault(r => r.BicepIdentifier == storageResourceName) ?? throw new InvalidOperationException($"Could not find storage account with '{storageResourceName}' name."); infrastructure.Add(storageAccount.CreateRoleAssignment(StorageBuiltInRole.StorageAccountContributor, principalTypeParameter, principalIdParameter)); }; diff --git a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs index 36346cd2693..ae3f5eb9490 100644 --- a/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResourceExtensions.cs @@ -43,14 +43,7 @@ public static IResourceBuilder AddAzureKeyVault(this IDis infrastructure.Add(new ProvisioningOutput("vaultUri", typeof(string)) { - Value = - new MemberExpression( - new MemberExpression( - new IdentifierExpression(keyVault.IdentifierName), - "properties"), - "vaultUri") - // TODO: this should be - //Value = keyVault.VaultUri + Value = keyVault.Properties.VaultUri }); keyVault.Tags["aspire-resource-name"] = infrastructure.AspireResource.Name; diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index 961cc36b41f..f8d4b4f9961 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -157,7 +157,7 @@ public static IResourceBuilder AddAzurePost var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string)); var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string)); - var admin = new PostgreSqlFlexibleServerActiveDirectoryAdministrator($"{postgres.IdentifierName}_admin") + var admin = new PostgreSqlFlexibleServerActiveDirectoryAdministrator($"{postgres.BicepIdentifier}_admin") { Parent = postgres, Name = principalIdParameter, @@ -167,7 +167,7 @@ public static IResourceBuilder AddAzurePost // This is a workaround for a bug in the API that requires the parent to be fully resolved admin.DependsOn.Add(postgres); - foreach (var firewall in infrastructure.GetResources().OfType()) + foreach (var firewall in infrastructure.GetProvisionableResources().OfType()) { admin.DependsOn.Add(firewall); } @@ -355,7 +355,7 @@ public static IResourceBuilder WithPassword RemoveActiveDirectoryAuthResources(infrastructure); - var postgres = infrastructure.GetResources().OfType().FirstOrDefault(r => r.IdentifierName == azureResource.GetBicepIdentifier()) + var postgres = infrastructure.GetProvisionableResources().OfType().FirstOrDefault(r => r.BicepIdentifier == azureResource.GetBicepIdentifier()) ?? throw new InvalidOperationException($"Could not find a PostgreSqlFlexibleServer with name {azureResource.Name}."); var administratorLogin = new ProvisioningParameter("administratorLogin", typeof(string)); @@ -393,7 +393,7 @@ public static IResourceBuilder WithPassword foreach (var database in azureResource.Databases) { - var dbSecret = new KeyVaultSecret(Infrastructure.NormalizeIdentifierName(database.Key + "_connectionString")) + var dbSecret = new KeyVaultSecret(Infrastructure.NormalizeBicepIdentifier(database.Key + "_connectionString")) { Parent = keyVault, Name = AzurePostgresFlexibleServerResource.GetDatabaseKeyVaultSecretName(database.Key), @@ -417,7 +417,7 @@ private static PostgreSqlFlexibleServer CreatePostgreSqlFlexibleServer(AzureReso Name = "Standard_B1ms", Tier = PostgreSqlFlexibleServerSkuTier.Burstable }, - Version = new StringLiteral("16"), + Version = new StringLiteralExpression("16"), HighAvailability = new PostgreSqlFlexibleServerHighAvailability() { Mode = PostgreSqlFlexibleServerHighAvailabilityMode.Disabled @@ -455,9 +455,9 @@ private static PostgreSqlFlexibleServer CreatePostgreSqlFlexibleServer(AzureReso foreach (var databaseNames in databases) { - var identifierName = Infrastructure.NormalizeIdentifierName(databaseNames.Key); + var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseNames.Key); var databaseName = databaseNames.Value; - var pgsqlDatabase = new PostgreSqlFlexibleServerDatabase(identifierName) + var pgsqlDatabase = new PostgreSqlFlexibleServerDatabase(bicepIdentifier) { Parent = postgres, Name = databaseName @@ -480,13 +480,13 @@ private static IResourceBuilder RemoveActiv private static void RemoveActiveDirectoryAuthResources(AzureResourceInfrastructure infrastructure) { var resourcesToRemove = new List(); - foreach (var resource in infrastructure.GetResources()) + foreach (var resource in infrastructure.GetProvisionableResources()) { if (resource is PostgreSqlFlexibleServerActiveDirectoryAdministrator) { resourcesToRemove.Add(resource); } - else if (resource is ProvisioningOutput output && output.IdentifierName == "connectionString") + else if (resource is ProvisioningOutput output && output.BicepIdentifier == "connectionString") { resourcesToRemove.Add(resource); } diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 97f76c422e8..3b2a0c20d8e 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -129,14 +129,11 @@ public static IResourceBuilder AddAzureRedis( { IsAadEnabled = "true" }; - - // TODO: This property should be available from the CDK in the latest version. - var disableAccessKeys = BicepValue.DefineProperty(redis, "DisableAccessKeyAuthentication", ["properties", "disableAccessKeyAuthentication"], isOutput: false, isRequired: false); - disableAccessKeys.Assign("true"); + redis.IsAccessKeyAuthenticationDisabled = true; var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string)); var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string)); - infrastructure.Add(new RedisCacheAccessPolicyAssignment($"{redis.IdentifierName}_contributor") + infrastructure.Add(new RedisCacheAccessPolicyAssignment($"{redis.BicepIdentifier}_contributor") { Parent = redis, AccessPolicyName = "Data Contributor", @@ -233,7 +230,7 @@ public static IResourceBuilder WithAccessKeyAuthenticat { RemoveActiveDirectoryAuthResources(infrastructure); - var redis = infrastructure.GetResources().OfType().FirstOrDefault(r => r.IdentifierName == builder.Resource.GetBicepIdentifier()) + var redis = infrastructure.GetProvisionableResources().OfType().FirstOrDefault(r => r.BicepIdentifier == builder.Resource.GetBicepIdentifier()) ?? throw new InvalidOperationException($"Could not find a RedisResource with name {builder.Resource.Name}."); var kvNameParam = new ProvisioningParameter("keyVaultName", typeof(string)); @@ -243,11 +240,8 @@ public static IResourceBuilder WithAccessKeyAuthenticat keyVault.Name = kvNameParam; infrastructure.Add(keyVault); - redis.RedisConfiguration.Value!.IsAadEnabled.Kind = BicepValueKind.Unset; - - // TODO: This property should be available from the CDK in the latest version. - var disableAccessKeys = BicepValue.DefineProperty(redis, "DisableAccessKeyAuthentication", ["properties", "disableAccessKeyAuthentication"], isOutput: false, isRequired: false); - disableAccessKeys.Kind = BicepValueKind.Unset; + redis.RedisConfiguration.IsAadEnabled.ClearValue(); + redis.IsAccessKeyAuthenticationDisabled.ClearValue(); var secret = new KeyVaultSecret("connectionString") { @@ -292,14 +286,14 @@ private static IResourceBuilder RemoveActiveDirectoryPa private static void RemoveActiveDirectoryAuthResources(AzureResourceInfrastructure infrastructure) { var resourcesToRemove = new List(); - foreach (var resource in infrastructure.GetResources()) + foreach (var resource in infrastructure.GetProvisionableResources()) { if (resource is RedisCacheAccessPolicyAssignment accessPolicy && - accessPolicy.IdentifierName == $"{infrastructure.AspireResource.GetBicepIdentifier()}_contributor") + accessPolicy.BicepIdentifier == $"{infrastructure.AspireResource.GetBicepIdentifier()}_contributor") { resourcesToRemove.Add(resource); } - else if (resource is ProvisioningOutput output && output.IdentifierName == "connectionString") + else if (resource is ProvisioningOutput output && output.BicepIdentifier == "connectionString") { resourcesToRemove.Add(resource); } diff --git a/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusExtensions.cs b/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusExtensions.cs index ec7c4b7720e..92c19b65752 100644 --- a/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusExtensions.cs +++ b/src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusExtensions.cs @@ -4,7 +4,6 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; using Azure.Provisioning; -using Azure.Provisioning.Expressions; using Azure.Provisioning.ServiceBus; namespace Aspire.Hosting; @@ -28,7 +27,7 @@ public static IResourceBuilder AddAzureServiceBus(this { var skuParameter = new ProvisioningParameter("sku", typeof(string)) { - Value = new StringLiteral("Standard") + Value = "Standard" }; infrastructure.Add(skuParameter); @@ -53,7 +52,7 @@ public static IResourceBuilder AddAzureServiceBus(this foreach (var queue in azureResource.Queues) { - var queueResource = new ServiceBusQueue(Infrastructure.NormalizeIdentifierName(queue)) + var queueResource = new ServiceBusQueue(Infrastructure.NormalizeBicepIdentifier(queue)) { Parent = serviceBusNamespace, Name = queue @@ -63,7 +62,7 @@ public static IResourceBuilder AddAzureServiceBus(this var topicDictionary = new Dictionary(); foreach (var topic in azureResource.Topics) { - var topicResource = new ServiceBusTopic(Infrastructure.NormalizeIdentifierName(topic)) + var topicResource = new ServiceBusTopic(Infrastructure.NormalizeBicepIdentifier(topic)) { Parent = serviceBusNamespace, Name = topic @@ -74,7 +73,7 @@ public static IResourceBuilder AddAzureServiceBus(this foreach (var subscription in azureResource.Subscriptions) { var topic = topicDictionary[subscription.TopicName]; - var subscriptionResource = new ServiceBusSubscription(Infrastructure.NormalizeIdentifierName(subscription.Name)) + var subscriptionResource = new ServiceBusSubscription(Infrastructure.NormalizeBicepIdentifier(subscription.Name)) { Parent = topic, Name = subscription.Name diff --git a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs index 5ee8df25f9b..99a2880ec91 100644 --- a/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs +++ b/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs @@ -243,7 +243,7 @@ private static void CreateSqlServer( // When in run mode we inject the users identity and we need to specify // the principalType. var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string)); - sqlServer.Administrators.Value!.PrincipalType = principalTypeParameter; + sqlServer.Administrators.PrincipalType = principalTypeParameter; infrastructure.Add(new SqlFirewallRule("sqlFirewallRule_AllowAllIps") { @@ -256,9 +256,9 @@ private static void CreateSqlServer( foreach (var databaseNames in databases) { - var identifierName = Infrastructure.NormalizeIdentifierName(databaseNames.Key); + var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseNames.Key); var databaseName = databaseNames.Value; - var sqlDatabase = new SqlDatabase(identifierName) + var sqlDatabase = new SqlDatabase(bicepIdentifier) { Parent = sqlServer, Name = databaseName diff --git a/src/Aspire.Hosting.Azure.Storage/AzureStorageExtensions.cs b/src/Aspire.Hosting.Azure.Storage/AzureStorageExtensions.cs index bc05dc4174c..3862041d16b 100644 --- a/src/Aspire.Hosting.Azure.Storage/AzureStorageExtensions.cs +++ b/src/Aspire.Hosting.Azure.Storage/AzureStorageExtensions.cs @@ -65,9 +65,9 @@ public static IResourceBuilder AddAzureStorage(this IDistr infrastructure.Add(storageAccount.CreateRoleAssignment(StorageBuiltInRole.StorageTableDataContributor, principalTypeParameter, principalIdParameter)); infrastructure.Add(storageAccount.CreateRoleAssignment(StorageBuiltInRole.StorageQueueDataContributor, principalTypeParameter, principalIdParameter)); - infrastructure.Add(new ProvisioningOutput("blobEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.Value!.BlobUri }); - infrastructure.Add(new ProvisioningOutput("queueEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.Value!.QueueUri }); - infrastructure.Add(new ProvisioningOutput("tableEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.Value!.TableUri }); + infrastructure.Add(new ProvisioningOutput("blobEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.BlobUri }); + infrastructure.Add(new ProvisioningOutput("queueEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.QueueUri }); + infrastructure.Add(new ProvisioningOutput("tableEndpoint", typeof(string)) { Value = storageAccount.PrimaryEndpoints.TableUri }); }; var resource = new AzureStorageResource(name, configureInfrastructure); diff --git a/src/Aspire.Hosting.Azure.WebPubSub/AzureWebPubSubExtensions.cs b/src/Aspire.Hosting.Azure.WebPubSub/AzureWebPubSubExtensions.cs index fc2e5a7ccd2..a4237f5705c 100644 --- a/src/Aspire.Hosting.Azure.WebPubSub/AzureWebPubSubExtensions.cs +++ b/src/Aspire.Hosting.Azure.WebPubSub/AzureWebPubSubExtensions.cs @@ -31,7 +31,7 @@ public static IResourceBuilder AddAzureWebPubSub(this ID // Supported values are Free_F1 Standard_S1 Premium_P1 var skuParameter = new ProvisioningParameter("sku", typeof(string)) { - Value = new StringLiteral("Free_F1") + Value = "Free_F1" }; infrastructure.Add(skuParameter); @@ -66,14 +66,14 @@ public static IResourceBuilder AddAzureWebPubSub(this ID var hubBuilder = setting.Value; var hubResource = hubBuilder; - var hub = new WebPubSubHub(Infrastructure.NormalizeIdentifierName(hubResource.Name)) + var hub = new WebPubSubHub(Infrastructure.NormalizeBicepIdentifier(hubResource.Name)) { Name = setting.Key, Parent = service, Properties = new WebPubSubHubProperties() }; - var hubProperties = hub.Properties.Value!; + var hubProperties = hub.Properties; // invoke the configure from AddEventHandler for (var i = 0; i < hubResource.EventHandlers.Count; i++) @@ -92,7 +92,7 @@ public static IResourceBuilder AddAzureWebPubSub(this ID // otherwise add parameter to the construct var parameter = new ProvisioningParameter($"{hubName}_url_{i}", typeof(string)); infrastructure.Add(parameter); - resource.Parameters[parameter.IdentifierName] = urlExpression; + resource.Parameters[parameter.BicepIdentifier] = urlExpression; urlParameter = parameter; } @@ -183,7 +183,7 @@ private static WebPubSubEventHandler GetWebPubSubEventHandler(BicepValue if (authSettings != null) { - handler.Auth = new BicepValue(authSettings); + handler.Auth = authSettings; } return handler; } diff --git a/src/Aspire.Hosting.Azure/AspireV8ResourceNamePropertyResolver.cs b/src/Aspire.Hosting.Azure/AspireV8ResourceNamePropertyResolver.cs index a2c7f776dcc..05488e39afe 100644 --- a/src/Aspire.Hosting.Azure/AspireV8ResourceNamePropertyResolver.cs +++ b/src/Aspire.Hosting.Azure/AspireV8ResourceNamePropertyResolver.cs @@ -16,9 +16,29 @@ namespace Aspire.Hosting.Azure; public sealed class AspireV8ResourceNamePropertyResolver : DynamicResourceNamePropertyResolver { /// - public override BicepValue? ResolveName(ProvisioningContext context, Resource resource, ResourceNameRequirements requirements) + public override BicepValue? ResolveName(ProvisioningBuildOptions options, ProvisionableResource resource, ResourceNameRequirements requirements) { - var suffix = GetUniqueSuffix(context, resource); - return BicepFunction.ToLower(BicepFunction.Take(BicepFunction.Interpolate($"{resource.IdentifierName}{suffix}"), 24)); + var suffix = GetUniqueSuffix(options, resource); + var prefix = GetNamePrefix(resource); + + return BicepFunction.ToLower(BicepFunction.Take(BicepFunction.Interpolate($"{prefix}{suffix}"), 24)); + } + + /// + /// Use the 'aspire-resource-name' tag to get the prefix for the resource name, if available. + /// + /// + /// The BicepIdentifier has already had any dashes changed to underscores, which we don't want to use since .NET Aspire 8.x used the dashes. + /// + private static string GetNamePrefix(ProvisionableResource resource) + { + BicepValue? aspireResourceName = null; + if (resource.ProvisionableProperties.TryGetValue("Tags", out var tags) && + tags is BicepDictionary tagDictionary) + { + tagDictionary.TryGetValue("aspire-resource-name", out aspireResourceName); + } + + return aspireResourceName?.Value ?? resource.BicepIdentifier; } } diff --git a/src/Aspire.Hosting.Azure/AzureProvisioningOptions.cs b/src/Aspire.Hosting.Azure/AzureProvisioningOptions.cs index 8084b6e4ed3..ea778c7bf19 100644 --- a/src/Aspire.Hosting.Azure/AzureProvisioningOptions.cs +++ b/src/Aspire.Hosting.Azure/AzureProvisioningOptions.cs @@ -15,8 +15,8 @@ namespace Aspire.Hosting.Azure; public sealed class AzureProvisioningOptions { /// - /// Gets the which contains common settings and + /// Gets the which contains common settings and /// functionality for building Azure resources. /// - public ProvisioningContext ProvisioningContext { get; } = new ProvisioningContext(); + public ProvisioningBuildOptions ProvisioningBuildOptions { get; } = new ProvisioningBuildOptions(); } diff --git a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs index 8122c30a540..c5c18228be7 100644 --- a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs +++ b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs @@ -20,10 +20,10 @@ public class AzureProvisioningResource(string name, Action ConfigureInfrastructure { get; internal set; } = configureInfrastructure; /// - /// Gets or sets the which contains common settings and + /// Gets or sets the which contains common settings and /// functionality for building Azure resources. /// - public ProvisioningContext? ProvisioningContext { get; set; } + public ProvisioningBuildOptions? ProvisioningBuildOptions { get; set; } /// public override BicepTemplateFile GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true) @@ -38,8 +38,8 @@ public override BicepTemplateFile GetBicepTemplateFile(string? directory = null, // put them into a dictionary for quick lookup so we don't need to scan // through the parameter enumerable each time. var infrastructureParameters = infrastructure.GetParameters(); - var distinctInfrastructureParameters = infrastructureParameters.DistinctBy(p => p.IdentifierName); - var distinctInfrastructureParametersLookup = distinctInfrastructureParameters.ToDictionary(p => p.IdentifierName); + var distinctInfrastructureParameters = infrastructureParameters.DistinctBy(p => p.BicepIdentifier); + var distinctInfrastructureParametersLookup = distinctInfrastructureParameters.ToDictionary(p => p.BicepIdentifier); foreach (var aspireParameter in this.Parameters) { @@ -56,7 +56,7 @@ public override BicepTemplateFile GetBicepTemplateFile(string? directory = null, var generationPath = Directory.CreateTempSubdirectory("aspire").FullName; var moduleSourcePath = Path.Combine(generationPath, "main.bicep"); - var plan = infrastructure.Build(ProvisioningContext); + var plan = infrastructure.Build(ProvisioningBuildOptions); var compilation = plan.Compile(); Debug.Assert(compilation.Count == 1); var compiledBicep = compilation.First(); diff --git a/src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs b/src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs index d6c0472d5bd..33b8d27f00b 100644 --- a/src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs @@ -69,11 +69,11 @@ public static ProvisioningParameter AsProvisioningParameter(this IResourceBuilde ArgumentNullException.ThrowIfNull(parameterResourceBuilder); ArgumentNullException.ThrowIfNull(infrastructure); - parameterName ??= Infrastructure.NormalizeIdentifierName(parameterResourceBuilder.Resource.Name); + parameterName ??= Infrastructure.NormalizeBicepIdentifier(parameterResourceBuilder.Resource.Name); infrastructure.AspireResource.Parameters[parameterName] = parameterResourceBuilder.Resource; - var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.IdentifierName == parameterName); + var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.BicepIdentifier == parameterName); if (parameter is null) { parameter = new ProvisioningParameter(parameterName, typeof(string)) @@ -112,7 +112,7 @@ public static ProvisioningParameter AsProvisioningParameter(this BicepOutputRefe infrastructure.AspireResource.Parameters[parameterName] = outputReference; - var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.IdentifierName == parameterName); + var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.BicepIdentifier == parameterName); if (parameter is null) { parameter = new ProvisioningParameter(parameterName, typeof(string)); diff --git a/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs b/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs index 1d8b2fc2c56..ac9a3b73b95 100644 --- a/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs @@ -30,5 +30,5 @@ public static IResourceBuilder PublishAsConnectionString(this IResourceBui /// The Azure resource. /// A valid Bicep identifier. public static string GetBicepIdentifier(this IAzureResource resource) => - Infrastructure.NormalizeIdentifierName(resource.Name); + Infrastructure.NormalizeBicepIdentifier(resource.Name); } diff --git a/src/Aspire.Hosting.Azure/AzureResourceInfrastructure.cs b/src/Aspire.Hosting.Azure/AzureResourceInfrastructure.cs index bb8e4808480..5396171d0ca 100644 --- a/src/Aspire.Hosting.Azure/AzureResourceInfrastructure.cs +++ b/src/Aspire.Hosting.Azure/AzureResourceInfrastructure.cs @@ -30,5 +30,5 @@ internal AzureResourceInfrastructure(AzureProvisioningResource resource, string /// public AzureProvisioningResource AspireResource { get; } - internal IEnumerable GetParameters() => GetResources().OfType(); + internal IEnumerable GetParameters() => GetProvisionableResources().OfType(); } diff --git a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs index 8e1aaf1c36e..ee690f8cee6 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Provisioners/AzureProvisioner.cs @@ -83,12 +83,12 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell return; } - // set the ProvisioningContext on the resource, if necessary + // set the ProvisioningBuildOptions on the resource, if necessary foreach (var r in azureResources) { if (r.AzureResource is AzureProvisioningResource provisioningResource) { - provisioningResource.ProvisioningContext = provisioningOptions.Value.ProvisioningContext; + provisioningResource.ProvisioningBuildOptions = provisioningOptions.Value.ProvisioningBuildOptions; } } diff --git a/src/Aspire.Hosting.Azure/PublicAPI.Unshipped.txt b/src/Aspire.Hosting.Azure/PublicAPI.Unshipped.txt index 005fda52ce5..830233db18e 100644 --- a/src/Aspire.Hosting.Azure/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting.Azure/PublicAPI.Unshipped.txt @@ -17,18 +17,18 @@ Aspire.Hosting.Azure.AspireV8ResourceNamePropertyResolver Aspire.Hosting.Azure.AspireV8ResourceNamePropertyResolver.AspireV8ResourceNamePropertyResolver() -> void Aspire.Hosting.Azure.AzureProvisioningOptions Aspire.Hosting.Azure.AzureProvisioningOptions.AzureProvisioningOptions() -> void -Aspire.Hosting.Azure.AzureProvisioningOptions.ProvisioningContext.get -> Azure.Provisioning.ProvisioningContext! +Aspire.Hosting.Azure.AzureProvisioningOptions.ProvisioningBuildOptions.get -> Azure.Provisioning.ProvisioningBuildOptions! Aspire.Hosting.Azure.AzureProvisioningResource.AzureProvisioningResource(string! name, System.Action! configureInfrastructure) -> void Aspire.Hosting.Azure.AzureProvisioningResource.ConfigureInfrastructure.get -> System.Action! Aspire.Hosting.Azure.AzureProvisioningResource -Aspire.Hosting.Azure.AzureProvisioningResource.ProvisioningContext.get -> Azure.Provisioning.ProvisioningContext? -Aspire.Hosting.Azure.AzureProvisioningResource.ProvisioningContext.set -> void +Aspire.Hosting.Azure.AzureProvisioningResource.ProvisioningBuildOptions.get -> Azure.Provisioning.ProvisioningBuildOptions? +Aspire.Hosting.Azure.AzureProvisioningResource.ProvisioningBuildOptions.set -> void Aspire.Hosting.Azure.AzureResourceInfrastructure Aspire.Hosting.Azure.AzureResourceInfrastructure.AspireResource.get -> Aspire.Hosting.Azure.AzureProvisioningResource! Aspire.Hosting.Azure.IResourceWithAzureFunctionsConfig Aspire.Hosting.Azure.IResourceWithAzureFunctionsConfig.ApplyAzureFunctionsConfiguration(System.Collections.Generic.IDictionary! target, string! connectionName) -> void Aspire.Hosting.AzureProvisioningResourceExtensions -override Aspire.Hosting.Azure.AspireV8ResourceNamePropertyResolver.ResolveName(Azure.Provisioning.ProvisioningContext! context, Azure.Provisioning.Primitives.Resource! resource, Azure.Provisioning.Primitives.ResourceNameRequirements requirements) -> Azure.Provisioning.BicepValue? +override Aspire.Hosting.Azure.AspireV8ResourceNamePropertyResolver.ResolveName(Azure.Provisioning.ProvisioningBuildOptions! options, Azure.Provisioning.Primitives.ProvisionableResource! resource, Azure.Provisioning.Primitives.ResourceNameRequirements requirements) -> Azure.Provisioning.BicepValue? override Aspire.Hosting.Azure.AzureProvisioningResource.GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true) -> Aspire.Hosting.Azure.BicepTemplateFile override Aspire.Hosting.Azure.AzureProvisioningResource.GetBicepTemplateString() -> string! static Aspire.Hosting.AzureBicepResourceExtensions.WithParameter(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.EndpointReference! value) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/Aspire.Hosting.Azure/Utils/BicepIdentifierHelpers.cs b/src/Aspire.Hosting.Azure/Utils/BicepIdentifierHelpers.cs index af38d6a69bf..20c83ee8b11 100644 --- a/src/Aspire.Hosting.Azure/Utils/BicepIdentifierHelpers.cs +++ b/src/Aspire.Hosting.Azure/Utils/BicepIdentifierHelpers.cs @@ -10,7 +10,7 @@ internal static class BicepIdentifierHelpers { internal static string ThrowIfInvalid(string name, [CallerArgumentExpression(nameof(name))] string? paramName = null) { - Infrastructure.ValidateIdentifierName(name, paramName); + Infrastructure.ValidateBicepIdentifier(name, paramName); return name; } } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs index 27a42ae5fad..fef6e8b8a8e 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs @@ -246,7 +246,7 @@ public async Task AddAzureCosmosDBViaRunMode() var cosmos = builder.AddAzureCosmosDB("cosmos") .ConfigureInfrastructure(infrastructure => { - callbackDatabases = infrastructure.GetResources().OfType(); + callbackDatabases = infrastructure.GetProvisionableResources().OfType(); }); cosmos.AddDatabase("mydatabase"); @@ -340,7 +340,7 @@ public async Task AddAzureCosmosDBViaPublishMode() var cosmos = builder.AddAzureCosmosDB("cosmos") .ConfigureInfrastructure(infrastructure => { - callbackDatabases = infrastructure.GetResources().OfType(); + callbackDatabases = infrastructure.GetProvisionableResources().OfType(); }); cosmos.AddDatabase("mydatabase"); @@ -774,8 +774,8 @@ public async Task AssignParameterPopulatesParametersEverywhere() var manifest = await ManifestUtils.GetManifest(infrastructure1.Resource); Assert.NotNull(moduleInfrastructure); - var infrastructureParameters = moduleInfrastructure.GetParameters().DistinctBy(x => x.IdentifierName); - var infrastructureParametersLookup = infrastructureParameters.ToDictionary(p => p.IdentifierName); + var infrastructureParameters = moduleInfrastructure.GetParameters().DistinctBy(x => x.BicepIdentifier); + var infrastructureParametersLookup = infrastructureParameters.ToDictionary(p => p.BicepIdentifier); Assert.True(infrastructureParametersLookup.ContainsKey("skuName")); var expectedManifest = """ @@ -813,8 +813,8 @@ public async Task AssignParameterWithSpecifiedNamePopulatesParametersEverywhere( var manifest = await ManifestUtils.GetManifest(infrastructure1.Resource); Assert.NotNull(moduleInfrastructure); - var infrastructureParameters = moduleInfrastructure.GetParameters().DistinctBy(x => x.IdentifierName); - var infrastructureParametersLookup = infrastructureParameters.ToDictionary(p => p.IdentifierName); + var infrastructureParameters = moduleInfrastructure.GetParameters().DistinctBy(x => x.BicepIdentifier); + var infrastructureParametersLookup = infrastructureParameters.ToDictionary(p => p.BicepIdentifier); Assert.True(infrastructureParametersLookup.ContainsKey("sku")); var expectedManifest = """ @@ -1879,7 +1879,7 @@ public async Task AddAzureStorageViaRunMode() var storage = builder.AddAzureStorage("storage") .ConfigureInfrastructure(infrastructure => { - var sa = infrastructure.GetResources().OfType().Single(); + var sa = infrastructure.GetProvisionableResources().OfType().Single(); sa.Sku = new StorageSku() { Name = storagesku.AsProvisioningParameter(infrastructure) @@ -2037,7 +2037,7 @@ public async Task AddAzureStorageViaRunModeAllowSharedKeyAccessOverridesDefaultF var storage = builder.AddAzureStorage("storage") .ConfigureInfrastructure(infrastructure => { - var sa = infrastructure.GetResources().OfType().Single(); + var sa = infrastructure.GetProvisionableResources().OfType().Single(); sa.Sku = new StorageSku() { Name = storagesku.AsProvisioningParameter(infrastructure) @@ -2196,7 +2196,7 @@ public async Task AddAzureStorageViaPublishMode() var storage = builder.AddAzureStorage("storage") .ConfigureInfrastructure(infrastructure => { - var sa = infrastructure.GetResources().OfType().Single(); + var sa = infrastructure.GetProvisionableResources().OfType().Single(); sa.Sku = new StorageSku() { Name = storagesku.AsProvisioningParameter(infrastructure) @@ -2354,7 +2354,7 @@ public async Task AddAzureStorageViaPublishModeEnableAllowSharedKeyAccessOverrid var storage = builder.AddAzureStorage("storage") .ConfigureInfrastructure(infrastructure => { - var sa = infrastructure.GetResources().OfType().Single(); + var sa = infrastructure.GetProvisionableResources().OfType().Single(); sa.Sku = new StorageSku() { Name = storagesku.AsProvisioningParameter(infrastructure) @@ -2514,7 +2514,7 @@ public async Task AddAzureSearch() var search = builder.AddAzureSearch("search") .ConfigureInfrastructure(infrastructure => { - var search = infrastructure.GetResources().OfType().Single(); + var search = infrastructure.GetProvisionableResources().OfType().Single(); search.SearchSkuName = sku.AsProvisioningParameter(infrastructure); }); @@ -2636,12 +2636,12 @@ public async Task AddAzureOpenAI(bool overrideLocalAuthDefault) var openai = builder.AddAzureOpenAI("openai") .ConfigureInfrastructure(infrastructure => { - aiDeployments = infrastructure.GetResources().OfType(); + aiDeployments = infrastructure.GetProvisionableResources().OfType(); if (overrideLocalAuthDefault) { - var account = infrastructure.GetResources().OfType().Single(); - account.Properties.Value!.DisableLocalAuth = false; + var account = infrastructure.GetProvisionableResources().OfType().Single(); + account.Properties.DisableLocalAuth = false; } }) .AddDeployment(new("mymodel", "gpt-35-turbo", "0613", "Basic", 4)) @@ -2781,24 +2781,17 @@ public async Task InfrastructureCanBeMutatedAfterCreation() }) .ConfigureInfrastructure(r => { - var vault = r.GetResources().OfType().Single(); + var vault = r.GetProvisionableResources().OfType().Single(); Assert.NotNull(vault); r.Add(new ProvisioningOutput("vaultUri", typeof(string)) { - Value = - new MemberExpression( - new MemberExpression( - new IdentifierExpression(vault.IdentifierName), - "properties"), - "vaultUri") - // TODO: this should be - //Value = keyVault.VaultUri + Value = vault.Properties.VaultUri }); }) .ConfigureInfrastructure(r => { - var vault = r.GetResources().OfType().Single(); + var vault = r.GetProvisionableResources().OfType().Single(); Assert.NotNull(vault); r.Add(new KeyVaultSecret("secret") diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs index a295117147e..a72765a3314 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs @@ -6,7 +6,9 @@ using System.Runtime.CompilerServices; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; +using Azure.Provisioning; using Azure.Provisioning.AppContainers; +using Azure.Provisioning.Primitives; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -257,8 +259,8 @@ public async Task AddContainerAppsInfrastructureWithParameterReference() Value = value.AsProvisioningParameter(module) }; - c.Template.Value!.Containers[0].Value!.Env.Add(val); - c.Template.Value!.Scale.Value!.MinReplicas = minReplicas.AsProvisioningParameter(module); + c.Template.Containers[0].Value!.Env.Add(val); + c.Template.Scale.MinReplicas = minReplicas.AsProvisioningParameter(module); }); using var app = builder.Build(); @@ -651,9 +653,9 @@ public async Task PublishAsContainerAppInfluencesContainerAppDefinition() builder.AddContainer("api", "myimage") .PublishAsAzureContainerApp((module, c) => { - Assert.Contains(c, module.GetResources()); + Assert.Contains(c, module.GetProvisionableResources()); - c.Template.Value!.Scale.Value!.MinReplicas = 0; + c.Template.Scale.MinReplicas = 0; }); using var app = builder.Build(); @@ -1165,6 +1167,94 @@ param outputs_azure_container_apps_environment_id string Assert.Equal(expectedBicep, bicep); } + [Fact] + public async Task CanCustomizeWithProvisioningBuildOptions() + { + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + builder.Services.Configure(options => options.ProvisioningBuildOptions.InfrastructureResolvers.Insert(0, new MyResourceNamePropertyResolver())); + builder.AddAzureContainerAppsInfrastructure(); + + builder.AddContainer("api1", "myimage"); + + using var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, default); + + var model = app.Services.GetRequiredService(); + + var container = Assert.Single(model.GetContainerResources()); + + container.TryGetLastAnnotation(out var target); + + var resource = target?.DeploymentTarget as AzureProvisioningResource; + + Assert.NotNull(resource); + + var (_, bicep) = await ManifestUtils.GetManifestWithBicep(resource); + + var expectedBicep = + """ + @description('The location for the resource(s) to be deployed.') + param location string = resourceGroup().location + + param outputs_azure_container_registry_managed_identity_id string + + param outputs_managed_identity_client_id string + + param outputs_azure_container_apps_environment_id string + + resource api1 'Microsoft.App/containerApps@2024-03-01' = { + name: 'api1-my' + location: location + properties: { + configuration: { + activeRevisionsMode: 'Single' + } + environmentId: outputs_azure_container_apps_environment_id + template: { + containers: [ + { + image: 'myimage:latest' + name: 'api1' + env: [ + { + name: 'AZURE_CLIENT_ID' + value: outputs_managed_identity_client_id + } + ] + } + ] + scale: { + minReplicas: 1 + } + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${outputs_azure_container_registry_managed_identity_id}': { } + } + } + } + """; + output.WriteLine(bicep); + Assert.Equal(expectedBicep, bicep); + } + + private sealed class MyResourceNamePropertyResolver : DynamicResourceNamePropertyResolver + { + public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options) + { + if (construct is ContainerApp app) + { + app.Name = app.Name.Value + "-my"; + } + + base.ResolveProperties(construct, options); + } + } + [Fact] public async Task ExternalEndpointBecomesIngress() { diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs index e195bcc1601..9ec34838203 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureRedisExtensionsTests.cs @@ -50,11 +50,11 @@ param principalName string capacity: 1 } enableNonSslPort: false + disableAccessKeyAuthentication: true minimumTlsVersion: '1.2' redisConfiguration: { 'aad-enabled': 'true' } - disableAccessKeyAuthentication: 'true' } tags: { 'aspire-resource-name': 'redis-cache' @@ -116,7 +116,6 @@ param keyVaultName string } enableNonSslPort: false minimumTlsVersion: '1.2' - redisConfiguration: { } } tags: { 'aspire-resource-name': 'redis-cache' diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureResourceOptionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureResourceOptionsTests.cs index e5ec91abf77..f532f930817 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureResourceOptionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureResourceOptionsTests.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 Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Utils; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -11,7 +12,7 @@ namespace Aspire.Hosting.Azure.Tests; public class AzureResourceOptionsTests(ITestOutputHelper output) { /// - /// Ensures that an AzureProvisioningOptions can be configured to modify the ProvisioningContext + /// Ensures that an AzureProvisioningOptions can be configured to modify the ProvisioningBuildOptions /// used when building the bicep for an Azure resource. /// /// This uses the .NET Aspire v8.x naming policy, which always calls toLower, appends a unique string with no separator, @@ -27,11 +28,16 @@ public async Task AzureResourceOptionsCanBeConfigured() { builder.Services.Configure(options => { - options.ProvisioningContext.PropertyResolvers.Insert(0, new AspireV8ResourceNamePropertyResolver()); + options.ProvisioningBuildOptions.InfrastructureResolvers.Insert(0, new AspireV8ResourceNamePropertyResolver()); }); var serviceBus = builder.AddAzureServiceBus("sb"); + // ensure that resources with a hyphen still have a hyphen in the bicep name + var sqlDatabase = builder.AddAzureSqlServer("sql-server") + .RunAsContainer(x => x.WithLifetime(ContainerLifetime.Persistent)) + .AddDatabase("evadexdb"); + using var app = builder.Build(); await app.StartAsync(); @@ -76,6 +82,56 @@ param principalType string output.WriteLine(actualBicep); Assert.Equal(expectedBicep, actualBicep); + actualBicep = await File.ReadAllTextAsync(Path.Combine(tempDir.FullName, "sql-server.module.bicep")); + + expectedBicep = """ + @description('The location for the resource(s) to be deployed.') + param location string = resourceGroup().location + + param principalId string + + param principalName string + + resource sql_server 'Microsoft.Sql/servers@2021-11-01' = { + name: toLower(take('sql-server${uniqueString(resourceGroup().id)}', 24)) + location: location + properties: { + administrators: { + administratorType: 'ActiveDirectory' + login: principalName + sid: principalId + tenantId: subscription().tenantId + azureADOnlyAuthentication: true + } + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + version: '12.0' + } + tags: { + 'aspire-resource-name': 'sql-server' + } + } + + resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2021-11-01' = { + name: 'AllowAllAzureIps' + properties: { + endIpAddress: '0.0.0.0' + startIpAddress: '0.0.0.0' + } + parent: sql_server + } + + resource evadexdb 'Microsoft.Sql/servers/databases@2021-11-01' = { + name: 'evadexdb' + location: location + parent: sql_server + } + + output sqlServerFqdn string = sql_server.properties.fullyQualifiedDomainName + """; + output.WriteLine(actualBicep); + Assert.Equal(expectedBicep, actualBicep); + await app.StopAsync(); } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureWebPubSubExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureWebPubSubExtensionsTests.cs index 2b43b5c002e..d9759778341 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureWebPubSubExtensionsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureWebPubSubExtensionsTests.cs @@ -29,7 +29,7 @@ public async Task AddWebPubSubHubNameWithSpecialChars() WebPubSubHub? realHub = null; var wps = builder.AddAzureWebPubSub("wps1").ConfigureInfrastructure(infrastructure => { - realHub = infrastructure.GetResources().OfType().Single(); + realHub = infrastructure.GetProvisionableResources().OfType().Single(); }); var hubName = "a-b-c"; var hub = wps.AddHub(hubName); @@ -38,7 +38,7 @@ public async Task AddWebPubSubHubNameWithSpecialChars() var manifest = await ManifestUtils.GetManifestWithBicep(wps.Resource); Assert.NotNull(realHub); Assert.Equal(hubName, realHub.Name.Value); - Assert.Equal("a_b_c", realHub.IdentifierName); + Assert.Equal("a_b_c", realHub.BicepIdentifier); } [Fact] @@ -74,9 +74,9 @@ public async Task AddAzureWebPubSubHubWorks() param sku string = 'Free_F1' param capacity int = 1 - + param principalId string - + param principalType string resource wps1 'Microsoft.SignalRService/webPubSub@2024-03-01' = { @@ -103,7 +103,6 @@ param principalType string resource abc 'Microsoft.SignalRService/webPubSub/hubs@2024-03-01' = { name: 'abc' - properties: { } parent: wps1 } @@ -119,8 +118,8 @@ public async Task AddWebPubSubWithHubConfigure() var hubName = "abc"; var wps = builder.AddAzureWebPubSub("wps1").ConfigureInfrastructure(infrastructure => { - var hub = infrastructure.GetResources().OfType().First(i => i.IdentifierName == hubName); - hub.Properties.Value!.AnonymousConnectPolicy = "allow"; + var hub = infrastructure.GetProvisionableResources().OfType().First(i => i.BicepIdentifier == hubName); + hub.Properties.AnonymousConnectPolicy = "allow"; }); wps.AddHub(hubName); @@ -150,9 +149,9 @@ public async Task AddWebPubSubWithHubConfigure() param sku string = 'Free_F1' param capacity int = 1 - + param principalId string - + param principalType string resource wps1 'Microsoft.SignalRService/webPubSub@2024-03-01' = { @@ -280,8 +279,8 @@ public async Task ConfigureConstructOverridesAddEventHandler() var serviceA = builder.AddProject("serviceA", o => o.ExcludeLaunchProfile = true).WithHttpsEndpoint(); var wps = builder.AddAzureWebPubSub("wps1").ConfigureInfrastructure(infrastructure => { - var hub = infrastructure.GetResources().OfType().First(i => string.Equals(i.IdentifierName, "abc", StringComparison.OrdinalIgnoreCase)); - hub.Properties.Value!.EventHandlers.Add(new WebPubSubEventHandler() { UrlTemplate = "http://fake.com" }); + var hub = infrastructure.GetProvisionableResources().OfType().First(i => string.Equals(i.BicepIdentifier, "abc", StringComparison.OrdinalIgnoreCase)); + hub.Properties.EventHandlers.Add(new WebPubSubEventHandler() { UrlTemplate = "http://fake.com" }); }); wps.AddHub("ABC").AddEventHandler($"http://fake1.com"); // Hub name is case insensitive @@ -358,10 +357,10 @@ public async Task AddAzureWebPubSubHubSettings() var url1 = "fake3.com"; var wps = builder.AddAzureWebPubSub("wps1").ConfigureInfrastructure(infrastructure => { - var hub = infrastructure.GetResources().OfType().First(i => i.IdentifierName == "hub1"); - hub.Properties.Value!.AnonymousConnectPolicy = "allow"; + var hub = infrastructure.GetProvisionableResources().OfType().First(i => i.BicepIdentifier == "hub1"); + hub.Properties.AnonymousConnectPolicy = "allow"; // allow directly event handler set - hub.Properties.Value!.EventHandlers.Add(new WebPubSubEventHandler() { UrlTemplate = "http://fake1.com" }); + hub.Properties.EventHandlers.Add(new WebPubSubEventHandler() { UrlTemplate = "http://fake1.com" }); }); // allow event handler set using a separate call // allow mulitple calls, and order matters @@ -448,7 +447,6 @@ param principalType string { urlTemplate: 'http://fake2.com' userEventPattern: 'event1' - auth: { } } { urlTemplate: 'http://fake3.com' diff --git a/tests/Aspire.Hosting.Tests/Schema/SchemaTests.cs b/tests/Aspire.Hosting.Tests/Schema/SchemaTests.cs index 12ed03647b2..01d3d7fb291 100644 --- a/tests/Aspire.Hosting.Tests/Schema/SchemaTests.cs +++ b/tests/Aspire.Hosting.Tests/Schema/SchemaTests.cs @@ -146,7 +146,7 @@ public static TheoryData> Applica builder.AddProject("project") .PublishAsAzureContainerApp((infrastructure, app) => { - app.Template.Value!.Scale.Value!.MinReplicas = minReplicas.AsProvisioningParameter(infrastructure); + app.Template.Scale.MinReplicas = minReplicas.AsProvisioningParameter(infrastructure); }); } @@ -167,7 +167,7 @@ public static TheoryData> Applica builder.AddContainer("mycontainer", "myimage") .PublishAsAzureContainerApp((infrastructure, app) => { - app.Template.Value!.Scale.Value!.MinReplicas = minReplicas.AsProvisioningParameter(infrastructure); + app.Template.Scale.MinReplicas = minReplicas.AsProvisioningParameter(infrastructure); }); }