From 5a611b8dc849b14348a0fc44476701e57084c886 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 11 Nov 2025 12:09:59 -0600 Subject: [PATCH 1/2] Fix Azure roles resources always redeploying Everytime we check if we need to redeploy an Azure "roles" resource in run mode, we are getting a different CheckSum. This is because we are overwriting the "known parameters" like PrincipalId and PrincipalType with 'null' values, because these values aren't available yet. The fix is to skip setting those known parameters, like we did in previous versions. Fix #12651 --- .../Provisioning/BicepUtilities.cs | 23 +++++++++-- .../BicepUtilitiesTests.cs | 41 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs index 0e263b7f1a3..38c43fea9ed 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs @@ -14,10 +14,20 @@ namespace Aspire.Hosting.Azure.Provisioning; /// internal static class BicepUtilities { + // Known values since they will be filled in by the provisioner + private static readonly string[] s_knownParameterNames = + [ + AzureBicepResource.KnownParameters.PrincipalName, + AzureBicepResource.KnownParameters.PrincipalId, + AzureBicepResource.KnownParameters.PrincipalType, + AzureBicepResource.KnownParameters.UserPrincipalId, + AzureBicepResource.KnownParameters.Location, + ]; + /// /// Converts the parameters to a JSON object compatible with the ARM template. /// - public static async Task SetParametersAsync(JsonObject parameters, AzureBicepResource resource, CancellationToken cancellationToken = default) + public static async Task SetParametersAsync(JsonObject parameters, AzureBicepResource resource, bool skipKnownValues = false, CancellationToken cancellationToken = default) { // Convert the parameters to a JSON object foreach (var parameter in resource.Parameters) @@ -25,6 +35,13 @@ public static async Task SetParametersAsync(JsonObject parameters, AzureBicepRes // Execute parameter values which are deferred. var parameterValue = parameter.Value is Func f ? f() : parameter.Value; + // Skip known parameters with 'null' values, like PrincipalType and PrincipalId, since they are filled in by the provisioner + // and are not available at this time. If we don't do this, the "roles" resources will be re-deployed every run. + if (skipKnownValues && s_knownParameterNames.Contains(parameter.Key) && parameterValue is null) + { + continue; + } + parameters[parameter.Key] = new JsonObject() { ["value"] = parameterValue switch @@ -109,7 +126,7 @@ public static string GetChecksum(AzureBicepResource resource, JsonObject paramet _ = resource.GetBicepTemplateString(); // Now overwrite with live object values skipping known values. - await SetParametersAsync(parameters, resource, cancellationToken: cancellationToken).ConfigureAwait(false); + await SetParametersAsync(parameters, resource, skipKnownValues: true, cancellationToken: cancellationToken).ConfigureAwait(false); if (scope is not null) { await SetScopeAsync(scope, resource, cancellationToken).ConfigureAwait(false); @@ -130,4 +147,4 @@ public static string GetChecksum(AzureBicepResource resource, JsonObject paramet (resource.TryGetLastAnnotation(out var existingResource) ? existingResource.ResourceGroup : null); -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs b/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs index 86f1a60fbb1..3b8809b8943 100644 --- a/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs @@ -428,6 +428,45 @@ public async Task GetCurrentChecksumAsync_ReturnsValidChecksumForValidParameters Assert.NotEmpty(result); } + /// + /// Ensures that known parameters are not overwritten when calculating the checksum. + /// This is important because if these known parameters are overwritten, it means the "roles" + /// resources will be redeployed every time the app is run. + /// + /// + [Fact] + public async Task GetCurrentChecksumAsync_DoesNotOverwriteKnownParameters() + { + // Arrange + using var builder = TestDistributedApplicationBuilder.Create(); + var bicep = builder.AddBicepTemplateString("test", "param name string").Resource; + bicep.Parameters[AzureBicepResource.KnownParameters.PrincipalType] = null; + bicep.Parameters[AzureBicepResource.KnownParameters.PrincipalId] = null; + + var parameters = new JsonObject + { + [AzureBicepResource.KnownParameters.PrincipalType] = "User", + [AzureBicepResource.KnownParameters.PrincipalId] = "1234" + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(new Dictionary + { + ["Parameters"] = parameters.ToJsonString() + }); + var config = configurationBuilder.Build(); + + // Act + var result = await BicepUtilities.GetCurrentChecksumAsync(bicep, config); + + // Assert + Assert.NotNull(result); + + // verify the checksum is the same as using the config parameters directly + var expected = BicepUtilities.GetChecksum(bicep, parameters, scope: null); + Assert.Equal(expected, result); + } + private sealed class ResourceWithConnectionString(string name, string connectionString) : Resource(name), IResourceWithConnectionString @@ -435,4 +474,4 @@ private sealed class ResourceWithConnectionString(string name, string connection public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"{connectionString}"); } -} \ No newline at end of file +} From a85b52f5b4ed07d586cbaf96ff881e10799f7466 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 11 Nov 2025 13:40:18 -0600 Subject: [PATCH 2/2] PR feedback --- tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs b/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs index 3b8809b8943..35a862f1079 100644 --- a/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs @@ -433,7 +433,6 @@ public async Task GetCurrentChecksumAsync_ReturnsValidChecksumForValidParameters /// This is important because if these known parameters are overwritten, it means the "roles" /// resources will be redeployed every time the app is run. /// - /// [Fact] public async Task GetCurrentChecksumAsync_DoesNotOverwriteKnownParameters() { @@ -445,8 +444,8 @@ public async Task GetCurrentChecksumAsync_DoesNotOverwriteKnownParameters() var parameters = new JsonObject { - [AzureBicepResource.KnownParameters.PrincipalType] = "User", - [AzureBicepResource.KnownParameters.PrincipalId] = "1234" + [AzureBicepResource.KnownParameters.PrincipalType] = new JsonObject { ["value"] = "User" }, + [AzureBicepResource.KnownParameters.PrincipalId] = new JsonObject { ["value"] = "1234" }, }; var configurationBuilder = new ConfigurationBuilder();