Skip to content

Commit

Permalink
Update Azure.Provisioning to latest (#6390)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
eerhardt and joperezr authored Oct 28, 2024
1 parent 08dd992 commit be2a895
Show file tree
Hide file tree
Showing 34 changed files with 350 additions and 262 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<TestcontainersPackageVersion>3.10.0</TestcontainersPackageVersion>
<AzureProvisiongVersion>1.0.0-beta.1</AzureProvisiongVersion>
<AzureProvisiongVersion>1.0.0</AzureProvisiongVersion>
</PropertyGroup>
<ItemGroup>
<!-- AWS SDK for .NET dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
app.ConfigureCustomDomain(customDomain, certificateName);
// Scale to 0
app.Template.Value!.Scale.Value!.MinReplicas = 0;
app.Template.Scale.MinReplicas = 0;
});

#if !SKIP_DASHBOARD_REFERENCE
Expand Down
2 changes: 1 addition & 1 deletion playground/bicep/BicepSample.AppHost/redis.module.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
23 changes: 9 additions & 14 deletions playground/cdk/CdkSample.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,7 +16,7 @@
var storage = builder.AddAzureStorage("storage")
.ConfigureInfrastructure(infrastructure =>
{
var account = infrastructure.GetResources().OfType<StorageAccount>().Single();
var account = infrastructure.GetProvisionableResources().OfType<StorageAccount>().Single();
account.Sku = new StorageSku() { Name = sku.AsProvisioningParameter(infrastructure) };
account.Location = locationOverride.AsProvisioningParameter(infrastructure);
});
Expand All @@ -30,7 +29,7 @@
var keyvault = builder.AddAzureKeyVault("mykv")
.ConfigureInfrastructure(infrastructure =>
{
var keyVault = infrastructure.GetResources().OfType<KeyVaultService>().Single();
var keyVault = infrastructure.GetProvisionableResources().OfType<KeyVaultService>().Single();
var secret = new KeyVaultSecret("mysecret")
{
Parent = keyVault,
Expand All @@ -55,26 +54,22 @@
.AddQueue("queue1")
.ConfigureInfrastructure(infrastructure =>
{
var queue = infrastructure.GetResources().OfType<ServiceBusQueue>().Single(q => q.IdentifierName == "queue1");
var queue = infrastructure.GetProvisionableResources().OfType<ServiceBusQueue>().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<ServiceBusTopic>().Single(q => q.IdentifierName == "topic1");
var topic = infrastructure.GetProvisionableResources().OfType<ServiceBusTopic>().Single(q => q.BicepIdentifier == "topic1");
topic.EnablePartitioning = true;
})
.AddTopic("topic2")
.AddSubscription("topic1", "subscription1")
.ConfigureInfrastructure(infrastructure =>
{
var subscription = infrastructure.GetResources().OfType<ServiceBusSubscription>().Single(q => q.IdentifierName == "subscription1");
subscription.LockDuration = new StringLiteral("PT5M");
// TODO: this should be
//subscription.LockDuration = TimeSpan.FromMinutes(5);
var subscription = infrastructure.GetProvisionableResources().OfType<ServiceBusSubscription>().Single(q => q.BicepIdentifier == "subscription1");
subscription.LockDuration = TimeSpan.FromMinutes(5);
subscription.RequiresSession = true;
})
.AddSubscription("topic1", "subscription2")
Expand All @@ -89,7 +84,7 @@
var logAnalyticsWorkspace = builder.AddAzureLogAnalyticsWorkspace("logAnalyticsWorkspace")
.ConfigureInfrastructure(infrastructure =>
{
var logAnalyticsWorkspace = infrastructure.GetResources().OfType<OperationalInsightsWorkspace>().Single();
var logAnalyticsWorkspace = infrastructure.GetProvisionableResources().OfType<OperationalInsightsWorkspace>().Single();
logAnalyticsWorkspace.Sku = new OperationalInsightsWorkspaceSku()
{
Name = OperationalInsightsWorkspaceSkuName.PerNode
Expand All @@ -99,7 +94,7 @@
var appInsights = builder.AddAzureApplicationInsights("appInsights", logAnalyticsWorkspace)
.ConfigureInfrastructure(infrastructure =>
{
var appInsights = infrastructure.GetResources().OfType<ApplicationInsightsComponent>().Single();
var appInsights = infrastructure.GetProvisionableResources().OfType<ApplicationInsightsComponent>().Single();
appInsights.IngestionMode = ComponentIngestionMode.LogAnalytics;
});

Expand Down
2 changes: 1 addition & 1 deletion playground/cdk/CdkSample.AppHost/cache.module.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,14 +11,18 @@
using Azure.Provisioning.KeyVault;
using Azure.Provisioning.Resources;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.Azure;

/// <summary>
/// Represents the infrastructure for Azure Container Apps within the Aspire Hosting environment.
/// Implements the <see cref="IDistributedApplicationLifecycleHook"/> interface to provide lifecycle hooks for distributed applications.
/// </summary>
internal sealed class AzureContainerAppsInfrastructure(ILogger<AzureContainerAppsInfrastructure> logger, DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
internal sealed class AzureContainerAppsInfrastructure(
ILogger<AzureContainerAppsInfrastructure> logger,
IOptions<AzureProvisioningOptions> provisioningOptions,
DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
{
public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -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));
}
Expand All @@ -75,11 +78,12 @@ IManifestExpressionProvider clientId

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

public async Task<AzureBicepResource> CreateContainerAppAsync(IResource resource, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
public async Task<AzureBicepResource> 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));

Expand Down Expand Up @@ -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()
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -497,7 +501,8 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
{
var managedIdentityParameter = AllocateManagedIdentityIdParameter();
secret.Identity = managedIdentityParameter;
secret.KeyVaultUri = new BicepValue<Uri>(argValue.Expression!);
// TODO: this should be able to use ToUri(), but it hit an issue
secret.KeyVaultUri = new BicepValue<Uri>(((BicepExpression?)argValue)!);
}
else
{
Expand Down Expand Up @@ -531,7 +536,6 @@ private static BicepValue<string> ResolveValue(object val)
{
BicepValue<string> s => s,
string s => s,
BicepValueFormattableString fs => Interpolate(fs),
ProvisioningParameter p => p,
_ => throw new NotSupportedException("Unsupported value type " + val.GetType())
};
Expand Down Expand Up @@ -698,7 +702,7 @@ BicepValue<string> GetHostValue(string? prefix = null, string? suffix = null)
args[index++] = val;
}

return (new BicepValueFormattableString(expr.Format, args), finalSecretType);
return (Interpolate(expr.Format, args), finalSecretType);

}

Expand All @@ -714,7 +718,7 @@ private BicepValue<string> 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;
Expand All @@ -723,19 +727,15 @@ private BicepValue<string> 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()
Expand Down Expand Up @@ -895,81 +895,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<string> Interpolate(BicepValueFormattableString text)
private static BicepValue<string> Interpolate(string format, object[] args)
{
var formatStringBuilder = new StringBuilder();
var arguments = new List<BicepValue<string>>();
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<string> 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<string> 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());

/// <summary>
/// A custom FormattableString implementation that allows us to inline nested formattable strings.
/// </summary>
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();
}

/// <summary>
Expand Down
Loading

0 comments on commit be2a895

Please sign in to comment.