diff --git a/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs b/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs index ee405ac0cfa..f0fc63b99b0 100644 --- a/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs +++ b/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs @@ -30,7 +30,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell } var options = provisioningOptions.Value; - if (!options.SupportsTargetedRoleAssignments) + if (!EnvironmentSupportsTargetedRoleAssignments(options)) { // If the app infrastructure does not support targeted role assignments, then we need to ensure that // there are no role assignment annotations in the app model because they won't be honored otherwise. @@ -79,6 +79,13 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell return azureResources; } + private bool EnvironmentSupportsTargetedRoleAssignments(AzureProvisioningOptions options) + { + // run mode always supports targeted role assignments + // publish mode only supports targeted role assignments if the environment supports it + return executionContext.IsRunMode || options.SupportsTargetedRoleAssignments; + } + private static void EnsureNoRoleAssignmentAnnotations(DistributedApplicationModel appModel) { foreach (var resource in appModel.Resources) @@ -94,7 +101,7 @@ private async Task BuildRoleAssignmentAnnotations(DistributedApplicationModel ap { var globalRoleAssignments = new Dictionary>(); - if (!options.SupportsTargetedRoleAssignments) + if (!EnvironmentSupportsTargetedRoleAssignments(options)) { // when the app infrastructure doesn't support targeted role assignments, just copy all the default role assignments to applied role assignments foreach (var resource in azureResources.Select(r => r.AzureResource).OfType()) @@ -183,6 +190,19 @@ private async Task BuildRoleAssignmentAnnotations(DistributedApplicationModel ap } } } + + if (executionContext.IsRunMode) + { + // in RunMode, any Azure resources that are not referenced by a compute resource should have their default role assignments applied + foreach (var azureResource in azureResources.Select(r => r.AzureResource).OfType()) + { + if (!globalRoleAssignments.TryGetValue(azureResource, out _) && + azureResource.TryGetLastAnnotation(out var defaultRoleAssignments)) + { + AppendGlobalRoleAssignments(globalRoleAssignments, azureResource, defaultRoleAssignments.Roles); + } + } + } } if (globalRoleAssignments.Count > 0) diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureResourcePreparerTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureResourcePreparerTests.cs index d4d06b650b3..564c1928c7c 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureResourcePreparerTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureResourcePreparerTests.cs @@ -13,10 +13,12 @@ namespace Aspire.Hosting.Azure.Tests; public class AzureResourcePreparerTests(ITestOutputHelper output) { - [Fact] - public void ThrowsExceptionsIfRoleAssignmentUnsupported() + [Theory] + [InlineData(DistributedApplicationOperation.Publish)] + [InlineData(DistributedApplicationOperation.Run)] + public async Task ThrowsExceptionsIfRoleAssignmentUnsupported(DistributedApplicationOperation operation) { - using var builder = TestDistributedApplicationBuilder.Create(); + using var builder = TestDistributedApplicationBuilder.Create(operation); var storage = builder.AddAzureStorage("storage"); @@ -25,8 +27,16 @@ public void ThrowsExceptionsIfRoleAssignmentUnsupported() var app = builder.Build(); - var ex = Assert.Throws(app.Start); - Assert.Contains("role assignments", ex.Message); + if (operation == DistributedApplicationOperation.Publish) + { + var ex = Assert.Throws(app.Start); + Assert.Contains("role assignments", ex.Message); + } + else + { + await app.StartAsync(); + // no exception is thrown in Run mode + } } [Theory]