Skip to content

Commit be2a895

Browse files
eerhardtjoperezr
andauthored
Update Azure.Provisioning to latest (#6390)
* Update Azure.Provisioning to latest Respond to latest renames getting the API ready for GA release. Fix #6376 * Rename properties to match new type names. * Use IsAccessKeyAuthenticationDisabled property * Fix ContainerAppExtensions for new Azure.Provisioning version * Simplify BicepValueFormattableString * Simplify ReferenceExpression to BicepValue<string> logic * Fix Azure Redis test for new property order * Update playground bicep * Fix issue in AspireV8ResourceNamePropertyResolver when resource name has a dash. In .NET Aspire 8.x, when the Aspire resource name has dashes, we used those dashes in the Azure resource name. But we are incorrectly converting them to underscores. Fix #6474 * Allow customizing Azure ContainerApp resources with ProvisioningBuildOptions. Fix #6496 --------- Co-authored-by: Jose Perez Rodriguez <[email protected]>
1 parent 08dd992 commit be2a895

File tree

34 files changed

+350
-262
lines changed

34 files changed

+350
-262
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
88
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
99
<TestcontainersPackageVersion>3.10.0</TestcontainersPackageVersion>
10-
<AzureProvisiongVersion>1.0.0-beta.1</AzureProvisiongVersion>
10+
<AzureProvisiongVersion>1.0.0</AzureProvisiongVersion>
1111
</PropertyGroup>
1212
<ItemGroup>
1313
<!-- AWS SDK for .NET dependencies -->

playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
app.ConfigureCustomDomain(customDomain, certificateName);
4343

4444
// Scale to 0
45-
app.Template.Value!.Scale.Value!.MinReplicas = 0;
45+
app.Template.Scale.MinReplicas = 0;
4646
});
4747

4848
#if !SKIP_DASHBOARD_REFERENCE

