Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Azure.Provisioning to latest #6390

Merged
merged 18 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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
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 @@ -484,7 +488,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 @@ -518,7 +523,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 @@ -685,7 +689,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 @@ -701,7 +705,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 @@ -710,19 +714,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 @@ -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<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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI - @davidfowl. We aren't able to get rid of our Format string parsing code from ReferenceExpression. But we are able to use the new BicepStringBuilder here.

Copy link
Member Author

@eerhardt eerhardt Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to simplify the BicepValueFormattableString class. No need for it to inherit from FormattableString.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to if you move this logic into the caller right?

Copy link
Member Author

@eerhardt eerhardt Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can build up the BicepStringBuilder earlier and not have the BicepValueFormattableString at all. But we will still need to parse the ReferenceExpression.Format string ourselves.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yea, ok.


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
Loading