|
#pragma warning disable ASPIREAZURE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure.AppContainers;
using Aspire.Hosting.Utils;
using Azure.Provisioning.ContainerRegistry;
using Azure.Provisioning.Storage;
using Microsoft.Extensions.DependencyInjection;
using static Aspire.Hosting.Utils.AzureManifestUtils;
namespace Aspire.Hosting.Azure.Tests;
public class AzureUserAssignedIdentityTests
{
[Fact]
public async Task AddAzureUserAssignedIdentity_GeneratesExpectedResourcesAndBicep()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
builder.AddAzureUserAssignedIdentity("myidentity");
using var app = builder.Build();
await ExecuteBeforeStartHooksAsync(app, default);
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
// Act
var resource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
var (_, bicep) = await GetManifestWithBicep(resource);
await Verify(bicep, extension: "bicep");
}
[Fact]
public async Task AddAzureUserAssignedIdentity_PublishAsExisting_Works()
{
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
builder.AddAzureUserAssignedIdentity("myidentity")
.PublishAsExisting("existingidentity", "my-rg");
using var app = builder.Build();
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
var resource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
var (_, bicep) = await GetManifestWithBicep(resource);
await Verify(bicep, extension: "bicep");
}
[Fact]
public async Task AddAzureUserAssignedIdentity_WithRoleAssignments_Works()
{
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
builder.AddAzureContainerAppEnvironment("cae");
var registry = builder.AddAzureContainerRegistry("myregistry");
builder.AddAzureUserAssignedIdentity("myidentity")
.WithRoleAssignments(registry, [ContainerRegistryBuiltInRole.AcrPush]);
using var app = builder.Build();
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
await ExecuteBeforeStartHooksAsync(app, default);
Assert.Collection(model.Resources.OrderBy(r => r.Name),
r => Assert.IsType<AzureEnvironmentResource>(r),
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
r =>
{
Assert.IsType<AzureProvisioningResource>(r);
Assert.Equal("myidentity-roles-myregistry", r.Name);
},
r => Assert.IsType<AzureContainerRegistryResource>(r));
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
var registryResource = Assert.Single(model.Resources.OfType<AzureContainerRegistryResource>());
var (_, registryBicep) = await GetManifestWithBicep(registryResource, skipPreparer: true);
var identityRoleAssignments = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(), r => r.Name == "myidentity-roles-myregistry");
var (_, identityRoleAssignmentsBicep) = await GetManifestWithBicep(identityRoleAssignments, skipPreparer: true);
await Verify(identityBicep, "bicep")
.AppendContentAsFile(registryBicep, "bicep")
.AppendContentAsFile(identityRoleAssignmentsBicep, "bicep");
}
[Fact]
public async Task WithAzureUserAssignedIdentity_Works()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
var env = builder.AddAzureContainerAppEnvironment("cae");
var identity = builder.AddAzureUserAssignedIdentity("myidentity");
// Use generic AddProject with TestProject type
var projectBuilder = builder.AddProject<TestProject>("myapp", launchProfileName: null);
projectBuilder.WithAzureUserAssignedIdentity(identity);
using var app = builder.Build();
await ExecuteBeforeStartHooksAsync(app, default);
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
// Check that only one AzureUserAssignedIdentityResource is created, the one that we explicitly constructed
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
Assert.Equal("myidentity", identityResource.Name);
// Check for IComputeResource having the correct identity
var computeResource = Assert.Single(model.Resources.OfType<IComputeResource>(), r => r.Name == "myapp");
var identityAnnotation = Assert.Single(computeResource.Annotations.OfType<AppIdentityAnnotation>());
Assert.Same(identity.Resource, identityAnnotation.IdentityResource);
var deploymentTarget = Assert.Single(computeResource.Annotations.OfType<DeploymentTargetAnnotation>());
var (_, computeResourceBicep) = await GetManifestWithBicep(deploymentTarget.DeploymentTarget, skipPreparer: true);
await Verify(computeResourceBicep, extension: "bicep");
}
[Fact]
public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_Works()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
var env = builder.AddAzureContainerAppEnvironment("cae");
// Use Azure Storage instead of Container Registry to test role assignments on WithReference
var storage = builder.AddAzureStorage("mystorage");
var identity = builder.AddAzureUserAssignedIdentity("myidentity");
var projectBuilder = builder.AddProject<TestProject>("myapp", launchProfileName: null);
projectBuilder
.WithAzureUserAssignedIdentity(identity)
.WithRoleAssignments(storage, StorageBuiltInRole.StorageAccountContributor);
using var app = builder.Build();
await ExecuteBeforeStartHooksAsync(app, default);
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType<AzureEnvironmentResource>(r),
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
r => Assert.IsType<AzureStorageResource>(r),
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
r => Assert.IsType<ProjectResource>(r),
r => Assert.IsType<AzureProvisioningResource>(r));
// Verify the identity resource is the only one that exists
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
Assert.Equal("myidentity", identityResource.Name);
// Verify the compute resource has the identity annotation
var computeResource = Assert.Single(model.Resources.OfType<IComputeResource>(), r => r.Name == "myapp");
var identityAnnotation = Assert.Single(computeResource.Annotations.OfType<AppIdentityAnnotation>());
Assert.Same(identity.Resource, identityAnnotation.IdentityResource);
// Verify the role assignment resource for the project
var roleAssignmentResource = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(),
r => r.Name == "myapp-roles-mystorage");
// Get the Bicep for all resources
var deploymentTarget = Assert.Single(computeResource.Annotations.OfType<DeploymentTargetAnnotation>());
var (_, computeResourceBicep) = await GetManifestWithBicep(deploymentTarget.DeploymentTarget, skipPreparer: true);
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
var (_, roleBicep) = await GetManifestWithBicep(roleAssignmentResource, skipPreparer: true);
await Verify(computeResourceBicep, extension: "bicep")
.AppendContentAsFile(identityBicep, "bicep")
.AppendContentAsFile(roleBicep, "bicep");
}
[Fact]
public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppService_Works()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
var env = builder.AddAzureAppServiceEnvironment("appservice");
// Use Azure Storage instead of Container Registry
var storage = builder.AddAzureStorage("mystorage");
var identity = builder.AddAzureUserAssignedIdentity("myidentity");
var projectBuilder = builder.AddProject<TestProject>("myapp", launchProfileName: null);
projectBuilder
.WithAzureUserAssignedIdentity(identity)
.WithRoleAssignments(storage, StorageBuiltInRole.StorageAccountContributor);
using var app = builder.Build();
await ExecuteBeforeStartHooksAsync(app, default);
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType<AzureEnvironmentResource>(r),
r => Assert.IsType<AzureAppServiceEnvironmentResource>(r),
r => Assert.IsType<AzureStorageResource>(r),
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
r => Assert.IsType<ProjectResource>(r),
r => Assert.IsType<AzureProvisioningResource>(r));
// Verify the identity resource is the only one that exists
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
Assert.Equal("myidentity", identityResource.Name);
// Verify the compute resource has the identity annotation
var computeResource = Assert.Single(model.Resources.OfType<IComputeResource>(), r => r.Name == "myapp");
var identityAnnotation = Assert.Single(computeResource.Annotations.OfType<AppIdentityAnnotation>());
Assert.Same(identity.Resource, identityAnnotation.IdentityResource);
// Verify the role assignment resource for the project
var roleAssignmentResource = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(),
r => r.Name == "myapp-roles-mystorage");
// Get the Bicep for all resources
var deploymentTarget = Assert.Single(computeResource.Annotations.OfType<DeploymentTargetAnnotation>());
var (_, computeResourceBicep) = await GetManifestWithBicep(deploymentTarget.DeploymentTarget, skipPreparer: true);
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
var (_, roleBicep) = await GetManifestWithBicep(roleAssignmentResource, skipPreparer: true);
await Verify(computeResourceBicep, extension: "bicep")
.AppendContentAsFile(identityBicep, "bicep")
.AppendContentAsFile(roleBicep, "bicep");
}
[Fact]
public async Task WithAzureUserAssignedIdentity_NoEnvironment_ThrowsException()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
var storage = builder.AddAzureStorage("mystorage");
var identity = builder.AddAzureUserAssignedIdentity("myidentity");
var projectBuilder = builder.AddProject<TestProject>("myapp", launchProfileName: null);
projectBuilder
.WithAzureUserAssignedIdentity(identity);
using var app = builder.Build();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await ExecuteBeforeStartHooksAsync(app, default));
}
[Fact]
public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_MultipleProjects_Works()
{
// Arrange
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
var env = builder.AddAzureContainerAppEnvironment("cae");
// Use Azure Storage instead of Container Registry
var storage = builder.AddAzureStorage("mystorage");
var identity = builder.AddAzureUserAssignedIdentity("myidentity");
var projectBuilder = builder.AddProject<TestProject>("myapp", launchProfileName: null);
projectBuilder
.WithAzureUserAssignedIdentity(identity)
.WithRoleAssignments(storage, StorageBuiltInRole.StorageAccountContributor);
var projectBuilder2 = builder.AddProject<TestProject>("myapp2", launchProfileName: null);
projectBuilder2
.WithAzureUserAssignedIdentity(identity)
.WithRoleAssignments(storage, StorageBuiltInRole.StorageBlobDataOwner);
using var app = builder.Build();
await ExecuteBeforeStartHooksAsync(app, default);
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType<AzureEnvironmentResource>(r),
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
r => Assert.IsType<AzureStorageResource>(r),
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
r => Assert.IsType<ProjectResource>(r),
r => Assert.IsType<ProjectResource>(r),
r => Assert.True(r is AzureProvisioningResource { Name: "myapp-roles-mystorage" }),
r => Assert.True(r is AzureProvisioningResource { Name: "myapp2-roles-mystorage" }));
// Verify the identity resource is the only one that exists
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
Assert.Equal("myidentity", identityResource.Name);
// Verify that both compute resources have the same identity annotation
var computeResource = Assert.Single(model.Resources.OfType<IComputeResource>(), r => r.Name == "myapp");
var identityAnnotation = Assert.Single(computeResource.Annotations.OfType<AppIdentityAnnotation>());
var computeResource2 = Assert.Single(model.Resources.OfType<IComputeResource>(), r => r.Name == "myapp2");
var identityAnnotation2 = Assert.Single(computeResource2.Annotations.OfType<AppIdentityAnnotation>());
Assert.Same(identity.Resource, identityAnnotation.IdentityResource);
Assert.Same(identity.Resource, identityAnnotation2.IdentityResource);
// Verify the role assignment resource for the project
var roleAssignmentResource = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(),
r => r.Name == "myapp-roles-mystorage");
var roleAssignmentResource2 = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(),
r => r.Name == "myapp2-roles-mystorage");
// Each project uses the same identity, but assigns different roles
Assert.NotSame(roleAssignmentResource, roleAssignmentResource2);
// Get the Bicep for all resources
var deploymentTarget = Assert.Single(computeResource.Annotations.OfType<DeploymentTargetAnnotation>());
var deploymentTarget2 = Assert.Single(computeResource2.Annotations.OfType<DeploymentTargetAnnotation>());
var (_, computeResourceBicep) = await GetManifestWithBicep(deploymentTarget.DeploymentTarget, skipPreparer: true);
var (_, computeResourceBicep2) = await GetManifestWithBicep(deploymentTarget2.DeploymentTarget, skipPreparer: true);
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
var (_, roleBicep) = await GetManifestWithBicep(roleAssignmentResource, skipPreparer: true);
var (_, roleBicep2) = await GetManifestWithBicep(roleAssignmentResource2, skipPreparer: true);
await Verify(computeResourceBicep, extension: "bicep")
.AppendContentAsFile(computeResourceBicep2, "bicep")
.AppendContentAsFile(identityBicep, "bicep")
.AppendContentAsFile(roleBicep, "bicep")
.AppendContentAsFile(roleBicep2, "bicep");
}
private sealed class TestProject : IProjectMetadata
{
public string ProjectPath => "some-path";
}
}
|