playground/bicep/BicepSample.AppHost/redis.module.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ resource redis 'Microsoft.Cache/redis@2024-03-01' = {
1515
capacity: 1
1616
}
1717
enableNonSslPort: false
18+
disableAccessKeyAuthentication: true
1819
minimumTlsVersion: '1.2'
1920
redisConfiguration: {
2021
'aad-enabled': 'true'
2122
}
22-
disableAccessKeyAuthentication: 'true'
2323
}
2424
tags: {
2525
'aspire-resource-name': 'redis'

playground/cdk/CdkSample.AppHost/Program.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Azure.Provisioning.ApplicationInsights;
5-
using Azure.Provisioning.Expressions;
65
using Azure.Provisioning.KeyVault;
76
using Azure.Provisioning.OperationalInsights;
87
using Azure.Provisioning.ServiceBus;
@@ -17,7 +16,7 @@
1716
var storage = builder.AddAzureStorage("storage")
1817
.ConfigureInfrastructure(infrastructure =>
1918
{
20-
var account = infrastructure.GetResources().OfType<StorageAccount>().Single();
19+
var account = infrastructure.GetProvisionableResources().OfType<StorageAccount>().Single();
2120
account.Sku = new StorageSku() { Name = sku.AsProvisioningParameter(infrastructure) };
2221
account.Location = locationOverride.AsProvisioningParameter(infrastructure);
2322
});
@@ -30,7 +29,7 @@
3029
var keyvault = builder.AddAzureKeyVault("mykv")
3130
.ConfigureInfrastructure(infrastructure =>
3231
{
33-
var keyVault = infrastructure.GetResources().OfType<KeyVaultService>().Single();
32+
var keyVault = infrastructure.GetProvisionableResources().OfType<KeyVaultService>().Single();
3433
var secret = new KeyVaultSecret("mysecret")
3534
{
3635
Parent = keyVault,
@@ -55,26 +54,22 @@
5554
.AddQueue("queue1")
5655
.ConfigureInfrastructure(infrastructure =>
5756
{
58-
var queue = infrastructure.GetResources().OfType<ServiceBusQueue>().Single(q => q.IdentifierName == "queue1");
57+
var queue = infrastructure.GetProvisionableResources().OfType<ServiceBusQueue>().Single(q => q.BicepIdentifier == "queue1");
5958
queue.MaxDeliveryCount = 5;
60-
queue.LockDuration = new StringLiteral("PT5M");
61-
// TODO: this should be
62-
// queue.LockDuration = TimeSpan.FromMinutes(5);
59+
queue.LockDuration = TimeSpan.FromMinutes(5);
6360
})
6461
.AddTopic("topic1")
6562
.ConfigureInfrastructure(infrastructure =>
6663
{
67-
var topic = infrastructure.GetResources().OfType<ServiceBusTopic>().Single(q => q.IdentifierName == "topic1");
64+
var topic = infrastructure.GetProvisionableResources().OfType<ServiceBusTopic>().Single(q => q.BicepIdentifier == "topic1");
6865
topic.EnablePartitioning = true;
6966
})
7067
.AddTopic("topic2")
7168
.AddSubscription("topic1", "subscription1")
7269
.ConfigureInfrastructure(infrastructure =>
7370
{
74-
var subscription = infrastructure.GetResources().OfType<ServiceBusSubscription>().Single(q => q.IdentifierName == "subscription1");
75-
subscription.LockDuration = new StringLiteral("PT5M");
76-
// TODO: this should be
77-
//subscription.LockDuration = TimeSpan.FromMinutes(5);
71+
var subscription = infrastructure.GetProvisionableResources().OfType<ServiceBusSubscription>().Single(q => q.BicepIdentifier == "subscription1");
72+
subscription.LockDuration = TimeSpan.FromMinutes(5);
7873
subscription.RequiresSession = true;
7974
})
8075
.AddSubscription("topic1", "subscription2")
@@ -89,7 +84,7 @@
8984
var logAnalyticsWorkspace = builder.AddAzureLogAnalyticsWorkspace("logAnalyticsWorkspace")
9085
.ConfigureInfrastructure(infrastructure =>
9186
{
92-
var logAnalyticsWorkspace = infrastructure.GetResources().OfType<OperationalInsightsWorkspace>().Single();
87+
var logAnalyticsWorkspace = infrastructure.GetProvisionableResources().OfType<OperationalInsightsWorkspace>().Single();
9388
logAnalyticsWorkspace.Sku = new OperationalInsightsWorkspaceSku()
9489
{
9590
Name = OperationalInsightsWorkspaceSkuName.PerNode
@@ -99,7 +94,7 @@
9994
var appInsights = builder.AddAzureApplicationInsights("appInsights", logAnalyticsWorkspace)
10095
.ConfigureInfrastructure(infrastructure =>
10196
{
102-
var appInsights = infrastructure.GetResources().OfType<ApplicationInsightsComponent>().Single();
97+
var appInsights = infrastructure.GetProvisionableResources().OfType<ApplicationInsightsComponent>().Single();
10398
appInsights.IngestionMode = ComponentIngestionMode.LogAnalytics;
10499
});
105100

playground/cdk/CdkSample.AppHost/cache.module.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ resource cache 'Microsoft.Cache/redis@2024-03-01' = {
1515
capacity: 1
1616
}
1717
enableNonSslPort: false
18+
disableAccessKeyAuthentication: true
1819
minimumTlsVersion: '1.2'
1920
redisConfiguration: {
2021
'aad-enabled': 'true'
2122
}
22-
disableAccessKeyAuthentication: 'true'
2323
}
2424
tags: {
2525
'aspire-resource-name': 'cache'

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

Lines changed: 46 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Globalization;
5-
using System.Text;
65
using System.Text.RegularExpressions;
76
using Aspire.Hosting.ApplicationModel;
87
using Aspire.Hosting.Lifecycle;
@@ -12,14 +11,18 @@
1211
using Azure.Provisioning.KeyVault;
1312
using Azure.Provisioning.Resources;
1413
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
1515

1616
namespace Aspire.Hosting.Azure;
1717

1818
/// <summary>
1919
/// Represents the infrastructure for Azure Container Apps within the Aspire Hosting environment.
2020
/// Implements the <see cref="IDistributedApplicationLifecycleHook"/> interface to provide lifecycle hooks for distributed applications.
2121
/// </summary>
22-
internal sealed class AzureContainerAppsInfrastructure(ILogger<AzureContainerAppsInfrastructure> logger, DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
22+
internal sealed class AzureContainerAppsInfrastructure(
23+
ILogger<AzureContainerAppsInfrastructure> logger,
24+
IOptions<AzureProvisioningOptions> provisioningOptions,
25+
DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
2326
{
2427
public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
2528
{
@@ -49,7 +52,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
4952
continue;
5053
}
5154

52-
var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, executionContext, cancellationToken).ConfigureAwait(false);
55+
var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, provisioningOptions.Value, executionContext, cancellationToken).ConfigureAwait(false);
5356

5457
r.Annotations.Add(new DeploymentTargetAnnotation(containerApp));
5558
}
@@ -75,11 +78,12 @@ IManifestExpressionProvider clientId
7578

7679
private readonly Dictionary<IResource, ContainerAppContext> _containerApps = [];
7780

78-
public async Task<AzureBicepResource> CreateContainerAppAsync(IResource resource, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
81+
public async Task<AzureBicepResource> CreateContainerAppAsync(IResource resource, AzureProvisioningOptions provisioningOptions, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
7982
{
8083
var context = await ProcessResourceAsync(resource, executionContext, cancellationToken).ConfigureAwait(false);
8184

8285
var provisioningResource = new AzureProvisioningResource(resource.Name, context.BuildContainerApp);
86+
provisioningResource.ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions;
8387

8488
provisioningResource.Annotations.Add(new ManifestPublishingCallbackAnnotation(provisioningResource.WriteToManifest));
8589

@@ -143,7 +147,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c)
143147
containerImageParam = AllocateContainerImageParameter();
144148
}
145149

146-
var containerAppResource = new ContainerApp(Infrastructure.NormalizeIdentifierName(resource.Name))
150+
var containerAppResource = new ContainerApp(Infrastructure.NormalizeBicepIdentifier(resource.Name))
147151
{
148152
Name = resource.Name.ToLowerInvariant()
149153
};
@@ -180,7 +184,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c)
180184
var containerAppContainer = new ContainerAppContainer();
181185
template.Containers = [containerAppContainer];
182186

183-
containerAppContainer.Image = containerImageParam is null ? containerImageName : containerImageParam;
187+
containerAppContainer.Image = containerImageParam is null ? containerImageName! : containerImageParam;
184188
containerAppContainer.Name = resource.Name;
185189

186190
AddEnvironmentVariablesAndCommandLineArgs(containerAppContainer);
@@ -497,7 +501,8 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
497501
{
498502
var managedIdentityParameter = AllocateManagedIdentityIdParameter();
499503
secret.Identity = managedIdentityParameter;
500-
secret.KeyVaultUri = new BicepValue<Uri>(argValue.Expression!);
504+
// TODO: this should be able to use ToUri(), but it hit an issue
505+
secret.KeyVaultUri = new BicepValue<Uri>(((BicepExpression?)argValue)!);
501506
}
502507
else
503508
{
@@ -531,7 +536,6 @@ private static BicepValue<string> ResolveValue(object val)
531536
{
532537
BicepValue<string> s => s,
533538
string s => s,
534-
BicepValueFormattableString fs => Interpolate(fs),
535539
ProvisioningParameter p => p,
536540
_ => throw new NotSupportedException("Unsupported value type " + val.GetType())
537541
};
@@ -698,7 +702,7 @@ BicepValue<string> GetHostValue(string? prefix = null, string? suffix = null)
698702
args[index++] = val;
699703
}
700704

701-
return (new BicepValueFormattableString(expr.Format, args), finalSecretType);
705+
return (Interpolate(expr.Format, args), finalSecretType);
702706

703707
}
704708

@@ -714,7 +718,7 @@ private BicepValue<string> AllocateKeyVaultSecretUriReference(BicepSecretOutputR
714718
{
715719
// We resolve the keyvault that represents the storage for secret outputs
716720
var parameter = AllocateParameter(SecretOutputExpression.GetSecretOutputKeyVault(secretOutputReference.Resource));
717-
kv = KeyVaultService.FromExisting($"{parameter.IdentifierName}_kv");
721+
kv = KeyVaultService.FromExisting($"{parameter.BicepIdentifier}_kv");
718722
kv.Name = parameter;
719723

720724
KeyVaultRefs[secretOutputReference.Resource.Name] = kv;
@@ -723,19 +727,15 @@ private BicepValue<string> AllocateKeyVaultSecretUriReference(BicepSecretOutputR
723727
if (!KeyVaultSecretRefs.TryGetValue(secretOutputReference.ValueExpression, out var secret))
724728
{
725729
// Now we resolve the secret
726-
var secretIdentifierName = Infrastructure.NormalizeIdentifierName($"{kv.IdentifierName}_{secretOutputReference.Name}");
727-
secret = KeyVaultSecret.FromExisting(secretIdentifierName);
730+
var secretBicepIdentifier = Infrastructure.NormalizeBicepIdentifier($"{kv.BicepIdentifier}_{secretOutputReference.Name}");
731+
secret = KeyVaultSecret.FromExisting(secretBicepIdentifier);
728732
secret.Name = secretOutputReference.Name;
729733
secret.Parent = kv;
730734

731735
KeyVaultSecretRefs[secretOutputReference.ValueExpression] = secret;
732736
}
733737

734-
// TODO: There should be a better way to do this?
735-
return new MemberExpression(
736-
new MemberExpression(
737-
new IdentifierExpression(secret.IdentifierName), "properties"),
738-
"secretUri");
738+
return secret.Properties.SecretUri;
739739
}
740740

741741
private ProvisioningParameter AllocateContainerImageParameter()
@@ -895,81 +895,45 @@ private void AddContainerRegistryParameters(ContainerAppConfiguration app)
895895
}
896896
}
897897

898-
// REVIEW: BicepFunction.Interpolate is buggy and doesn't handle nested formattable strings correctly
899-
// This is a workaround to handle nested formattable strings until the bug is fixed.
900-
private static BicepValue<string> Interpolate(BicepValueFormattableString text)
898+
private static BicepValue<string> Interpolate(string format, object[] args)
901899
{
902-
var formatStringBuilder = new StringBuilder();
903-
var arguments = new List<BicepValue<string>>();
900+
var bicepStringBuilder = new BicepStringBuilder();
904901

905-
void ProcessFormattableString(BicepValueFormattableString formattableString, int argumentIndex)
906-
{
907-
var span = formattableString.Format.AsSpan();
908-
var skip = 0;
909-
910-
foreach (var match in Regex.EnumerateMatches(span, @"{\d+}"))
911-
{
912-
formatStringBuilder.Append(span[..(match.Index - skip)]);
902+
var span = format.AsSpan();
903+
var skip = 0;
904+
var argumentIndex = 0;
913905

914-
var argument = formattableString.GetArgument(argumentIndex);
906+
foreach (var match in Regex.EnumerateMatches(span, @"{\d+}"))
907+
{
908+
bicepStringBuilder.Append(span[..(match.Index - skip)].ToString());
915909

916-
if (argument is BicepValueFormattableString nested)
917-
{
918-
// Inline the nested formattable string
919-
ProcessFormattableString(nested, 0);
920-
}
921-
else
922-
{
923-
formatStringBuilder.Append(CultureInfo.InvariantCulture, $"{{{arguments.Count}}}");
924-
if (argument is BicepValue<string> bicepValue)
925-
{
926-
arguments.Add(bicepValue);
927-
}
928-
else if (argument is string s)
929-
{
930-
arguments.Add(s);
931-
}
932-
else if (argument is ProvisioningParameter provisioningParameter)
933-
{
934-
arguments.Add(provisioningParameter);
935-
}
936-
else
937-
{
938-
throw new NotSupportedException($"{argument} is not supported");
939-
}
940-
}
910+
var argument = args[argumentIndex];
941911

942-
argumentIndex++;
943-
span = span[(match.Index + match.Length - skip)..];
944-
skip = match.Index + match.Length;
912+
if (argument is BicepValue<string> bicepValue)
913+
{
914+
bicepStringBuilder.Append($"{bicepValue}");
915+
}
916+
else if (argument is string s)
917+
{
918+
bicepStringBuilder.Append(s);
919+
}
920+
else if (argument is ProvisioningParameter provisioningParameter)
921+
{
922+
bicepStringBuilder.Append($"{provisioningParameter}");
923+
}
924+
else
925+
{
926+
throw new NotSupportedException($"{argument} is not supported");
945927
}
946928

947-
formatStringBuilder.Append(span);
948-
}
949-
950-
ProcessFormattableString(text, 0);
951-
952-
var formatString = formatStringBuilder.ToString();
953-
954-
if (formatString == "{0}")
955-
{
956-
return arguments[0];
929+
argumentIndex++;
930+
span = span[(match.Index + match.Length - skip)..];
931+
skip = match.Index + match.Length;
957932
}
958933

959-
return BicepFunction.Interpolate(new BicepValueFormattableString(formatString, [.. arguments]));
960-
}
934+
bicepStringBuilder.Append(span.ToString());
961935

962-
/// <summary>
963-
/// A custom FormattableString implementation that allows us to inline nested formattable strings.
964-
/// </summary>
965-
private sealed class BicepValueFormattableString(string formatString, object[] values) : FormattableString
966-
{
967-
public override int ArgumentCount => values.Length;
968-
public override string Format => formatString;
969-
public override object? GetArgument(int index) => values[index];
970-
public override object?[] GetArguments() => values;
971-
public override string ToString(IFormatProvider? formatProvider) => Format;
972-
public override string ToString() => formatString;
936+
return bicepStringBuilder.Build();
973937
}
974938

975939
/// <summary>

0 commit comments

Comments
 (0)