File: Provisioning\Provisioners\AzureResourceProvisionerOfT.cs
Web Access
Project: src\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj (Aspire.Hosting.Azure)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Azure.Core;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Configuration;
using Azure.ResourceManager.Authorization.Models;
using Azure.ResourceManager.Authorization;
using Azure;
using Aspire.Hosting.ApplicationModel;
 
namespace Aspire.Hosting.Azure.Provisioning;
 
internal sealed record UserPrincipal(Guid Id, string Name);
 
internal sealed class ProvisioningContext(
    TokenCredential credential,
    ArmClient armClient,
    SubscriptionResource subscription,
    ResourceGroupResource resourceGroup,
    TenantResource tenant,
    IReadOnlyDictionary<string, ArmResource> resourceMap,
    AzureLocation location,
    UserPrincipal principal,
    JsonObject userSecrets)
{
    public TokenCredential Credential => credential;
    public ArmClient ArmClient => armClient;
    public SubscriptionResource Subscription => subscription;
    public TenantResource Tenant => tenant;
    public ResourceGroupResource ResourceGroup => resourceGroup;
    public IReadOnlyDictionary<string, ArmResource> ResourceMap => resourceMap;
    public AzureLocation Location => location;
    public UserPrincipal Principal => principal;
    public JsonObject UserSecrets => userSecrets;
}
 
internal interface IAzureResourceProvisioner
{
    Task<bool> ConfigureResourceAsync(IConfiguration configuration, IAzureResource resource, CancellationToken cancellationToken);
 
    bool ShouldProvision(IConfiguration configuration, IAzureResource resource);
 
    Task GetOrCreateResourceAsync(
        IAzureResource resource,
        ProvisioningContext context,
        CancellationToken cancellationToken);
}
 
internal abstract class AzureResourceProvisioner<TResource> : IAzureResourceProvisioner
    where TResource : IAzureResource
{
    Task<bool> IAzureResourceProvisioner.ConfigureResourceAsync(IConfiguration configuration, IAzureResource resource, CancellationToken cancellationToken) =>
        ConfigureResourceAsync(configuration, (TResource)resource, cancellationToken);
 
    bool IAzureResourceProvisioner.ShouldProvision(IConfiguration configuration, IAzureResource resource) =>
        ShouldProvision(configuration, (TResource)resource);
 
    Task IAzureResourceProvisioner.GetOrCreateResourceAsync(
        IAzureResource resource,
        ProvisioningContext context,
        CancellationToken cancellationToken)
        => GetOrCreateResourceAsync((TResource)resource, context, cancellationToken);
 
    public abstract Task<bool> ConfigureResourceAsync(IConfiguration configuration, TResource resource, CancellationToken cancellationToken);
 
    public virtual bool ShouldProvision(IConfiguration configuration, TResource resource) => true;
 
    public abstract Task GetOrCreateResourceAsync(
        TResource resource,
        ProvisioningContext context,
        CancellationToken cancellationToken);
 
    protected static ResourceIdentifier CreateRoleDefinitionId(SubscriptionResource subscription, string roleDefinitionId) =>
        new($"{subscription.Id}/providers/Microsoft.Authorization/roleDefinitions/{roleDefinitionId}");
 
    protected static async Task DoRoleAssignmentAsync(
        ArmClient armClient,
        ResourceIdentifier resourceId,
        Guid principalId,
        ResourceIdentifier roleDefinitionId,
        CancellationToken cancellationToken)
    {
        var roleAssignments = armClient.GetRoleAssignments(resourceId);
        await foreach (var ra in roleAssignments.GetAllAsync(cancellationToken: cancellationToken).ConfigureAwait(false))
        {
            if (ra.Data.PrincipalId == principalId &&
                ra.Data.RoleDefinitionId.Equals(roleDefinitionId))
            {
                return;
            }
        }
 
        var roleAssignmentInfo = new RoleAssignmentCreateOrUpdateContent(roleDefinitionId, principalId);
 
        var roleAssignmentId = Guid.NewGuid().ToString();
        await roleAssignments.CreateOrUpdateAsync(WaitUntil.Completed, roleAssignmentId, roleAssignmentInfo, cancellationToken).ConfigureAwait(false);
    }
}